PageRenderTime 48ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Tracker/Visit.php

https://github.com/CodeYellowBV/piwik
PHP | 666 lines | 394 code | 82 blank | 190 comment | 56 complexity | 3ec3ed8b4a737282ec6902b37f55da54 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\Tracker;
  10. use DeviceDetector;
  11. use Piwik\Common;
  12. use Piwik\Config;
  13. use Piwik\IP;
  14. use Piwik\Piwik;
  15. use Piwik\Plugins\CustomVariables\CustomVariables;
  16. use Piwik\Tracker;
  17. /**
  18. * Class used to handle a Visit.
  19. * A visit is either NEW or KNOWN.
  20. * - If a visit is NEW then we process the visitor information (settings, referrers, etc.) and save
  21. * a new line in the log_visit table.
  22. * - If a visit is KNOWN then we update the visit row in the log_visit table, updating the number of pages
  23. * views, time spent, etc.
  24. *
  25. * Whether a visit is NEW or KNOWN we also save the action in the DB.
  26. * One request to the piwik.php script is associated to one action.
  27. *
  28. */
  29. class Visit implements VisitInterface
  30. {
  31. const UNKNOWN_CODE = 'xx';
  32. /**
  33. * @var GoalManager
  34. */
  35. protected $goalManager;
  36. /**
  37. * @var Request
  38. */
  39. protected $request;
  40. protected $visitorInfo = array();
  41. /**
  42. * @var Settings
  43. */
  44. protected $userSettings;
  45. protected $visitorCustomVariables = array();
  46. protected $visitorKnown;
  47. /**
  48. * @param Request $request
  49. */
  50. public function setRequest(Request $request)
  51. {
  52. $this->request = $request;
  53. }
  54. /**
  55. * Main algorithm to handle the visit.
  56. *
  57. * Once we have the visitor information, we have to determine if the visit is a new or a known visit.
  58. *
  59. * 1) When the last action was done more than 30min ago,
  60. * or if the visitor is new, then this is a new visit.
  61. *
  62. * 2) If the last action is less than 30min ago, then the same visit is going on.
  63. * Because the visit goes on, we can get the time spent during the last action.
  64. *
  65. * NB:
  66. * - In the case of a new visit, then the time spent
  67. * during the last action of the previous visit is unknown.
  68. *
  69. * - In the case of a new visit but with a known visitor,
  70. * we can set the 'returning visitor' flag.
  71. *
  72. * In all the cases we set a cookie to the visitor with the new information.
  73. */
  74. public function handle()
  75. {
  76. // the IP is needed by isExcluded() and GoalManager->recordGoals()
  77. $ip = $this->request->getIp();
  78. $this->visitorInfo['location_ip'] = $ip;
  79. $excluded = new VisitExcluded($this->request, $ip);
  80. if ($excluded->isExcluded()) {
  81. return;
  82. }
  83. /**
  84. * Triggered after visits are tested for exclusion so plugins can modify the IP address
  85. * persisted with a visit.
  86. *
  87. * This event is primarily used by the **PrivacyManager** plugin to anonymize IP addresses.
  88. *
  89. * @param string &$ip The visitor's IP address.
  90. */
  91. Piwik::postEvent('Tracker.setVisitorIp', array(&$this->visitorInfo['location_ip']));
  92. $this->visitorCustomVariables = $this->request->getCustomVariables($scope = 'visit');
  93. if (!empty($this->visitorCustomVariables)) {
  94. Common::printDebug("Visit level Custom Variables: ");
  95. Common::printDebug($this->visitorCustomVariables);
  96. }
  97. $this->goalManager = new GoalManager($this->request);
  98. $visitIsConverted = false;
  99. $action = null;
  100. $requestIsManualGoalConversion = ($this->goalManager->idGoal > 0);
  101. $requestIsEcommerce = $this->goalManager->requestIsEcommerce;
  102. if ($requestIsEcommerce) {
  103. $someGoalsConverted = true;
  104. // Mark the visit as Converted only if it is an order (not for a Cart update)
  105. if ($this->goalManager->isGoalAnOrder) {
  106. $visitIsConverted = true;
  107. }
  108. } // this request is from the JS call to piwikTracker.trackGoal()
  109. elseif ($requestIsManualGoalConversion) {
  110. $someGoalsConverted = $this->goalManager->detectGoalId($this->request->getIdSite());
  111. $visitIsConverted = $someGoalsConverted;
  112. // if we find a idgoal in the URL, but then the goal is not valid, this is most likely a fake request
  113. if (!$someGoalsConverted) {
  114. throw new \Exception('Invalid goal tracking request for goal id = ' . $this->goalManager->idGoal);
  115. }
  116. } // normal page view, potentially triggering a URL matching goal
  117. else {
  118. $action = Action::factory($this->request);
  119. $action->writeDebugInfo();
  120. $someGoalsConverted = $this->goalManager->detectGoalsMatchingUrl($this->request->getIdSite(), $action);
  121. $visitIsConverted = $someGoalsConverted;
  122. $action->loadIdsFromLogActionTable();
  123. }
  124. /***
  125. * Visitor recognition
  126. */
  127. $visitor = new Visitor($this->request, $this->getSettingsObject(), $this->visitorInfo, $this->visitorCustomVariables);
  128. $visitor->recognize();
  129. $this->visitorKnown = $visitor->isVisitorKnown();
  130. $this->visitorInfo = $visitor->getVisitorInfo();
  131. $isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit();
  132. if (!$isLastActionInTheSameVisit) {
  133. Common::printDebug("Visitor detected, but last action was more than 30 minutes ago...");
  134. }
  135. // Known visit when:
  136. // ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor
  137. // OR
  138. // - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor()
  139. // )
  140. // AND
  141. // - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit()
  142. if ($this->isVisitorKnown()
  143. && $isLastActionInTheSameVisit
  144. ) {
  145. $idReferrerActionUrl = $this->visitorInfo['visit_exit_idaction_url'];
  146. $idReferrerActionName = $this->visitorInfo['visit_exit_idaction_name'];
  147. try {
  148. $this->handleExistingVisit($action, $visitIsConverted);
  149. if (!is_null($action)) {
  150. $action->record($this->visitorInfo['idvisit'],
  151. $this->visitorInfo['idvisitor'],
  152. $idReferrerActionUrl,
  153. $idReferrerActionName,
  154. $this->visitorInfo['time_spent_ref_action']
  155. );
  156. }
  157. } catch (VisitorNotFoundInDb $e) {
  158. // There is an edge case when:
  159. // - two manual goal conversions happen in the same second
  160. // - which result in handleExistingVisit throwing the exception
  161. // because the UPDATE didn't affect any rows (one row was found, but not updated since no field changed)
  162. // - the exception is caught here and will result in a new visit incorrectly
  163. // In this case, we cancel the current conversion to be recorded:
  164. if ($requestIsManualGoalConversion
  165. || $requestIsEcommerce
  166. ) {
  167. $someGoalsConverted = $visitIsConverted = false;
  168. } // When the row wasn't found in the logs, and this is a pageview or
  169. // goal matching URL, we force a new visitor
  170. else {
  171. $this->visitorKnown = false;
  172. }
  173. }
  174. }
  175. // New visit when:
  176. // - the visitor has the Piwik cookie but the last action was performed more than 30 min ago @see isLastActionInTheSameVisit()
  177. // - the visitor doesn't have the Piwik cookie, and couldn't be matched in @see recognizeTheVisitor()
  178. // - 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
  179. if (!$this->isVisitorKnown()
  180. || !$isLastActionInTheSameVisit
  181. ) {
  182. $this->handleNewVisit($action, $visitIsConverted);
  183. if (!is_null($action)) {
  184. $action->record($this->visitorInfo['idvisit'], $this->visitorInfo['idvisitor'], 0, 0, 0);
  185. }
  186. }
  187. // update the cookie with the new visit information
  188. $this->request->setThirdPartyCookie($this->visitorInfo['idvisitor']);
  189. // record the goals if applicable
  190. if ($someGoalsConverted) {
  191. $this->goalManager->recordGoals(
  192. $this->request->getIdSite(),
  193. $this->visitorInfo,
  194. $this->visitorCustomVariables,
  195. $action
  196. );
  197. }
  198. unset($this->goalManager);
  199. unset($action);
  200. }
  201. /**
  202. * In the case of a known visit, we have to do the following actions:
  203. *
  204. * 1) Insert the new action
  205. * 2) Update the visit information
  206. *
  207. * @param Action $action
  208. * @param $visitIsConverted
  209. * @throws VisitorNotFoundInDb
  210. */
  211. protected function handleExistingVisit($action, $visitIsConverted)
  212. {
  213. Common::printDebug("Visit is known (IP = " . IP::N2P($this->getVisitorIp()) . ")");
  214. $valuesToUpdate = $this->getExistingVisitFieldsToUpdate($action, $visitIsConverted);
  215. $this->visitorInfo['time_spent_ref_action'] = $this->getTimeSpentReferrerAction();
  216. $this->request->overrideLocation($valuesToUpdate);
  217. // update visitorInfo
  218. foreach ($valuesToUpdate AS $name => $value) {
  219. $this->visitorInfo[$name] = $value;
  220. }
  221. /**
  222. * Triggered before a [visit entity](/guides/persistence-and-the-mysql-backend#visits) is updated when
  223. * tracking an action for an existing visit.
  224. *
  225. * This event can be used to modify the visit properties that will be updated before the changes
  226. * are persisted.
  227. *
  228. * @param array &$valuesToUpdate Visit entity properties that will be updated.
  229. * @param array $visit The entire visit entity. Read [this](/guides/persistence-and-the-mysql-backend#visits)
  230. * to see what it contains.
  231. */
  232. Piwik::postEvent('Tracker.existingVisitInformation', array(&$valuesToUpdate, $this->visitorInfo));
  233. $this->updateExistingVisit($valuesToUpdate);
  234. }
  235. /**
  236. * @return int Time in seconds
  237. */
  238. protected function getTimeSpentReferrerAction()
  239. {
  240. $timeSpent = $this->request->getCurrentTimestamp() - $this->visitorInfo['visit_last_action_time'];
  241. if ($timeSpent < 0
  242. || $timeSpent > Config::getInstance()->Tracker['visit_standard_length']
  243. ) {
  244. $timeSpent = 0;
  245. }
  246. return $timeSpent;
  247. }
  248. /**
  249. * In the case of a new visit, we have to do the following actions:
  250. *
  251. * 1) Insert the new action
  252. *
  253. * 2) Insert the visit information
  254. *
  255. * @param Action $action
  256. * @param bool $visitIsConverted
  257. */
  258. protected function handleNewVisit($action, $visitIsConverted)
  259. {
  260. Common::printDebug("New Visit (IP = " . IP::N2P($this->getVisitorIp()) . ")");
  261. $this->visitorInfo = $this->getNewVisitorInformation($action);
  262. // Add Custom variable key,value to the visitor array
  263. $this->visitorInfo = array_merge($this->visitorInfo, $this->visitorCustomVariables);
  264. $this->visitorInfo['visit_goal_converted'] = $visitIsConverted ? 1 : 0;
  265. $this->visitorInfo['referer_name'] = substr($this->visitorInfo['referer_name'], 0, 70);
  266. $this->visitorInfo['referer_keyword'] = substr($this->visitorInfo['referer_keyword'], 0, 255);
  267. $this->visitorInfo['config_resolution'] = substr($this->visitorInfo['config_resolution'], 0, 9);
  268. /**
  269. * Triggered before a new [visit entity](/guides/persistence-and-the-mysql-backend#visits) is persisted.
  270. *
  271. * This event can be used to modify the visit entity or add new information to it before it is persisted.
  272. * The UserCountry plugin, for example, uses this event to add location information for each visit.
  273. *
  274. * @param array &$visit The visit entity. Read [this](/guides/persistence-and-the-mysql-backend#visits) to see
  275. * what information it contains.
  276. * @param \Piwik\Tracker\Request $request An object describing the tracking request being processed.
  277. */
  278. Piwik::postEvent('Tracker.newVisitorInformation', array(&$this->visitorInfo, $this->request));
  279. $this->request->overrideLocation($this->visitorInfo);
  280. $this->printVisitorInformation();
  281. $idVisit = $this->insertNewVisit( $this->visitorInfo );
  282. $this->visitorInfo['idvisit'] = $idVisit;
  283. $this->visitorInfo['visit_first_action_time'] = $this->request->getCurrentTimestamp();
  284. $this->visitorInfo['visit_last_action_time'] = $this->request->getCurrentTimestamp();
  285. }
  286. static private function cleanupVisitTotalTime($t)
  287. {
  288. $t = (int)$t;
  289. if ($t < 0) {
  290. $t = 0;
  291. }
  292. $smallintMysqlLimit = 65534;
  293. if ($t > $smallintMysqlLimit) {
  294. $t = $smallintMysqlLimit;
  295. }
  296. return $t;
  297. }
  298. /**
  299. * Returns visitor cookie
  300. *
  301. * @return string binary
  302. */
  303. protected function getVisitorIdcookie()
  304. {
  305. if ($this->isVisitorKnown()) {
  306. return $this->visitorInfo['idvisitor'];
  307. }
  308. // If the visitor had a first party ID cookie, then we use this value
  309. if (!empty($this->visitorInfo['idvisitor'])
  310. && strlen($this->visitorInfo['idvisitor']) == Tracker::LENGTH_BINARY_ID
  311. ) {
  312. return $this->visitorInfo['idvisitor'];
  313. }
  314. return Common::hex2bin($this->generateUniqueVisitorId());
  315. }
  316. /**
  317. * @return string returns random 16 chars hex string
  318. */
  319. static public function generateUniqueVisitorId()
  320. {
  321. $uniqueId = substr(Common::generateUniqId(), 0, Tracker::LENGTH_HEX_ID_STRING);
  322. return $uniqueId;
  323. }
  324. /**
  325. * Returns the visitor's IP address
  326. *
  327. * @return string
  328. */
  329. protected function getVisitorIp()
  330. {
  331. return $this->visitorInfo['location_ip'];
  332. }
  333. /**
  334. * Gets the UserSettings object
  335. *
  336. * @return Settings
  337. */
  338. protected function getSettingsObject()
  339. {
  340. if(is_null($this->userSettings)) {
  341. $this->userSettings = new Settings( $this->request, $this->getVisitorIp() );
  342. }
  343. return $this->userSettings;
  344. }
  345. /**
  346. * Returns true if the last action was done during the last 30 minutes
  347. * @return bool
  348. */
  349. protected function isLastActionInTheSameVisit()
  350. {
  351. return isset($this->visitorInfo['visit_last_action_time'])
  352. && ($this->visitorInfo['visit_last_action_time']
  353. > ($this->request->getCurrentTimestamp() - Config::getInstance()->Tracker['visit_standard_length']));
  354. }
  355. /**
  356. * Returns true if the recognizeTheVisitor() method did recognize the visitor
  357. * @return bool
  358. */
  359. protected function isVisitorKnown()
  360. {
  361. return $this->visitorKnown === true;
  362. }
  363. // is the referrer host any of the registered URLs for this website?
  364. static public function isHostKnownAliasHost($urlHost, $idSite)
  365. {
  366. $websiteData = Cache::getCacheWebsiteAttributes($idSite);
  367. if (isset($websiteData['hosts'])) {
  368. $canonicalHosts = array();
  369. foreach ($websiteData['hosts'] as $host) {
  370. $canonicalHosts[] = str_replace('www.', '', mb_strtolower($host, 'UTF-8'));
  371. }
  372. $canonicalHost = str_replace('www.', '', mb_strtolower($urlHost, 'UTF-8'));
  373. if (in_array($canonicalHost, $canonicalHosts)) {
  374. return true;
  375. }
  376. }
  377. return false;
  378. }
  379. /**
  380. * @return mixed
  381. */
  382. protected function insertNewVisit($visit)
  383. {
  384. $fields = implode(", ", array_keys($visit));
  385. $values = Common::getSqlStringFieldsArray($visit);
  386. $sql = "INSERT INTO " . Common::prefixTable('log_visit') . " ($fields) VALUES ($values)";
  387. $bind = array_values($visit);
  388. Tracker::getDatabase()->query($sql, $bind);
  389. $idVisit = Tracker::getDatabase()->lastInsertId();
  390. return $idVisit;
  391. }
  392. /**
  393. * @param $valuesToUpdate
  394. * @throws VisitorNotFoundInDb
  395. */
  396. protected function updateExistingVisit($valuesToUpdate)
  397. {
  398. $sqlQuery = "UPDATE " . Common::prefixTable('log_visit') . "
  399. SET %s
  400. WHERE idsite = ?
  401. AND idvisit = ?";
  402. // build sql query
  403. $updateParts = $sqlBind = array();
  404. foreach ($valuesToUpdate AS $name => $value) {
  405. // Case where bind parameters don't work
  406. if(strpos($value, $name) !== false) {
  407. //$name = 'visit_total_events'
  408. //$value = 'visit_total_events + 1';
  409. $updateParts[] = " $name = $value ";
  410. } else {
  411. $updateParts[] = $name . " = ?";
  412. $sqlBind[] = $value;
  413. }
  414. }
  415. $sqlQuery = sprintf($sqlQuery, implode($updateParts, ', ') );
  416. array_push($sqlBind, $this->request->getIdSite(), (int)$this->visitorInfo['idvisit']);
  417. $result = Tracker::getDatabase()->query($sqlQuery, $sqlBind);
  418. $this->visitorInfo['visit_last_action_time'] = $this->request->getCurrentTimestamp();
  419. // Debug output
  420. if (isset($valuesToUpdate['idvisitor'])) {
  421. $valuesToUpdate['idvisitor'] = bin2hex($valuesToUpdate['idvisitor']);
  422. }
  423. Common::printDebug('Updating existing visit: ' . var_export($valuesToUpdate, true));
  424. if (Tracker::getDatabase()->rowCount($result) == 0) {
  425. Common::printDebug("Visitor with this idvisit wasn't found in the DB.");
  426. Common::printDebug("$sqlQuery --- ");
  427. Common::printDebug($sqlBind);
  428. throw new VisitorNotFoundInDb(
  429. "The visitor with idvisitor=" . bin2hex($this->visitorInfo['idvisitor']) . " and idvisit=" . $this->visitorInfo['idvisit']
  430. . " wasn't found in the DB, we fallback to a new visitor");
  431. }
  432. }
  433. protected function printVisitorInformation()
  434. {
  435. $debugVisitInfo = $this->visitorInfo;
  436. $debugVisitInfo['idvisitor'] = bin2hex($debugVisitInfo['idvisitor']);
  437. $debugVisitInfo['config_id'] = bin2hex($debugVisitInfo['config_id']);
  438. Common::printDebug($debugVisitInfo);
  439. }
  440. protected function getNewVisitorInformation($action)
  441. {
  442. $actionType = $idActionName = $idActionUrl = false;
  443. if($action) {
  444. $idActionUrl = $action->getIdActionUrlForEntryAndExitIds();
  445. $idActionName = $action->getIdActionNameForEntryAndExitIds();
  446. $actionType = $action->getActionType();
  447. }
  448. $daysSinceFirstVisit = $this->request->getDaysSinceFirstVisit();
  449. $visitCount = $this->request->getVisitCount();
  450. $daysSinceLastVisit = $this->request->getDaysSinceLastVisit();
  451. $daysSinceLastOrder = $this->request->getDaysSinceLastOrder();
  452. $isReturningCustomer = ($daysSinceLastOrder !== false);
  453. if ($daysSinceLastOrder === false) {
  454. $daysSinceLastOrder = 0;
  455. }
  456. // User settings
  457. $userInfo = $this->getSettingsObject();
  458. $userInfo = $userInfo->getInfo();
  459. // Referrer data
  460. $referrer = new Referrer();
  461. $referrerUrl = $this->request->getParam('urlref');
  462. $currentUrl = $this->request->getParam('url');
  463. $referrerInfo = $referrer->getReferrerInformation($referrerUrl, $currentUrl, $this->request->getIdSite());
  464. $visitorReturning = $isReturningCustomer
  465. ? 2 /* Returning customer */
  466. : ($visitCount > 1 || $this->isVisitorKnown() || $daysSinceLastVisit > 0
  467. ? 1 /* Returning */
  468. : 0 /* New */);
  469. $defaultTimeOnePageVisit = Config::getInstance()->Tracker['default_time_one_page_visit'];
  470. return array(
  471. 'idsite' => $this->request->getIdSite(),
  472. 'visitor_localtime' => $this->request->getLocalTime(),
  473. 'idvisitor' => $this->getVisitorIdcookie(),
  474. 'visitor_returning' => $visitorReturning,
  475. 'visitor_count_visits' => $visitCount,
  476. 'visitor_days_since_last' => $daysSinceLastVisit,
  477. 'visitor_days_since_order' => $daysSinceLastOrder,
  478. 'visitor_days_since_first' => $daysSinceFirstVisit,
  479. 'visit_first_action_time' => Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp()),
  480. 'visit_last_action_time' => Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp()),
  481. 'visit_entry_idaction_url' => (int)$idActionUrl,
  482. 'visit_entry_idaction_name' => (int)$idActionName,
  483. 'visit_exit_idaction_url' => (int)$idActionUrl,
  484. 'visit_exit_idaction_name' => (int)$idActionName,
  485. 'visit_total_actions' => in_array($actionType,
  486. array(Action::TYPE_PAGE_URL,
  487. Action::TYPE_DOWNLOAD,
  488. Action::TYPE_OUTLINK,
  489. Action::TYPE_SITE_SEARCH,
  490. Action::TYPE_EVENT))
  491. ? 1 : 0, // if visit starts with something else (e.g. ecommerce order), don't record as an action
  492. 'visit_total_searches' => $actionType == Action::TYPE_SITE_SEARCH ? 1 : 0,
  493. 'visit_total_events' => $actionType == Action::TYPE_EVENT ? 1 : 0,
  494. 'visit_total_time' => self::cleanupVisitTotalTime($defaultTimeOnePageVisit),
  495. 'visit_goal_buyer' => $this->goalManager->getBuyerType(),
  496. 'referer_type' => $referrerInfo['referer_type'],
  497. 'referer_name' => $referrerInfo['referer_name'],
  498. 'referer_url' => $referrerInfo['referer_url'],
  499. 'referer_keyword' => $referrerInfo['referer_keyword'],
  500. 'config_id' => $userInfo['config_id'],
  501. 'config_os' => $userInfo['config_os'],
  502. 'config_browser_name' => $userInfo['config_browser_name'],
  503. 'config_browser_version' => $userInfo['config_browser_version'],
  504. 'config_resolution' => $userInfo['config_resolution'],
  505. 'config_pdf' => $userInfo['config_pdf'],
  506. 'config_flash' => $userInfo['config_flash'],
  507. 'config_java' => $userInfo['config_java'],
  508. 'config_director' => $userInfo['config_director'],
  509. 'config_quicktime' => $userInfo['config_quicktime'],
  510. 'config_realplayer' => $userInfo['config_realplayer'],
  511. 'config_windowsmedia' => $userInfo['config_windowsmedia'],
  512. 'config_gears' => $userInfo['config_gears'],
  513. 'config_silverlight' => $userInfo['config_silverlight'],
  514. 'config_cookie' => $userInfo['config_cookie'],
  515. 'location_ip' => $this->getVisitorIp(),
  516. 'location_browser_lang' => $userInfo['location_browser_lang'],
  517. );
  518. }
  519. /**
  520. * Gather fields=>values that needs to be updated for the existing visit in log_visit
  521. *
  522. * @param $action
  523. * @param $visitIsConverted
  524. * @return array
  525. */
  526. protected function getExistingVisitFieldsToUpdate($action, $visitIsConverted)
  527. {
  528. $valuesToUpdate = array();
  529. if ($action) {
  530. $idActionUrl = $action->getIdActionUrlForEntryAndExitIds();
  531. $idActionName = $action->getIdActionNameForEntryAndExitIds();
  532. $actionType = $action->getActionType();
  533. if ($idActionName !== false) {
  534. $valuesToUpdate['visit_exit_idaction_name'] = $idActionName;
  535. }
  536. $incrementActions = false;
  537. if ($idActionUrl !== false) {
  538. $valuesToUpdate['visit_exit_idaction_url'] = $idActionUrl;
  539. $incrementActions = true;
  540. }
  541. if ($actionType == Action::TYPE_SITE_SEARCH) {
  542. $valuesToUpdate['visit_total_searches'] = 'visit_total_searches + 1';
  543. $incrementActions = true;
  544. } else if ($actionType == Action::TYPE_EVENT) {
  545. $valuesToUpdate['visit_total_events'] = 'visit_total_events + 1';
  546. $incrementActions = true;
  547. }
  548. if ($incrementActions) {
  549. $valuesToUpdate['visit_total_actions'] = 'visit_total_actions + 1';
  550. }
  551. }
  552. $datetimeServer = Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp());
  553. $valuesToUpdate['visit_last_action_time'] = $datetimeServer;
  554. // Add 1 so it's always > 0
  555. $visitTotalTime = 1 + $this->request->getCurrentTimestamp() - $this->visitorInfo['visit_first_action_time'];
  556. $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime($visitTotalTime);
  557. // Goal conversion
  558. if ($visitIsConverted) {
  559. $valuesToUpdate['visit_goal_converted'] = 1;
  560. // If a pageview and goal conversion in the same second, with previously a goal conversion recorded
  561. // the request would not "update" the row since all values are the same as previous
  562. // therefore the request below throws exception, instead we make sure the UPDATE will affect the row
  563. $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime(
  564. $valuesToUpdate['visit_total_time']
  565. + $this->goalManager->idGoal
  566. // +2 to offset idgoal=-1 and idgoal=0
  567. + 2);
  568. }
  569. // Might update the idvisitor when it was forced or overwritten for this visit
  570. if (strlen($this->visitorInfo['idvisitor']) == Tracker::LENGTH_BINARY_ID) {
  571. $valuesToUpdate['idvisitor'] = $this->visitorInfo['idvisitor'];
  572. }
  573. // Ecommerce buyer status
  574. $visitEcommerceStatus = $this->goalManager->getBuyerType($this->visitorInfo['visit_goal_buyer']);
  575. if($visitEcommerceStatus != GoalManager::TYPE_BUYER_NONE
  576. // only update if the value has changed (prevents overwriting the value in case a request has updated it in the meantime)
  577. && $visitEcommerceStatus != $this->visitorInfo['visit_goal_buyer']) {
  578. $valuesToUpdate['visit_goal_buyer'] = $visitEcommerceStatus;
  579. }
  580. // Custom Variables overwrite previous values on each page view
  581. $valuesToUpdate = array_merge($valuesToUpdate, $this->visitorCustomVariables);
  582. return $valuesToUpdate;
  583. }
  584. }