| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Tracker; |
|
|
| use Piwik\Common; |
| use Piwik\Config; |
| use Piwik\Container\StaticContainer; |
| use Matomo\Network\IPUtils; |
| use Piwik\Plugin\Dimension\VisitDimension; |
| use Piwik\Plugin\LogTablesProvider; |
| use Piwik\Plugins\Actions\Tracker\ActionsRequestProcessor; |
| use Piwik\Tracker; |
| use Piwik\Tracker\Visit\VisitProperties; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class Visit implements VisitInterface |
| { |
| use RequestHandlerTrait; |
|
|
| public const UNKNOWN_CODE = 'xx'; |
|
|
| |
| |
| |
| protected $goalManager; |
|
|
| |
| |
| |
| protected $request; |
|
|
| |
| |
| |
| protected $userSettings; |
|
|
| public static $dimensions; |
|
|
| |
| |
| |
| protected $requestProcessors; |
|
|
| |
| |
| |
| protected $visitProperties; |
|
|
| |
| |
| |
| protected $previousVisitProperties; |
|
|
| public function __construct() |
| { |
| $requestProcessors = StaticContainer::get('Piwik\Plugin\RequestProcessors'); |
| $this->requestProcessors = $requestProcessors->getRequestProcessors(); |
| $this->visitProperties = null; |
| $this->userSettings = StaticContainer::get('Piwik\Tracker\Settings'); |
| } |
|
|
| public function setRequest(Request $request) |
| { |
| $this->request = $request; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function handle() |
| { |
| $this->checkSiteExists($this->request); |
|
|
| foreach ($this->requestProcessors as $processor) { |
| Common::printDebug("Executing " . get_class($processor) . "::manipulateRequest()..."); |
|
|
| $processor->manipulateRequest($this->request); |
| } |
|
|
| $this->validateRequest($this->request); |
|
|
| $this->visitProperties = new VisitProperties(); |
|
|
| foreach ($this->requestProcessors as $processor) { |
| Common::printDebug("Executing " . get_class($processor) . "::processRequestParams()..."); |
|
|
| $abort = $processor->processRequestParams($this->visitProperties, $this->request); |
| if ($abort) { |
| Common::printDebug("-> aborting due to processRequestParams method"); |
| return; |
| } |
| } |
|
|
| $isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit'); |
| if (!$isNewVisit) { |
| $isNewVisit = $this->triggerPredicateHookOnDimensions($this->getAllVisitDimensions(), 'shouldForceNewVisit'); |
| $this->request->setMetadata('CoreHome', 'isNewVisit', $isNewVisit); |
| } |
|
|
| foreach ($this->requestProcessors as $processor) { |
| Common::printDebug("Executing " . get_class($processor) . "::afterRequestProcessed()..."); |
|
|
| $abort = $processor->afterRequestProcessed($this->visitProperties, $this->request); |
| if ($abort) { |
| Common::printDebug("-> aborting due to afterRequestProcessed method"); |
| return; |
| } |
| } |
|
|
| $isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit'); |
| $this->previousVisitProperties = new VisitProperties($this->request->getMetadata('CoreHome', 'lastKnownVisit') ?: []); |
|
|
| |
| |
| |
| |
| |
| |
| |
| if (!$isNewVisit) { |
| try { |
| $this->handleExistingVisit($this->request->getMetadata('Goals', 'visitIsConverted')); |
| } catch (VisitorNotFoundInDb $e) { |
| $this->request->setMetadata('CoreHome', 'visitorNotFoundInDb', true); |
| } |
| } |
|
|
| |
| |
| |
| |
| if ($isNewVisit) { |
| $this->handleNewVisit($this->request->getMetadata('Goals', 'visitIsConverted')); |
| } |
|
|
| |
| $this->request->setThirdPartyCookie($this->request->getVisitorIdForThirdPartyCookie()); |
|
|
| foreach ($this->requestProcessors as $processor) { |
| if (!$isNewVisit && $processor instanceof ActionsRequestProcessor) { |
| |
| continue; |
| } |
| Common::printDebug("Executing " . get_class($processor) . "::recordLogs()..."); |
|
|
| $processor->recordLogs($this->visitProperties, $this->request); |
| } |
| $this->markArchivedReportsAsInvalidIfArchiveAlreadyFinished($this->request); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function handleExistingVisit($visitIsConverted) |
| { |
| Common::printDebug("Visit is known (IP = " . IPUtils::binaryToStringIP($this->getVisitorIp()) . ")"); |
|
|
| |
| $this->visitProperties->setProperty('time_spent_ref_action', $this->getTimeSpentReferrerAction()); |
|
|
| $valuesToUpdate = $this->getExistingVisitFieldsToUpdate($visitIsConverted); |
|
|
| |
| foreach ($valuesToUpdate as $name => $value) { |
| $this->visitProperties->setProperty($name, $value); |
| } |
|
|
| foreach ($this->requestProcessors as $processor) { |
| |
| |
| |
| if ($processor instanceof ActionsRequestProcessor) { |
| Common::printDebug("Executing " . get_class($processor) . "::recordLogs()..."); |
| $processor->recordLogs($this->visitProperties, $this->request); |
| } |
| } |
|
|
| foreach ($this->requestProcessors as $processor) { |
| $processor->onExistingVisit($valuesToUpdate, $this->visitProperties, $this->request); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| $visitorRecognizer = StaticContainer::get(VisitorRecognizer::class); |
| $valuesToUpdate = $visitorRecognizer->removeUnchangedValues($valuesToUpdate, $this->previousVisitProperties); |
|
|
| $this->updateExistingVisit($valuesToUpdate); |
|
|
| $this->visitProperties->setProperty('visit_last_action_time', $this->request->getCurrentTimestamp()); |
| } |
|
|
| |
| |
| |
| protected function getTimeSpentReferrerAction() |
| { |
| $timeSpent = $this->request->getCurrentTimestamp() - |
| $this->visitProperties->getProperty('visit_last_action_time'); |
| if ($timeSpent < 0) { |
| $timeSpent = 0; |
| } |
| $visitStandardLength = $this->getVisitStandardLength(); |
| if ($timeSpent > $visitStandardLength) { |
| $timeSpent = $visitStandardLength; |
| } |
| return $timeSpent; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function handleNewVisit($visitIsConverted) |
| { |
| Common::printDebug("New Visit (IP = " . IPUtils::binaryToStringIP($this->getVisitorIp()) . ")"); |
|
|
| $this->setNewVisitorInformation(); |
|
|
| $dimensions = $this->getAllVisitDimensions(); |
|
|
| $this->triggerHookOnDimensions($dimensions, 'onNewVisit'); |
|
|
| if ($visitIsConverted) { |
| $this->triggerHookOnDimensions($dimensions, 'onConvertedVisit'); |
| } |
|
|
| foreach ($this->requestProcessors as $processor) { |
| $processor->onNewVisit($this->visitProperties, $this->request); |
| } |
|
|
| $this->printVisitorInformation(); |
|
|
| $idVisit = $this->insertNewVisit($this->visitProperties->getProperties()); |
|
|
| $this->visitProperties->setProperty('idvisit', $idVisit); |
| $this->visitProperties->setProperty('visit_first_action_time', $this->request->getCurrentTimestamp()); |
| $this->visitProperties->setProperty('visit_last_action_time', $this->request->getCurrentTimestamp()); |
| } |
|
|
| private function getModel() |
| { |
| return new Model(); |
| } |
|
|
| |
| |
| |
| |
| |
| protected function getVisitorIdcookie() |
| { |
| $isKnown = $this->request->getMetadata('CoreHome', 'isVisitorKnown'); |
| if ($isKnown) { |
| return $this->visitProperties->getProperty('idvisitor'); |
| } |
|
|
| |
| $idVisitor = $this->visitProperties->getProperty('idvisitor'); |
| if ( |
| !empty($idVisitor) |
| && Tracker::LENGTH_BINARY_ID == strlen($this->visitProperties->getProperty('idvisitor')) |
| ) { |
| return $this->visitProperties->getProperty('idvisitor'); |
| } |
|
|
| return Common::hex2bin($this->generateUniqueVisitorId()); |
| } |
|
|
| |
| |
| |
| public static function generateUniqueVisitorId() |
| { |
| return substr(Common::generateUniqId(), 0, Tracker::LENGTH_HEX_ID_STRING); |
| } |
|
|
| |
| |
| |
| |
| |
| protected function getVisitorIp() |
| { |
| return $this->visitProperties->getProperty('location_ip'); |
| } |
|
|
| |
| public static function isHostKnownAliasHost($urlHost, $idSite) |
| { |
| $websiteData = Cache::getCacheWebsiteAttributes($idSite); |
|
|
| if (isset($websiteData['hosts'])) { |
| $canonicalHosts = array(); |
| foreach ($websiteData['hosts'] as $host) { |
| $canonicalHosts[] = self::toCanonicalHost($host); |
| } |
|
|
| $canonicalHost = self::toCanonicalHost($urlHost); |
| if (in_array($canonicalHost, $canonicalHosts)) { |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| private static function toCanonicalHost($host) |
| { |
| $hostLower = mb_strtolower($host); |
| return str_replace('www.', '', $hostLower); |
| } |
|
|
| |
| |
| |
| |
| protected function updateExistingVisit($valuesToUpdate) |
| { |
| if (empty($valuesToUpdate)) { |
| Common::printDebug('There are no values to be updated for this visit'); |
| return; |
| } |
|
|
| $idSite = $this->request->getIdSite(); |
| $idVisit = $this->visitProperties->getProperty('idvisit'); |
|
|
| $wasInserted = $this->getModel()->updateVisit($idSite, $idVisit, $valuesToUpdate); |
|
|
| if (isset($valuesToUpdate['idvisitor'])) { |
| $this->updateIdVisitorAcrossLogTables($valuesToUpdate['idvisitor']); |
| Common::printDebug('Updating idvisitor across tables for idvisit = ' . $idVisit); |
|
|
| |
| $valuesToUpdate['idvisitor'] = bin2hex($valuesToUpdate['idvisitor']); |
| } |
|
|
| if ($wasInserted) { |
| Common::printDebug('Updated existing visit: ' . var_export($valuesToUpdate, true)); |
| } elseif (!$this->getModel()->hasVisit($idSite, $idVisit)) { |
| |
| |
| |
| throw new VisitorNotFoundInDb( |
| "The visitor with idvisitor=" . bin2hex($this->visitProperties->getProperty('idvisitor')) |
| . " and idvisit=" . @$this->visitProperties->getProperty('idvisit') |
| . " wasn't found in the DB, we fallback to a new visitor" |
| ); |
| } |
| } |
|
|
| protected function updateIdVisitorAcrossLogTables(string $idVisitor): void |
| { |
| $allLogTables = StaticContainer::get(LogTablesProvider::class)->getAllLogTables(); |
|
|
| foreach ($allLogTables as $logTable) { |
| if (!$logTable->hasIdVisitorColumn() || $logTable instanceof \Piwik\Plugins\CoreHome\Tracker\LogTable\Visit) { |
| |
| continue; |
| } |
|
|
| $conditions = ['idvisitor' => $this->previousVisitProperties->getProperty('idvisitor')]; |
|
|
| if ($logTable->getIdColumn() !== 'idvisitor' && $logTable->getColumnToJoinOnIdVisit()) { |
| $conditions[$logTable->getColumnToJoinOnIdVisit()] = $this->visitProperties->getProperty('idvisit'); |
| } |
|
|
| $this->getModel()->updateIdVisitorInLogTable($logTable->getName(), $idVisitor, $conditions); |
| } |
| } |
|
|
| private function printVisitorInformation() |
| { |
| $debugVisitInfo = $this->visitProperties->getProperties(); |
| $debugVisitInfo['idvisitor'] = isset($debugVisitInfo['idvisitor']) ? bin2hex($debugVisitInfo['idvisitor']) : ''; |
| $debugVisitInfo['config_id'] = isset($debugVisitInfo['config_id']) ? bin2hex($debugVisitInfo['config_id']) : ''; |
| $debugVisitInfo['location_ip'] = IPUtils::binaryToStringIP($debugVisitInfo['location_ip']); |
| Common::printDebug($debugVisitInfo); |
| } |
|
|
| private function setNewVisitorInformation() |
| { |
| $idVisitor = $this->getVisitorIdcookie(); |
| $visitorIp = $this->getVisitorIp(); |
| $configId = $this->request->getMetadata('CoreHome', 'visitorId'); |
|
|
| $this->visitProperties->clearProperties(); |
|
|
| $this->visitProperties->setProperty('idvisitor', $idVisitor); |
| $this->visitProperties->setProperty('config_id', $configId); |
| $this->visitProperties->setProperty('location_ip', $visitorIp); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function getExistingVisitFieldsToUpdate($visitIsConverted) |
| { |
| $valuesToUpdate = array(); |
|
|
| $valuesToUpdate = $this->setIdVisitorForExistingVisit($valuesToUpdate); |
|
|
| $dimensions = $this->getAllVisitDimensions(); |
| $valuesToUpdate = $this->triggerHookOnDimensions($dimensions, 'onExistingVisit', $valuesToUpdate); |
|
|
| if ($visitIsConverted) { |
| $valuesToUpdate = $this->triggerHookOnDimensions($dimensions, 'onConvertedVisit', $valuesToUpdate); |
| } |
|
|
| |
| return $valuesToUpdate; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| private function triggerHookOnDimensions($dimensions, $hook, $valuesToUpdate = null) |
| { |
| $visitor = $this->makeVisitorFacade(); |
|
|
| |
| $action = $this->request->getMetadata('Actions', 'action'); |
|
|
| foreach ($dimensions as $dimension) { |
| $value = $dimension->$hook($this->request, $visitor, $action); |
|
|
| if ($value !== false) { |
| $fieldName = $dimension->getColumnName(); |
| $visitor->setVisitorColumn($fieldName, $value); |
|
|
| if (is_float($value)) { |
| $value = Common::forceDotAsSeparatorForDecimalPoint($value); |
| } |
|
|
| if ($valuesToUpdate !== null) { |
| $valuesToUpdate[$fieldName] = $value; |
| } else { |
| $this->visitProperties->setProperty($fieldName, $value); |
| } |
| } |
| } |
|
|
| return $valuesToUpdate; |
| } |
|
|
| private function triggerPredicateHookOnDimensions($dimensions, $hook) |
| { |
| $visitor = $this->makeVisitorFacade(); |
|
|
| |
| $action = $this->request->getMetadata('Actions', 'action'); |
|
|
| foreach ($dimensions as $dimension) { |
| if ($dimension->$hook($this->request, $visitor, $action)) { |
| return true; |
| } |
| } |
| return false; |
| } |
|
|
| protected function getAllVisitDimensions() |
| { |
| if (is_null(self::$dimensions)) { |
| self::$dimensions = VisitDimension::getAllDimensions(); |
|
|
| $dimensionNames = array(); |
| foreach (self::$dimensions as $dimension) { |
| $dimensionNames[] = $dimension->getColumnName(); |
| } |
|
|
| Common::printDebug("Following dimensions have been collected from plugins: " . implode( |
| ", ", |
| $dimensionNames |
| )); |
| } |
|
|
| return self::$dimensions; |
| } |
|
|
| private function getVisitStandardLength() |
| { |
| return Config::getInstance()->Tracker['visit_standard_length']; |
| } |
|
|
| |
| |
| |
| |
| private function setIdVisitorForExistingVisit($valuesToUpdate) |
| { |
| $idVisitor = $this->visitProperties->getProperty('idvisitor'); |
| if (!empty($idVisitor) && Tracker::LENGTH_BINARY_ID == strlen($idVisitor)) { |
| $valuesToUpdate['idvisitor'] = $idVisitor; |
| } |
|
|
| $visitorId = $this->request->getVisitorId(); |
| if ($visitorId && strlen($visitorId) === Tracker::LENGTH_BINARY_ID) { |
| |
| $valuesToUpdate['idvisitor'] = $this->request->getVisitorId(); |
| } |
|
|
| if (TrackerConfig::getConfigValue('enable_userid_overwrites_visitorid', $this->request->getIdSiteIfExists())) { |
| |
| $userId = $this->request->getForcedUserId(); |
| if ($userId) { |
| $userIdHash = $this->request->getUserIdHashed($userId); |
| $binIdVisitor = Common::hex2bin($userIdHash); |
| $this->visitProperties->setProperty('idvisitor', $binIdVisitor); |
| $valuesToUpdate['idvisitor'] = $binIdVisitor; |
| } |
| } |
|
|
| return $valuesToUpdate; |
| } |
|
|
| protected function insertNewVisit($visit) |
| { |
| return $this->getModel()->createVisit($visit); |
| } |
|
|
| private function makeVisitorFacade() |
| { |
| return Visitor::makeFromVisitProperties($this->visitProperties, $this->request, $this->previousVisitProperties); |
| } |
| } |
|
|