diff --git a/config/global.ini.php b/config/global.ini.php index f0bf948fb205378738bdd9bd0fb1f5d3ec886e2d..a0ba449dbe078cebdb56dafd4a5f6c5bb4739b97 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -245,6 +245,12 @@ api_service_url = http://api.piwik.org ; this is useful when you want to do cross websites analysis use_third_party_id_cookie = 0 +; By default, Piwik does not trust the idcookie as accurate and will always check that if the visitor visited +; the website earlier by looking for a visitor with the same IP and user configuration (to avoid abuse or misbehaviour) +; This setting should only be set to 1 in an intranet setting, where most users have the same configuration (browsers, OS) +; and the same IP. If left to 0 in this setting, all visitors will be counted as one single visitor. +trust_visitors_cookies = 0 + ; name of the cookie used to store the visitor information ; This is used only if use_third_party_id_cookie = 1 cookie_name = piwik_visitor @@ -268,12 +274,6 @@ visit_standard_length = 1800 ; visitors that stay on the website and view only one page will be considered as time on site of 0 second default_time_one_page_visit = 0 -; By default, Piwik does not trust the idcookie as accurate and will always check that if the visitor visited -; the website earlier by looking for a visitor with the same IP and user configuration (to avoid abuse or misbehaviour) -; This setting should only be set to 1 in an intranet setting, where most users have the same configuration (browsers, OS) -; and the same IP. If left to 0 in this setting, all visitors will be counted as one single visitor. -trust_visitors_cookies = 0 - ; if set to 1, Piwik attempts a "best guess" at the visitor's country of ; origin when the preferred language tag omits region information. ; The mapping is defined in core/DataFiles/LanguageToCountry.php, diff --git a/core/Db/Schema/Myisam.php b/core/Db/Schema/Myisam.php index c3f08c0764b810bae2893170c1a49c1c9ce26884..9c529d39bc1ba06998ab2b94ef5b2b0dc0d50bc7 100644 --- a/core/Db/Schema/Myisam.php +++ b/core/Db/Schema/Myisam.php @@ -221,7 +221,8 @@ class Piwik_Db_Schema_Myisam implements Piwik_Db_Schema_Interface custom_var_v5 VARCHAR(50) DEFAULT NULL, PRIMARY KEY(idvisit), INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time), - INDEX index_idsite_datetime (idsite, visit_last_action_time) + INDEX index_idsite_datetime (idsite, visit_last_action_time), + INDEX index_idsite_idvisitor (idsite, idvisitor) ) DEFAULT CHARSET=utf8 ", diff --git a/core/Tracker.php b/core/Tracker.php index 8511b14a3777c2bda8c6ff247539f9e0361ac7ab..b639d653b41a9ba58009c1d1e6d0fb86645fbe51 100644 --- a/core/Tracker.php +++ b/core/Tracker.php @@ -43,6 +43,7 @@ class Piwik_Tracker static protected $forcedDateTime = null; static protected $forcedIpString = null; + static protected $forcedVisitorId = null; static protected $pluginsNotToLoad = array(); @@ -59,6 +60,11 @@ class Piwik_Tracker self::$forcedDateTime = $dateTime; } + public static function setForceVisitorId($visitorId) + { + self::$forcedVisitorId = $visitorId; + } + public function getCurrentTimestamp() { if(!is_null(self::$forcedDateTime)) @@ -263,6 +269,7 @@ class Piwik_Tracker if(is_null($visit)) { $visit = new Piwik_Tracker_Visit( self::$forcedIpString, self::$forcedDateTime ); + $visit->setForcedVisitorId(self::$forcedVisitorId); } elseif(!($visit instanceof Piwik_Tracker_Visit_Interface )) { @@ -373,19 +380,24 @@ class Piwik_Tracker // Custom IP to use for this visitor $customIp = Piwik_Common::getRequestVar('cip', false); - if(!empty($customIp)) { $this->setForceIp($customIp); } - + // Custom server date time to use $customDatetime = Piwik_Common::getRequestVar('cdt', false); - if(!empty($customDatetime)) { $this->setForceDateTime($customDatetime); } + + // Forced Visitor ID to record the visit / action + $customVisitorId = Piwik_Common::getRequestVar('cid', false); + if(!empty($customVisitorId)) + { + $this->setForceVisitorId($customVisitorId); + } } } diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index 802adcf30a4e34384ecfd3927f5d998573c7b38a..5ce28a5d41ec1bb791aedea876e6335b6bade8a7 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -49,6 +49,8 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface // can be overwritten in constructor protected $timestamp; protected $ipString; + // via setForcedVisitorId() + protected $forcedVisitorId; const TIME_IN_PAST_TO_SEARCH_FOR_VISITOR = 86400; @@ -68,6 +70,11 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $this->ipString = Piwik_Common::getIp($ipString); } + function setForcedVisitorId($visitorId) + { + $this->forcedVisitorId = $visitorId; + } + function setRequest($requestArray) { $this->request = $requestArray; @@ -772,19 +779,35 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $this->printCookie(); - // Read ID Visitor - $found = false; - // - If set to use 3rd party cookies for Visit ID, read the cookies - // - By default, reads the first party cookie ID - $useThirdPartyCookie = $this->shouldUseThirdPartyCookie(); - if($useThirdPartyCookie) + $found = $forcedVisitorId = false; + + // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request? + $idVisitor = $this->forcedVisitorId; + if(!empty($idVisitor)) + { + if(strlen($idVisitor) != Piwik_Tracker::LENGTH_HEX_ID_STRING) + { + throw new Exception("Visitor ID (cid) must be ".Piwik_Tracker::LENGTH_HEX_ID_STRING." characters long"); + } + printDebug("Request will be forced to record for this idvisitor = ".$idVisitor); + $forcedVisitorId = true; + $found = true; + } + + if(!$found) { - $idVisitor = $this->cookie->get(0); - if($idVisitor !== false - && strlen($idVisitor) == Piwik_Tracker::LENGTH_HEX_ID_STRING) + // - If set to use 3rd party cookies for Visit ID, read the cookies + // - By default, reads the first party cookie ID + $useThirdPartyCookie = $this->shouldUseThirdPartyCookie(); + if($useThirdPartyCookie) + { + $idVisitor = $this->cookie->get(0); + if($idVisitor !== false + && strlen($idVisitor) == Piwik_Tracker::LENGTH_HEX_ID_STRING) { $found = true; } + } } // If a third party cookie was not found, we default to the first party cookie if(!$found) @@ -809,16 +832,25 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $timeLookBack = date('Y-m-d H:i:s', $this->getCurrentTimestamp() - self::TIME_IN_PAST_TO_SEARCH_FOR_VISITOR); $where = "visit_last_action_time >= ? - AND idsite = ? - AND config_id = ?"; - $bindSql = array( $timeLookBack, $this->idsite, $configId); + AND idsite = ?"; + $bindSql = array( $timeLookBack, $this->idsite); - if(Piwik_Tracker_Config::getInstance()->Tracker['trust_visitors_cookies'] - && !empty($this->visitorInfo['idvisitor'])) + // we always match on the config_id, except if the current request forces the visitor id + if(!$forcedVisitorId) + { + $where .= ' AND config_id = ? '; + $bindSql[] = $configId; + } + + // We force to match a visitor ID + // 1) If the visitor cookies should be trusted (ie. intranet) - config file setting + // 2) or if the Visitor ID was forced via the Tracking API setVisitorId() + if(!empty($this->visitorInfo['idvisitor']) + && ( Piwik_Tracker_Config::getInstance()->Tracker['trust_visitors_cookies'] + || $forcedVisitorId )) { printDebug("Matching the visitor based on his idcookie: ".bin2hex($this->visitorInfo['idvisitor']) ."..."); - // If the visitor cookies should be trusted (ie. intranet) we add this condition $where .= ' AND idvisitor = ?'; $bindSql[] = $this->visitorInfo['idvisitor']; } diff --git a/core/Updates/1.2.5-rc7.php b/core/Updates/1.2.5-rc7.php new file mode 100644 index 0000000000000000000000000000000000000000..cee6496307a5d98dad8688cd21411804388bd84e --- /dev/null +++ b/core/Updates/1.2.5-rc7.php @@ -0,0 +1,32 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik + * @package Updates + */ + +/** + * @package Updates + */ +class Piwik_Updates_1_2_5_rc7 extends Piwik_Updates +{ + static function getSql($schema = 'Myisam') + { + return array( + 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'` + ADD INDEX index_idsite_idvisitor (idsite, idvisitor)' => false, + ); + } + + static function update() + { + Piwik_Updater::updateDatabase(__FILE__, self::getSql()); + } +} + + diff --git a/core/Version.php b/core/Version.php index ef16837da28b7117ae3a9e42a42ea3346068bba2..bc5e3590d4da34e02ec358db3e576c6e371ce1e1 100644 --- a/core/Version.php +++ b/core/Version.php @@ -17,5 +17,5 @@ */ final class Piwik_Version { - const VERSION = '1.2.5-rc6'; + const VERSION = '1.2.5-rc7'; } diff --git a/libs/PiwikTracker/PiwikTracker.php b/libs/PiwikTracker/PiwikTracker.php index f4568514fe79ac061e7c11596e6eccb677df9cd5..dd15c2effc935ab41e6354525cd388f5e8c6423e 100644 --- a/libs/PiwikTracker/PiwikTracker.php +++ b/libs/PiwikTracker/PiwikTracker.php @@ -268,7 +268,7 @@ class PiwikTracker { throw new Exception("setVisitorId() expects a 16 characters ID"); } - $this->visitorId = $visitorId; + $this->forcedVisitorId = $visitorId; } /** @@ -281,6 +281,10 @@ class PiwikTracker */ public function getVisitorId() { + if(!empty($this->forcedVisitorId)) + { + return $this->forcedVisitorId; + } return $this->visitorId; } diff --git a/tests/integration/Main.test.php b/tests/integration/Main.test.php index 8bd6c6b774d5a742bba9c4056a029e09f1bb929b..1cc6b4f6555c39fb1bafa65bdc7881a3c7eb76d4 100644 --- a/tests/integration/Main.test.php +++ b/tests/integration/Main.test.php @@ -639,4 +639,39 @@ class Test_Piwik_Integration_Main extends Test_Integration $this->visitorId ); } + + function test_PiwikTracker_trackForceUsingVisitId_insteadOfHeuristics() + { + $this->setApiToCall( 'VisitsSummary.get' ); + $dateTime = '2009-01-04 00:11:42'; + $idSite = $this->createWebsite($dateTime); + $idGoal = Piwik_Goals_API::getInstance()->addGoal($idSite, 'triggered js', 'manually', '', ''); + + $t = $this->getTracker($idSite, $dateTime, $defaultInit = true); + + // Record 1st page view + $t->setUrl( 'http://example.org/index.htm' ); + $this->checkResponse($t->doTrackPageView( 'incredible title!')); + + $visitorId = $t->getVisitorId(); + $this->assertTrue(strlen($visitorId) == 16); + + // Create a new Tracker object, with different attributes + $t2 = $this->getTracker($idSite, $dateTime, $defaultInit = false); + + // Make sure the ID is different at first + $visitorId2 = $t2->getVisitorId(); + $this->assertTrue($visitorId != $visitorId2); + + // Then force the visitor ID + $t2->setVisitorId($visitorId); + + // And Record a Goal: The previous visit should be updated rather than a new visit Created + $t2->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(0.3)->getDatetime()); + $this->checkResponse($t2->doTrackGoal($idGoal, $revenue = 42.256)); + + // TOTAL should be: 1 visit, 1 converted goal, 1 page view + $this->callGetApiCompareOutput(__FUNCTION__, 'xml', $idSite, $dateTime); + } + } diff --git a/tests/integration/expected/test_PiwikTracker_trackForceUsingVisitId_insteadOfHeuristics__VisitsSummary.get_day.xml b/tests/integration/expected/test_PiwikTracker_trackForceUsingVisitId_insteadOfHeuristics__VisitsSummary.get_day.xml new file mode 100644 index 0000000000000000000000000000000000000000..21459e9535f5848f93a7942fe04d1066c8c7f0db --- /dev/null +++ b/tests/integration/expected/test_PiwikTracker_trackForceUsingVisitId_insteadOfHeuristics__VisitsSummary.get_day.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_actions>1</nb_actions> + <nb_visits_converted>1</nb_visits_converted> + <bounce_count>1</bounce_count> + <sum_visit_length>1080</sum_visit_length> + <max_actions>1</max_actions> + <bounce_rate>100%</bounce_rate> + <nb_actions_per_visit>1</nb_actions_per_visit> + <avg_time_on_site>1080</avg_time_on_site> +</result> \ No newline at end of file diff --git a/tests/integration/proxy-piwik.php b/tests/integration/proxy-piwik.php index 3921c3312f96b567da2c9ebc58fad2ec58c247c7..da2fb845205c8c29155aaeccd5219a3f538f022b 100644 --- a/tests/integration/proxy-piwik.php +++ b/tests/integration/proxy-piwik.php @@ -46,6 +46,13 @@ if(!empty($customDatetime)) Piwik_Tracker::setForceDateTime($customDatetime); } +// Custom server date time to use +$customVisitorId = Piwik_Common::getRequestVar('cid', false); +if(!empty($customVisitorId)) +{ + Piwik_Tracker::setForceVisitorId($customVisitorId); +} + // Disable provider plugin, because it is so slow to do reverse ip lookup in dev environment somehow Piwik_Tracker::setPluginsNotToLoad(array('Provider')); include '../../piwik.php';