Skip to content

Commit

Permalink
Refs #3490 Adding segment 'userId==' + some '&segment=userId==' integ…
Browse files Browse the repository at this point in the history
…ration tests + doc blocks
  • Loading branch information
mattab committed Sep 8, 2014
1 parent 8a0a7d8 commit e2b61f5
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 50 deletions.
12 changes: 12 additions & 0 deletions core/Common.php
Expand Up @@ -574,6 +574,18 @@ public static function convertVisitorIdToBin($id)
return self::hex2bin($id);
}

/**
* Converts a User ID string to the Visitor ID Binary representation.
*
* @param $userId
* @return string
*/
public static function convertUserIdToVisitorIdBin($userId)
{
$userIdHashed = \PiwikTracker::getUserIdHashed($userId);
return self::convertVisitorIdToBin($userIdHashed);
}

/**
* Convert IP address (in network address format) to presentation format.
* This is a backward compatibility function for code that only expects
Expand Down
15 changes: 13 additions & 2 deletions core/Tracker/Request.php
Expand Up @@ -450,8 +450,8 @@ public function getVisitorId()
// If User ID is set it takes precedence
$userId = $this->getForcedUserId();
if(strlen($userId) > 0) {
$idVisitor = md5($userId);
$idVisitor = $this->truncateIdAsVisitorId($idVisitor);
$userIdHashed = $this->getUserIdHashed($userId);
$idVisitor = $this->truncateIdAsVisitorId($userIdHashed);
Common::printDebug("Request will be recorded for this user_id = " . $userId . " (idvisitor = $idVisitor)");
$found = true;
}
Expand Down Expand Up @@ -573,4 +573,15 @@ private function truncateIdAsVisitorId($idVisitor)
{
return substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING);
}

/**
* Matches implementation of PiwikTracker::getUserIdHashed
*
* @param $userId
* @return string
*/
private function getUserIdHashed($userId)
{
return sha1($userId);
}
}
1 change: 1 addition & 0 deletions lang/en.json
Expand Up @@ -405,6 +405,7 @@
"Unknown": "Unknown",
"Upload": "Upload",
"UsePlusMinusIconsDocumentation": "Use the plus and minus icons on the left to navigate.",
"UserId": "User ID",
"Username": "Username",
"UseSMTPServerForEmail": "Use SMTP server for e-mail",
"Value": "Value",
Expand Down
75 changes: 47 additions & 28 deletions libs/PiwikTracker/PiwikTracker.php
Expand Up @@ -49,8 +49,8 @@
* $t->setIp( "134.10.22.1" );
* $t->setForceVisitDateTime( '2011-04-05 23:55:02' );
*
* // if you wanted to force to record the page view or conversion to a specific visitorId
* // $t->setVisitorId( "33c31e01394bdc63" );
* // if you wanted to force to record the page view or conversion to a specific User ID
* // $t->setUserId( "username@example.org" );
* // Mandatory: set the URL being tracked
* $t->setUrl( $url = 'http://example.org/store/list-category-toys/' );
*
Expand Down Expand Up @@ -948,17 +948,48 @@ public function setIp($ip)
}

/**
* Forces the requests to be recorded for the specified Visitor ID
* rather than using the heuristics based on IP and other attributes.
* The User ID is a string representing a given user in your system.
*
* Allowed only for Admin/Super User, must be used along with setTokenAuth().
* A User ID can be a username, UUID or an email address, or any number or string that uniquely identifies a user or client.
*
* @param string $userId Any user ID string (eg. email address, ID, username). Must be non empty. Set to false to de-assign a user id previously set.
* @throws Exception
*/
public function setUserId($userId)
{
if($userId === false) {
$this->setNewVisitorId();
return;
}
if($userId === '') {
throw new Exception("User ID cannot be empty.");
}
$this->userId = $userId;
}

/**
* Hash function used internally by Piwik to hash a User ID into the Visitor ID.
*
* @param $id
* @return string
*/
static public function getUserIdHashed($id)
{
return substr( sha1( $id ), 0, 16);
}


/**
* Forces the requests to be recorded for the specified Visitor ID.
* Note: it is recommended to use ->setUserId($userId); instead.
*
* You may set the Visitor ID based on a user attribute, for example the user email:
* $v->setVisitorId( substr(md5( $userEmail ), 0, 16));
* Rather than letting Piwik attribute the user with a heuristic based on IP and other user fingeprinting attributes,
* force the action to be recorded for a particular visitor.
*
* If you use both setVisitorId and setUserId, setUserId will take precedence.
* If not set, the visitor ID will be fetched from the 1st party cookie, or will be set to a random UUID.
*
* @see setTokenAuth()
* @deprecated We recommend to use ->setUserId($userId).
* @param string $visitorId 16 hexadecimal characters visitor ID, eg. "33c31e01394bdc63"
* @throws Exception
*/
Expand All @@ -977,25 +1008,6 @@ public function setVisitorId($visitorId)
$this->forcedVisitorId = $visitorId;
}


/**
*
* @param string $userId Any user ID string (eg. email address, ID, username). Must be non empty. Set to false to de-assign a user id previously set.
* @throws Exception
*/
public function setUserId($userId)
{
if($userId === '') {
throw new Exception("User ID cannot be empty.");
}
$this->userId = $userId;
}

static public function getIdHashed($id)
{
return substr(md5( $id ), 0, 16);
}

/**
* If the user initiating the request has the Piwik first party cookie,
* this function will try and return the ID parsed from this first party cookie (found in $_COOKIE).
Expand All @@ -1011,7 +1023,7 @@ static public function getIdHashed($id)
public function getVisitorId()
{
if (!empty($this->userId)) {
return $this->getIdHashed($this->userId);
return $this->getUserIdHashed($this->userId);
}
if (!empty($this->forcedVisitorId)) {
return $this->forcedVisitorId;
Expand All @@ -1022,6 +1034,13 @@ public function getVisitorId()
return $this->randomVisitorId;
}


/**
* Returns the User ID string, which may have been set via:
* $v->setUserId('username@example.org');
*
* @return bool
*/
public function getUserId()
{
return $this->userId;
Expand Down
15 changes: 15 additions & 0 deletions plugins/API/API.php
Expand Up @@ -151,6 +151,20 @@ public function getSegmentsMetadata($idSites = array(), $_hideImplementationData

$isAuthenticatedWithViewAccess = Piwik::isUserHasViewAccess($idSites) && !Piwik::isUserIsAnonymous();

$segments[] = array(
'type' => 'dimension',
'category' => Piwik::translate('General_Visit'),
'name' => 'General_UserId',

This comment has been minimized.

Copy link
@tsteur

tsteur Sep 22, 2014

Member

Just wondering if it maybe should be log_visit.userId? Was just wondering as the method in sqlFilterValue says convertUserIdTo... so I was assuming it gets a userId and not idvisitor. But I didn't have a deeper look so if not just ignore it

This comment has been minimized.

Copy link
@mattab

mattab Sep 23, 2014

Author Member

the userId column is only in the log_visit table, so the only way to segment match a visitor across all tables including log_link_visit_action and _conversion then we must use idvisitor which is a hash of user id

This comment has been minimized.

Copy link
@mattab

mattab Sep 23, 2014

Author Member

Umh didn't notice the branch until I made this change: 252798d - I guess we have to merge PR + manually merge back my change in

'segment' => 'userId',
'acceptedValues' => 'any non empty unique string identifying the user (such as an email address or a username).',
'sqlSegment' => 'log_visit.idvisitor',
'sqlFilterValue' => array('Piwik\Common', 'convertUserIdToVisitorIdBin'),
'permission' => $isAuthenticatedWithViewAccess,

// TODO specify that this segment is not compatible with some operators
// 'unsupportedOperators' = array(MATCH_CONTAINS, MATCH_DOES_NOT_CONTAIN),
);

$segments[] = array(
'type' => 'dimension',
'category' => Piwik::translate('General_Visit'),
Expand All @@ -161,6 +175,7 @@ public function getSegmentsMetadata($idSites = array(), $_hideImplementationData
'sqlFilterValue' => array('Piwik\Common', 'convertVisitorIdToBin'),
'permission' => $isAuthenticatedWithViewAccess,
);

$segments[] = array(
'type' => 'dimension',
'category' => Piwik::translate('General_Visit'),
Expand Down
20 changes: 15 additions & 5 deletions tests/PHPUnit/Fixtures/FewVisitsWithSetVisitorIdAndUserId.php
Expand Up @@ -113,8 +113,8 @@ private function trackVisits_setUserId()
// Change User ID -> This will create a new visit
$t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(2.2)->getDatetime());
$t->setNewVisitorId();
$anotherUserId = 'new-email@example.com';
$t->setUserId($anotherUserId);
$secondUserId = 'new-email@example.com';
$t->setUserId($secondUserId);
self::checkResponse($t->doTrackPageView('a new user id was set -> new visit'));

// A NEW VISIT BY THE SAME USER
Expand All @@ -125,19 +125,29 @@ private function trackVisits_setUserId()
$t->setIp('67.51.31.21');
$t->setUserAgent("Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)");
$t->setBrowserLanguage('fr');
$t->setUserId($anotherUserId);
$t->setUserId($secondUserId);
self::checkResponse($t->doTrackPageView('same user id was set -> this is the same unique user'));

// Do not pass User ID in this request, it should still attribute to previous visit
$t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(5.1)->getDatetime());
self::checkResponse($t->doTrackPageView('second pageview - by this user id'));

// Request from a different computer not yet logged in, this should not be added to our User ID session
$t->setUserId(false);
self::checkResponse($t->doTrackPageView('second pageview by this user id'));
// make sure the Id is not so random as to not fail the test
$t->randomVisitorId = '5e15b4d842cc294d';

$t->setIp('1.2.4.7');
$t->setUserAgent("New unique device");
self::checkResponse($t->doTrackPageView('pageview - should not be tracked by our user id but in a new visit'));

// User has now logged in so we measure her interactions to her User ID
$t->setUserId($secondUserId);

// Trigger a goal conversion
$t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(5.2)->getDatetime());
self::checkResponse($t->doTrackGoal(1));


// An ecommerce add to cart
// (helpful to test that &segment=userId==x will return all items purchased by a specific user ID
$t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(5.3)->getDatetime());
Expand Down
25 changes: 23 additions & 2 deletions tests/PHPUnit/Integration/TrackingAPISetVisitorIdTest.php
Expand Up @@ -47,13 +47,15 @@ public function testApi($api, $params)
public function getApiForTesting()
{
return array(
array('VisitsSummary.get', array('idSite' => self::$fixture->idSite,
array('VisitsSummary.get',
array('idSite' => self::$fixture->idSite,
'date' => self::$fixture->dateTime,
'periods' => 'day',
'testSuffix' => '',
)),

array('Live.getLastVisitsDetails', array('idSite' => self::$fixture->idSite,
array('Live.getLastVisitsDetails',
array('idSite' => self::$fixture->idSite,
'date' => self::$fixture->dateTime,
'periods' => 'day',
'keepLiveIds' => true,
Expand All @@ -65,6 +67,25 @@ public function getApiForTesting()
)
)),

// Testing userId segment matches both log_visits and log_conversion
array(array('VisitsSummary.get', 'Goals.get'),
array('idSite' => self::$fixture->idSite,
'date' => self::$fixture->dateTime,
'periods' => 'day',
'segment' => 'userId==' . urlencode('new-email@example.com'),
'testSuffix' => '_segmentUserId',
)),

array('Goals.getItemsName',
array('idSite' => self::$fixture->idSite,
'date' => self::$fixture->dateTime,
'periods' => 'day',
'segment' => 'visitEcommerceStatus==abandonedCart;userId==' . urlencode('new-email@example.com'),
'testSuffix' => '_segmentUserIdAndCartAbandoned_getAbandonedCartItems',
'otherRequestParameters' => array(
'abandonedCarts' => 1
),
)),
);
}
}
Expand Down
Expand Up @@ -84,10 +84,10 @@
<location>Raleigh, North Carolina, United States</location>
<latitude>35.771999</latitude>
<longitude>-78.639000</longitude>
<operatingSystem>Windows 8</operatingSystem>
<operatingSystemCode>WI8</operatingSystemCode>
<operatingSystemShortName>Win 8</operatingSystemShortName>
<operatingSystemIcon>plugins/UserSettings/images/os/WI8.gif</operatingSystemIcon>
<operatingSystem>Windows 8.1</operatingSystem>
<operatingSystemCode>W81</operatingSystemCode>
<operatingSystemShortName>W81</operatingSystemShortName>
<operatingSystemIcon>plugins/UserSettings/images/os/UNK.gif</operatingSystemIcon>
<browserFamily>webkit</browserFamily>
<browserFamilyDescription>WebKit (Safari, Chrome)</browserFamilyDescription>
<browserName>Chrome 33.0</browserName>
Expand Down
Expand Up @@ -50,7 +50,7 @@
<row>
<idVisit>3</idVisit>
<userId>email@example.com</userId>
<visitorId>5658ffccee7f0ebf</visitorId>
<visitorId>9395988394d4568d</visitorId>
<actionDetails>
<row>
<type>action</type>
Expand Down Expand Up @@ -79,7 +79,7 @@
<row>
<idVisit>4</idVisit>
<userId>new-email@example.com</userId>
<visitorId>4b60563d119613fb</visitorId>
<visitorId>c9ade7a5a103b2ed</visitorId>
<actionDetails>
<row>
<type>action</type>
Expand All @@ -94,4 +94,77 @@
<lastActionDateTime>2010-03-06 13:34:33</lastActionDateTime>
<actions>1</actions>
</row>
<row>
<idVisit>5</idVisit>
<userId>new-email@example.com</userId>
<visitorId>c9ade7a5a103b2ed</visitorId>
<actionDetails>
<row>
<type>action</type>
<url>http://localhost/piwik-master/</url>
<pageTitle>same user id was set -&gt; this is the same unique user</pageTitle>
<pageIdAction>8</pageIdAction>
<serverTimePretty>Sat 6 Mar 16:22:33</serverTimePretty>
<pageId>7</pageId>
<timeSpent>360</timeSpent>
<timeSpentPretty>6 min 0s</timeSpentPretty>
<icon />
</row>
<row>
<type>action</type>
<url>http://localhost/piwik-master/</url>
<pageTitle>second pageview - by this user id</pageTitle>
<pageIdAction>8</pageIdAction>
<serverTimePretty>Sat 6 Mar 16:28:33</serverTimePretty>
<pageId>8</pageId>
<icon />
</row>
<row>
<type>goal</type>
<goalName>triggered js</goalName>
<goalId>1</goalId>
<revenue>0</revenue>
<goalPageId />
<serverTimePretty>Sat 6 Mar 16:34:33</serverTimePretty>
<url>http://localhost/piwik-master/</url>
<icon>plugins/Morpheus/images/goal.png</icon>
</row>
<row>
<type>ecommerceAbandonedCart</type>
<revenue>10000000000</revenue>
<items>1</items>
<serverTimePretty>Sat 6 Mar 16:40:33</serverTimePretty>
<itemDetails>
<row>
<itemSKU>sku-007-PRISM</itemSKU>
<itemName>My secret spy tech</itemName>
<itemCategory>Surveillance</itemCategory>
<price>10000000000</price>
<quantity>1</quantity>
</row>
</itemDetails>
<icon>plugins/Morpheus/images/ecommerceAbandonedCart.gif</icon>
</row>
</actionDetails>
<lastActionDateTime>2010-03-06 16:40:33</lastActionDateTime>
<actions>2</actions>
</row>
<row>
<idVisit>6</idVisit>
<userId />
<visitorId>5e15b4d842cc294d</visitorId>
<actionDetails>
<row>
<type>action</type>
<url>http://localhost/piwik-master/</url>
<pageTitle>pageview - should not be tracked by our user id but in a new visit</pageTitle>
<pageIdAction>8</pageIdAction>
<serverTimePretty>Sat 6 Mar 16:28:33</serverTimePretty>
<pageId>9</pageId>
<icon />
</row>
</actionDetails>
<lastActionDateTime>2010-03-06 16:28:33</lastActionDateTime>
<actions>1</actions>
</row>
</result>

0 comments on commit e2b61f5

Please sign in to comment.