Skip to content
Extraits de code Groupes Projets
Valider af41fb21 rédigé par diosmosis's avatar diosmosis
Parcourir les fichiers

Move bulk of logic in Visitor class to new stateless service,...

Move bulk of logic in Visitor class to new stateless service, VisitorRecognizer which is stored in DI. Also fix two integration tests.
parent 5c284d72
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
......@@ -75,4 +75,9 @@ return array(
'tracker.request.processors' => array(),
'Piwik\Tracker\VisitorRecognizer' => DI\object()
->constructorParameter('trustCookiesOnly', DI\get('ini.Tracker.trust_visitors_cookies'))
->constructorParameter('visitStandardLength', DI\get('ini.Tracker.visit_standard_length'))
->constructorParameter('lookbackNSecondsCustom', DI\get('ini.Tracker.window_look_back_for_visitor'))
->constructorParameter('trackerAlwaysNewVisitor', DI\get('ini.Debug.tracker_always_new_visitor')),
);
......@@ -13,7 +13,7 @@ use Piwik\Tracker;
use Piwik\DeviceDetectorFactory;
use Piwik\SettingsPiwik;
class Settings
class Settings // TODO: merge w/ visitor recognizer
{
const OS_BOT = 'BOT';
......
......@@ -120,8 +120,6 @@ class Visit implements VisitInterface
$visitor = new Visitor($this->request, $visitorId, $this->visitProperties);
$visitor->recognize();
$this->visitProperties->visitorInfo = $visitor->getVisitorInfo();
/** @var Action $action */
$action = $this->visitProperties->getRequestMetadata('Actions', 'action');
......
......@@ -16,6 +16,8 @@ class VisitProperties
{
/**
* TODO
*
* @var array
*/
public $visitorInfo = array();
......
......@@ -8,11 +8,9 @@
*/
namespace Piwik\Tracker;
use Piwik\Common;
use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugins\CustomVariables\CustomVariables;
use Piwik\Piwik;
use Piwik\Tracker;
use Piwik\Tracker\Visit\VisitProperties;
......@@ -23,11 +21,18 @@ class Visitor
private $visitProperties;
private $configId;
public function __construct(Request $request, $configId, VisitProperties $visitProperties)
/**
* @var VisitorRecognizer
*/
private $visitorRecognizer;
public function __construct(Request $request, $configId, VisitProperties $visitProperties, $isVisitorKnown = false)
{
$this->request = $request;
$this->configId = $configId;
$this->visitProperties = $visitProperties;
$this->visitorRecognizer = StaticContainer::get('Piwik\Tracker\VisitorRecognizer');
$this->setIsVisitorKnown($isVisitorKnown);
}
/**
......@@ -41,206 +46,11 @@ class Visitor
{
$this->setIsVisitorKnown(false);
$configId = $this->configId;
$idSite = $this->request->getIdSite();
$idVisitor = $this->request->getVisitorId();
$isVisitorIdToLookup = !empty($idVisitor);
if ($isVisitorIdToLookup) {
$this->visitProperties->visitorInfo['idvisitor'] = $idVisitor;
Common::printDebug("Matching visitors with: visitorId=" . bin2hex($idVisitor) . " OR configId=" . bin2hex($configId));
} else {
Common::printDebug("Visitor doesn't have the piwik cookie...");
}
$persistedVisitAttributes = $this->getVisitFieldsPersist();
$shouldMatchOneFieldOnly = $this->shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup);
list($timeLookBack, $timeLookAhead) = $this->getWindowLookupThisVisit();
$model = $this->getModel();
$visitRow = $model->findVisitor($idSite, $configId, $idVisitor, $persistedVisitAttributes, $shouldMatchOneFieldOnly, $isVisitorIdToLookup, $timeLookBack, $timeLookAhead);
$isNewVisitForced = $this->request->getParam('new_visit');
$isNewVisitForced = !empty($isNewVisitForced);
$enforceNewVisit = $isNewVisitForced || Config::getInstance()->Debug['tracker_always_new_visitor'];
if (!$enforceNewVisit
&& $visitRow
&& count($visitRow) > 0
) {
// These values will be used throughout the request
foreach ($persistedVisitAttributes as $field) {
$this->visitProperties->visitorInfo[$field] = $visitRow[$field];
}
$this->visitProperties->visitorInfo['visit_last_action_time'] = strtotime($visitRow['visit_last_action_time']);
$this->visitProperties->visitorInfo['visit_first_action_time'] = strtotime($visitRow['visit_first_action_time']);
// Custom Variables copied from Visit in potential later conversion
if (!empty($numCustomVarsToRead)) {
for ($i = 1; $i <= $numCustomVarsToRead; $i++) {
if (isset($visitRow['custom_var_k' . $i])
&& strlen($visitRow['custom_var_k' . $i])
) {
$this->visitProperties->visitorInfo['custom_var_k' . $i] = $visitRow['custom_var_k' . $i];
}
if (isset($visitRow['custom_var_v' . $i])
&& strlen($visitRow['custom_var_v' . $i])
) {
$this->visitProperties->visitorInfo['custom_var_v' . $i] = $visitRow['custom_var_v' . $i];
}
}
}
$this->setIsVisitorKnown(true);
Common::printDebug("The visitor is known (idvisitor = " . bin2hex($this->visitProperties->visitorInfo['idvisitor']) . ",
config_id = " . bin2hex($configId) . ",
idvisit = {$this->visitProperties->visitorInfo['idvisit']},
last action = " . date("r", $this->visitProperties->visitorInfo['visit_last_action_time']) . ",
first action = " . date("r", $this->visitProperties->visitorInfo['visit_first_action_time']) . ",
visit_goal_buyer' = " . $this->visitProperties->visitorInfo['visit_goal_buyer'] . ")");
} else {
Common::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).
* In some cases, it is useful to look back and count unique visitors more accurately. You can set custom lookback window in
* [Tracker] window_look_back_for_visitor
*
* The returned value is the window range (Min, max) that the matched visitor should fall within
*
* @return array( datetimeMin, datetimeMax )
*/
protected function getWindowLookupThisVisit()
{
$visitStandardLength = Config::getInstance()->Tracker['visit_standard_length'];
$lookBackNSecondsCustom = Config::getInstance()->Tracker['window_look_back_for_visitor'];
$lookAheadNSeconds = $visitStandardLength;
$lookBackNSeconds = $visitStandardLength;
if ($lookBackNSecondsCustom > $lookBackNSeconds) {
$lookBackNSeconds = $lookBackNSecondsCustom;
}
$timeLookBack = date('Y-m-d H:i:s', $this->request->getCurrentTimestamp() - $lookBackNSeconds);
$timeLookAhead = date('Y-m-d H:i:s', $this->request->getCurrentTimestamp() + $lookAheadNSeconds);
return array($timeLookBack, $timeLookAhead);
}
protected function shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup)
{
$isForcedUserIdMustMatch = (false !== $this->request->getForcedUserId());
if ($isForcedUserIdMustMatch) {
// if &iud was set, we must try and match both idvisitor and config_id
return false;
}
// This setting would be enabled for Intranet websites, to ensure that visitors using all the same computer config, same IP
// are not counted as 1 visitor. In this case, we want to enforce and trust the visitor ID from the cookie.
$trustCookiesOnly = Config::getInstance()->Tracker['trust_visitors_cookies'];
if ($isVisitorIdToLookup && $trustCookiesOnly) {
return true;
}
// If a &cid= was set, we force to select this visitor (or create a new one)
$isForcedVisitorIdMustMatch = ($this->request->getForcedVisitorId() != null);
if ($isForcedVisitorIdMustMatch) {
return true;
}
if (!$isVisitorIdToLookup) {
return true;
}
return false;
}
/**
* @return array
*/
private function getVisitFieldsPersist()
{
static $fields;
if (is_null($fields)) {
$fields = array(
'idvisitor',
'idvisit',
'user_id',
'visit_exit_idaction_url',
'visit_exit_idaction_name',
'visitor_returning',
'visitor_days_since_first',
'visitor_days_since_order',
'visitor_count_visits',
'visit_goal_buyer',
'location_country',
'location_region',
'location_city',
'location_latitude',
'location_longitude',
'referer_name',
'referer_keyword',
'referer_type',
);
$dimensions = VisitDimension::getAllDimensions();
foreach ($dimensions as $dimension) {
if ($dimension->hasImplementedEvent('onExistingVisit')) {
$fields[] = $dimension->getColumnName();
}
foreach ($dimension->getRequiredVisitFields() as $field) {
$fields[] = $field;
}
}
/**
* This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading
* the existing visit. Properties that appear in this list will be available in other tracking
* events such as 'onExistingVisit'.
*
* Plugins can use this event to load additional visit entity properties for later use during tracking.
*/
Piwik::postEvent('Tracker.getVisitFieldsToPersist', array(&$fields));
array_unshift($fields, 'visit_first_action_time');
array_unshift($fields, 'visit_last_action_time');
for ($index = 1; $index <= CustomVariables::getMaxCustomVariables(); $index++) {
$fields[] = 'custom_var_k' . $index;
$fields[] = 'custom_var_v' . $index;
}
$fields = array_unique($fields);
}
return $fields;
$isKnown = $this->visitorRecognizer->findKnownVisitor($this->configId, $this->visitProperties, $this->request);
$this->setIsVisitorKnown($isKnown);
}
public function getVisitorInfo()
{
return $this->visitProperties->visitorInfo;
}
public function clearVisitorInfo()
{
$this->visitProperties->visitorInfo = array();
}
public function setVisitorColumn($column, $value)
public function setVisitorColumn($column, $value) // TODO: remove this eventually
{
$this->visitProperties->visitorInfo[$column] = $value;
}
......@@ -259,13 +69,8 @@ class Visitor
return $this->visitorKnown === true;
}
public function setIsVisitorKnown($isVisitorKnown)
private function setIsVisitorKnown($isVisitorKnown)
{
return $this->visitorKnown = $isVisitorKnown;
}
private function getModel()
{
return new Model();
}
}
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Tracker;
use Piwik\Common;
use Piwik\EventDispatcher;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugins\CustomVariables\CustomVariables;
use Piwik\Tracker\Visit\VisitProperties;
/**
* TODO
*/
class VisitorRecognizer
{
/**
* TODO
*
* @var array
*/
private $visitFieldsToSelect;
/**
* TODO
*
* @var bool
*/
private $trustCookiesOnly;
/**
* TODO
*
* @var int
*/
private $visitStandardLength;
/**
* TODO
*
* @var int
*/
private $lookBackNSecondsCustom;
/**
* TODO
*
* @var int
*/
private $trackerAlwaysNewVisitor;
/**
* TODO
*
* @var Model
*/
private $model;
/**
* TODO
*
* @var EventDispatcher
*/
private $eventDispatcher;
public function __construct($trustCookiesOnly, $visitStandardLength, $lookbackNSecondsCustom, $trackerAlwaysNewVisitor,
Model $model, EventDispatcher $eventDispatcher)
{
$this->trustCookiesOnly = $trustCookiesOnly;
$this->visitStandardLength = $visitStandardLength;
$this->lookBackNSecondsCustom = $lookbackNSecondsCustom;
$this->trackerAlwaysNewVisitor = $trackerAlwaysNewVisitor;
$this->model = $model;
$this->eventDispatcher = $eventDispatcher;
}
/**
* 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
*
* TODO: move docs to class docs
*/
public function findKnownVisitor($configId, VisitProperties $visitProperties, Request $request)
{
$idSite = $request->getIdSite();
$idVisitor = $request->getVisitorId();
$isVisitorIdToLookup = !empty($idVisitor);
if ($isVisitorIdToLookup) {
$visitProperties->visitorInfo['idvisitor'] = $idVisitor;
Common::printDebug("Matching visitors with: visitorId=" . bin2hex($idVisitor) . " OR configId=" . bin2hex($configId));
} else {
Common::printDebug("Visitor doesn't have the piwik cookie...");
}
$persistedVisitAttributes = $this->getVisitFieldsPersist();
$shouldMatchOneFieldOnly = $this->shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup, $request);
list($timeLookBack, $timeLookAhead) = $this->getWindowLookupThisVisit($request);
$visitRow = $this->model->findVisitor($idSite, $configId, $idVisitor, $persistedVisitAttributes, $shouldMatchOneFieldOnly, $isVisitorIdToLookup, $timeLookBack, $timeLookAhead);
$isNewVisitForced = $request->getParam('new_visit');
$isNewVisitForced = !empty($isNewVisitForced);
$enforceNewVisit = $isNewVisitForced || $this->trackerAlwaysNewVisitor;
if (!$enforceNewVisit
&& $visitRow
&& count($visitRow) > 0
) {
// These values will be used throughout the request
foreach ($persistedVisitAttributes as $field) {
$visitProperties->visitorInfo[$field] = $visitRow[$field];
}
$visitProperties->visitorInfo['visit_last_action_time'] = strtotime($visitRow['visit_last_action_time']);
$visitProperties->visitorInfo['visit_first_action_time'] = strtotime($visitRow['visit_first_action_time']);
// Custom Variables copied from Visit in potential later conversion
if (!empty($numCustomVarsToRead)) {
for ($i = 1; $i <= $numCustomVarsToRead; $i++) {
if (isset($visitRow['custom_var_k' . $i])
&& strlen($visitRow['custom_var_k' . $i])
) {
$visitProperties->visitorInfo['custom_var_k' . $i] = $visitRow['custom_var_k' . $i];
}
if (isset($visitRow['custom_var_v' . $i])
&& strlen($visitRow['custom_var_v' . $i])
) {
$visitProperties->visitorInfo['custom_var_v' . $i] = $visitRow['custom_var_v' . $i];
}
}
}
Common::printDebug("The visitor is known (idvisitor = " . bin2hex($visitProperties->visitorInfo['idvisitor']) . ",
config_id = " . bin2hex($configId) . ",
idvisit = {$visitProperties->visitorInfo['idvisit']},
last action = " . date("r", $visitProperties->visitorInfo['visit_last_action_time']) . ",
first action = " . date("r", $visitProperties->visitorInfo['visit_first_action_time']) . ",
visit_goal_buyer' = " . $visitProperties->visitorInfo['visit_goal_buyer'] . ")");
return true;
} else {
Common::printDebug("The visitor was not matched with an existing visitor...");
return false;
}
}
protected function shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup, Request $request)
{
$isForcedUserIdMustMatch = (false !== $request->getForcedUserId());
if ($isForcedUserIdMustMatch) {
// if &iud was set, we must try and match both idvisitor and config_id
return false;
}
// This setting would be enabled for Intranet websites, to ensure that visitors using all the same computer config, same IP
// are not counted as 1 visitor. In this case, we want to enforce and trust the visitor ID from the cookie.
if ($isVisitorIdToLookup && $this->trustCookiesOnly) {
return true;
}
// If a &cid= was set, we force to select this visitor (or create a new one)
$isForcedVisitorIdMustMatch = ($request->getForcedVisitorId() != null);
if ($isForcedVisitorIdMustMatch) {
return true;
}
if (!$isVisitorIdToLookup) {
return true;
}
return false;
}
/**
* By default, we look back 30 minutes to find a previous visitor (for performance reasons).
* In some cases, it is useful to look back and count unique visitors more accurately. You can set custom lookback window in
* [Tracker] window_look_back_for_visitor
*
* The returned value is the window range (Min, max) that the matched visitor should fall within
*
* @return array( datetimeMin, datetimeMax )
*/
protected function getWindowLookupThisVisit(Request $request)
{
$lookAheadNSeconds = $this->visitStandardLength;
$lookBackNSeconds = $this->visitStandardLength;
if ($this->lookBackNSecondsCustom > $lookBackNSeconds) {
$lookBackNSeconds = $lookBackNSecondsCustom;
}
$timeLookBack = date('Y-m-d H:i:s', $request->getCurrentTimestamp() - $lookBackNSeconds);
$timeLookAhead = date('Y-m-d H:i:s', $request->getCurrentTimestamp() + $lookAheadNSeconds);
return array($timeLookBack, $timeLookAhead);
}
/**
* @return array
*/
private function getVisitFieldsPersist()
{
if (is_null($this->visitFieldsToSelect)) {
$fields = array(
'idvisitor',
'idvisit',
'user_id',
'visit_exit_idaction_url',
'visit_exit_idaction_name',
'visitor_returning',
'visitor_days_since_first',
'visitor_days_since_order',
'visitor_count_visits',
'visit_goal_buyer',
'location_country',
'location_region',
'location_city',
'location_latitude',
'location_longitude',
'referer_name',
'referer_keyword',
'referer_type',
);
$dimensions = VisitDimension::getAllDimensions();
foreach ($dimensions as $dimension) {
if ($dimension->hasImplementedEvent('onExistingVisit')) {
$fields[] = $dimension->getColumnName();
}
foreach ($dimension->getRequiredVisitFields() as $field) {
$fields[] = $field;
}
}
/**
* This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading
* the existing visit. Properties that appear in this list will be available in other tracking
* events such as 'onExistingVisit'.
*
* Plugins can use this event to load additional visit entity properties for later use during tracking.
*/
$this->eventDispatcher->postEvent('Tracker.getVisitFieldsToPersist', array(&$fields));
array_unshift($fields, 'visit_first_action_time');
array_unshift($fields, 'visit_last_action_time');
for ($index = 1; $index <= CustomVariables::getMaxCustomVariables(); $index++) {
$fields[] = 'custom_var_k' . $index;
$fields[] = 'custom_var_v' . $index;
}
$this->visitFieldsToSelect = array_unique($fields);
}
return $this->visitFieldsToSelect;
}
}
\ No newline at end of file
......@@ -94,7 +94,10 @@ class FakeTrackerVisit extends Visit
{
public function __construct($request)
{
parent::__construct();
$this->request = $request;
$this->visitProperties = new Visit\VisitProperties();
$this->visitProperties->visitorInfo['location_ip'] = $request->getIp();
$this->visitProperties->visitorInfo['idvisitor'] = 1;
}
......@@ -151,7 +154,7 @@ class Visit2Test extends IntegrationTestCase
public function test_handleNewVisitWithoutConversion_shouldTriggerDimensions()
{
$request = new Request(array());
$visitor = new Visitor($request, '');
$visitor = new Visitor($request, '', new Visit\VisitProperties());
$visit = new FakeTrackerVisit($request);
$visit->handleNewVisit($visitor, null, false);
......@@ -173,7 +176,7 @@ class Visit2Test extends IntegrationTestCase
public function test_handleNewVisitWithConversion_shouldTriggerDimensions()
{
$request = new Request(array());
$visitor = new Visitor($request, '');
$visitor = new Visitor($request, '', new Visit\VisitProperties());
$visit = new FakeTrackerVisit($request);
$visit->handleNewVisit($visitor, null, true);
......@@ -191,7 +194,7 @@ class Visit2Test extends IntegrationTestCase
public function test_handleExistingVisitWithoutConversion_shouldTriggerDimensions()
{
$request = new Request(array());
$visitor = new Visitor($request, '');
$visitor = new Visitor($request, '', new Visit\VisitProperties());
$visit = new FakeTrackerVisit($request);
$visit->handleNewVisit($visitor, null, false);
......@@ -214,7 +217,7 @@ class Visit2Test extends IntegrationTestCase
public function test_handleExistingVisitWithConversion_shouldTriggerDimensions()
{
$request = new Request(array());
$visitor = new Visitor($request, '');
$visitor = new Visitor($request, '', new Visit\VisitProperties());
$visit = new FakeTrackerVisit($request);
$visit->handleNewVisit($visitor, null, false);
......
......@@ -432,8 +432,7 @@ class VisitTest extends IntegrationTestCase
$visitProperties = new Visit\VisitProperties();
$visitProperties->visitorInfo = array('visit_last_action_time' => Date::factory($lastActionTimestamp)->getTimestamp());
$visitor = new Visitor($request, 'configid', $visitProperties);
$visitor->setIsVisitorKnown($isVisitorKnown);
$visitor = new Visitor($request, 'configid', $visitProperties, $isVisitorKnown);
$action = new ActionPageview($request);
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter