diff options
Diffstat (limited to 'core/Tracker/Visit.php')
-rw-r--r-- | core/Tracker/Visit.php | 3311 |
1 files changed, 1598 insertions, 1713 deletions
diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index ef8b9e3cee..c4a661ec62 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -13,9 +13,11 @@ * @package Piwik * @subpackage Piwik_Tracker */ -interface Piwik_Tracker_Visit_Interface { - function setRequest($requestArray); - function handle(); +interface Piwik_Tracker_Visit_Interface +{ + function setRequest($requestArray); + + function handle(); } /** @@ -34,12 +36,12 @@ interface Piwik_Tracker_Visit_Interface { */ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface { - const UNKNOWN_CODE = 'xx'; - - /** - * @var Piwik_Cookie - */ - protected $cookie = null; + const UNKNOWN_CODE = 'xx'; + + /** + * @var Piwik_Cookie + */ + protected $cookie = null; protected $visitorInfo = array(); protected $userSettingsInformation = null; protected $visitorCustomVariables = array(); @@ -53,722 +55,673 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface protected $ip; protected $authenticated = false; - // Set to true when we set some custom variables from the cookie - protected $customVariablesSetFromRequest = false; - - /** - * @var Piwik_Tracker_GoalManager - */ - protected $goalManager; - - public function __construct($forcedIpString = null, $forcedDateTime = null, $authenticated = false) - { - $this->timestamp = time(); - if(!empty($forcedDateTime)) - { - if(!is_numeric($forcedDateTime)) - { - $forcedDateTime = strtotime($forcedDateTime); - } - $this->timestamp = $forcedDateTime; - } - $ipString = $forcedIpString; - if(empty($ipString)) - { - $ipString = Piwik_IP::getIpFromHeader(); - } - - $ip = Piwik_IP::P2N($ipString); - $this->ip = $ip; - - $this->authenticated = $authenticated; - } - - function setForcedVisitorId($visitorId) - { - $this->forcedVisitorId = $visitorId; - } - - function setRequest($requestArray) - { - $this->request = $requestArray; - - $idsite = Piwik_Common::getRequestVar('idsite', 0, 'int', $this->request); - Piwik_PostEvent('Tracker.setRequest.idSite', $idsite, $requestArray); - if($idsite <= 0) - { - throw new Exception('Invalid idSite'); - } - $this->idsite = $idsite; - - // When the 'url' and referer url parameter are not given, we might be in the 'Simple Image Tracker' mode. - // The URL can default to the Referer, which will be in this case - // the URL of the page containing the Simple Image beacon - if(empty($this->request['urlref']) - && empty($this->request['url'])) - { - $this->request['url'] = @$_SERVER['HTTP_REFERER']; - } - } - - /** - * Main algorithm to handle the visit. - * - * Once we have the visitor information, we have to determine if the visit is a new or a known visit. - * - * 1) When the last action was done more than 30min ago, - * or if the visitor is new, then this is a new visit. - * - * 2) If the last action is less than 30min ago, then the same visit is going on. - * Because the visit goes on, we can get the time spent during the last action. - * - * NB: - * - In the case of a new visit, then the time spent - * during the last action of the previous visit is unknown. - * - * - In the case of a new visit but with a known visitor, - * we can set the 'returning visitor' flag. - * - * In all the cases we set a cookie to the visitor with the new information. - */ - public function handle() - { - // the IP is needed by isExcluded() and GoalManager->recordGoals() - $this->visitorInfo['location_ip'] = $this->ip; - - if($this->isExcluded()) - { - return; - } - - // Anonymize IP (after testing for IP exclusion) - $ip = $this->ip; - Piwik_PostEvent('Tracker.Visit.setVisitorIp', $ip); - $this->visitorInfo['location_ip'] = $ip; - - $this->visitorCustomVariables = self::getCustomVariables($scope = 'visit', $this->request); - if(!empty($this->visitorCustomVariables)) - { - printDebug("Visit level Custom Variables: "); - printDebug($this->visitorCustomVariables); - $this->customVariablesSetFromRequest = true; - } - - $this->goalManager = new Piwik_Tracker_GoalManager(); - - $someGoalsConverted = $visitIsConverted = false; - $idActionUrl = $idActionName = $actionType = false; - $action = null; - - $this->goalManager->init($this->request); - - $requestIsManualGoalConversion = ($this->goalManager->idGoal > 0); - $requestIsEcommerce = $this->goalManager->requestIsEcommerce; - if($requestIsEcommerce) - { - $someGoalsConverted = true; - - // Mark the visit as Converted only if it is an order (not for a Cart update) - if($this->goalManager->isGoalAnOrder) - { - $visitIsConverted = true; - } - } - // this request is from the JS call to piwikTracker.trackGoal() - elseif($requestIsManualGoalConversion) - { - $someGoalsConverted = $this->goalManager->detectGoalId($this->idsite); - $visitIsConverted = $someGoalsConverted; - // if we find a idgoal in the URL, but then the goal is not valid, this is most likely a fake request - if(!$someGoalsConverted) - { - printDebug('Invalid goal tracking request for goal id = '.$this->goalManager->idGoal); - unset($this->goalManager); - return; - } - } - // normal page view, potentially triggering a URL matching goal - else - { - $action = $this->newAction(); - $this->handleAction($action); - $someGoalsConverted = $this->goalManager->detectGoalsMatchingUrl($this->idsite, $action); - $visitIsConverted = $someGoalsConverted; - - $action->loadIdActionNameAndUrl(); - $idActionUrl = $action->getIdActionUrl(); - if ($idActionUrl !== null) { - $idActionUrl = (int)$idActionUrl; - } - $idActionName = (int)$action->getIdActionName(); - $actionType = $action->getActionType(); - } - - // the visitor and session - $this->recognizeTheVisitor(); - - $isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit(); - - if(!$isLastActionInTheSameVisit) - { - printDebug("Visitor detected, but last action was more than 30 minutes ago..."); - } - // Known visit when: - // ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor - // OR - // - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor() - // ) - // AND - // - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit() - if( $this->isVisitorKnown() - && $isLastActionInTheSameVisit) - { - $idRefererActionUrl = $this->visitorInfo['visit_exit_idaction_url']; - $idRefererActionName = $this->visitorInfo['visit_exit_idaction_name']; - try { - $this->handleKnownVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted); - if(!is_null($action)) - { - $action->record( $this->visitorInfo['idvisit'], - $this->visitorInfo['idvisitor'], - $idRefererActionUrl, - $idRefererActionName, - $this->visitorInfo['time_spent_ref_action'] - ); - } - } catch(Piwik_Tracker_Visit_VisitorNotFoundInDatabase $e) { - - // There is an edge case when: - // - two manual goal conversions happen in the same second - // - which result in handleKnownVisit throwing the exception - // because the UPDATE didn't affect any rows (one row was found, but not updated since no field changed) - // - the exception is caught here and will result in a new visit incorrectly - // In this case, we cancel the current conversion to be recorded: - if($requestIsManualGoalConversion - || $requestIsEcommerce) - { - $someGoalsConverted = $visitIsConverted = false; - } - // When the row wasn't found in the logs, and this is a pageview or - // goal matching URL, we force a new visitor - else - { - $this->visitorKnown = false; - } - } - } - - // New visit when: - // - the visitor has the Piwik cookie but the last action was performed more than 30 min ago @see isLastActionInTheSameVisit() - // - the visitor doesn't have the Piwik cookie, and couldn't be matched in @see recognizeTheVisitor() - // - the visitor does have the Piwik cookie but the idcookie and idvisit found in the cookie didn't match to any existing visit in the DB - if(!$this->isVisitorKnown() - || !$isLastActionInTheSameVisit) - { - $this->handleNewVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted); - if(!is_null($action)) - { - $action->record( $this->visitorInfo['idvisit'], $this->visitorInfo['idvisitor'], 0, 0, 0 ); - } - } - - // update the cookie with the new visit information - $this->setThirdPartyCookie(); - - // record the goals if applicable - if($someGoalsConverted) - { - $refererTimestamp = Piwik_Common::getRequestVar('_refts', 0, 'int', $this->request); - $refererUrl = Piwik_Common::getRequestVar('_ref', '', 'string', $this->request); - $refererCampaignName = trim(urldecode(Piwik_Common::getRequestVar('_rcn', '', 'string', $this->request))); - $refererCampaignKeyword = trim(urldecode(Piwik_Common::getRequestVar('_rck', '', 'string', $this->request))); - - $this->goalManager->recordGoals( - $this->idsite, - $this->visitorInfo, - $this->visitorCustomVariables, - $action, - $refererTimestamp, - $refererUrl, - $refererCampaignName, - $refererCampaignKeyword, - $this->getBrowserLanguage() - ); - } - unset($this->goalManager); - unset($action); - $this->printCookie(); - } - - protected function printCookie() - { - printDebug($this->cookie); - } - - protected function handleAction($action) - { - $action->setIdSite($this->idsite); - $action->setRequest($this->request); - $action->setTimestamp($this->getCurrentTimestamp()); - $action->init(); - - if($this->detectActionIsOutlinkOnAliasHost($action)) - { - printDebug("Info: The outlink URL host is one of the known host for this website. "); - } - if(isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) - { - $type = Piwik_Tracker_Action::getActionTypeName($action->getActionType()); - printDebug("Action is a $type, - Action name = ". $action->getActionName() .", - Action URL = ". $action->getActionUrl() ); - } - } - - /** - * In the case of a known visit, we have to do the following actions: - * - * 1) Insert the new action - * 2) Update the visit information - * - * This method triggers two events: - * - * Tracker.knownVisitorUpdate is triggered before the visit information is updated - * Event data is an array with the values to be updated (could be changed by plugins) - * - * Tracker.knownVisitorInformation is triggered after saving the new visit data - * Even data is an array with updated information about the visit - * @param $idActionUrl - * @param $idActionName - * @param $actionType - * @param $visitIsConverted - * @throws Piwik_Tracker_Visit_VisitorNotFoundInDatabase - */ - protected function handleKnownVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted) - { - // gather information that needs to be updated - $valuesToUpdate = array(); - $incrementActions = false; - $sqlActionUpdate = ''; - - if(!empty($idActionName)) - { - $valuesToUpdate['visit_exit_idaction_name'] = (int)$idActionName; - } - if($idActionUrl !== false) - { - $valuesToUpdate['visit_exit_idaction_url'] = $idActionUrl; - $incrementActions = true; - } - if($actionType == Piwik_Tracker_Action::TYPE_SITE_SEARCH) - { - $sqlActionUpdate .= "visit_total_searches = visit_total_searches + 1, "; - $incrementActions = true; - } - if($incrementActions) - { - $sqlActionUpdate .= "visit_total_actions = visit_total_actions + 1, "; - } - - $datetimeServer = Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()); - printDebug("Visit is known (IP = ".Piwik_IP::N2P($this->getVisitorIp()).")"); + // Set to true when we set some custom variables from the cookie + protected $customVariablesSetFromRequest = false; + + /** + * @var Piwik_Tracker_GoalManager + */ + protected $goalManager; + + public function __construct($forcedIpString = null, $forcedDateTime = null, $authenticated = false) + { + $this->timestamp = time(); + if (!empty($forcedDateTime)) { + if (!is_numeric($forcedDateTime)) { + $forcedDateTime = strtotime($forcedDateTime); + } + $this->timestamp = $forcedDateTime; + } + $ipString = $forcedIpString; + if (empty($ipString)) { + $ipString = Piwik_IP::getIpFromHeader(); + } + + $ip = Piwik_IP::P2N($ipString); + $this->ip = $ip; + + $this->authenticated = $authenticated; + } + + function setForcedVisitorId($visitorId) + { + $this->forcedVisitorId = $visitorId; + } + + function setRequest($requestArray) + { + $this->request = $requestArray; + + $idsite = Piwik_Common::getRequestVar('idsite', 0, 'int', $this->request); + Piwik_PostEvent('Tracker.setRequest.idSite', $idsite, $requestArray); + if ($idsite <= 0) { + throw new Exception('Invalid idSite'); + } + $this->idsite = $idsite; + + // When the 'url' and referer url parameter are not given, we might be in the 'Simple Image Tracker' mode. + // The URL can default to the Referer, which will be in this case + // the URL of the page containing the Simple Image beacon + if (empty($this->request['urlref']) + && empty($this->request['url']) + ) { + $this->request['url'] = @$_SERVER['HTTP_REFERER']; + } + } + + /** + * Main algorithm to handle the visit. + * + * Once we have the visitor information, we have to determine if the visit is a new or a known visit. + * + * 1) When the last action was done more than 30min ago, + * or if the visitor is new, then this is a new visit. + * + * 2) If the last action is less than 30min ago, then the same visit is going on. + * Because the visit goes on, we can get the time spent during the last action. + * + * NB: + * - In the case of a new visit, then the time spent + * during the last action of the previous visit is unknown. + * + * - In the case of a new visit but with a known visitor, + * we can set the 'returning visitor' flag. + * + * In all the cases we set a cookie to the visitor with the new information. + */ + public function handle() + { + // the IP is needed by isExcluded() and GoalManager->recordGoals() + $this->visitorInfo['location_ip'] = $this->ip; + + if ($this->isExcluded()) { + return; + } + + // Anonymize IP (after testing for IP exclusion) + $ip = $this->ip; + Piwik_PostEvent('Tracker.Visit.setVisitorIp', $ip); + $this->visitorInfo['location_ip'] = $ip; + + $this->visitorCustomVariables = self::getCustomVariables($scope = 'visit', $this->request); + if (!empty($this->visitorCustomVariables)) { + printDebug("Visit level Custom Variables: "); + printDebug($this->visitorCustomVariables); + $this->customVariablesSetFromRequest = true; + } + + $this->goalManager = new Piwik_Tracker_GoalManager(); + + $someGoalsConverted = $visitIsConverted = false; + $idActionUrl = $idActionName = $actionType = false; + $action = null; + + $this->goalManager->init($this->request); + + $requestIsManualGoalConversion = ($this->goalManager->idGoal > 0); + $requestIsEcommerce = $this->goalManager->requestIsEcommerce; + if ($requestIsEcommerce) { + $someGoalsConverted = true; + + // Mark the visit as Converted only if it is an order (not for a Cart update) + if ($this->goalManager->isGoalAnOrder) { + $visitIsConverted = true; + } + } // this request is from the JS call to piwikTracker.trackGoal() + elseif ($requestIsManualGoalConversion) { + $someGoalsConverted = $this->goalManager->detectGoalId($this->idsite); + $visitIsConverted = $someGoalsConverted; + // if we find a idgoal in the URL, but then the goal is not valid, this is most likely a fake request + if (!$someGoalsConverted) { + printDebug('Invalid goal tracking request for goal id = ' . $this->goalManager->idGoal); + unset($this->goalManager); + return; + } + } // normal page view, potentially triggering a URL matching goal + else { + $action = $this->newAction(); + $this->handleAction($action); + $someGoalsConverted = $this->goalManager->detectGoalsMatchingUrl($this->idsite, $action); + $visitIsConverted = $someGoalsConverted; + + $action->loadIdActionNameAndUrl(); + $idActionUrl = $action->getIdActionUrl(); + if ($idActionUrl !== null) { + $idActionUrl = (int)$idActionUrl; + } + $idActionName = (int)$action->getIdActionName(); + $actionType = $action->getActionType(); + } + + // the visitor and session + $this->recognizeTheVisitor(); + + $isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit(); + + if (!$isLastActionInTheSameVisit) { + printDebug("Visitor detected, but last action was more than 30 minutes ago..."); + } + // Known visit when: + // ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor + // OR + // - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor() + // ) + // AND + // - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit() + if ($this->isVisitorKnown() + && $isLastActionInTheSameVisit + ) { + $idRefererActionUrl = $this->visitorInfo['visit_exit_idaction_url']; + $idRefererActionName = $this->visitorInfo['visit_exit_idaction_name']; + try { + $this->handleKnownVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted); + if (!is_null($action)) { + $action->record($this->visitorInfo['idvisit'], + $this->visitorInfo['idvisitor'], + $idRefererActionUrl, + $idRefererActionName, + $this->visitorInfo['time_spent_ref_action'] + ); + } + } catch (Piwik_Tracker_Visit_VisitorNotFoundInDatabase $e) { + + // There is an edge case when: + // - two manual goal conversions happen in the same second + // - which result in handleKnownVisit throwing the exception + // because the UPDATE didn't affect any rows (one row was found, but not updated since no field changed) + // - the exception is caught here and will result in a new visit incorrectly + // In this case, we cancel the current conversion to be recorded: + if ($requestIsManualGoalConversion + || $requestIsEcommerce + ) { + $someGoalsConverted = $visitIsConverted = false; + } // When the row wasn't found in the logs, and this is a pageview or + // goal matching URL, we force a new visitor + else { + $this->visitorKnown = false; + } + } + } + + // New visit when: + // - the visitor has the Piwik cookie but the last action was performed more than 30 min ago @see isLastActionInTheSameVisit() + // - the visitor doesn't have the Piwik cookie, and couldn't be matched in @see recognizeTheVisitor() + // - the visitor does have the Piwik cookie but the idcookie and idvisit found in the cookie didn't match to any existing visit in the DB + if (!$this->isVisitorKnown() + || !$isLastActionInTheSameVisit + ) { + $this->handleNewVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted); + if (!is_null($action)) { + $action->record($this->visitorInfo['idvisit'], $this->visitorInfo['idvisitor'], 0, 0, 0); + } + } + + // update the cookie with the new visit information + $this->setThirdPartyCookie(); + + // record the goals if applicable + if ($someGoalsConverted) { + $refererTimestamp = Piwik_Common::getRequestVar('_refts', 0, 'int', $this->request); + $refererUrl = Piwik_Common::getRequestVar('_ref', '', 'string', $this->request); + $refererCampaignName = trim(urldecode(Piwik_Common::getRequestVar('_rcn', '', 'string', $this->request))); + $refererCampaignKeyword = trim(urldecode(Piwik_Common::getRequestVar('_rck', '', 'string', $this->request))); + + $this->goalManager->recordGoals( + $this->idsite, + $this->visitorInfo, + $this->visitorCustomVariables, + $action, + $refererTimestamp, + $refererUrl, + $refererCampaignName, + $refererCampaignKeyword, + $this->getBrowserLanguage() + ); + } + unset($this->goalManager); + unset($action); + $this->printCookie(); + } + + protected function printCookie() + { + printDebug($this->cookie); + } + + protected function handleAction($action) + { + $action->setIdSite($this->idsite); + $action->setRequest($this->request); + $action->setTimestamp($this->getCurrentTimestamp()); + $action->init(); + + if ($this->detectActionIsOutlinkOnAliasHost($action)) { + printDebug("Info: The outlink URL host is one of the known host for this website. "); + } + if (isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) { + $type = Piwik_Tracker_Action::getActionTypeName($action->getActionType()); + printDebug("Action is a $type, + Action name = " . $action->getActionName() . ", + Action URL = " . $action->getActionUrl()); + } + } + + /** + * In the case of a known visit, we have to do the following actions: + * + * 1) Insert the new action + * 2) Update the visit information + * + * This method triggers two events: + * + * Tracker.knownVisitorUpdate is triggered before the visit information is updated + * Event data is an array with the values to be updated (could be changed by plugins) + * + * Tracker.knownVisitorInformation is triggered after saving the new visit data + * Even data is an array with updated information about the visit + * @param $idActionUrl + * @param $idActionName + * @param $actionType + * @param $visitIsConverted + * @throws Piwik_Tracker_Visit_VisitorNotFoundInDatabase + */ + protected function handleKnownVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted) + { + // gather information that needs to be updated + $valuesToUpdate = array(); + $incrementActions = false; + $sqlActionUpdate = ''; + + if (!empty($idActionName)) { + $valuesToUpdate['visit_exit_idaction_name'] = (int)$idActionName; + } + if ($idActionUrl !== false) { + $valuesToUpdate['visit_exit_idaction_url'] = $idActionUrl; + $incrementActions = true; + } + if ($actionType == Piwik_Tracker_Action::TYPE_SITE_SEARCH) { + $sqlActionUpdate .= "visit_total_searches = visit_total_searches + 1, "; + $incrementActions = true; + } + if ($incrementActions) { + $sqlActionUpdate .= "visit_total_actions = visit_total_actions + 1, "; + } + + $datetimeServer = Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()); + printDebug("Visit is known (IP = " . Piwik_IP::N2P($this->getVisitorIp()) . ")"); // Add 1 so it's always > 0 - $visitTotalTime = 1 + $this->getCurrentTimestamp() - $this->visitorInfo['visit_first_action_time']; - $valuesToUpdate['visit_last_action_time'] = $datetimeServer; - $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime($visitTotalTime); - - // Goal conversion - if($visitIsConverted) - { - $valuesToUpdate['visit_goal_converted'] = 1; - // If a pageview and goal conversion in the same second, with previously a goal conversion recorded - // the request would not "update" the row since all values are the same as previous - // therefore the request below throws exception, instead we make sure the UPDATE will affect the row - $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime( - $valuesToUpdate['visit_total_time'] - + $this->goalManager->idGoal - // +2 to offset idgoal=-1 and idgoal=0 - + 2 ); - } - - // Might update the idvisitor when it was forced or overwritten for this visit - if(strlen($this->visitorInfo['idvisitor']) == Piwik_Tracker::LENGTH_BINARY_ID) - { - $valuesToUpdate['idvisitor'] = $this->visitorInfo['idvisitor']; - } - - // Ecommerce buyer status - $valuesToUpdate['visit_goal_buyer'] = $this->goalManager->getBuyerType($this->visitorInfo['visit_goal_buyer']); - - // Custom Variables overwrite previous values on each page view - $valuesToUpdate = array_merge($valuesToUpdate, $this->visitorCustomVariables); - - // trigger event before update - Piwik_PostEvent('Tracker.knownVisitorUpdate', $valuesToUpdate); - - // Will be updated in cookie - $timeSpentRefererAction = $this->getCurrentTimestamp() - $this->visitorInfo['visit_last_action_time']; - if($timeSpentRefererAction > Piwik_Config::getInstance()->Tracker['visit_standard_length']) - { - $timeSpentRefererAction = 0; - } - $this->visitorInfo['time_spent_ref_action'] = $timeSpentRefererAction; - - // update visitorInfo - foreach($valuesToUpdate AS $name => $value) - { - $this->visitorInfo[$name] = $value; - } - - // build sql query - $updateParts = $sqlBind = array(); - - foreach($valuesToUpdate AS $name => $value) - { - $updateParts[] = $name." = ?"; - $sqlBind[] = $value; - } - $sqlQuery = "UPDATE ". Piwik_Common::prefixTable('log_visit')." - SET $sqlActionUpdate ".implode($updateParts, ', ')." + $visitTotalTime = 1 + $this->getCurrentTimestamp() - $this->visitorInfo['visit_first_action_time']; + $valuesToUpdate['visit_last_action_time'] = $datetimeServer; + $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime($visitTotalTime); + + // Goal conversion + if ($visitIsConverted) { + $valuesToUpdate['visit_goal_converted'] = 1; + // If a pageview and goal conversion in the same second, with previously a goal conversion recorded + // the request would not "update" the row since all values are the same as previous + // therefore the request below throws exception, instead we make sure the UPDATE will affect the row + $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime( + $valuesToUpdate['visit_total_time'] + + $this->goalManager->idGoal + // +2 to offset idgoal=-1 and idgoal=0 + + 2); + } + + // Might update the idvisitor when it was forced or overwritten for this visit + if (strlen($this->visitorInfo['idvisitor']) == Piwik_Tracker::LENGTH_BINARY_ID) { + $valuesToUpdate['idvisitor'] = $this->visitorInfo['idvisitor']; + } + + // Ecommerce buyer status + $valuesToUpdate['visit_goal_buyer'] = $this->goalManager->getBuyerType($this->visitorInfo['visit_goal_buyer']); + + // Custom Variables overwrite previous values on each page view + $valuesToUpdate = array_merge($valuesToUpdate, $this->visitorCustomVariables); + + // trigger event before update + Piwik_PostEvent('Tracker.knownVisitorUpdate', $valuesToUpdate); + + // Will be updated in cookie + $timeSpentRefererAction = $this->getCurrentTimestamp() - $this->visitorInfo['visit_last_action_time']; + if ($timeSpentRefererAction > Piwik_Config::getInstance()->Tracker['visit_standard_length']) { + $timeSpentRefererAction = 0; + } + $this->visitorInfo['time_spent_ref_action'] = $timeSpentRefererAction; + + // update visitorInfo + foreach ($valuesToUpdate AS $name => $value) { + $this->visitorInfo[$name] = $value; + } + + // build sql query + $updateParts = $sqlBind = array(); + + foreach ($valuesToUpdate AS $name => $value) { + $updateParts[] = $name . " = ?"; + $sqlBind[] = $value; + } + $sqlQuery = "UPDATE " . Piwik_Common::prefixTable('log_visit') . " + SET $sqlActionUpdate " . implode($updateParts, ', ') . " WHERE idsite = ? AND idvisit = ?"; - array_push($sqlBind, $this->idsite, (int)$this->visitorInfo['idvisit'] ); - - $result = Piwik_Tracker::getDatabase()->query($sqlQuery, $sqlBind); - - $this->visitorInfo['visit_last_action_time'] = $this->getCurrentTimestamp(); - - // Debug output - if(isset($valuesToUpdate['idvisitor'])) - { - $valuesToUpdate['idvisitor'] = bin2hex($valuesToUpdate['idvisitor']); - } - printDebug('Updating existing visit: '. var_export($valuesToUpdate, true) ); - - if(Piwik_Tracker::getDatabase()->rowCount($result) == 0) - { - printDebug("Visitor with this idvisit wasn't found in the DB."); - printDebug("$sqlQuery --- ");printDebug($sqlBind); - throw new Piwik_Tracker_Visit_VisitorNotFoundInDatabase( - "The visitor with idvisitor=".bin2hex($this->visitorInfo['idvisitor'])." and idvisit=".$this->visitorInfo['idvisit'] - ." wasn't found in the DB, we fallback to a new visitor"); - } - - Piwik_PostEvent('Tracker.knownVisitorInformation', $this->visitorInfo); - } - - protected function isTimestampValid($time) - { - return $time <= $this->getCurrentTimestamp() - && $time > $this->getCurrentTimestamp() - 10*365*86400; - } - - /** - * In the case of a new visit, we have to do the following actions: - * - * 1) Insert the new action - * - * 2) Insert the visit information - * @param $idActionUrl - * @param $idActionName - * @param $actionType - * @param $visitIsConverted - */ - protected function handleNewVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted) - { - printDebug("New Visit (IP = ".Piwik_IP::N2P($this->getVisitorIp()).")"); - - $localTimes = array( - 'h' => (string) Piwik_Common::getRequestVar( 'h', $this->getCurrentDate("H"), 'int', $this->request), - 'i' => (string) Piwik_Common::getRequestVar( 'm', $this->getCurrentDate("i"), 'int', $this->request), - 's' => (string) Piwik_Common::getRequestVar( 's', $this->getCurrentDate("s"), 'int', $this->request) - ); - foreach($localTimes as $k => $time) - { - if(strlen($time) == 1) - { - $localTimes[$k] = '0' . $time; - } - } - $localTime = $localTimes['h'] .':'. $localTimes['i'] .':'. $localTimes['s']; - - $idcookie = $this->getVisitorIdcookie(); - - $defaultTimeOnePageVisit = Piwik_Config::getInstance()->Tracker['default_time_one_page_visit']; - - // Days since first visit - $cookieFirstVisitTimestamp = Piwik_Common::getRequestVar('_idts', 0, 'int', $this->request); - if(!$this->isTimestampValid($cookieFirstVisitTimestamp)) - { - $cookieFirstVisitTimestamp = $this->getCurrentTimestamp(); - } - $daysSinceFirstVisit = round(($this->getCurrentTimestamp() - $cookieFirstVisitTimestamp)/86400, $precision = 0); - if($daysSinceFirstVisit < 0) $daysSinceFirstVisit = 0; - - // Number of Visits - $visitCount = Piwik_Common::getRequestVar('_idvc', 1, 'int', $this->request); - if($visitCount < 1) $visitCount = 1; - - // Days since last visit - $daysSinceLastVisit = 0; - $lastVisitTimestamp = Piwik_Common::getRequestVar('_viewts', 0, 'int', $this->request); - if($this->isTimestampValid($lastVisitTimestamp)) - { - $daysSinceLastVisit = round(($this->getCurrentTimestamp() - $lastVisitTimestamp)/86400, $precision = 0); - if($daysSinceLastVisit < 0) $daysSinceLastVisit = 0; - } - - $daysSinceLastOrder = 0; - $isReturningCustomer = false; - $lastOrderTimestamp = Piwik_Common::getRequestVar('_ects', 0, 'int', $this->request); - if($this->isTimestampValid($lastOrderTimestamp)) - { - $daysSinceLastOrder = round(($this->getCurrentTimestamp() - $lastOrderTimestamp)/86400, $precision = 0); - if($daysSinceLastOrder < 0) - { - $daysSinceLastOrder = 0; - } - $isReturningCustomer = true; - } - - // User settings - $userInfo = $this->getUserSettingsInformation(); - - // Referrer data - $referrer = new Piwik_Tracker_Visit_Referer(); - $refererUrl = Piwik_Common::getRequestVar( 'urlref', '', 'string', $this->request); - $currentUrl = Piwik_Common::getRequestVar( 'url', '', 'string', $this->request); - $refererInfo = $referrer->getRefererInformation($refererUrl, $currentUrl, $this->idsite); - - $visitorReturning = $isReturningCustomer - ? 2 /* Returning customer */ - : ($visitCount > 1 || $this->isVisitorKnown() || $daysSinceLastVisit > 0 - ? 1 /* Returning */ - : 0 /* New */ ); - /** - * Save the visitor - */ - $this->visitorInfo = array( - 'idsite' => $this->idsite, - 'visitor_localtime' => $localTime, - 'idvisitor' => $idcookie, - 'visitor_returning' => $visitorReturning, - 'visitor_count_visits' => $visitCount, - 'visitor_days_since_last' => $daysSinceLastVisit, - 'visitor_days_since_order' => $daysSinceLastOrder, - 'visitor_days_since_first' => $daysSinceFirstVisit, - 'visit_first_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()), - 'visit_last_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()), - 'visit_entry_idaction_url' => (int)$idActionUrl, - 'visit_entry_idaction_name' => (int)$idActionName, - 'visit_exit_idaction_url' => (int)$idActionUrl, - 'visit_exit_idaction_name' => (int)$idActionName, - 'visit_total_actions' => in_array($actionType, - array(Piwik_Tracker_Action::TYPE_ACTION_URL, - Piwik_Tracker_Action::TYPE_DOWNLOAD, - Piwik_Tracker_Action::TYPE_OUTLINK, - Piwik_Tracker_Action::TYPE_SITE_SEARCH)) - ? 1 : 0, // if visit starts with something else (e.g. ecommerce order), don't record as an action - 'visit_total_searches' => $actionType == Piwik_Tracker_Action::TYPE_SITE_SEARCH ? 1 : 0, - 'visit_total_time' => self::cleanupVisitTotalTime($defaultTimeOnePageVisit), - 'visit_goal_converted' => $visitIsConverted ? 1: 0, - 'visit_goal_buyer' => $this->goalManager->getBuyerType(), - 'referer_type' => $refererInfo['referer_type'], - 'referer_name' => $refererInfo['referer_name'], - 'referer_url' => $refererInfo['referer_url'], - 'referer_keyword' => $refererInfo['referer_keyword'], - 'config_id' => $userInfo['config_id'], - 'config_os' => $userInfo['config_os'], - 'config_browser_name' => $userInfo['config_browser_name'], - 'config_browser_version' => $userInfo['config_browser_version'], - 'config_resolution' => $userInfo['config_resolution'], - 'config_pdf' => $userInfo['config_pdf'], - 'config_flash' => $userInfo['config_flash'], - 'config_java' => $userInfo['config_java'], - 'config_director' => $userInfo['config_director'], - 'config_quicktime' => $userInfo['config_quicktime'], - 'config_realplayer' => $userInfo['config_realplayer'], - 'config_windowsmedia' => $userInfo['config_windowsmedia'], - 'config_gears' => $userInfo['config_gears'], - 'config_silverlight' => $userInfo['config_silverlight'], - 'config_cookie' => $userInfo['config_cookie'], - 'location_ip' => $this->getVisitorIp(), - 'location_browser_lang' => $userInfo['location_browser_lang'], - ); - - // add optional location components - $location = $this->getVisitorLocation($userInfo['location_browser_lang']); - $this->updateVisitInfoWithLocation($location); - - // Add Custom variable key,value to the visitor array - $this->visitorInfo = array_merge($this->visitorInfo, $this->visitorCustomVariables); - - Piwik_PostEvent('Tracker.newVisitorInformation', $this->visitorInfo); - - $debugVisitInfo = $this->visitorInfo; - $debugVisitInfo['idvisitor'] = bin2hex($debugVisitInfo['idvisitor']); - $debugVisitInfo['config_id'] = bin2hex($debugVisitInfo['config_id']); - printDebug($debugVisitInfo); - - $this->saveVisitorInformation(); - } + array_push($sqlBind, $this->idsite, (int)$this->visitorInfo['idvisit']); + + $result = Piwik_Tracker::getDatabase()->query($sqlQuery, $sqlBind); + + $this->visitorInfo['visit_last_action_time'] = $this->getCurrentTimestamp(); + + // Debug output + if (isset($valuesToUpdate['idvisitor'])) { + $valuesToUpdate['idvisitor'] = bin2hex($valuesToUpdate['idvisitor']); + } + printDebug('Updating existing visit: ' . var_export($valuesToUpdate, true)); + + if (Piwik_Tracker::getDatabase()->rowCount($result) == 0) { + printDebug("Visitor with this idvisit wasn't found in the DB."); + printDebug("$sqlQuery --- "); + printDebug($sqlBind); + throw new Piwik_Tracker_Visit_VisitorNotFoundInDatabase( + "The visitor with idvisitor=" . bin2hex($this->visitorInfo['idvisitor']) . " and idvisit=" . $this->visitorInfo['idvisit'] + . " wasn't found in the DB, we fallback to a new visitor"); + } + + Piwik_PostEvent('Tracker.knownVisitorInformation', $this->visitorInfo); + } + + protected function isTimestampValid($time) + { + return $time <= $this->getCurrentTimestamp() + && $time > $this->getCurrentTimestamp() - 10 * 365 * 86400; + } + + /** + * In the case of a new visit, we have to do the following actions: + * + * 1) Insert the new action + * + * 2) Insert the visit information + * @param $idActionUrl + * @param $idActionName + * @param $actionType + * @param $visitIsConverted + */ + protected function handleNewVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted) + { + printDebug("New Visit (IP = " . Piwik_IP::N2P($this->getVisitorIp()) . ")"); + + $localTimes = array( + 'h' => (string)Piwik_Common::getRequestVar('h', $this->getCurrentDate("H"), 'int', $this->request), + 'i' => (string)Piwik_Common::getRequestVar('m', $this->getCurrentDate("i"), 'int', $this->request), + 's' => (string)Piwik_Common::getRequestVar('s', $this->getCurrentDate("s"), 'int', $this->request) + ); + foreach ($localTimes as $k => $time) { + if (strlen($time) == 1) { + $localTimes[$k] = '0' . $time; + } + } + $localTime = $localTimes['h'] . ':' . $localTimes['i'] . ':' . $localTimes['s']; + + $idcookie = $this->getVisitorIdcookie(); + + $defaultTimeOnePageVisit = Piwik_Config::getInstance()->Tracker['default_time_one_page_visit']; + + // Days since first visit + $cookieFirstVisitTimestamp = Piwik_Common::getRequestVar('_idts', 0, 'int', $this->request); + if (!$this->isTimestampValid($cookieFirstVisitTimestamp)) { + $cookieFirstVisitTimestamp = $this->getCurrentTimestamp(); + } + $daysSinceFirstVisit = round(($this->getCurrentTimestamp() - $cookieFirstVisitTimestamp) / 86400, $precision = 0); + if ($daysSinceFirstVisit < 0) $daysSinceFirstVisit = 0; + + // Number of Visits + $visitCount = Piwik_Common::getRequestVar('_idvc', 1, 'int', $this->request); + if ($visitCount < 1) $visitCount = 1; + + // Days since last visit + $daysSinceLastVisit = 0; + $lastVisitTimestamp = Piwik_Common::getRequestVar('_viewts', 0, 'int', $this->request); + if ($this->isTimestampValid($lastVisitTimestamp)) { + $daysSinceLastVisit = round(($this->getCurrentTimestamp() - $lastVisitTimestamp) / 86400, $precision = 0); + if ($daysSinceLastVisit < 0) $daysSinceLastVisit = 0; + } + + $daysSinceLastOrder = 0; + $isReturningCustomer = false; + $lastOrderTimestamp = Piwik_Common::getRequestVar('_ects', 0, 'int', $this->request); + if ($this->isTimestampValid($lastOrderTimestamp)) { + $daysSinceLastOrder = round(($this->getCurrentTimestamp() - $lastOrderTimestamp) / 86400, $precision = 0); + if ($daysSinceLastOrder < 0) { + $daysSinceLastOrder = 0; + } + $isReturningCustomer = true; + } + + // User settings + $userInfo = $this->getUserSettingsInformation(); + + // Referrer data + $referrer = new Piwik_Tracker_Visit_Referer(); + $refererUrl = Piwik_Common::getRequestVar('urlref', '', 'string', $this->request); + $currentUrl = Piwik_Common::getRequestVar('url', '', 'string', $this->request); + $refererInfo = $referrer->getRefererInformation($refererUrl, $currentUrl, $this->idsite); + + $visitorReturning = $isReturningCustomer + ? 2 /* Returning customer */ + : ($visitCount > 1 || $this->isVisitorKnown() || $daysSinceLastVisit > 0 + ? 1 /* Returning */ + : 0 /* New */); + /** + * Save the visitor + */ + $this->visitorInfo = array( + 'idsite' => $this->idsite, + 'visitor_localtime' => $localTime, + 'idvisitor' => $idcookie, + 'visitor_returning' => $visitorReturning, + 'visitor_count_visits' => $visitCount, + 'visitor_days_since_last' => $daysSinceLastVisit, + 'visitor_days_since_order' => $daysSinceLastOrder, + 'visitor_days_since_first' => $daysSinceFirstVisit, + 'visit_first_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()), + 'visit_last_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()), + 'visit_entry_idaction_url' => (int)$idActionUrl, + 'visit_entry_idaction_name' => (int)$idActionName, + 'visit_exit_idaction_url' => (int)$idActionUrl, + 'visit_exit_idaction_name' => (int)$idActionName, + 'visit_total_actions' => in_array($actionType, + array(Piwik_Tracker_Action::TYPE_ACTION_URL, + Piwik_Tracker_Action::TYPE_DOWNLOAD, + Piwik_Tracker_Action::TYPE_OUTLINK, + Piwik_Tracker_Action::TYPE_SITE_SEARCH)) + ? 1 : 0, // if visit starts with something else (e.g. ecommerce order), don't record as an action + 'visit_total_searches' => $actionType == Piwik_Tracker_Action::TYPE_SITE_SEARCH ? 1 : 0, + 'visit_total_time' => self::cleanupVisitTotalTime($defaultTimeOnePageVisit), + 'visit_goal_converted' => $visitIsConverted ? 1 : 0, + 'visit_goal_buyer' => $this->goalManager->getBuyerType(), + 'referer_type' => $refererInfo['referer_type'], + 'referer_name' => $refererInfo['referer_name'], + 'referer_url' => $refererInfo['referer_url'], + 'referer_keyword' => $refererInfo['referer_keyword'], + 'config_id' => $userInfo['config_id'], + 'config_os' => $userInfo['config_os'], + 'config_browser_name' => $userInfo['config_browser_name'], + 'config_browser_version' => $userInfo['config_browser_version'], + 'config_resolution' => $userInfo['config_resolution'], + 'config_pdf' => $userInfo['config_pdf'], + 'config_flash' => $userInfo['config_flash'], + 'config_java' => $userInfo['config_java'], + 'config_director' => $userInfo['config_director'], + 'config_quicktime' => $userInfo['config_quicktime'], + 'config_realplayer' => $userInfo['config_realplayer'], + 'config_windowsmedia' => $userInfo['config_windowsmedia'], + 'config_gears' => $userInfo['config_gears'], + 'config_silverlight' => $userInfo['config_silverlight'], + 'config_cookie' => $userInfo['config_cookie'], + 'location_ip' => $this->getVisitorIp(), + 'location_browser_lang' => $userInfo['location_browser_lang'], + ); + + // add optional location components + $location = $this->getVisitorLocation($userInfo['location_browser_lang']); + $this->updateVisitInfoWithLocation($location); + + // Add Custom variable key,value to the visitor array + $this->visitorInfo = array_merge($this->visitorInfo, $this->visitorCustomVariables); + + Piwik_PostEvent('Tracker.newVisitorInformation', $this->visitorInfo); + + $debugVisitInfo = $this->visitorInfo; + $debugVisitInfo['idvisitor'] = bin2hex($debugVisitInfo['idvisitor']); + $debugVisitInfo['config_id'] = bin2hex($debugVisitInfo['config_id']); + printDebug($debugVisitInfo); + + $this->saveVisitorInformation(); + } static private function cleanupVisitTotalTime($t) { $t = (int)$t; $smallintMysqlLimit = 65534; - if($t > $smallintMysqlLimit) { + if ($t > $smallintMysqlLimit) { $t = $smallintMysqlLimit; } return $t; } - - /** - * Returns the location of the visitor, based on the visitor's IP and browser language. - * - * @param string $browserLang - * @return array See Piwik_UserCountry_LocationProvider::getLocation for more info. - */ - private function getVisitorLocation( $browserLang ) - { - $location = array(); - $userInfo = array('lang' => $browserLang, 'ip' => Piwik_IP::N2P($this->getVisitorIp())); - Piwik_PostEvent('Tracker.getVisitorLocation', $location, $userInfo); - - if($this->authenticated) - { - // check for location override query parameters (ie, lat, long, country, region, city) - $locationOverrideParams = array( - 'country' => array('string', Piwik_UserCountry_LocationProvider::COUNTRY_CODE_KEY), - 'region' => array('string', Piwik_UserCountry_LocationProvider::REGION_CODE_KEY), - 'city' => array('string', Piwik_UserCountry_LocationProvider::CITY_NAME_KEY), - 'lat' => array('float', Piwik_UserCountry_LocationProvider::LATITUDE_KEY), - 'long' => array('float', Piwik_UserCountry_LocationProvider::LONGITUDE_KEY), - ); - foreach ($locationOverrideParams as $queryParamName => $info) - { - list($type, $locationResultKey) = $info; - - $value = Piwik_Common::getRequestVar($queryParamName, false, $type, $this->request); - if (!empty($value)) - { - $location[$locationResultKey] = $value; - } - } - } - - if (empty($location['country_code'])) // sanity check - { - $location['country_code'] = self::UNKNOWN_CODE; - } - - return $location; - } - - /** - * Sets visitor info array with location info. - * - * @param array $location See Piwik_UserCountry_LocationProvider::getLocation for more info. - */ - private function updateVisitInfoWithLocation( $location ) - { - static $logVisitToLowerLocationMapping = array( - 'location_country' => Piwik_UserCountry_LocationProvider::COUNTRY_CODE_KEY, - ); - - static $logVisitToLocationMapping = array( - 'location_region' => Piwik_UserCountry_LocationProvider::REGION_CODE_KEY, - 'location_city' => Piwik_UserCountry_LocationProvider::CITY_NAME_KEY, - 'location_latitude' => Piwik_UserCountry_LocationProvider::LATITUDE_KEY, - 'location_longitude' => Piwik_UserCountry_LocationProvider::LONGITUDE_KEY, - ); - - foreach ($logVisitToLowerLocationMapping as $column => $locationKey) - { - if (!empty($location[$locationKey])) - { - $this->visitorInfo[$column] = strtolower($location[$locationKey]); - } - } - - foreach ($logVisitToLocationMapping as $column => $locationKey) - { - if (!empty($location[$locationKey])) - { - $this->visitorInfo[$column] = $location[$locationKey]; - } - } - - // if the location has provider/organization info, set it - if (!empty($location[Piwik_UserCountry_LocationProvider::ISP_KEY])) - { - $providerValue = $location[Piwik_UserCountry_LocationProvider::ISP_KEY]; - - // if the org is set and not the same as the isp, add it to the provider value - if (!empty($location[Piwik_UserCountry_LocationProvider::ORG_KEY]) - && $location[Piwik_UserCountry_LocationProvider::ORG_KEY] != $providerValue) - { - $providerValue .= ' - ' . $location[Piwik_UserCountry_LocationProvider::ORG_KEY]; - } - } - else if (!empty($location[Piwik_UserCountry_LocationProvider::ORG_KEY])) - { - $providerValue = $location[Piwik_UserCountry_LocationProvider::ORG_KEY]; - } - - if (isset($providerValue)) - { - $this->visitorInfo['location_provider'] = $providerValue; - } - } - - /** - * Save new visitor information to log_visit table. - * Provides pre- and post- event hooks (Tracker.saveVisitorInformation and Tracker.saveVisitorInformation.end) for plugins - */ - protected function saveVisitorInformation() - { - Piwik_PostEvent('Tracker.saveVisitorInformation', $this->visitorInfo); - - $this->visitorInfo['location_browser_lang'] = substr($this->visitorInfo['location_browser_lang'], 0, 20); - $this->visitorInfo['referer_name'] = substr($this->visitorInfo['referer_name'], 0, 70); - $this->visitorInfo['referer_keyword'] = substr($this->visitorInfo['referer_keyword'], 0, 255); - $this->visitorInfo['config_resolution'] = substr($this->visitorInfo['config_resolution'], 0, 9); - - $fields = implode(", ", array_keys($this->visitorInfo)); - $values = Piwik_Common::getSqlStringFieldsArray($this->visitorInfo); - - $sql = "INSERT INTO ".Piwik_Common::prefixTable('log_visit'). " ($fields) VALUES ($values)"; - $bind = array_values($this->visitorInfo); - Piwik_Tracker::getDatabase()->query( $sql, $bind); - - $idVisit = Piwik_Tracker::getDatabase()->lastInsertId(); - $this->visitorInfo['idvisit'] = $idVisit; - - $this->visitorInfo['visit_first_action_time'] = $this->getCurrentTimestamp(); - $this->visitorInfo['visit_last_action_time'] = $this->getCurrentTimestamp(); - - Piwik_PostEvent('Tracker.saveVisitorInformation.end', $this->visitorInfo); - } - - /** - * Returns visitor cookie - * - * @return binary - */ - protected function getVisitorIdcookie() - { - if($this->isVisitorKnown()) - { - return $this->visitorInfo['idvisitor']; - } - // If the visitor had a first party ID cookie, then we use this value - if(!empty($this->visitorInfo['idvisitor']) - && strlen($this->visitorInfo['idvisitor']) == Piwik_Tracker::LENGTH_BINARY_ID) - { - return $this->visitorInfo['idvisitor']; - } + + /** + * Returns the location of the visitor, based on the visitor's IP and browser language. + * + * @param string $browserLang + * @return array See Piwik_UserCountry_LocationProvider::getLocation for more info. + */ + private function getVisitorLocation($browserLang) + { + $location = array(); + $userInfo = array('lang' => $browserLang, 'ip' => Piwik_IP::N2P($this->getVisitorIp())); + Piwik_PostEvent('Tracker.getVisitorLocation', $location, $userInfo); + + if ($this->authenticated) { + // check for location override query parameters (ie, lat, long, country, region, city) + $locationOverrideParams = array( + 'country' => array('string', Piwik_UserCountry_LocationProvider::COUNTRY_CODE_KEY), + 'region' => array('string', Piwik_UserCountry_LocationProvider::REGION_CODE_KEY), + 'city' => array('string', Piwik_UserCountry_LocationProvider::CITY_NAME_KEY), + 'lat' => array('float', Piwik_UserCountry_LocationProvider::LATITUDE_KEY), + 'long' => array('float', Piwik_UserCountry_LocationProvider::LONGITUDE_KEY), + ); + foreach ($locationOverrideParams as $queryParamName => $info) { + list($type, $locationResultKey) = $info; + + $value = Piwik_Common::getRequestVar($queryParamName, false, $type, $this->request); + if (!empty($value)) { + $location[$locationResultKey] = $value; + } + } + } + + if (empty($location['country_code'])) // sanity check + { + $location['country_code'] = self::UNKNOWN_CODE; + } + + return $location; + } + + /** + * Sets visitor info array with location info. + * + * @param array $location See Piwik_UserCountry_LocationProvider::getLocation for more info. + */ + private function updateVisitInfoWithLocation($location) + { + static $logVisitToLowerLocationMapping = array( + 'location_country' => Piwik_UserCountry_LocationProvider::COUNTRY_CODE_KEY, + ); + + static $logVisitToLocationMapping = array( + 'location_region' => Piwik_UserCountry_LocationProvider::REGION_CODE_KEY, + 'location_city' => Piwik_UserCountry_LocationProvider::CITY_NAME_KEY, + 'location_latitude' => Piwik_UserCountry_LocationProvider::LATITUDE_KEY, + 'location_longitude' => Piwik_UserCountry_LocationProvider::LONGITUDE_KEY, + ); + + foreach ($logVisitToLowerLocationMapping as $column => $locationKey) { + if (!empty($location[$locationKey])) { + $this->visitorInfo[$column] = strtolower($location[$locationKey]); + } + } + + foreach ($logVisitToLocationMapping as $column => $locationKey) { + if (!empty($location[$locationKey])) { + $this->visitorInfo[$column] = $location[$locationKey]; + } + } + + // if the location has provider/organization info, set it + if (!empty($location[Piwik_UserCountry_LocationProvider::ISP_KEY])) { + $providerValue = $location[Piwik_UserCountry_LocationProvider::ISP_KEY]; + + // if the org is set and not the same as the isp, add it to the provider value + if (!empty($location[Piwik_UserCountry_LocationProvider::ORG_KEY]) + && $location[Piwik_UserCountry_LocationProvider::ORG_KEY] != $providerValue + ) { + $providerValue .= ' - ' . $location[Piwik_UserCountry_LocationProvider::ORG_KEY]; + } + } else if (!empty($location[Piwik_UserCountry_LocationProvider::ORG_KEY])) { + $providerValue = $location[Piwik_UserCountry_LocationProvider::ORG_KEY]; + } + + if (isset($providerValue)) { + $this->visitorInfo['location_provider'] = $providerValue; + } + } + + /** + * Save new visitor information to log_visit table. + * Provides pre- and post- event hooks (Tracker.saveVisitorInformation and Tracker.saveVisitorInformation.end) for plugins + */ + protected function saveVisitorInformation() + { + Piwik_PostEvent('Tracker.saveVisitorInformation', $this->visitorInfo); + + $this->visitorInfo['location_browser_lang'] = substr($this->visitorInfo['location_browser_lang'], 0, 20); + $this->visitorInfo['referer_name'] = substr($this->visitorInfo['referer_name'], 0, 70); + $this->visitorInfo['referer_keyword'] = substr($this->visitorInfo['referer_keyword'], 0, 255); + $this->visitorInfo['config_resolution'] = substr($this->visitorInfo['config_resolution'], 0, 9); + + $fields = implode(", ", array_keys($this->visitorInfo)); + $values = Piwik_Common::getSqlStringFieldsArray($this->visitorInfo); + + $sql = "INSERT INTO " . Piwik_Common::prefixTable('log_visit') . " ($fields) VALUES ($values)"; + $bind = array_values($this->visitorInfo); + Piwik_Tracker::getDatabase()->query($sql, $bind); + + $idVisit = Piwik_Tracker::getDatabase()->lastInsertId(); + $this->visitorInfo['idvisit'] = $idVisit; + + $this->visitorInfo['visit_first_action_time'] = $this->getCurrentTimestamp(); + $this->visitorInfo['visit_last_action_time'] = $this->getCurrentTimestamp(); + + Piwik_PostEvent('Tracker.saveVisitorInformation.end', $this->visitorInfo); + } + + /** + * Returns visitor cookie + * + * @return binary + */ + protected function getVisitorIdcookie() + { + if ($this->isVisitorKnown()) { + return $this->visitorInfo['idvisitor']; + } + // If the visitor had a first party ID cookie, then we use this value + if (!empty($this->visitorInfo['idvisitor']) + && strlen($this->visitorInfo['idvisitor']) == Piwik_Tracker::LENGTH_BINARY_ID + ) { + return $this->visitorInfo['idvisitor']; + } return Piwik_Common::hex2bin($this->generateUniqueVisitorId()); } @@ -782,333 +735,310 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface } /** - * Returns the visitor's IP address - * - * @return long - */ - protected function getVisitorIp() - { - return $this->visitorInfo['location_ip']; - } - - /** - * Returns the visitor's browser (user agent) - * - * @return string - */ - static public function getUserAgent($request) - { - $default = @$_SERVER['HTTP_USER_AGENT']; - return Piwik_Common::getRequestVar('ua', is_null($default) ? false : $default, 'string', $request); - } - - /** - * Returns the language the visitor is viewing. - * - * @return string browser language code, eg. "en-gb,en;q=0.5" - */ - protected function getBrowserLanguage() - { - return Piwik_Common::getRequestVar('lang', Piwik_Common::getBrowserLanguage(), 'string', $this->request); - } - - /** - * Returns the current date in the "Y-m-d" PHP format - * - * @param string $format - * @return string - */ - protected function getCurrentDate( $format = "Y-m-d") - { - return date($format, $this->getCurrentTimestamp() ); - } - - /** - * Returns the current Timestamp - * - * @return int - */ - protected function getCurrentTimestamp() - { - return $this->timestamp; - } - - /** - * Test if the current visitor is excluded from the statistics. - * - * Plugins can for example exclude visitors based on the - * - IP - * - If a given cookie is found - * - * @return bool True if the visit must not be saved, false otherwise - */ - protected function isExcluded() - { - $excluded = false; - - $ip = $this->getVisitorIp(); - $ua = $this->getUserAgent($this->request); - - /* - * Live/Bing/MSN bot and Googlebot are evolving to detect cloaked websites. - * As a result, these sophisticated bots exhibit characteristics of - * browsers (cookies enabled, executing JavaScript, etc). - */ - $allowBots = Piwik_Common::getRequestVar('bots', false) != false; - if ( !$allowBots - && (strpos($ua, 'Googlebot') !== false // Googlebot - || strpos($ua, 'Google Web Preview') !== false // Google Instant - || strpos($ua, 'bingbot') !== false // Bingbot - || strpos($ua, 'YottaaMonitor') !== false // Yottaa - || Piwik_IP::isIpInRange($ip, - array( - // Live/Bing/MSN - '64.4.0.0/18', - '65.52.0.0/14', - '157.54.0.0/15', - '157.56.0.0/14', - '157.60.0.0/16', - '207.46.0.0/16', - '207.68.128.0/18', - '207.68.192.0/20', - '131.253.26.0/20', - '131.253.24.0/20', - // Yahoo - '72.30.198.0/20', - '72.30.196.0/20', - '98.137.207.0/20', - // Chinese bot hammering websites - '1.202.218.8' - )))) - { - printDebug('Search bot detected, visit excluded'); - $excluded = true; - } - - /* - * Requests built with piwik.js will contain a rec=1 parameter. This is used as - * an indication that the request is made by a JS enabled device. By default, Piwik - * doesn't track non-JS visitors. - */ - if(!$excluded) - { - $parameterForceRecord = 'rec'; - $toRecord = Piwik_Common::getRequestVar($parameterForceRecord, false, 'int', $this->request); - if(!$toRecord) - { - printDebug(@$_SERVER['REQUEST_METHOD'].' parameter '.$parameterForceRecord.' not found in URL, request excluded'); - $excluded = true; - printDebug("'$parameterForceRecord' parameter not found."); - } - } - - /* custom filters can override the built-in filters above */ - Piwik_PostEvent('Tracker.Visit.isExcluded', $excluded); - - /* - * Following exclude operations happen after the hook. - * These are of higher priority and should not be overwritten by plugins. - */ - - // Checking if the Piwik ignore cookie is set - if(!$excluded) - { - $excluded = $this->isIgnoreCookieFound(); - if($excluded) - { - printDebug("Ignore cookie found."); - } - } - - // Checking for excluded IPs - if(!$excluded) - { - $excluded = $this->isVisitorIpExcluded($ip); - if($excluded) - { - printDebug("IP excluded."); - } - } - - // Check if user agent should be excluded - if (!$excluded) - { - $excluded = $this->isUserAgentExcluded($ua); - if ($excluded) - { - printDebug("User agent excluded."); - } - } - - if(!$excluded) - { - if( (isset($_SERVER["HTTP_X_PURPOSE"]) - && in_array($_SERVER["HTTP_X_PURPOSE"], array("preview", "instant"))) - || (isset($_SERVER['HTTP_X_MOZ']) - && $_SERVER['HTTP_X_MOZ'] == "prefetch")) - { - $excluded = true; - printDebug("Prefetch request detected, not a real visit so we Ignore this visit/pageview"); - } - } - - if($excluded) - { - printDebug("Visitor excluded."); - return true; - } - - return false; - } - - /** - * Looks for the ignore cookie that users can set in the Piwik admin screen. - * @return bool - */ - protected function isIgnoreCookieFound() - { - if(Piwik_Tracker_IgnoreCookie::isIgnoreCookieFound()) - { - printDebug('Piwik ignore cookie was found, visit not tracked.'); - return true; - } - return false; - } - - /** - * Checks if the visitor ip is in the excluded list - * - * @param string $ip Long IP - * @return bool - */ - protected function isVisitorIpExcluded($ip) - { - $websiteAttributes = Piwik_Tracker_Cache::getCacheWebsiteAttributes( $this->idsite ); - if(!empty($websiteAttributes['excluded_ips'])) - { - if(Piwik_IP::isIpInRange($ip, $websiteAttributes['excluded_ips'])) - { - printDebug('Visitor IP '.Piwik_IP::N2P($ip).' is excluded from being tracked'); - return true; - } - } - return false; - } - - /** - * Returns true if the specified user agent should be excluded for the current site or not. - * - * Visits whose user agent string contains one of the excluded_user_agents strings for the - * site being tracked (or one of the global strings) will be excluded. - * - * @param string $ua The user agent string. - * @return bool - */ - protected function isUserAgentExcluded( $ua ) - { - $websiteAttributes = Piwik_Tracker_Cache::getCacheWebsiteAttributes($this->idsite); - if (!empty($websiteAttributes['excluded_user_agents'])) - { - foreach ($websiteAttributes['excluded_user_agents'] as $excludedUserAgent) - { - // if the excluded user agent string part is in this visit's user agent, this visit should be excluded - if (stripos($ua, $excludedUserAgent) !== false) - { - return true; - } - } - } - return false; - } - - /** - * Returns the cookie name used for the Piwik Tracker cookie - * - * @return string - */ - protected function getCookieName() - { - return Piwik_Config::getInstance()->Tracker['cookie_name']; - } - - /** - * Returns the cookie expiration date. - * - * @return int - */ - protected function getCookieExpire() - { - return $this->getCurrentTimestamp() + Piwik_Config::getInstance()->Tracker['cookie_expire']; - } - - /** - * Returns cookie path - * - * @return string - */ - protected function getCookiePath() - { - return Piwik_Config::getInstance()->Tracker['cookie_path']; - } - - protected function shouldUseThirdPartyCookie() - { - return (bool)Piwik_Config::getInstance()->Tracker['use_third_party_id_cookie']; - } - - /** - * Is the request for a known VisitorId, based on 1st party, 3rd party (optional) cookies or Tracking API forced Visitor ID - * @throws Exception - */ - protected function assignVisitorIdFromRequest() - { - $found = false; - - // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request? - $idVisitor = $this->getForcedVisitorId(); - if(!empty($idVisitor)) - { - if(strlen($idVisitor) != Piwik_Tracker::LENGTH_HEX_ID_STRING) - { - throw new Exception("Visitor ID (cid) $idVisitor must be ".Piwik_Tracker::LENGTH_HEX_ID_STRING." characters long"); - } - printDebug("Request will be recorded for this idvisitor = ".$idVisitor); - $found = true; - } - - // - If set to use 3rd party cookies for Visit ID, read the cookie - if(!$found) - { - // - 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) - { - $idVisitor = Piwik_Common::getRequestVar('_id', '', 'string', $this->request); - $found = strlen($idVisitor) >= Piwik_Tracker::LENGTH_HEX_ID_STRING; - } - - if( $found ) - { - $truncated = substr($idVisitor, 0, Piwik_Tracker::LENGTH_HEX_ID_STRING); - $binVisitorId = @Piwik_Common::hex2bin($truncated); - if(!empty($binVisitorId)) - { - $this->visitorInfo['idvisitor'] = $binVisitorId; - } - - } - } + * Returns the visitor's IP address + * + * @return long + */ + protected function getVisitorIp() + { + return $this->visitorInfo['location_ip']; + } + + /** + * Returns the visitor's browser (user agent) + * + * @return string + */ + static public function getUserAgent($request) + { + $default = @$_SERVER['HTTP_USER_AGENT']; + return Piwik_Common::getRequestVar('ua', is_null($default) ? false : $default, 'string', $request); + } + + /** + * Returns the language the visitor is viewing. + * + * @return string browser language code, eg. "en-gb,en;q=0.5" + */ + protected function getBrowserLanguage() + { + return Piwik_Common::getRequestVar('lang', Piwik_Common::getBrowserLanguage(), 'string', $this->request); + } + + /** + * Returns the current date in the "Y-m-d" PHP format + * + * @param string $format + * @return string + */ + protected function getCurrentDate($format = "Y-m-d") + { + return date($format, $this->getCurrentTimestamp()); + } + + /** + * Returns the current Timestamp + * + * @return int + */ + protected function getCurrentTimestamp() + { + return $this->timestamp; + } + + /** + * Test if the current visitor is excluded from the statistics. + * + * Plugins can for example exclude visitors based on the + * - IP + * - If a given cookie is found + * + * @return bool True if the visit must not be saved, false otherwise + */ + protected function isExcluded() + { + $excluded = false; + + $ip = $this->getVisitorIp(); + $ua = $this->getUserAgent($this->request); + + /* + * Live/Bing/MSN bot and Googlebot are evolving to detect cloaked websites. + * As a result, these sophisticated bots exhibit characteristics of + * browsers (cookies enabled, executing JavaScript, etc). + */ + $allowBots = Piwik_Common::getRequestVar('bots', false) != false; + if (!$allowBots + && (strpos($ua, 'Googlebot') !== false // Googlebot + || strpos($ua, 'Google Web Preview') !== false // Google Instant + || strpos($ua, 'bingbot') !== false // Bingbot + || strpos($ua, 'YottaaMonitor') !== false // Yottaa + || Piwik_IP::isIpInRange($ip, + array( + // Live/Bing/MSN + '64.4.0.0/18', + '65.52.0.0/14', + '157.54.0.0/15', + '157.56.0.0/14', + '157.60.0.0/16', + '207.46.0.0/16', + '207.68.128.0/18', + '207.68.192.0/20', + '131.253.26.0/20', + '131.253.24.0/20', + // Yahoo + '72.30.198.0/20', + '72.30.196.0/20', + '98.137.207.0/20', + // Chinese bot hammering websites + '1.202.218.8' + ))) + ) { + printDebug('Search bot detected, visit excluded'); + $excluded = true; + } + + /* + * Requests built with piwik.js will contain a rec=1 parameter. This is used as + * an indication that the request is made by a JS enabled device. By default, Piwik + * doesn't track non-JS visitors. + */ + if (!$excluded) { + $parameterForceRecord = 'rec'; + $toRecord = Piwik_Common::getRequestVar($parameterForceRecord, false, 'int', $this->request); + if (!$toRecord) { + printDebug(@$_SERVER['REQUEST_METHOD'] . ' parameter ' . $parameterForceRecord . ' not found in URL, request excluded'); + $excluded = true; + printDebug("'$parameterForceRecord' parameter not found."); + } + } + + /* custom filters can override the built-in filters above */ + Piwik_PostEvent('Tracker.Visit.isExcluded', $excluded); + + /* + * Following exclude operations happen after the hook. + * These are of higher priority and should not be overwritten by plugins. + */ + + // Checking if the Piwik ignore cookie is set + if (!$excluded) { + $excluded = $this->isIgnoreCookieFound(); + if ($excluded) { + printDebug("Ignore cookie found."); + } + } + + // Checking for excluded IPs + if (!$excluded) { + $excluded = $this->isVisitorIpExcluded($ip); + if ($excluded) { + printDebug("IP excluded."); + } + } + + // Check if user agent should be excluded + if (!$excluded) { + $excluded = $this->isUserAgentExcluded($ua); + if ($excluded) { + printDebug("User agent excluded."); + } + } + + if (!$excluded) { + if ((isset($_SERVER["HTTP_X_PURPOSE"]) + && in_array($_SERVER["HTTP_X_PURPOSE"], array("preview", "instant"))) + || (isset($_SERVER['HTTP_X_MOZ']) + && $_SERVER['HTTP_X_MOZ'] == "prefetch") + ) { + $excluded = true; + printDebug("Prefetch request detected, not a real visit so we Ignore this visit/pageview"); + } + } + + if ($excluded) { + printDebug("Visitor excluded."); + return true; + } + + return false; + } + + /** + * Looks for the ignore cookie that users can set in the Piwik admin screen. + * @return bool + */ + protected function isIgnoreCookieFound() + { + if (Piwik_Tracker_IgnoreCookie::isIgnoreCookieFound()) { + printDebug('Piwik ignore cookie was found, visit not tracked.'); + return true; + } + return false; + } + + /** + * Checks if the visitor ip is in the excluded list + * + * @param string $ip Long IP + * @return bool + */ + protected function isVisitorIpExcluded($ip) + { + $websiteAttributes = Piwik_Tracker_Cache::getCacheWebsiteAttributes($this->idsite); + if (!empty($websiteAttributes['excluded_ips'])) { + if (Piwik_IP::isIpInRange($ip, $websiteAttributes['excluded_ips'])) { + printDebug('Visitor IP ' . Piwik_IP::N2P($ip) . ' is excluded from being tracked'); + return true; + } + } + return false; + } + + /** + * Returns true if the specified user agent should be excluded for the current site or not. + * + * Visits whose user agent string contains one of the excluded_user_agents strings for the + * site being tracked (or one of the global strings) will be excluded. + * + * @param string $ua The user agent string. + * @return bool + */ + protected function isUserAgentExcluded($ua) + { + $websiteAttributes = Piwik_Tracker_Cache::getCacheWebsiteAttributes($this->idsite); + if (!empty($websiteAttributes['excluded_user_agents'])) { + foreach ($websiteAttributes['excluded_user_agents'] as $excludedUserAgent) { + // if the excluded user agent string part is in this visit's user agent, this visit should be excluded + if (stripos($ua, $excludedUserAgent) !== false) { + return true; + } + } + } + return false; + } + + /** + * Returns the cookie name used for the Piwik Tracker cookie + * + * @return string + */ + protected function getCookieName() + { + return Piwik_Config::getInstance()->Tracker['cookie_name']; + } + + /** + * Returns the cookie expiration date. + * + * @return int + */ + protected function getCookieExpire() + { + return $this->getCurrentTimestamp() + Piwik_Config::getInstance()->Tracker['cookie_expire']; + } + + /** + * Returns cookie path + * + * @return string + */ + protected function getCookiePath() + { + return Piwik_Config::getInstance()->Tracker['cookie_path']; + } + + protected function shouldUseThirdPartyCookie() + { + return (bool)Piwik_Config::getInstance()->Tracker['use_third_party_id_cookie']; + } + + /** + * Is the request for a known VisitorId, based on 1st party, 3rd party (optional) cookies or Tracking API forced Visitor ID + * @throws Exception + */ + protected function assignVisitorIdFromRequest() + { + $found = false; + + // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request? + $idVisitor = $this->getForcedVisitorId(); + if (!empty($idVisitor)) { + if (strlen($idVisitor) != Piwik_Tracker::LENGTH_HEX_ID_STRING) { + throw new Exception("Visitor ID (cid) $idVisitor must be " . Piwik_Tracker::LENGTH_HEX_ID_STRING . " characters long"); + } + printDebug("Request will be recorded for this idvisitor = " . $idVisitor); + $found = true; + } + + // - If set to use 3rd party cookies for Visit ID, read the cookie + if (!$found) { + // - 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) { + $idVisitor = Piwik_Common::getRequestVar('_id', '', 'string', $this->request); + $found = strlen($idVisitor) >= Piwik_Tracker::LENGTH_HEX_ID_STRING; + } + + if ($found) { + $truncated = substr($idVisitor, 0, Piwik_Tracker::LENGTH_HEX_ID_STRING); + $binVisitorId = @Piwik_Common::hex2bin($truncated); + if (!empty($binVisitorId)) { + $this->visitorInfo['idvisitor'] = $binVisitorId; + } + + } + } protected function getForcedVisitorId() { @@ -1116,49 +1046,45 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface } /** - * This methods tries to see if the visitor has visited the website before. - * - * We have to split the visitor into one of the category - * - Known visitor - * - New visitor - */ - protected function recognizeTheVisitor() - { - $this->visitorKnown = false; - $this->setCookie( new Piwik_Cookie( - $this->getCookieName(), - $this->getCookieExpire(), - $this->getCookiePath()) ); - $this->printCookie(); - - $userInfo = $this->getUserSettingsInformation(); - $configId = $userInfo['config_id']; - - $this->assignVisitorIdFromRequest(); - $isVisitorIdToLookup = !empty($this->visitorInfo['idvisitor']); - - if($isVisitorIdToLookup) - { - printDebug("Matching visitors with: visitorId=".bin2hex($this->visitorInfo['idvisitor'])." OR configId=".bin2hex($configId)); - } - else - { - printDebug("Visitor doesn't have the piwik cookie..."); - } - - $selectCustomVariables = ''; - // No custom var were found in the request, so let's copy the previous one in a potential conversion later - if(!$this->customVariablesSetFromRequest) - { - $selectCustomVariables = ', + * This methods tries to see if the visitor has visited the website before. + * + * We have to split the visitor into one of the category + * - Known visitor + * - New visitor + */ + protected function recognizeTheVisitor() + { + $this->visitorKnown = false; + $this->setCookie(new Piwik_Cookie( + $this->getCookieName(), + $this->getCookieExpire(), + $this->getCookiePath())); + $this->printCookie(); + + $userInfo = $this->getUserSettingsInformation(); + $configId = $userInfo['config_id']; + + $this->assignVisitorIdFromRequest(); + $isVisitorIdToLookup = !empty($this->visitorInfo['idvisitor']); + + if ($isVisitorIdToLookup) { + printDebug("Matching visitors with: visitorId=" . bin2hex($this->visitorInfo['idvisitor']) . " OR configId=" . bin2hex($configId)); + } else { + printDebug("Visitor doesn't have the piwik cookie..."); + } + + $selectCustomVariables = ''; + // No custom var were found in the request, so let's copy the previous one in a potential conversion later + if (!$this->customVariablesSetFromRequest) { + $selectCustomVariables = ', custom_var_k1, custom_var_v1, custom_var_k2, custom_var_v2, custom_var_k3, custom_var_v3, custom_var_k4, custom_var_v4, custom_var_k5, custom_var_v5'; - } - - $select = "SELECT idvisitor, + } + + $select = "SELECT idvisitor, visit_last_action_time, visit_first_action_time, idvisit, @@ -1179,9 +1105,9 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface visit_goal_buyer $selectCustomVariables "; - $from = "FROM ".Piwik_Common::prefixTable('log_visit'); + $from = "FROM " . Piwik_Common::prefixTable('log_visit'); - $bindSql = array(); + $bindSql = array(); $timeLookBack = $this->getWindowLookupPreviousVisit(); @@ -1189,142 +1115,132 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $shouldMatchOneFieldOnly = $this->shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup); // Two use cases: - // 1) there is no visitor ID so we try to match only on config_id (heuristics) - // Possible causes of no visitor ID: no browser cookie support, direct Tracking API request without visitor ID passed, etc. - // We can use config_id heuristics to try find the visitor in the past, there is a risk to assign - // this page view to the wrong visitor, but this is better than creating artificial visits. - // 2) there is a visitor ID and we trust it (config setting trust_visitors_cookies, OR it was set using &cid= in tracking API), + // 1) there is no visitor ID so we try to match only on config_id (heuristics) + // Possible causes of no visitor ID: no browser cookie support, direct Tracking API request without visitor ID passed, etc. + // We can use config_id heuristics to try find the visitor in the past, there is a risk to assign + // this page view to the wrong visitor, but this is better than creating artificial visits. + // 2) there is a visitor ID and we trust it (config setting trust_visitors_cookies, OR it was set using &cid= in tracking API), // and in these cases, we force to look up this visitor id - if($shouldMatchOneFieldOnly) - { - $where = "visit_last_action_time >= ? AND idsite = ?"; - $bindSql[] = $timeLookBack; - $bindSql[] = $this->idsite; - if(!$isVisitorIdToLookup) - { - $where .= ' AND config_id = ?'; - $bindSql[] = $configId; - } - else - { - $where .= ' AND idvisitor = ?'; - $bindSql[] = $this->visitorInfo['idvisitor']; - } - - $sql = "$select + if ($shouldMatchOneFieldOnly) { + $where = "visit_last_action_time >= ? AND idsite = ?"; + $bindSql[] = $timeLookBack; + $bindSql[] = $this->idsite; + if (!$isVisitorIdToLookup) { + $where .= ' AND config_id = ?'; + $bindSql[] = $configId; + } else { + $where .= ' AND idvisitor = ?'; + $bindSql[] = $this->visitorInfo['idvisitor']; + } + + $sql = "$select $from - WHERE ".$where." + WHERE " . $where . " ORDER BY visit_last_action_time DESC LIMIT 1"; - } - // We have a config_id AND a visitor_id. We match on either of these. - // Why do we also match on config_id? - // we do not trust the visitor ID only. Indeed, some browsers, or browser addons, - // cause the visitor id from the 1st party cookie to be different on each page view! - // It is not acceptable to create a new visit every time such browser does a page view, - // so we also backup by searching for matching config_id. - // We use a UNION here so that each sql query uses its own INDEX - else - { - $whereSameBothQueries = "visit_last_action_time >= ? AND idsite = ?"; - - - // will use INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time) - $bindSql[] = $timeLookBack; - $bindSql[] = $this->idsite; - $where = ' AND config_id = ?'; - $bindSql[] = $configId; - $sqlConfigId = "$select , + } // We have a config_id AND a visitor_id. We match on either of these. + // Why do we also match on config_id? + // we do not trust the visitor ID only. Indeed, some browsers, or browser addons, + // cause the visitor id from the 1st party cookie to be different on each page view! + // It is not acceptable to create a new visit every time such browser does a page view, + // so we also backup by searching for matching config_id. + // We use a UNION here so that each sql query uses its own INDEX + else { + $whereSameBothQueries = "visit_last_action_time >= ? AND idsite = ?"; + + + // will use INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time) + $bindSql[] = $timeLookBack; + $bindSql[] = $this->idsite; + $where = ' AND config_id = ?'; + $bindSql[] = $configId; + $sqlConfigId = "$select , 0 as priority $from WHERE $whereSameBothQueries $where ORDER BY visit_last_action_time DESC LIMIT 1 "; - - // will use INDEX index_idsite_idvisitor (idsite, idvisitor) - $bindSql[] = $timeLookBack; - $bindSql[] = $this->idsite; - $where = ' AND idvisitor = ?'; - $bindSql[] = $this->visitorInfo['idvisitor']; - $sqlVisitorId = "$select , + + // will use INDEX index_idsite_idvisitor (idsite, idvisitor) + $bindSql[] = $timeLookBack; + $bindSql[] = $this->idsite; + $where = ' AND idvisitor = ?'; + $bindSql[] = $this->visitorInfo['idvisitor']; + $sqlVisitorId = "$select , 1 as priority $from WHERE $whereSameBothQueries $where LIMIT 1 "; - - // We join both queries and favor the one matching the visitor_id if it did match - $sql = " ( $sqlConfigId ) + + // We join both queries and favor the one matching the visitor_id if it did match + $sql = " ( $sqlConfigId ) UNION ( $sqlVisitorId ) ORDER BY priority DESC LIMIT 1"; - } - - - $visitRow = Piwik_Tracker::getDatabase()->fetch($sql, $bindSql); - - if( !Piwik_Config::getInstance()->Debug['tracker_always_new_visitor'] - && $visitRow - && count($visitRow) > 0) - { - // These values will be used throughout the request - $this->visitorInfo['visit_last_action_time'] = strtotime($visitRow['visit_last_action_time']); - $this->visitorInfo['visit_first_action_time'] = strtotime($visitRow['visit_first_action_time']); - - // We always keep the first idvisitor seen for this visit (so that all page views for this visit have the same idvisitor) - $this->visitorInfo['idvisitor'] = $visitRow['idvisitor']; - $this->visitorInfo['idvisit'] = $visitRow['idvisit']; - $this->visitorInfo['visit_exit_idaction_url'] = $visitRow['visit_exit_idaction_url']; - $this->visitorInfo['visit_exit_idaction_name'] = $visitRow['visit_exit_idaction_name']; - $this->visitorInfo['visitor_returning'] = $visitRow['visitor_returning']; - $this->visitorInfo['visitor_days_since_first'] = $visitRow['visitor_days_since_first']; - $this->visitorInfo['visitor_days_since_order'] = $visitRow['visitor_days_since_order']; - $this->visitorInfo['visitor_count_visits'] = $visitRow['visitor_count_visits']; - $this->visitorInfo['visit_goal_buyer'] = $visitRow['visit_goal_buyer']; - $this->visitorInfo['location_country'] = $visitRow['location_country']; - $this->visitorInfo['location_region'] = $visitRow['location_region']; - $this->visitorInfo['location_city'] = $visitRow['location_city']; - $this->visitorInfo['location_latitude'] = $visitRow['location_latitude']; - $this->visitorInfo['location_longitude'] = $visitRow['location_longitude']; - - // Referer information will be potentially used for Goal Conversion attribution - $this->visitorInfo['referer_name'] = $visitRow['referer_name']; - $this->visitorInfo['referer_keyword'] = $visitRow['referer_keyword']; - $this->visitorInfo['referer_type'] = $visitRow['referer_type']; - - // Custom Variables copied from Visit in potential later conversion - if(!empty($selectCustomVariables)) - { - for($i=1; $i<=Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) - { - if(isset($visitRow['custom_var_k'.$i]) - && strlen($visitRow['custom_var_k'.$i])) - { - $this->visitorInfo['custom_var_k'.$i] = $visitRow['custom_var_k'.$i]; - } - if(isset($visitRow['custom_var_v'.$i]) - && strlen($visitRow['custom_var_v'.$i])) - { - $this->visitorInfo['custom_var_v'.$i] = $visitRow['custom_var_v'.$i]; - } - } - } - - $this->visitorKnown = true; - printDebug("The visitor is known (idvisitor = ".bin2hex($this->visitorInfo['idvisitor']).", - config_id = ".bin2hex($configId).", + } + + + $visitRow = Piwik_Tracker::getDatabase()->fetch($sql, $bindSql); + + if (!Piwik_Config::getInstance()->Debug['tracker_always_new_visitor'] + && $visitRow + && count($visitRow) > 0 + ) { + // These values will be used throughout the request + $this->visitorInfo['visit_last_action_time'] = strtotime($visitRow['visit_last_action_time']); + $this->visitorInfo['visit_first_action_time'] = strtotime($visitRow['visit_first_action_time']); + + // We always keep the first idvisitor seen for this visit (so that all page views for this visit have the same idvisitor) + $this->visitorInfo['idvisitor'] = $visitRow['idvisitor']; + $this->visitorInfo['idvisit'] = $visitRow['idvisit']; + $this->visitorInfo['visit_exit_idaction_url'] = $visitRow['visit_exit_idaction_url']; + $this->visitorInfo['visit_exit_idaction_name'] = $visitRow['visit_exit_idaction_name']; + $this->visitorInfo['visitor_returning'] = $visitRow['visitor_returning']; + $this->visitorInfo['visitor_days_since_first'] = $visitRow['visitor_days_since_first']; + $this->visitorInfo['visitor_days_since_order'] = $visitRow['visitor_days_since_order']; + $this->visitorInfo['visitor_count_visits'] = $visitRow['visitor_count_visits']; + $this->visitorInfo['visit_goal_buyer'] = $visitRow['visit_goal_buyer']; + $this->visitorInfo['location_country'] = $visitRow['location_country']; + $this->visitorInfo['location_region'] = $visitRow['location_region']; + $this->visitorInfo['location_city'] = $visitRow['location_city']; + $this->visitorInfo['location_latitude'] = $visitRow['location_latitude']; + $this->visitorInfo['location_longitude'] = $visitRow['location_longitude']; + + // Referer information will be potentially used for Goal Conversion attribution + $this->visitorInfo['referer_name'] = $visitRow['referer_name']; + $this->visitorInfo['referer_keyword'] = $visitRow['referer_keyword']; + $this->visitorInfo['referer_type'] = $visitRow['referer_type']; + + // Custom Variables copied from Visit in potential later conversion + if (!empty($selectCustomVariables)) { + for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) { + if (isset($visitRow['custom_var_k' . $i]) + && strlen($visitRow['custom_var_k' . $i]) + ) { + $this->visitorInfo['custom_var_k' . $i] = $visitRow['custom_var_k' . $i]; + } + if (isset($visitRow['custom_var_v' . $i]) + && strlen($visitRow['custom_var_v' . $i]) + ) { + $this->visitorInfo['custom_var_v' . $i] = $visitRow['custom_var_v' . $i]; + } + } + } + + $this->visitorKnown = true; + printDebug("The visitor is known (idvisitor = " . bin2hex($this->visitorInfo['idvisitor']) . ", + config_id = " . bin2hex($configId) . ", idvisit = {$this->visitorInfo['idvisit']}, - last action = ".date("r", $this->visitorInfo['visit_last_action_time']).", - first action = ".date("r", $this->visitorInfo['visit_first_action_time']) .", - visit_goal_buyer' = ".$this->visitorInfo['visit_goal_buyer'].")"); - } - else - { - printDebug("The visitor was not matched with an existing visitor..."); - } - } + last action = " . date("r", $this->visitorInfo['visit_last_action_time']) . ", + first action = " . date("r", $this->visitorInfo['visit_first_action_time']) . ", + visit_goal_buyer' = " . $this->visitorInfo['visit_goal_buyer'] . ")"); + } else { + printDebug("The visitor was not matched with an existing visitor..."); + } + } /** * By default, we look back 30 minutes to find a previous visitor (for performance reasons). @@ -1356,280 +1272,266 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $isForcedVisitorIdMustMatch = ($this->getForcedVisitorId() != null); $shouldMatchOneFieldOnly = (($isVisitorIdToLookup && $trustCookiesOnly) - || $isForcedVisitorIdMustMatch - || !$isVisitorIdToLookup); + || $isForcedVisitorIdMustMatch + || !$isVisitorIdToLookup); return $shouldMatchOneFieldOnly; } static public function getCustomVariables($scope, $request) - { - if($scope == 'visit') { - $parameter = '_cvar'; - } else { - $parameter = 'cvar'; - } - - $customVar = Piwik_Common::unsanitizeInputValues(Piwik_Common::getRequestVar($parameter, '', 'json', $request)); - if(!is_array($customVar)) - { - return array(); - } - $customVariables = array(); - foreach($customVar as $id => $keyValue) - { - $id = (int)$id; - if($id < 1 - || $id > Piwik_Tracker::MAX_CUSTOM_VARIABLES - || count($keyValue) != 2 - || (!is_string($keyValue[0]) && !is_numeric($keyValue[0])) - ) - { - printDebug("Invalid custom variables detected (id=$id)"); - continue; - } - if(strlen($keyValue[1]) == 0) - { - $keyValue[1] = ""; - } - // We keep in the URL when Custom Variable have empty names - // and values, as it means they can be deleted server side - - $key = self::truncateCustomVariable($keyValue[0]); - $value = self::truncateCustomVariable($keyValue[1]); - $customVariables['custom_var_k'.$id] = $key; - $customVariables['custom_var_v'.$id] = $value; - } - - return $customVariables; - } - - static public function truncateCustomVariable($input) - { - return substr(trim($input), 0, Piwik_Tracker::MAX_LENGTH_CUSTOM_VARIABLE); - } - - /** - * Gets the UserSettings information and returns them in an array of name => value - * - * @return array - */ - protected function getUserSettingsInformation() - { - // we already called this method before, simply returns the result - if(is_array($this->userSettingsInformation)) - { - return $this->userSettingsInformation; - } - require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php'; - - $plugin_Flash = Piwik_Common::getRequestVar( 'fla', 0, 'int', $this->request); - $plugin_Java = Piwik_Common::getRequestVar( 'java', 0, 'int', $this->request); - $plugin_Director = Piwik_Common::getRequestVar( 'dir', 0, 'int', $this->request); - $plugin_Quicktime = Piwik_Common::getRequestVar( 'qt', 0, 'int', $this->request); - $plugin_RealPlayer = Piwik_Common::getRequestVar( 'realp', 0, 'int', $this->request); - $plugin_PDF = Piwik_Common::getRequestVar( 'pdf', 0, 'int', $this->request); - $plugin_WindowsMedia = Piwik_Common::getRequestVar( 'wma', 0, 'int', $this->request); - $plugin_Gears = Piwik_Common::getRequestVar( 'gears', 0, 'int', $this->request); - $plugin_Silverlight = Piwik_Common::getRequestVar( 'ag', 0, 'int', $this->request); - $plugin_Cookie = Piwik_Common::getRequestVar( 'cookie', 0, 'int', $this->request); - - $userAgent = $this->getUserAgent($this->request); - $aBrowserInfo = UserAgentParser::getBrowser($userAgent); - - $browserName = ($aBrowserInfo !== false && $aBrowserInfo['id'] !== false) ? $aBrowserInfo['id'] : 'UNK'; - $browserVersion = ($aBrowserInfo !== false && $aBrowserInfo['version'] !== false) ? $aBrowserInfo['version'] : ''; - - $os = UserAgentParser::getOperatingSystem($userAgent); - $os = $os === false ? 'UNK' : $os['id']; - - $resolution = Piwik_Common::getRequestVar('res', 'unknown', 'string', $this->request); - - $browserLang = $this->getBrowserLanguage(); - - $configurationHash = $this->getConfigHash( - $os, - $browserName, - $browserVersion, - $resolution, - $plugin_Flash, - $plugin_Java, - $plugin_Director, - $plugin_Quicktime, - $plugin_RealPlayer, - $plugin_PDF, - $plugin_WindowsMedia, - $plugin_Gears, - $plugin_Silverlight, - $plugin_Cookie, - $this->getVisitorIp(), - $browserLang); - - $this->userSettingsInformation = array( - 'config_id' => $configurationHash, - 'config_os' => $os, - 'config_browser_name' => $browserName, - 'config_browser_version' => $browserVersion, - 'config_resolution' => $resolution, - 'config_pdf' => $plugin_PDF, - 'config_flash' => $plugin_Flash, - 'config_java' => $plugin_Java, - 'config_director' => $plugin_Director, - 'config_quicktime' => $plugin_Quicktime, - 'config_realplayer' => $plugin_RealPlayer, - 'config_windowsmedia' => $plugin_WindowsMedia, - 'config_gears' => $plugin_Gears, - 'config_silverlight' => $plugin_Silverlight, - 'config_cookie' => $plugin_Cookie, - 'location_browser_lang' => $browserLang, - ); - - return $this->userSettingsInformation; - } - - /** - * Returns true if the last action was done during the last 30 minutes - * @return bool - */ - protected function isLastActionInTheSameVisit() - { - return isset($this->visitorInfo['visit_last_action_time']) - && ($this->visitorInfo['visit_last_action_time'] - > ($this->getCurrentTimestamp() - Piwik_Config::getInstance()->Tracker['visit_standard_length'])); - } - - /** - * Returns true if the recognizeTheVisitor() method did recognize the visitor - * @return bool - */ - protected function isVisitorKnown() - { - return $this->visitorKnown === true; - } - - /** - * Update the cookie information. - */ - protected function setThirdPartyCookie() - { - if(!$this->shouldUseThirdPartyCookie()) - { - return; - } - printDebug("We manage the cookie..."); - - // idcookie has been generated in handleNewVisit or we simply propagate the old value - $this->cookie->set(0, bin2hex($this->visitorInfo['idvisitor']) ); - $this->cookie->save(); - } - - /** - * Returns an object able to handle the current action - * Plugins can return an override Action that for example, does not record the action in the DB - * - * @throws Exception - * @return Piwik_Tracker_Action child or fake but with same public interface - */ - protected function newAction() - { - $action = null; - Piwik_PostEvent('Tracker.newAction', $action); - - if(is_null($action)) - { - $action = new Piwik_Tracker_Action(); - } - elseif(!($action instanceof Piwik_Tracker_Action_Interface)) - { - throw new Exception("The Action object set in the plugin must implement the interface Piwik_Tracker_Action_Interface"); - } - return $action; - } - - /** - * Detect whether action is an outlink given host aliases - * - * @param Piwik_Tracker_Action_Interface $action - * @return bool true if the outlink the visitor clicked on points to one of the known hosts for this website - */ - protected function detectActionIsOutlinkOnAliasHost(Piwik_Tracker_Action_Interface $action) - { - if($action->getActionType() != Piwik_Tracker_Action_Interface::TYPE_OUTLINK) - { - return false; - } - $decodedActionUrl = $action->getActionUrl(); - $actionUrlParsed = @parse_url($decodedActionUrl); - if(!isset($actionUrlParsed['host'])) - { - return false; - } - return Piwik_Tracker_Visit::isHostKnownAliasHost($actionUrlParsed['host'], $this->idsite); - } - - /** - * Returns a 64-bit hash of all the configuration settings - * @param $os - * @param $browserName - * @param $browserVersion - * @param $resolution - * @param $plugin_Flash - * @param $plugin_Java - * @param $plugin_Director - * @param $plugin_Quicktime - * @param $plugin_RealPlayer - * @param $plugin_PDF - * @param $plugin_WindowsMedia - * @param $plugin_Gears - * @param $plugin_Silverlight - * @param $plugin_Cookie - * @param $ip - * @param $browserLang - * @return string - */ - protected function getConfigHash( $os, $browserName, $browserVersion, $resolution, $plugin_Flash, $plugin_Java, $plugin_Director, $plugin_Quicktime, $plugin_RealPlayer, $plugin_PDF, $plugin_WindowsMedia, $plugin_Gears, $plugin_Silverlight, $plugin_Cookie, $ip, $browserLang) - { - $hash = md5( $os . $browserName . $browserVersion . $plugin_Flash . $plugin_Java . $plugin_Director . $plugin_Quicktime . $plugin_RealPlayer . $plugin_PDF . $plugin_WindowsMedia . $plugin_Gears . $plugin_Silverlight . $plugin_Cookie . $ip . $browserLang, $raw_output = true ); - return Piwik_Common::substr( $hash, 0, Piwik_Tracker::LENGTH_BINARY_ID ); - } - - /** - * Returns either - * - "-1" for a known visitor - * - at least 16 char identifier in hex @see Piwik_Common::generateUniqId() - * @return int|string - */ - protected function getVisitorUniqueId() - { - if($this->isVisitorKnown()) - { - return -1; - } - return Piwik_Common::generateUniqId(); - } - - protected function setCookie( $cookie ) - { - $this->cookie = $cookie; - } - - // is the referer host any of the registered URLs for this website? - static public function isHostKnownAliasHost($urlHost, $idSite) - { - $websiteData = Piwik_Tracker_Cache::getCacheWebsiteAttributes($idSite); - if(isset($websiteData['hosts'])) - { - $canonicalHosts = array(); - foreach($websiteData['hosts'] as $host) { - $canonicalHosts[] = str_replace('www.', '' , mb_strtolower($host, 'UTF-8')); - } - $canonicalHost = str_replace('www.', '', mb_strtolower($urlHost, 'UTF-8')); - if(in_array($canonicalHost, $canonicalHosts)) - { - return true; - } - } - return false; - } + { + if ($scope == 'visit') { + $parameter = '_cvar'; + } else { + $parameter = 'cvar'; + } + + $customVar = Piwik_Common::unsanitizeInputValues(Piwik_Common::getRequestVar($parameter, '', 'json', $request)); + if (!is_array($customVar)) { + return array(); + } + $customVariables = array(); + foreach ($customVar as $id => $keyValue) { + $id = (int)$id; + if ($id < 1 + || $id > Piwik_Tracker::MAX_CUSTOM_VARIABLES + || count($keyValue) != 2 + || (!is_string($keyValue[0]) && !is_numeric($keyValue[0])) + ) { + printDebug("Invalid custom variables detected (id=$id)"); + continue; + } + if (strlen($keyValue[1]) == 0) { + $keyValue[1] = ""; + } + // We keep in the URL when Custom Variable have empty names + // and values, as it means they can be deleted server side + + $key = self::truncateCustomVariable($keyValue[0]); + $value = self::truncateCustomVariable($keyValue[1]); + $customVariables['custom_var_k' . $id] = $key; + $customVariables['custom_var_v' . $id] = $value; + } + + return $customVariables; + } + + static public function truncateCustomVariable($input) + { + return substr(trim($input), 0, Piwik_Tracker::MAX_LENGTH_CUSTOM_VARIABLE); + } + + /** + * Gets the UserSettings information and returns them in an array of name => value + * + * @return array + */ + protected function getUserSettingsInformation() + { + // we already called this method before, simply returns the result + if (is_array($this->userSettingsInformation)) { + return $this->userSettingsInformation; + } + require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php'; + + $plugin_Flash = Piwik_Common::getRequestVar('fla', 0, 'int', $this->request); + $plugin_Java = Piwik_Common::getRequestVar('java', 0, 'int', $this->request); + $plugin_Director = Piwik_Common::getRequestVar('dir', 0, 'int', $this->request); + $plugin_Quicktime = Piwik_Common::getRequestVar('qt', 0, 'int', $this->request); + $plugin_RealPlayer = Piwik_Common::getRequestVar('realp', 0, 'int', $this->request); + $plugin_PDF = Piwik_Common::getRequestVar('pdf', 0, 'int', $this->request); + $plugin_WindowsMedia = Piwik_Common::getRequestVar('wma', 0, 'int', $this->request); + $plugin_Gears = Piwik_Common::getRequestVar('gears', 0, 'int', $this->request); + $plugin_Silverlight = Piwik_Common::getRequestVar('ag', 0, 'int', $this->request); + $plugin_Cookie = Piwik_Common::getRequestVar('cookie', 0, 'int', $this->request); + + $userAgent = $this->getUserAgent($this->request); + $aBrowserInfo = UserAgentParser::getBrowser($userAgent); + + $browserName = ($aBrowserInfo !== false && $aBrowserInfo['id'] !== false) ? $aBrowserInfo['id'] : 'UNK'; + $browserVersion = ($aBrowserInfo !== false && $aBrowserInfo['version'] !== false) ? $aBrowserInfo['version'] : ''; + + $os = UserAgentParser::getOperatingSystem($userAgent); + $os = $os === false ? 'UNK' : $os['id']; + + $resolution = Piwik_Common::getRequestVar('res', 'unknown', 'string', $this->request); + + $browserLang = $this->getBrowserLanguage(); + + $configurationHash = $this->getConfigHash( + $os, + $browserName, + $browserVersion, + $resolution, + $plugin_Flash, + $plugin_Java, + $plugin_Director, + $plugin_Quicktime, + $plugin_RealPlayer, + $plugin_PDF, + $plugin_WindowsMedia, + $plugin_Gears, + $plugin_Silverlight, + $plugin_Cookie, + $this->getVisitorIp(), + $browserLang); + + $this->userSettingsInformation = array( + 'config_id' => $configurationHash, + 'config_os' => $os, + 'config_browser_name' => $browserName, + 'config_browser_version' => $browserVersion, + 'config_resolution' => $resolution, + 'config_pdf' => $plugin_PDF, + 'config_flash' => $plugin_Flash, + 'config_java' => $plugin_Java, + 'config_director' => $plugin_Director, + 'config_quicktime' => $plugin_Quicktime, + 'config_realplayer' => $plugin_RealPlayer, + 'config_windowsmedia' => $plugin_WindowsMedia, + 'config_gears' => $plugin_Gears, + 'config_silverlight' => $plugin_Silverlight, + 'config_cookie' => $plugin_Cookie, + 'location_browser_lang' => $browserLang, + ); + + return $this->userSettingsInformation; + } + + /** + * Returns true if the last action was done during the last 30 minutes + * @return bool + */ + protected function isLastActionInTheSameVisit() + { + return isset($this->visitorInfo['visit_last_action_time']) + && ($this->visitorInfo['visit_last_action_time'] + > ($this->getCurrentTimestamp() - Piwik_Config::getInstance()->Tracker['visit_standard_length'])); + } + + /** + * Returns true if the recognizeTheVisitor() method did recognize the visitor + * @return bool + */ + protected function isVisitorKnown() + { + return $this->visitorKnown === true; + } + + /** + * Update the cookie information. + */ + protected function setThirdPartyCookie() + { + if (!$this->shouldUseThirdPartyCookie()) { + return; + } + printDebug("We manage the cookie..."); + + // idcookie has been generated in handleNewVisit or we simply propagate the old value + $this->cookie->set(0, bin2hex($this->visitorInfo['idvisitor'])); + $this->cookie->save(); + } + + /** + * Returns an object able to handle the current action + * Plugins can return an override Action that for example, does not record the action in the DB + * + * @throws Exception + * @return Piwik_Tracker_Action child or fake but with same public interface + */ + protected function newAction() + { + $action = null; + Piwik_PostEvent('Tracker.newAction', $action); + + if (is_null($action)) { + $action = new Piwik_Tracker_Action(); + } elseif (!($action instanceof Piwik_Tracker_Action_Interface)) { + throw new Exception("The Action object set in the plugin must implement the interface Piwik_Tracker_Action_Interface"); + } + return $action; + } + + /** + * Detect whether action is an outlink given host aliases + * + * @param Piwik_Tracker_Action_Interface $action + * @return bool true if the outlink the visitor clicked on points to one of the known hosts for this website + */ + protected function detectActionIsOutlinkOnAliasHost(Piwik_Tracker_Action_Interface $action) + { + if ($action->getActionType() != Piwik_Tracker_Action_Interface::TYPE_OUTLINK) { + return false; + } + $decodedActionUrl = $action->getActionUrl(); + $actionUrlParsed = @parse_url($decodedActionUrl); + if (!isset($actionUrlParsed['host'])) { + return false; + } + return Piwik_Tracker_Visit::isHostKnownAliasHost($actionUrlParsed['host'], $this->idsite); + } + + /** + * Returns a 64-bit hash of all the configuration settings + * @param $os + * @param $browserName + * @param $browserVersion + * @param $resolution + * @param $plugin_Flash + * @param $plugin_Java + * @param $plugin_Director + * @param $plugin_Quicktime + * @param $plugin_RealPlayer + * @param $plugin_PDF + * @param $plugin_WindowsMedia + * @param $plugin_Gears + * @param $plugin_Silverlight + * @param $plugin_Cookie + * @param $ip + * @param $browserLang + * @return string + */ + protected function getConfigHash($os, $browserName, $browserVersion, $resolution, $plugin_Flash, $plugin_Java, $plugin_Director, $plugin_Quicktime, $plugin_RealPlayer, $plugin_PDF, $plugin_WindowsMedia, $plugin_Gears, $plugin_Silverlight, $plugin_Cookie, $ip, $browserLang) + { + $hash = md5($os . $browserName . $browserVersion . $plugin_Flash . $plugin_Java . $plugin_Director . $plugin_Quicktime . $plugin_RealPlayer . $plugin_PDF . $plugin_WindowsMedia . $plugin_Gears . $plugin_Silverlight . $plugin_Cookie . $ip . $browserLang, $raw_output = true); + return Piwik_Common::substr($hash, 0, Piwik_Tracker::LENGTH_BINARY_ID); + } + + /** + * Returns either + * - "-1" for a known visitor + * - at least 16 char identifier in hex @see Piwik_Common::generateUniqId() + * @return int|string + */ + protected function getVisitorUniqueId() + { + if ($this->isVisitorKnown()) { + return -1; + } + return Piwik_Common::generateUniqId(); + } + + protected function setCookie($cookie) + { + $this->cookie = $cookie; + } + + // is the referer host any of the registered URLs for this website? + static public function isHostKnownAliasHost($urlHost, $idSite) + { + $websiteData = Piwik_Tracker_Cache::getCacheWebsiteAttributes($idSite); + if (isset($websiteData['hosts'])) { + $canonicalHosts = array(); + foreach ($websiteData['hosts'] as $host) { + $canonicalHosts[] = str_replace('www.', '', mb_strtolower($host, 'UTF-8')); + } + $canonicalHost = str_replace('www.', '', mb_strtolower($urlHost, 'UTF-8')); + if (in_array($canonicalHost, $canonicalHosts)) { + return true; + } + } + return false; + } } /** @@ -1638,273 +1540,256 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface */ class Piwik_Tracker_Visit_Referer { - // @see detect*() referer methods - protected $typeRefererAnalyzed; - protected $nameRefererAnalyzed; - protected $keywordRefererAnalyzed; - protected $refererHost; - protected $refererUrl; - protected $refererUrlParse; - protected $currentUrlParse; - protected $idsite; - - // Used to prefix when a adsense referer is detected - const LABEL_PREFIX_ADSENSE_KEYWORD = '(adsense) '; - - - /** - * Returns an array containing the following information: - * - referer_type - * - direct -- absence of referer URL OR referer URL has the same host - * - site -- based on the referer URL - * - search_engine -- based on the referer URL - * - campaign -- based on campaign URL parameter - * - * - referer_name - * - () - * - piwik.net -- site host name - * - google.fr -- search engine host name - * - adwords-search -- campaign name - * - * - referer_keyword - * - () - * - () - * - my keyword - * - my paid keyword - * - () - * - () - * - * - referer_url : the same for all the referer types - * - * @param $refererUrl must be URL Encoded - * @param $currentUrl - * @param $idSite - * @return array - */ - public function getRefererInformation($refererUrl, $currentUrl, $idSite) - { - $this->idsite = $idSite; - - // default values for the referer_* fields - $refererUrl = Piwik_Common::unsanitizeInputValue($refererUrl); - if(!empty($refererUrl) - && !Piwik_Common::isLookLikeUrl($refererUrl)) - { - $refererUrl = ''; - } - - $currentUrl = Piwik_Tracker_Action::cleanupUrl($currentUrl); - - $this->refererUrl = $refererUrl; - $this->refererUrlParse = @parse_url($this->refererUrl); - $this->currentUrlParse = @parse_url($currentUrl); - $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; - $this->nameRefererAnalyzed = ''; - $this->keywordRefererAnalyzed = ''; - $this->refererHost = ''; - - if(isset($this->refererUrlParse['host'])) - { - $this->refererHost = $this->refererUrlParse['host']; - } - - $refererDetected = false; - - if( !empty($this->currentUrlParse['host']) - && $this->detectRefererCampaign() ) - { - $refererDetected = true; - } - - if(!$refererDetected) - { - if( $this->detectRefererDirectEntry() - || $this->detectRefererSearchEngine() ) - { - $refererDetected = true; - } - } - - if(!empty($this->refererHost) - && !$refererDetected) - { - $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_WEBSITE; - $this->nameRefererAnalyzed = mb_strtolower($this->refererHost, 'UTF-8'); - } - - $refererInformation = array( - 'referer_type' => $this->typeRefererAnalyzed, - 'referer_name' => $this->nameRefererAnalyzed, - 'referer_keyword' => $this->keywordRefererAnalyzed, - 'referer_url' => $this->refererUrl, - ); - - return $refererInformation; - } - - /** - * Search engine detection + // @see detect*() referer methods + protected $typeRefererAnalyzed; + protected $nameRefererAnalyzed; + protected $keywordRefererAnalyzed; + protected $refererHost; + protected $refererUrl; + protected $refererUrlParse; + protected $currentUrlParse; + protected $idsite; + + // Used to prefix when a adsense referer is detected + const LABEL_PREFIX_ADSENSE_KEYWORD = '(adsense) '; + + + /** + * Returns an array containing the following information: + * - referer_type + * - direct -- absence of referer URL OR referer URL has the same host + * - site -- based on the referer URL + * - search_engine -- based on the referer URL + * - campaign -- based on campaign URL parameter + * + * - referer_name + * - () + * - piwik.net -- site host name + * - google.fr -- search engine host name + * - adwords-search -- campaign name + * + * - referer_keyword + * - () + * - () + * - my keyword + * - my paid keyword + * - () + * - () + * + * - referer_url : the same for all the referer types + * + * @param $refererUrl must be URL Encoded + * @param $currentUrl + * @param $idSite + * @return array + */ + public function getRefererInformation($refererUrl, $currentUrl, $idSite) + { + $this->idsite = $idSite; + + // default values for the referer_* fields + $refererUrl = Piwik_Common::unsanitizeInputValue($refererUrl); + if (!empty($refererUrl) + && !Piwik_Common::isLookLikeUrl($refererUrl) + ) { + $refererUrl = ''; + } + + $currentUrl = Piwik_Tracker_Action::cleanupUrl($currentUrl); + + $this->refererUrl = $refererUrl; + $this->refererUrlParse = @parse_url($this->refererUrl); + $this->currentUrlParse = @parse_url($currentUrl); + $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; + $this->nameRefererAnalyzed = ''; + $this->keywordRefererAnalyzed = ''; + $this->refererHost = ''; + + if (isset($this->refererUrlParse['host'])) { + $this->refererHost = $this->refererUrlParse['host']; + } + + $refererDetected = false; + + if (!empty($this->currentUrlParse['host']) + && $this->detectRefererCampaign() + ) { + $refererDetected = true; + } + + if (!$refererDetected) { + if ($this->detectRefererDirectEntry() + || $this->detectRefererSearchEngine() + ) { + $refererDetected = true; + } + } + + if (!empty($this->refererHost) + && !$refererDetected + ) { + $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_WEBSITE; + $this->nameRefererAnalyzed = mb_strtolower($this->refererHost, 'UTF-8'); + } + + $refererInformation = array( + 'referer_type' => $this->typeRefererAnalyzed, + 'referer_name' => $this->nameRefererAnalyzed, + 'referer_keyword' => $this->keywordRefererAnalyzed, + 'referer_url' => $this->refererUrl, + ); + + return $refererInformation; + } + + /** + * Search engine detection * @return bool - */ - protected function detectRefererSearchEngine() - { - $searchEngineInformation = Piwik_Common::extractSearchEngineInformationFromUrl($this->refererUrl); - Piwik_PostEvent('Tracker.detectRefererSearchEngine', $searchEngineInformation, $this->refererUrl); - if($searchEngineInformation === false) - { - return false; - } - $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_SEARCH_ENGINE; - $this->nameRefererAnalyzed = $searchEngineInformation['name']; - $this->keywordRefererAnalyzed = $searchEngineInformation['keywords']; - return true; - } + */ + protected function detectRefererSearchEngine() + { + $searchEngineInformation = Piwik_Common::extractSearchEngineInformationFromUrl($this->refererUrl); + Piwik_PostEvent('Tracker.detectRefererSearchEngine', $searchEngineInformation, $this->refererUrl); + if ($searchEngineInformation === false) { + return false; + } + $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_SEARCH_ENGINE; + $this->nameRefererAnalyzed = $searchEngineInformation['name']; + $this->keywordRefererAnalyzed = $searchEngineInformation['keywords']; + return true; + } /** * @param string $string * @return bool */ protected function detectCampaignFromString($string) - { - foreach($this->campaignNames as $campaignNameParameter) - { - $campaignName = trim(urldecode(Piwik_Common::getParameterFromQueryString($string, $campaignNameParameter))); - if( !empty($campaignName)) - { - break; - } - } - - if(!empty($campaignName)) - { - $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_CAMPAIGN; - $this->nameRefererAnalyzed = $campaignName; - - foreach($this->campaignKeywords as $campaignKeywordParameter) - { - $campaignKeyword = Piwik_Common::getParameterFromQueryString($string, $campaignKeywordParameter); - if( !empty($campaignKeyword)) - { - $this->keywordRefererAnalyzed = trim(urldecode($campaignKeyword)); - break; - } - } - - // if the campaign keyword is empty, try to get a keyword from the referrer URL - if (empty($this->keywordRefererAnalyzed)) - { - // Set the Campaign keyword to the keyword found in the Referer URL if any - $referrerUrlInfo = Piwik_Common::extractSearchEngineInformationFromUrl($this->refererUrl); - if (!empty($referrerUrlInfo['keywords'])) - { - $this->keywordRefererAnalyzed = $referrerUrlInfo['keywords']; - } - - // Set the keyword, to the hostname found, in a Adsense Referer URL '&url=' parameter - if(empty($this->keywordRefererAnalyzed) - && !empty($this->refererUrlParse['query']) - && !empty($this->refererHost) - && (strpos($this->refererHost, 'google') !== false || strpos($this->refererHost,'doubleclick') !== false) - ) - { - // This parameter sometimes is found & contains the page with the adsense ad bringing visitor to our site - $adsenseReferrerParameter = 'url'; - $value = trim(urldecode(Piwik_Common::getParameterFromQueryString($this->refererUrlParse['query'], $adsenseReferrerParameter))); - if(!empty($value)) - { - $parsedAdsenseReferrerUrl = parse_url($value); - if(!empty($parsedAdsenseReferrerUrl['host'])) - { - $this->keywordRefererAnalyzed = self::LABEL_PREFIX_ADSENSE_KEYWORD . $parsedAdsenseReferrerUrl['host']; - } - } - } - - // or we default to the referrer hostname otherwise - if(empty($this->keywordRefererAnalyzed)) - { - $this->keywordRefererAnalyzed = $this->refererHost; - } - } - - return true; - } - return false; - } - - /** - * Campaign analysis + { + foreach ($this->campaignNames as $campaignNameParameter) { + $campaignName = trim(urldecode(Piwik_Common::getParameterFromQueryString($string, $campaignNameParameter))); + if (!empty($campaignName)) { + break; + } + } + + if (!empty($campaignName)) { + $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_CAMPAIGN; + $this->nameRefererAnalyzed = $campaignName; + + foreach ($this->campaignKeywords as $campaignKeywordParameter) { + $campaignKeyword = Piwik_Common::getParameterFromQueryString($string, $campaignKeywordParameter); + if (!empty($campaignKeyword)) { + $this->keywordRefererAnalyzed = trim(urldecode($campaignKeyword)); + break; + } + } + + // if the campaign keyword is empty, try to get a keyword from the referrer URL + if (empty($this->keywordRefererAnalyzed)) { + // Set the Campaign keyword to the keyword found in the Referer URL if any + $referrerUrlInfo = Piwik_Common::extractSearchEngineInformationFromUrl($this->refererUrl); + if (!empty($referrerUrlInfo['keywords'])) { + $this->keywordRefererAnalyzed = $referrerUrlInfo['keywords']; + } + + // Set the keyword, to the hostname found, in a Adsense Referer URL '&url=' parameter + if (empty($this->keywordRefererAnalyzed) + && !empty($this->refererUrlParse['query']) + && !empty($this->refererHost) + && (strpos($this->refererHost, 'google') !== false || strpos($this->refererHost, 'doubleclick') !== false) + ) { + // This parameter sometimes is found & contains the page with the adsense ad bringing visitor to our site + $adsenseReferrerParameter = 'url'; + $value = trim(urldecode(Piwik_Common::getParameterFromQueryString($this->refererUrlParse['query'], $adsenseReferrerParameter))); + if (!empty($value)) { + $parsedAdsenseReferrerUrl = parse_url($value); + if (!empty($parsedAdsenseReferrerUrl['host'])) { + $this->keywordRefererAnalyzed = self::LABEL_PREFIX_ADSENSE_KEYWORD . $parsedAdsenseReferrerUrl['host']; + } + } + } + + // or we default to the referrer hostname otherwise + if (empty($this->keywordRefererAnalyzed)) { + $this->keywordRefererAnalyzed = $this->refererHost; + } + } + + return true; + } + return false; + } + + /** + * Campaign analysis * @return bool - */ - protected function detectRefererCampaign() - { - if(!isset($this->currentUrlParse['query']) - && !isset($this->currentUrlParse['fragment'])) - { - return false; - } - $campaignParameters = Piwik_Common::getCampaignParameters(); - $this->campaignNames = $campaignParameters[0]; - $this->campaignKeywords = $campaignParameters[1]; - - $found = false; - - // 1) Detect campaign from query string - if(isset($this->currentUrlParse['query'])) - { - $found = $this->detectCampaignFromString($this->currentUrlParse['query']); - } - - // 2) Detect from fragment #hash - if(!$found - && isset($this->currentUrlParse['fragment'])) - { - $found = $this->detectCampaignFromString($this->currentUrlParse['fragment']); - } - return $found; - } - - /** - * We have previously tried to detect the campaign variables in the URL - * so at this stage, if the referer host is the current host, - * or if the referer host is any of the registered URL for this website, - * it is considered a direct entry + */ + protected function detectRefererCampaign() + { + if (!isset($this->currentUrlParse['query']) + && !isset($this->currentUrlParse['fragment']) + ) { + return false; + } + $campaignParameters = Piwik_Common::getCampaignParameters(); + $this->campaignNames = $campaignParameters[0]; + $this->campaignKeywords = $campaignParameters[1]; + + $found = false; + + // 1) Detect campaign from query string + if (isset($this->currentUrlParse['query'])) { + $found = $this->detectCampaignFromString($this->currentUrlParse['query']); + } + + // 2) Detect from fragment #hash + if (!$found + && isset($this->currentUrlParse['fragment']) + ) { + $found = $this->detectCampaignFromString($this->currentUrlParse['fragment']); + } + return $found; + } + + /** + * We have previously tried to detect the campaign variables in the URL + * so at this stage, if the referer host is the current host, + * or if the referer host is any of the registered URL for this website, + * it is considered a direct entry * @return bool - */ - protected function detectRefererDirectEntry() - { - if(!empty($this->refererHost)) - { - // is the referer host the current host? - if(isset($this->currentUrlParse['host'])) - { - $currentHost = mb_strtolower($this->currentUrlParse['host'], 'UTF-8'); - if($currentHost == mb_strtolower($this->refererHost, 'UTF-8')) - { - $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; - return true; - } - } - if(Piwik_Tracker_Visit::isHostKnownAliasHost($this->refererHost, $this->idsite)) - { - $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; - return true; - } - } - return false; - } + */ + protected function detectRefererDirectEntry() + { + if (!empty($this->refererHost)) { + // is the referer host the current host? + if (isset($this->currentUrlParse['host'])) { + $currentHost = mb_strtolower($this->currentUrlParse['host'], 'UTF-8'); + if ($currentHost == mb_strtolower($this->refererHost, 'UTF-8')) { + $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; + return true; + } + } + if (Piwik_Tracker_Visit::isHostKnownAliasHost($this->refererHost, $this->idsite)) { + $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; + return true; + } + } + return false; + } } /** * @package Piwik * @subpackage Piwik_Tracker */ -class Piwik_Tracker_Visit_VisitorNotFoundInDatabase extends Exception { +class Piwik_Tracker_Visit_VisitorNotFoundInDatabase extends Exception +{ } /** * @package Piwik * @subpackage Piwik_Tracker */ -class Piwik_Tracker_Visit_Excluded extends Exception { +class Piwik_Tracker_Visit_Excluded extends Exception +{ } |