/** * @param Request $request * @param Visitor $visitor * @param Action|null $action * @return mixed */ public function onNewVisit(Request $request, Visitor $visitor, $action) { $visitCount = $request->getVisitCount(); $daysSinceLastVisit = $request->getDaysSinceLastVisit(); $daysSinceLastOrder = $request->getDaysSinceLastOrder(); $isReturningCustomer = $daysSinceLastOrder !== false; if ($isReturningCustomer) { return self::IS_RETURNING_CUSTOMER; } if ($visitCount > 1 || $visitor->isVisitorKnown() || $daysSinceLastVisit > 0) { return self::IS_RETURNING; } return self::IS_NEW; }
/** * @param Request $request * @param Visitor $visitor * @param Action|null $action * @return int */ public function onConvertedVisit(Request $request, Visitor $visitor, $action) { if (!$visitor->isVisitorKnown()) { return false; } $totalTime = $visitor->getVisitorColumn('visit_total_time'); // 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 $totalTime = $totalTime + $request->getParam('idgoal'); // +2 to offset idgoal=-1 and idgoal=0 $totalTime = $totalTime + 2; return $this->cleanupVisitTotalTime($totalTime); }
/** * Returns visitor cookie * * @return string binary */ protected function getVisitorIdcookie(Visitor $visitor) { if ($visitor->isVisitorKnown()) { return $this->visitorInfo['idvisitor']; } // If the visitor had a first party ID cookie, then we use this value if (!empty($this->visitorInfo['idvisitor']) && Tracker::LENGTH_BINARY_ID == strlen($this->visitorInfo['idvisitor'])) { return $this->visitorInfo['idvisitor']; } return Common::hex2bin($this->generateUniqueVisitorId()); }
/** * This event is triggered when an any custom goal is converted. In this example we would store a the id of the * goal in the 'example_conversion_dimension' column if the visitor is known and nothing otherwise. * Return boolean false if you do not want to change the value in some cases. If you do not want to perform any * action on an ecommerce order at all it is recommended to just remove this method. * * @param Request $request * @param Visitor $visitor * @param Action|null $action * @param GoalManager $goalManager * * @return mixed|false */ public function onGoalConversion(Request $request, Visitor $visitor, $action, GoalManager $goalManager) { $goalId = $goalManager->getGoalColumn('idgoal'); if ($visitor->isVisitorKnown()) { return $goalId; } return false; }
/** * 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() $ip = $this->request->getIp(); $this->visitorInfo['location_ip'] = $ip; $excluded = new VisitExcluded($this->request, $ip); if ($excluded->isExcluded()) { return; } /** * Triggered after visits are tested for exclusion so plugins can modify the IP address * persisted with a visit. * * This event is primarily used by the **PrivacyManager** plugin to anonymize IP addresses. * * @param string &$ip The visitor's IP address. */ Piwik::postEvent('Tracker.setVisitorIp', array(&$this->visitorInfo['location_ip'])); $this->visitorCustomVariables = $this->request->getCustomVariables($scope = 'visit'); if (!empty($this->visitorCustomVariables)) { Common::printDebug("Visit level Custom Variables: "); Common::printDebug($this->visitorCustomVariables); } $this->goalManager = new GoalManager($this->request); $visitIsConverted = false; $action = null; $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; } } elseif ($requestIsManualGoalConversion) { $someGoalsConverted = $this->goalManager->detectGoalId($this->request->getIdSite()); $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) { throw new \Exception('Invalid goal tracking request for goal id = ' . $this->goalManager->idGoal); } } else { $action = Action::factory($this->request); $action->writeDebugInfo(); $someGoalsConverted = $this->goalManager->detectGoalsMatchingUrl($this->request->getIdSite(), $action); $visitIsConverted = $someGoalsConverted; $action->loadIdsFromLogActionTable(); } /*** * Visitor recognition */ $visitor = new Visitor($this->request, $this->getSettingsObject(), $this->visitorInfo, $this->visitorCustomVariables); $visitor->recognize(); $this->visitorKnown = $visitor->isVisitorKnown(); $this->visitorInfo = $visitor->getVisitorInfo(); $isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit(); if (!$isLastActionInTheSameVisit) { Common::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) { $idReferrerActionUrl = $this->visitorInfo['visit_exit_idaction_url']; $idReferrerActionName = $this->visitorInfo['visit_exit_idaction_name']; try { $this->handleExistingVisit($action, $visitIsConverted); if (!is_null($action)) { $action->record($this->visitorInfo['idvisit'], $this->visitorInfo['idvisitor'], $idReferrerActionUrl, $idReferrerActionName, $this->visitorInfo['time_spent_ref_action']); } } catch (VisitorNotFoundInDb $e) { // There is an edge case when: // - two manual goal conversions happen in the same second // - which result in handleExistingVisit 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; } 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($action, $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->request->setThirdPartyCookie($this->visitorInfo['idvisitor']); // record the goals if applicable if ($someGoalsConverted) { $this->goalManager->recordGoals($this->request->getIdSite(), $this->visitorInfo, $this->visitorCustomVariables, $action); } unset($this->goalManager); unset($action); }
/** * Determines if the tracker if the current action should be treated as the start of a new visit or * an action in an existing visit. * * @param Visitor $visitor The current visit/visitor information. * @param Action|null $action The current action being tracked. * @return bool */ public function isVisitNew(Visitor $visitor, Action $action = null) { if (!$visitor->isVisitorKnown()) { return true; } $isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit($visitor); if (!$isLastActionInTheSameVisit) { Common::printDebug("Visitor detected, but last action was more than 30 minutes ago..."); return true; } $wasLastActionYesterday = $this->wasLastActionNotToday($visitor); if ($wasLastActionYesterday) { Common::printDebug("Visitor detected, but last action was yesterday..."); return true; } $shouldForceNewVisit = $this->triggerPredicateHookOnDimensions($this->getAllVisitDimensions(), 'shouldForceNewVisit', $visitor, $action); if ($shouldForceNewVisit) { return true; } return false; }