PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Tracker/Visit.php

https://github.com/quarkness/piwik
PHP | 1501 lines | 1161 code | 109 blank | 231 comment | 85 complexity | 595ab0cd1cb030e9865d99c2ec801dbd MD5 | raw file
  1. <?php
  2. /**
  3. * Piwik - Open source web analytics
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. * @version $Id$
  8. *
  9. * @category Piwik
  10. * @package Piwik
  11. */
  12. /**
  13. * @package Piwik
  14. * @subpackage Piwik_Tracker
  15. */
  16. interface Piwik_Tracker_Visit_Interface {
  17. function setRequest($requestArray);
  18. function handle();
  19. }
  20. /**
  21. * Class used to handle a Visit.
  22. * A visit is either NEW or KNOWN.
  23. * - If a visit is NEW then we process the visitor information (settings, referers, etc.) and save
  24. * a new line in the log_visit table.
  25. * - If a visit is KNOWN then we update the visit row in the log_visit table, updating the number of pages
  26. * views, time spent, etc.
  27. *
  28. * Whether a visit is NEW or KNOWN we also save the action in the DB.
  29. * One request to the piwik.php script is associated to one action.
  30. *
  31. * @package Piwik
  32. * @subpackage Piwik_Tracker
  33. */
  34. class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
  35. {
  36. /**
  37. * @var Piwik_Cookie
  38. */
  39. protected $cookie = null;
  40. protected $visitorInfo = array();
  41. protected $userSettingsInformation = null;
  42. protected $idsite;
  43. protected $visitorKnown;
  44. protected $request;
  45. // can be overwritten in constructor
  46. protected $timestamp;
  47. protected $ip;
  48. // Set to true when we set some custom variables from the cookie
  49. protected $customVariablesSetFromRequest = false;
  50. /**
  51. * @var Piwik_Tracker_GoalManager
  52. */
  53. protected $goalManager;
  54. const TIME_IN_PAST_TO_SEARCH_FOR_VISITOR = 86400;
  55. public function __construct($forcedIpString = null, $forcedDateTime = null)
  56. {
  57. $this->timestamp = time();
  58. if(!empty($forcedDateTime))
  59. {
  60. if(!is_numeric($forcedDateTime))
  61. {
  62. $forcedDateTime = strtotime($forcedDateTime);
  63. }
  64. $this->timestamp = $forcedDateTime;
  65. }
  66. $ipString = $forcedIpString;
  67. if(empty($ipString))
  68. {
  69. $ipString = Piwik_IP::getIpFromHeader();
  70. }
  71. $ip = Piwik_IP::P2N($ipString);
  72. Piwik_PostEvent('Tracker.Visit.setVisitorIp', $ip);
  73. $this->ip = $ip;
  74. }
  75. function setForcedVisitorId($visitorId)
  76. {
  77. $this->forcedVisitorId = $visitorId;
  78. }
  79. function setRequest($requestArray)
  80. {
  81. $this->request = $requestArray;
  82. $idsite = Piwik_Common::getRequestVar('idsite', 0, 'int', $this->request);
  83. Piwik_PostEvent('Tracker.setRequest.idSite', $idsite, $requestArray);
  84. if($idsite <= 0)
  85. {
  86. Piwik_Tracker_ExitWithException(new Exception('Invalid idSite'));
  87. }
  88. $this->idsite = $idsite;
  89. // When the 'url' and referer url parameter are not given, we might be in the 'Simple Image Tracker' mode.
  90. // The URL can default to the Referer, which will be in this case
  91. // the URL of the page containing the Simple Image beacon
  92. if(empty($this->request['urlref'])
  93. && empty($this->request['url']))
  94. {
  95. $this->request['url'] = @$_SERVER['HTTP_REFERER'];
  96. }
  97. }
  98. /**
  99. * Main algorithm to handle the visit.
  100. *
  101. * Once we have the visitor information, we have to determine if the visit is a new or a known visit.
  102. *
  103. * 1) When the last action was done more than 30min ago,
  104. * or if the visitor is new, then this is a new visit.
  105. *
  106. * 2) If the last action is less than 30min ago, then the same visit is going on.
  107. * Because the visit goes on, we can get the time spent during the last action.
  108. *
  109. * NB:
  110. * - In the case of a new visit, then the time spent
  111. * during the last action of the previous visit is unknown.
  112. *
  113. * - In the case of a new visit but with a known visitor,
  114. * we can set the 'returning visitor' flag.
  115. *
  116. * In all the cases we set a cookie to the visitor with the new information.
  117. */
  118. public function handle()
  119. {
  120. // the IP is needed by isExcluded() and GoalManager->recordGoals()
  121. $this->visitorInfo['location_ip'] = $this->ip;
  122. if($this->isExcluded())
  123. {
  124. return;
  125. }
  126. $this->visitorCustomVariables = self::getCustomVariables($scope = 'visit', $this->request);
  127. if(!empty($this->visitorCustomVariables))
  128. {
  129. $this->customVariablesSetFromRequest = true;
  130. }
  131. $this->goalManager = new Piwik_Tracker_GoalManager();
  132. $someGoalsConverted = $visitIsConverted = false;
  133. $idActionUrl = $idActionName = $actionType = false;
  134. $action = null;
  135. $this->goalManager->init($this->request);
  136. $requestIsManualGoalConversion = ($this->goalManager->idGoal > 0);
  137. $requestIsEcommerce = $this->goalManager->requestIsEcommerce;
  138. if($requestIsEcommerce)
  139. {
  140. $someGoalsConverted = true;
  141. // Mark the visit as Converted only if it is an order (not for a Cart update)
  142. if($this->goalManager->isGoalAnOrder)
  143. {
  144. $visitIsConverted = true;
  145. }
  146. }
  147. // this request is from the JS call to piwikTracker.trackGoal()
  148. elseif($requestIsManualGoalConversion)
  149. {
  150. $someGoalsConverted = $this->goalManager->detectGoalId($this->idsite);
  151. $visitIsConverted = $someGoalsConverted;
  152. // if we find a idgoal in the URL, but then the goal is not valid, this is most likely a fake request
  153. if(!$someGoalsConverted)
  154. {
  155. printDebug('Invalid goal tracking request for goal id = '.$this->goalManager->idGoal);
  156. unset($this->goalManager);
  157. return;
  158. }
  159. }
  160. // normal page view, potentially triggering a URL matching goal
  161. else
  162. {
  163. $action = $this->newAction();
  164. $this->handleAction($action);
  165. $someGoalsConverted = $this->goalManager->detectGoalsMatchingUrl($this->idsite, $action);
  166. $visitIsConverted = $someGoalsConverted;
  167. $action->loadIdActionNameAndUrl();
  168. $idActionUrl = (int)$action->getIdActionUrl();
  169. $idActionName = (int)$action->getIdActionName();
  170. $actionType = $action->getActionType();
  171. }
  172. // the visitor and session
  173. $this->recognizeTheVisitor();
  174. $isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit();
  175. if(!$isLastActionInTheSameVisit)
  176. {
  177. printDebug("Visitor detected, but last action was more than 30 minutes ago...");
  178. }
  179. // Known visit when:
  180. // ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor
  181. // OR
  182. // - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor()
  183. // )
  184. // AND
  185. // - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit()
  186. if( $this->isVisitorKnown()
  187. && $isLastActionInTheSameVisit)
  188. {
  189. $idRefererActionUrl = $this->visitorInfo['visit_exit_idaction_url'];
  190. $idRefererActionName = $this->visitorInfo['visit_exit_idaction_name'];
  191. try {
  192. $this->handleKnownVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted);
  193. if(!is_null($action))
  194. {
  195. $action->record( $this->visitorInfo['idvisit'],
  196. $this->visitorInfo['idvisitor'],
  197. $idRefererActionUrl,
  198. $idRefererActionName,
  199. $this->visitorInfo['time_spent_ref_action']
  200. );
  201. }
  202. } catch(Piwik_Tracker_Visit_VisitorNotFoundInDatabase $e) {
  203. // There is an edge case when:
  204. // - two manual goal conversions happen in the same second
  205. // - which result in handleKnownVisit throwing the exception
  206. // because the UPDATE didn't affect any rows (one row was found, but not updated since no field changed)
  207. // - the exception is caught here and will result in a new visit incorrectly
  208. // In this case, we cancel the current conversion to be recorded:
  209. if($requestIsManualGoalConversion
  210. || $requestIsEcommerce)
  211. {
  212. $someGoalsConverted = $visitIsConverted = false;
  213. }
  214. // When the row wasn't found in the logs, and this is a pageview or
  215. // goal matching URL, we force a new visitor
  216. else
  217. {
  218. $this->visitorKnown = false;
  219. }
  220. }
  221. }
  222. // New visit when:
  223. // - the visitor has the Piwik cookie but the last action was performed more than 30 min ago @see isLastActionInTheSameVisit()
  224. // - the visitor doesn't have the Piwik cookie, and couldn't be matched in @see recognizeTheVisitor()
  225. // - 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
  226. if(!$this->isVisitorKnown()
  227. || !$isLastActionInTheSameVisit)
  228. {
  229. $this->handleNewVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted);
  230. if(!is_null($action))
  231. {
  232. $action->record( $this->visitorInfo['idvisit'], $this->visitorInfo['idvisitor'], 0, 0, 0 );
  233. }
  234. }
  235. // update the cookie with the new visit information
  236. $this->setThirdPartyCookie();
  237. // record the goals if applicable
  238. if($someGoalsConverted)
  239. {
  240. $refererTimestamp = Piwik_Common::getRequestVar('_refts', 0, 'int', $this->request);
  241. $refererUrl = Piwik_Common::getRequestVar('_ref', '', 'string', $this->request);
  242. $refererCampaignName = trim(urldecode(Piwik_Common::getRequestVar('_rcn', '', 'string', $this->request)));
  243. $refererCampaignKeyword = trim(urldecode(Piwik_Common::getRequestVar('_rck', '', 'string', $this->request)));
  244. $this->goalManager->recordGoals(
  245. $this->idsite,
  246. $this->visitorInfo,
  247. $this->visitorCustomVariables,
  248. $action,
  249. $refererTimestamp,
  250. $refererUrl,
  251. $refererCampaignName,
  252. $refererCampaignKeyword
  253. );
  254. }
  255. unset($this->goalManager);
  256. unset($action);
  257. $this->printCookie();
  258. }
  259. protected function printCookie()
  260. {
  261. printDebug($this->cookie);
  262. }
  263. protected function handleAction($action)
  264. {
  265. $action->setIdSite($this->idsite);
  266. $action->setRequest($this->request);
  267. $action->setTimestamp($this->getCurrentTimestamp());
  268. $action->init();
  269. if($this->detectActionIsOutlinkOnAliasHost($action))
  270. {
  271. printDebug("Info: The outlink URL host is one of the known host for this website. ");
  272. }
  273. if(isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG'])
  274. {
  275. $type = Piwik_Tracker_Action::getActionTypeName($action->getActionType());
  276. printDebug("Action is a $type,
  277. Action name = ". $action->getActionName() . ",
  278. Action URL = ". $action->getActionUrl() );
  279. }
  280. }
  281. /**
  282. * In the case of a known visit, we have to do the following actions:
  283. *
  284. * 1) Insert the new action
  285. * 2) Update the visit information
  286. *
  287. * This method triggers two events:
  288. *
  289. * Tracker.knownVisitorUpdate is triggered before the visit information is updated
  290. * Event data is an array with the values to be updated (could be changed by plugins)
  291. *
  292. * Tracker.knownVisitorInformation is triggered after saving the new visit data
  293. * Even data is an array with updated information about the visit
  294. */
  295. protected function handleKnownVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted)
  296. {
  297. // gather information that needs to be updated
  298. $valuesToUpdate = array();
  299. $sqlActionUpdate = '';
  300. if($idActionUrl !== false)
  301. {
  302. $valuesToUpdate['visit_exit_idaction_url'] = $idActionUrl;
  303. $sqlActionUpdate = "visit_total_actions = visit_total_actions + 1, ";
  304. $valuesToUpdate['visit_exit_idaction_name'] = (int)$idActionName;
  305. }
  306. $datetimeServer = Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp());
  307. printDebug("Visit is known (IP = ".Piwik_IP::N2P($this->getVisitorIp()).")");
  308. $visitTotalTime = $this->getCurrentTimestamp() - $this->visitorInfo['visit_first_action_time'];
  309. $valuesToUpdate['visit_last_action_time'] = $datetimeServer;
  310. $valuesToUpdate['visit_total_time'] = $visitTotalTime + 1;
  311. // Goal conversion
  312. if($visitIsConverted)
  313. {
  314. $valuesToUpdate['visit_goal_converted'] = 1;
  315. // If a pageview and goal conversion in the same second, with previously a goal conversion recorded
  316. // the request would not "update" the row since all values are the same as previous
  317. // therefore the request below throws exception, instead we make sure the UPDATE will affect the row
  318. $valuesToUpdate['visit_total_time'] += (int)$this->goalManager->idGoal
  319. // +2 to offset idgoal=-1 and idgoal=0
  320. + 2 ;
  321. }
  322. // Might update the idvisitor when it was forced or overwritten for this visit
  323. if(strlen($this->visitorInfo['idvisitor']) == Piwik_Tracker::LENGTH_BINARY_ID)
  324. {
  325. $valuesToUpdate['idvisitor'] = $this->visitorInfo['idvisitor'];
  326. }
  327. // Ecommerce buyer status
  328. $valuesToUpdate['visit_goal_buyer'] = $this->goalManager->getBuyerType($this->visitorInfo['visit_goal_buyer']);
  329. // Custom Variables overwrite previous values on each page view
  330. $valuesToUpdate = array_merge($valuesToUpdate, $this->visitorCustomVariables);
  331. // trigger event before update
  332. Piwik_PostEvent('Tracker.knownVisitorUpdate', $valuesToUpdate);
  333. // Will be updated in cookie
  334. $timeSpentRefererAction = $this->getCurrentTimestamp() - $this->visitorInfo['visit_last_action_time'];
  335. if($timeSpentRefererAction > Piwik_Tracker_Config::getInstance()->Tracker['visit_standard_length'])
  336. {
  337. $timeSpentRefererAction = 0;
  338. }
  339. $this->visitorInfo['time_spent_ref_action'] = $timeSpentRefererAction;
  340. // update visitorInfo
  341. foreach($valuesToUpdate AS $name => $value)
  342. {
  343. $this->visitorInfo[$name] = $value;
  344. }
  345. // build sql query
  346. $updateParts = $sqlBind = array();
  347. foreach($valuesToUpdate AS $name => $value)
  348. {
  349. $updateParts[] = $name." = ?";
  350. $sqlBind[] = $value;
  351. }
  352. $sqlQuery = "UPDATE ". Piwik_Common::prefixTable('log_visit')."
  353. SET $sqlActionUpdate ".implode($updateParts, ', ')."
  354. WHERE idsite = ?
  355. AND idvisit = ?";
  356. array_push($sqlBind, $this->idsite, (int)$this->visitorInfo['idvisit'] );
  357. $result = Piwik_Tracker::getDatabase()->query($sqlQuery, $sqlBind);
  358. $this->visitorInfo['visit_last_action_time'] = $this->getCurrentTimestamp();
  359. // Debug output
  360. if(isset($valuesToUpdate['idvisitor']))
  361. {
  362. $valuesToUpdate['idvisitor'] = bin2hex($valuesToUpdate['idvisitor']);
  363. }
  364. printDebug('Updating existing visit: '. var_export($valuesToUpdate, true) );
  365. if(Piwik_Tracker::getDatabase()->rowCount($result) == 0)
  366. {
  367. printDebug("Visitor with this idvisit wasn't found in the DB.");
  368. printDebug("$sqlQuery --- ");printDebug($sqlBind);
  369. throw new Piwik_Tracker_Visit_VisitorNotFoundInDatabase(
  370. "The visitor with idvisitor=".bin2hex($this->visitorInfo['idvisitor'])." and idvisit=".$this->visitorInfo['idvisit']
  371. ." wasn't found in the DB, we fallback to a new visitor");
  372. }
  373. Piwik_PostEvent('Tracker.knownVisitorInformation', $this->visitorInfo);
  374. }
  375. protected function isTimestampValid($time)
  376. {
  377. return $time <= $this->getCurrentTimestamp()
  378. && $time > $this->getCurrentTimestamp() - 10*365*86400;
  379. }
  380. /**
  381. * In the case of a new visit, we have to do the following actions:
  382. *
  383. * 1) Insert the new action
  384. *
  385. * 2) Insert the visit information
  386. */
  387. protected function handleNewVisit($idActionUrl, $idActionName, $actionType, $visitIsConverted)
  388. {
  389. printDebug("New Visit (IP = ".Piwik_IP::N2P($this->getVisitorIp()).")");
  390. $localTimes = array(
  391. 'h' => (string) Piwik_Common::getRequestVar( 'h', $this->getCurrentDate("H"), 'int', $this->request),
  392. 'i' => (string) Piwik_Common::getRequestVar( 'm', $this->getCurrentDate("i"), 'int', $this->request),
  393. 's' => (string) Piwik_Common::getRequestVar( 's', $this->getCurrentDate("s"), 'int', $this->request)
  394. );
  395. foreach($localTimes as $k => $time)
  396. {
  397. if(strlen($time) == 1)
  398. {
  399. $localTimes[$k] = '0' . $time;
  400. }
  401. }
  402. $localTime = $localTimes['h'] .':'. $localTimes['i'] .':'. $localTimes['s'];
  403. $idcookie = $this->getVisitorIdcookie();
  404. $defaultTimeOnePageVisit = Piwik_Tracker_Config::getInstance()->Tracker['default_time_one_page_visit'];
  405. // Days since first visit
  406. $cookieFirstVisitTimestamp = Piwik_Common::getRequestVar('_idts', 0, 'int', $this->request);
  407. if(!$this->isTimestampValid($cookieFirstVisitTimestamp))
  408. {
  409. $cookieFirstVisitTimestamp = $this->getCurrentTimestamp();
  410. }
  411. $daysSinceFirstVisit = round(($this->getCurrentTimestamp() - $cookieFirstVisitTimestamp)/86400, $precision = 0);
  412. if($daysSinceFirstVisit < 0) $daysSinceFirstVisit = 0;
  413. // Number of Visits
  414. $visitCount = Piwik_Common::getRequestVar('_idvc', 1, 'int', $this->request);
  415. if($visitCount < 1) $visitCount = 1;
  416. // Days since last visit
  417. $daysSinceLastVisit = 0;
  418. $lastVisitTimestamp = Piwik_Common::getRequestVar('_viewts', 0, 'int', $this->request);
  419. if($this->isTimestampValid($lastVisitTimestamp))
  420. {
  421. $daysSinceLastVisit = round(($this->getCurrentTimestamp() - $lastVisitTimestamp)/86400, $precision = 0);
  422. if($daysSinceLastVisit < 0) $daysSinceLastVisit = 0;
  423. }
  424. $daysSinceLastOrder = 0;
  425. $isReturningCustomer = false;
  426. $lastOrderTimestamp = Piwik_Common::getRequestVar('_ects', 0, 'int', $this->request);
  427. if($this->isTimestampValid($lastOrderTimestamp))
  428. {
  429. $daysSinceLastOrder = round(($this->getCurrentTimestamp() - $lastOrderTimestamp)/86400, $precision = 0);
  430. if($daysSinceLastOrder < 0)
  431. {
  432. $daysSinceLastOrder = 0;
  433. }
  434. $isReturningCustomer = true;
  435. }
  436. // User settings
  437. $userInfo = $this->getUserSettingsInformation();
  438. $country = Piwik_Common::getCountry($userInfo['location_browser_lang'],
  439. $enableLanguageToCountryGuess = Piwik_Tracker_Config::getInstance()->Tracker['enable_language_to_country_guess'],
  440. $this->getVisitorIp());
  441. // Referrer data
  442. $referrer = new Piwik_Tracker_Visit_Referer();
  443. $refererUrl = Piwik_Common::getRequestVar( 'urlref', '', 'string', $this->request);
  444. $currentUrl = Piwik_Common::getRequestVar( 'url', '', 'string', $this->request);
  445. $refererInfo = $referrer->getRefererInformation($refererUrl, $currentUrl, $this->idsite);
  446. /**
  447. * Save the visitor
  448. */
  449. $this->visitorInfo = array(
  450. 'idsite' => $this->idsite,
  451. 'visitor_localtime' => $localTime,
  452. 'idvisitor' => $idcookie,
  453. 'visitor_returning' => $isReturningCustomer ? 2 : ($visitCount > 1 || $this->isVisitorKnown() ? 1 : 0),
  454. 'visitor_count_visits' => $visitCount,
  455. 'visitor_days_since_last' => $daysSinceLastVisit,
  456. 'visitor_days_since_order' => $daysSinceLastOrder,
  457. 'visitor_days_since_first' => $daysSinceFirstVisit,
  458. 'visit_first_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()),
  459. 'visit_last_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($this->getCurrentTimestamp()),
  460. 'visit_entry_idaction_url' => (int)$idActionUrl,
  461. 'visit_entry_idaction_name' => (int)$idActionName,
  462. 'visit_exit_idaction_url' => (int)$idActionUrl,
  463. 'visit_exit_idaction_name' => (int)$idActionName,
  464. 'visit_total_actions' => in_array($actionType,
  465. array(Piwik_Tracker_Action::TYPE_ACTION_URL,
  466. Piwik_Tracker_Action::TYPE_DOWNLOAD,
  467. Piwik_Tracker_Action::TYPE_OUTLINK))
  468. ? 1 : 0, // if visit starts with something else (e.g. ecommerce order), don't record as an action
  469. 'visit_total_time' => $defaultTimeOnePageVisit,
  470. 'visit_goal_converted' => $visitIsConverted ? 1: 0,
  471. 'visit_goal_buyer' => $this->goalManager->getBuyerType(),
  472. 'referer_type' => $refererInfo['referer_type'],
  473. 'referer_name' => $refererInfo['referer_name'],
  474. 'referer_url' => $refererInfo['referer_url'],
  475. 'referer_keyword' => $refererInfo['referer_keyword'],
  476. 'config_id' => $userInfo['config_id'],
  477. 'config_os' => $userInfo['config_os'],
  478. 'config_browser_name' => $userInfo['config_browser_name'],
  479. 'config_browser_version' => $userInfo['config_browser_version'],
  480. 'config_resolution' => $userInfo['config_resolution'],
  481. 'config_pdf' => $userInfo['config_pdf'],
  482. 'config_flash' => $userInfo['config_flash'],
  483. 'config_java' => $userInfo['config_java'],
  484. 'config_director' => $userInfo['config_director'],
  485. 'config_quicktime' => $userInfo['config_quicktime'],
  486. 'config_realplayer' => $userInfo['config_realplayer'],
  487. 'config_windowsmedia' => $userInfo['config_windowsmedia'],
  488. 'config_gears' => $userInfo['config_gears'],
  489. 'config_silverlight' => $userInfo['config_silverlight'],
  490. 'config_cookie' => $userInfo['config_cookie'],
  491. 'location_ip' => $this->getVisitorIp(),
  492. 'location_browser_lang' => $userInfo['location_browser_lang'],
  493. 'location_country' => $country,
  494. );
  495. // Add Custom variable key,value to the visitor array
  496. $this->visitorInfo = array_merge($this->visitorInfo, $this->visitorCustomVariables);
  497. Piwik_PostEvent('Tracker.newVisitorInformation', $this->visitorInfo);
  498. $debugVisitInfo = $this->visitorInfo;
  499. $debugVisitInfo['idvisitor'] = bin2hex($debugVisitInfo['idvisitor']);
  500. $debugVisitInfo['config_id'] = bin2hex($debugVisitInfo['config_id']);
  501. printDebug($debugVisitInfo);
  502. $this->saveVisitorInformation();
  503. }
  504. /**
  505. * Save new visitor information to log_visit table.
  506. * Provides pre- and post- event hooks (Tracker.saveVisitorInformation and Tracker.saveVisitorInformation.end) for plugins
  507. */
  508. protected function saveVisitorInformation()
  509. {
  510. Piwik_PostEvent('Tracker.saveVisitorInformation', $this->visitorInfo);
  511. if(empty($this->visitorInfo['location_country']))
  512. {
  513. $this->visitorInfo['location_country'] = 'xx';
  514. }
  515. $this->visitorInfo['location_continent'] = Piwik_Common::getContinent( $this->visitorInfo['location_country'] );
  516. $this->visitorInfo['location_browser_lang'] = substr($this->visitorInfo['location_browser_lang'], 0, 20);
  517. $this->visitorInfo['referer_name'] = substr($this->visitorInfo['referer_name'], 0, 70);
  518. $this->visitorInfo['referer_keyword'] = substr($this->visitorInfo['referer_keyword'], 0, 255);
  519. $this->visitorInfo['config_resolution'] = substr($this->visitorInfo['config_resolution'], 0, 9);
  520. $fields = implode(", ", array_keys($this->visitorInfo));
  521. $values = Piwik_Common::getSqlStringFieldsArray($this->visitorInfo);
  522. $sql = "INSERT INTO ".Piwik_Common::prefixTable('log_visit'). " ($fields) VALUES ($values)";
  523. $bind = array_values($this->visitorInfo);
  524. Piwik_Tracker::getDatabase()->query( $sql, $bind);
  525. $idVisit = Piwik_Tracker::getDatabase()->lastInsertId();
  526. $this->visitorInfo['idvisit'] = $idVisit;
  527. $this->visitorInfo['visit_first_action_time'] = $this->getCurrentTimestamp();
  528. $this->visitorInfo['visit_last_action_time'] = $this->getCurrentTimestamp();
  529. Piwik_PostEvent('Tracker.saveVisitorInformation.end', $this->visitorInfo);
  530. }
  531. /**
  532. * Returns visitor cookie
  533. *
  534. * @return binary
  535. */
  536. protected function getVisitorIdcookie()
  537. {
  538. if($this->isVisitorKnown())
  539. {
  540. return $this->visitorInfo['idvisitor'];
  541. }
  542. // If the visitor had a first party ID cookie, then we use this value
  543. if(!empty($this->visitorInfo['idvisitor'])
  544. && strlen($this->visitorInfo['idvisitor']) == Piwik_Tracker::LENGTH_BINARY_ID)
  545. {
  546. return $this->visitorInfo['idvisitor'];
  547. }
  548. // Return Random UUID
  549. $uniqueId = substr($this->getVisitorUniqueId(), 0, Piwik_Tracker::LENGTH_HEX_ID_STRING);
  550. return Piwik_Common::hex2bin($uniqueId);
  551. }
  552. /**
  553. * Returns the visitor's IP address
  554. *
  555. * @return long
  556. */
  557. protected function getVisitorIp()
  558. {
  559. return $this->visitorInfo['location_ip'];
  560. }
  561. /**
  562. * Returns the visitor's browser (user agent)
  563. *
  564. * @return string
  565. */
  566. protected function getUserAgent()
  567. {
  568. return @$_SERVER['HTTP_USER_AGENT'];
  569. }
  570. /**
  571. * Returns the current date in the "Y-m-d" PHP format
  572. *
  573. * @return string
  574. */
  575. protected function getCurrentDate( $format = "Y-m-d")
  576. {
  577. return date($format, $this->getCurrentTimestamp() );
  578. }
  579. /**
  580. * Returns the current Timestamp
  581. *
  582. * @return int
  583. */
  584. protected function getCurrentTimestamp()
  585. {
  586. return $this->timestamp;
  587. }
  588. /**
  589. * Test if the current visitor is excluded from the statistics.
  590. *
  591. * Plugins can for example exclude visitors based on the
  592. * - IP
  593. * - If a given cookie is found
  594. *
  595. * @return bool True if the visit must not be saved, false otherwise
  596. */
  597. protected function isExcluded()
  598. {
  599. $excluded = false;
  600. $ip = $this->getVisitorIp();
  601. $ua = $this->getUserAgent();
  602. /*
  603. * Live/Bing/MSN bot and Googlebot are evolving to detect cloaked websites.
  604. * As a result, these sophisticated bots exhibit characteristics of
  605. * browsers (cookies enabled, executing JavaScript, etc).
  606. */
  607. if ( strpos($ua, 'Googlebot') !== false // Googlebot
  608. || strpos($ua, 'Google Web Preview') !== false // Google Instant
  609. || strpos($ua, 'bingbot') !== false // Bingbot
  610. || strpos($ua, 'YottaaMonitor') !== false // Yottaa
  611. || Piwik_IP::isIpInRange($ip,
  612. array(
  613. '64.4.0.0/18',
  614. '65.52.0.0/14',
  615. '157.54.0.0/15',
  616. '157.56.0.0/14',
  617. '157.60.0.0/16',
  618. '207.46.0.0/16',
  619. '207.68.128.0/18',
  620. '207.68.192.0/20',
  621. ))) // Live/Bing/MSN
  622. {
  623. printDebug('Search bot detected, visit excluded');
  624. $excluded = true;
  625. }
  626. /*
  627. * Requests built with piwik.js will contain a rec=1 parameter. This is used as
  628. * an indication that the request is made by a JS enabled device. By default, Piwik
  629. * doesn't track non-JS visitors.
  630. */
  631. if(!$excluded)
  632. {
  633. $parameterForceRecord = 'rec';
  634. $toRecord = Piwik_Common::getRequestVar($parameterForceRecord, false, 'int', $this->request);
  635. if(!$toRecord)
  636. {
  637. printDebug($_SERVER['REQUEST_METHOD'].' parameter '.$parameterForceRecord.' not found in URL, request excluded');
  638. $excluded = true;
  639. }
  640. }
  641. /* custom filters can override the built-in filters above */
  642. Piwik_PostEvent('Tracker.Visit.isExcluded', $excluded);
  643. /*
  644. * Following exclude operations happen after the hook.
  645. * These are of higher priority and should not be overwritten by plugins.
  646. */
  647. // Checking if the Piwik ignore cookie is set
  648. if(!$excluded)
  649. {
  650. $excluded = $this->isIgnoreCookieFound();
  651. }
  652. // Checking for excluded IPs
  653. if(!$excluded)
  654. {
  655. $excluded = $this->isVisitorIpExcluded($ip);
  656. }
  657. if($excluded)
  658. {
  659. printDebug("Visitor excluded.");
  660. return true;
  661. }
  662. return false;
  663. }
  664. /**
  665. * Looks for the ignore cookie that users can set in the Piwik admin screen.
  666. * @return bool
  667. */
  668. protected function isIgnoreCookieFound()
  669. {
  670. if(Piwik_Tracker_IgnoreCookie::isIgnoreCookieFound())
  671. {
  672. printDebug('Piwik ignore cookie was found, visit not tracked.');
  673. return true;
  674. }
  675. return false;
  676. }
  677. /**
  678. * Checks if the visitor ip is in the excluded list
  679. *
  680. * @param string $ip Long IP
  681. * @return bool
  682. */
  683. protected function isVisitorIpExcluded($ip)
  684. {
  685. $websiteAttributes = Piwik_Common::getCacheWebsiteAttributes( $this->idsite );
  686. if(!empty($websiteAttributes['excluded_ips']))
  687. {
  688. if(Piwik_IP::isIpInRange($ip, $websiteAttributes['excluded_ips']))
  689. {
  690. printDebug('Visitor IP '.Piwik_IP::N2P($ip).' is excluded from being tracked');
  691. return true;
  692. }
  693. }
  694. return false;
  695. }
  696. /**
  697. * Returns the cookie name used for the Piwik Tracker cookie
  698. *
  699. * @return string
  700. */
  701. protected function getCookieName()
  702. {
  703. return Piwik_Tracker_Config::getInstance()->Tracker['cookie_name'];
  704. }
  705. /**
  706. * Returns the cookie expiration date.
  707. *
  708. * @return int
  709. */
  710. protected function getCookieExpire()
  711. {
  712. return $this->getCurrentTimestamp() + Piwik_Tracker_Config::getInstance()->Tracker['cookie_expire'];
  713. }
  714. /**
  715. * Returns cookie path
  716. *
  717. * @return string
  718. */
  719. protected function getCookiePath()
  720. {
  721. return Piwik_Tracker_Config::getInstance()->Tracker['cookie_path'];
  722. }
  723. protected function shouldUseThirdPartyCookie()
  724. {
  725. return (bool)Piwik_Tracker_Config::getInstance()->Tracker['use_third_party_id_cookie'];
  726. }
  727. /**
  728. * Is the request for a known VisitorId, based on 1st party, 3rd party (optional) cookies or Tracking API forced Visitor ID
  729. */
  730. protected function assignVisitorIdFromRequest()
  731. {
  732. $found = false;
  733. // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request?
  734. $idVisitor = $this->forcedVisitorId;
  735. if(!empty($idVisitor))
  736. {
  737. if(strlen($idVisitor) != Piwik_Tracker::LENGTH_HEX_ID_STRING)
  738. {
  739. throw new Exception("Visitor ID (cid) must be ".Piwik_Tracker::LENGTH_HEX_ID_STRING." characters long");
  740. }
  741. printDebug("Request will be recorded for this idvisitor = ".$idVisitor);
  742. $found = true;
  743. }
  744. // - If set to use 3rd party cookies for Visit ID, read the cookie
  745. if(!$found)
  746. {
  747. // - By default, reads the first party cookie ID
  748. $useThirdPartyCookie = $this->shouldUseThirdPartyCookie();
  749. if($useThirdPartyCookie)
  750. {
  751. $idVisitor = $this->cookie->get(0);
  752. if($idVisitor !== false
  753. && strlen($idVisitor) == Piwik_Tracker::LENGTH_HEX_ID_STRING)
  754. {
  755. $found = true;
  756. }
  757. }
  758. }
  759. // If a third party cookie was not found, we default to the first party cookie
  760. if(!$found)
  761. {
  762. $idVisitor = Piwik_Common::getRequestVar('_id', '', 'string', $this->request);
  763. $found = strlen($idVisitor) >= Piwik_Tracker::LENGTH_HEX_ID_STRING;
  764. }
  765. if( $found )
  766. {
  767. $truncated = substr($idVisitor, 0, Piwik_Tracker::LENGTH_HEX_ID_STRING);
  768. $binVisitorId = @Piwik_Common::hex2bin($truncated);
  769. if(!empty($binVisitorId))
  770. {
  771. $this->visitorInfo['idvisitor'] = $binVisitorId;
  772. }
  773. }
  774. }
  775. /**
  776. * This methods tries to see if the visitor has visited the website before.
  777. *
  778. * We have to split the visitor into one of the category
  779. * - Known visitor
  780. * - New visitor
  781. */
  782. protected function recognizeTheVisitor()
  783. {
  784. $this->visitorKnown = false;
  785. $this->setCookie( new Piwik_Cookie(
  786. $this->getCookieName(),
  787. $this->getCookieExpire(),
  788. $this->getCookiePath()) );
  789. $this->printCookie();
  790. $userInfo = $this->getUserSettingsInformation();
  791. $configId = $userInfo['config_id'];
  792. $timeLookBack = date('Y-m-d H:i:s', $this->getCurrentTimestamp() - self::TIME_IN_PAST_TO_SEARCH_FOR_VISITOR);
  793. $this->assignVisitorIdFromRequest();
  794. $matchVisitorId = !empty($this->visitorInfo['idvisitor']);
  795. if($matchVisitorId)
  796. {
  797. printDebug("Matching visitors with: visitorId=".bin2hex($this->visitorInfo['idvisitor'])." OR configId=".bin2hex($configId));
  798. }
  799. else
  800. {
  801. printDebug("Visitor doesn't have the piwik cookie...");
  802. }
  803. $bindSql = array();
  804. // See code below if/else
  805. $trustCookiesOnly = Piwik_Tracker_Config::getInstance()->Tracker['trust_visitors_cookies'];
  806. $visitPriority = 1;
  807. if($matchVisitorId && !$trustCookiesOnly)
  808. {
  809. $visitPriority = 'case when idvisitor = ? then 1 else 0 end';
  810. $bindSql[] = $this->visitorInfo['idvisitor'];
  811. }
  812. $where = "visit_last_action_time >= ?
  813. AND idsite = ?";
  814. $bindSql[] = $timeLookBack;
  815. $bindSql[] = $this->idsite;
  816. if($matchVisitorId)
  817. {
  818. // This setting would be enabled for Intranet websites, to ensure that visitors using all the same computer config, same IP
  819. // are not counted as 1 visitor. In this case, we want to enforce and trust the visitor ID from the cookie.
  820. if($trustCookiesOnly)
  821. {
  822. $where .= ' AND idvisitor = ?';
  823. $bindSql[] = $this->visitorInfo['idvisitor'];
  824. }
  825. // However, for all other cases, we do not trust this ID. Indeed, some browsers, or browser addons,
  826. // cause the visitor id from 1st party cookie to be different on each page view!
  827. // It is not acceptable to create a new visit every time such browser does a page view,
  828. // so we also backup by searching for matching configId.
  829. // NOTE Above we sort the visitors by first selecting the one that matches idvisitor, if it was found
  830. else
  831. {
  832. $where .= ' AND (idvisitor = ? OR config_id = ?)';
  833. $bindSql[] = $this->visitorInfo['idvisitor'];
  834. $bindSql[] = $configId;
  835. }
  836. }
  837. // No visitor ID, possible causes: no browser cookie support, direct Tracking API request without visitor ID passed, etc.
  838. // We can use configId heuristics to try find the visitor in the past, there is a risk to assign
  839. // this page view to the wrong visitor, but this is better than creating artificial visits.
  840. else
  841. {
  842. $where .= ' AND config_id = ?';
  843. $bindSql[] = $configId;
  844. }
  845. $selectCustomVariables = '';
  846. // No custom var were found in the request, so let's copy the previous one in a potential conversion later
  847. if(!$this->customVariablesSetFromRequest)
  848. {
  849. $selectCustomVariables = '
  850. , custom_var_k1, custom_var_v1,
  851. custom_var_k2, custom_var_v2,
  852. custom_var_k3, custom_var_v3,
  853. custom_var_k4, custom_var_v4,
  854. custom_var_k5, custom_var_v5';
  855. }
  856. $sql = " SELECT idvisitor,
  857. visit_last_action_time,
  858. visit_first_action_time,
  859. idvisit,
  860. visit_exit_idaction_url,
  861. visit_exit_idaction_name,
  862. visitor_returning,
  863. visitor_days_since_first,
  864. visitor_days_since_order,
  865. referer_name,
  866. referer_keyword,
  867. referer_type,
  868. $visitPriority AS priority,
  869. visitor_count_visits,
  870. visit_goal_buyer
  871. $selectCustomVariables
  872. FROM ".Piwik_Common::prefixTable('log_visit').
  873. " WHERE ".$where."
  874. ORDER BY priority DESC, visit_last_action_time DESC
  875. LIMIT 1";
  876. $visitRow = Piwik_Tracker::getDatabase()->fetch($sql, $bindSql);
  877. // var_dump($sql);var_dump($bindSql);var_dump($visitRow);
  878. if( !Piwik_Tracker_Config::getInstance()->Debug['tracker_always_new_visitor']
  879. && $visitRow
  880. && count($visitRow) > 0)
  881. {
  882. // These values will be used throughout the request
  883. $this->visitorInfo['visit_last_action_time'] = strtotime($visitRow['visit_last_action_time']);
  884. $this->visitorInfo['visit_first_action_time'] = strtotime($visitRow['visit_first_action_time']);
  885. // if visitor id was not found in Third party cookies / forced visitor id / first party cookie, then we use previous ID
  886. if(empty($this->visitorInfo['idvisitor']))
  887. {
  888. $this->visitorInfo['idvisitor'] = $visitRow['idvisitor'];
  889. }
  890. $this->visitorInfo['idvisit'] = $visitRow['idvisit'];
  891. $this->visitorInfo['visit_exit_idaction_url'] = $visitRow['visit_exit_idaction_url'];
  892. $this->visitorInfo['visit_exit_idaction_name'] = $visitRow['visit_exit_idaction_name'];
  893. $this->visitorInfo['visitor_returning'] = $visitRow['visitor_returning'];
  894. $this->visitorInfo['visitor_days_since_first'] = $visitRow['visitor_days_since_first'];
  895. $this->visitorInfo['visitor_days_since_order'] = $visitRow['visitor_days_since_order'];
  896. $this->visitorInfo['visitor_count_visits'] = $visitRow['visitor_count_visits'];
  897. $this->visitorInfo['visit_goal_buyer'] = $visitRow['visit_goal_buyer'];
  898. // Referer information will be potentially used for Goal Conversion attribution
  899. $this->visitorInfo['referer_name'] = $visitRow['referer_name'];
  900. $this->visitorInfo['referer_keyword'] = $visitRow['referer_keyword'];
  901. $this->visitorInfo['referer_type'] = $visitRow['referer_type'];
  902. // Custom Variables copied from Visit in potential later conversion
  903. if(!empty($selectCustomVariables))
  904. {
  905. for($i=1; $i<=Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++)
  906. {
  907. if(!empty($visitRow['custom_var_k'.$i]))
  908. {
  909. $this->visitorInfo['custom_var_k'.$i] = $visitRow['custom_var_k'.$i];
  910. }
  911. if(!empty($visitRow['custom_var_v'.$i]))
  912. {
  913. $this->visitorInfo['custom_var_v'.$i] = $visitRow['custom_var_v'.$i];
  914. }
  915. }
  916. }
  917. $this->visitorKnown = true;
  918. printDebug("The visitor is known (idvisitor = ".bin2hex($this->visitorInfo['idvisitor']).",
  919. config_id = ".bin2hex($configId).",
  920. idvisit = {$this->visitorInfo['idvisit']},
  921. last action = ".date("r", $this->visitorInfo['visit_last_action_time']).",
  922. first action = ".date("r", $this->visitorInfo['visit_first_action_time']) .",
  923. visit_goal_buyer' = ".$this->visitorInfo['visit_goal_buyer'].")");
  924. }
  925. else
  926. {
  927. printDebug("The visitor was not matched with an existing visitor...");
  928. }
  929. }
  930. static public function getCustomVariables($scope, $request)
  931. {
  932. if($scope == 'visit')
  933. {
  934. $parameter = '_cvar';
  935. $debug = 'Visit level';
  936. }
  937. else
  938. {
  939. $parameter = 'cvar';
  940. $debug = 'Page level';
  941. }
  942. $customVar = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar( $parameter, '', 'string', $request));
  943. $customVar = @Piwik_Common::json_decode($customVar, $assoc = true);
  944. if(!is_array($customVar))
  945. {
  946. return array();
  947. }
  948. $customVariables = array();
  949. foreach($customVar as $id => $keyValue)
  950. {
  951. $id = (int)$id;
  952. if($id < 1
  953. || $id > Piwik_Tracker::MAX_CUSTOM_VARIABLES
  954. || count($keyValue) != 2
  955. || (!is_string($keyValue[0]) && !is_numeric($keyValue[0]))
  956. )
  957. {
  958. printDebug("Invalid custom variables detected (id=$id)");
  959. continue;
  960. }
  961. if(empty($keyValue[1]))
  962. {
  963. $keyValue[1] = "";
  964. }
  965. // We keep in the URL when Custom Variable have empty names
  966. // and values, as it means they can be deleted server side
  967. $key = self::truncateCustomVariable($keyValue[0]);
  968. $value = self::truncateCustomVariable($keyValue[1]);
  969. $customVariables['custom_var_k'.$id] = $key;
  970. $customVariables['custom_var_v'.$id] = $value;
  971. }
  972. if(!empty($customVariables))
  973. {
  974. printDebug("$debug Custom Variables: ");
  975. printDebug($customVariables);
  976. }
  977. return $customVariables;
  978. }
  979. static public function truncateCustomVariable($input)
  980. {
  981. return substr($input, 0, Piwik_Tracker::MAX_LENGTH_CUSTOM_VARIABLE);
  982. }
  983. /**
  984. * Gets the UserSettings information and returns them in an array of name => value
  985. *
  986. * @return array
  987. */
  988. protected function getUserSettingsInformation()
  989. {
  990. // we already called this method before, simply returns the result
  991. if(is_array($this->userSettingsInformation))
  992. {
  993. return $this->userSettingsInformation;
  994. }
  995. require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php';
  996. $plugin_Flash = Piwik_Common::getRequestVar( 'fla', 0, 'int', $this->request);
  997. $plugin_Java = Piwik_Common::getRequestVar( 'java', 0, 'int', $this->request);
  998. $plugin_Director = Piwik_Common::getRequestVar( 'dir', 0, 'int', $this->request);
  999. $plugin_Quicktime = Piwik_Common::getRequestVar( 'qt', 0, 'int', $this->request);
  1000. $plugin_RealPlayer = Piwik_Common::getRequestVar( 'realp', 0, 'int', $this->request);
  1001. $plugin_PDF = Piwik_Common::getRequestVar( 'pdf', 0, 'int', $this->request);
  1002. $plugin_WindowsMedia = Piwik_Common::getRequestVar( 'wma', 0, 'int', $this->request);
  1003. $plugin_Gears = Piwik_Common::getRequestVar( 'gears', 0, 'int', $this->request);
  1004. $plugin_Silverlight = Piwik_Common::getRequestVar( 'ag', 0, 'int', $this->request);
  1005. $plugin_Cookie = Piwik_Common::getRequestVar( 'cookie', 0, 'int', $this->request);
  1006. $userAgent = Piwik_Common::sanitizeInputValues($this->getUserAgent());
  1007. $aBrowserInfo = UserAgentParser::getBrowser($userAgent);
  1008. $browserName = ($aBrowserInfo !== false && $aBrowserInfo['id'] !== false) ? $aBrowserInfo['id'] : 'UNK';
  1009. $browserVersion = ($aBrowserInfo !== false && $aBrowserInfo['version'] !== false) ? $aBrowserInfo['version'] : '';
  1010. $os = UserAgentParser::getOperatingSystem($userAgent);
  1011. $os = $os === false ? 'UNK' : $os['id'];
  1012. $resolution = Piwik_Common::getRequestVar('res', 'unknown', 'string', $this->request);
  1013. $browserLang = Piwik_Common::getBrowserLanguage();
  1014. $configurationHash = $this->getConfigHash(
  1015. $os,
  1016. $browserName,
  1017. $browserVersion,
  1018. $resolution,
  1019. $plugin_Flash,
  1020. $plugin_Java,
  1021. $plugin_Director,
  1022. $plugin_Quicktime,
  1023. $plugin_RealPlayer,
  1024. $plugin_PDF,
  1025. $plugin_WindowsMedia,
  1026. $plugin_Gears,
  1027. $plugin_Silverlight,
  1028. $plugin_Cookie,
  1029. $this->getVisitorIp(),
  1030. $browserLang);
  1031. $this->userSettingsInformation = array(
  1032. 'config_id' => $configurationHash,
  1033. 'config_os' => $os,
  1034. 'config_browser_name' => $browserName,
  1035. 'config_browser_version' => $browserVersion,
  1036. 'config_resolution' => $resolution,
  1037. 'config_pdf' => $plugin_PDF,
  1038. 'config_flash' => $plugin_Flash,
  1039. 'config_java' => $plugin_Java,
  1040. 'config_director' => $plugin_Director,
  1041. 'config_quicktime' => $plugin_Quicktime,
  1042. 'config_realplayer' => $plugin_RealPlayer,
  1043. 'config_windowsmedia' => $plugin_WindowsMedia,
  1044. 'config_gears' => $plugin_Gears,
  1045. 'config_silverlight' => $plugin_Silverlight,
  1046. 'config_cookie' => $plugin_Cookie,
  1047. 'location_browser_lang' => $browserLang,
  1048. );
  1049. return $this->userSettingsInformation;
  1050. }
  1051. /**
  1052. * Returns true if the last action was done during the last 30 minutes
  1053. * @return bool
  1054. */
  1055. protected function isLastActionInTheSameVisit()
  1056. {
  1057. return isset($this->visitorInfo['visit_last_action_time'])
  1058. && ($this->visitorInfo['visit_last_action_time']
  1059. > ($this->getCurrentTimestamp() - Piwik_Tracker_Config::getInstance()->Tracker['visit_standard_length']));
  1060. }
  1061. /**
  1062. * Returns true if the recognizeTheVisitor() method did recognize the visitor
  1063. */
  1064. protected function isVisitorKnown()
  1065. {
  1066. return $this->visitorKnown === true;
  1067. }
  1068. /**
  1069. * Update the cookie information.
  1070. */
  1071. protected function setThirdPartyCookie()
  1072. {
  1073. if(!$this->shouldUseThirdPartyCookie())
  1074. {
  1075. return;
  1076. }
  1077. printDebug("We manage the cookie...");
  1078. // idcookie has been generated in handleNewVisit or we simply propagate the old value
  1079. $this->cookie->set(0, bin2hex($this->visitorInfo['idvisitor']) );
  1080. $this->cookie->save();
  1081. }
  1082. /**
  1083. * Returns an object able to handle the current action
  1084. * Plugins can return an override Action that for example, does not record the action in the DB
  1085. *
  1086. * @return Piwik_Tracker_Action child or fake but with same public interface
  1087. */
  1088. protected function newAction()
  1089. {
  1090. $action = null;
  1091. Piwik_PostEvent('Tracker.newAction', $action);
  1092. if(is_null($action))
  1093. {
  1094. $action = new Piwik_Tracker_Action();
  1095. }
  1096. elseif(!($action instanceof Piwik_Tracker_Action_Interface))
  1097. {
  1098. throw new Exception("The Action object set in the plugin must implement the interface Piwik_Tracker_Action_Interface");
  1099. }
  1100. return $action;
  1101. }
  1102. /**
  1103. * Detect whether action is an outlink given host aliases
  1104. *
  1105. * @param Piwik_Tracker_Action_Interface $action
  1106. * @return bool true if the outlink the visitor clicked on points to one of the known hosts for this website
  1107. */
  1108. protected function detectActionIsOutlinkOnAliasHost(Piwik_Tracker_Action_Interface $action)
  1109. {
  1110. if($action->getActionType() != Piwik_Tracker_Action_Interface::TYPE_OUTLINK)
  1111. {
  1112. return false;
  1113. }
  1114. $decodedActionUrl = $action->getActionUrl();
  1115. $actionUrlParsed = @parse_url($decodedActionUrl);
  1116. if(!isset($actionUrlParsed['host']))
  1117. {
  1118. return false;
  1119. }
  1120. return Piwik_Tracker_Visit::isHostKnownAliasHost($actionUrlParsed['host'], $this->idsite);
  1121. }
  1122. /**
  1123. * Returns a 64-bit hash of all the configuration settings
  1124. * @return string
  1125. */
  1126. 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)
  1127. {
  1128. $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 );
  1129. return Piwik_Common::substr( $hash, 0, Piwik_Tracker::LENGTH_BINARY_ID );
  1130. }
  1131. /**
  1132. * Returns either
  1133. * - "-1" for a known visitor
  1134. * - at least 16 char identifier in hex @see Piwik_Common::generateUniqId()
  1135. */
  1136. protected function getVisitorUniqueId()
  1137. {
  1138. if($this->isVisitorKnown())
  1139. {
  1140. return -1;
  1141. }
  1142. return Piwik_Common::generateUniqId();
  1143. }
  1144. protected function setCookie( $cookie )
  1145. {
  1146. $this->cookie = $cookie;
  1147. }
  1148. // is the referer host any of the registered URLs for this website?
  1149. static public function isHostKnownAliasHost($urlHost, $idSite)
  1150. {
  1151. $websiteData = Piwik_Common::getCacheWebsiteAttributes($idSite);
  1152. if(isset($websiteData['hosts']))
  1153. {
  1154. $canonicalHosts = array();
  1155. foreach($websiteData['hosts'] as $host) {
  1156. $canonicalHosts[] = str_replace('www.', '' , mb_strtolower($host, 'UTF-8'));
  1157. }
  1158. $canonicalHost = str_replace('www.', '', mb_strtolower($urlHost, 'UTF-8'));
  1159. if(in_array($canonicalHost, $canonicalHosts))
  1160. {
  1161. return true;
  1162. }
  1163. }
  1164. return false;
  1165. }
  1166. }
  1167. /**
  1168. * @package Piwik
  1169. * @subpackage Piwik_Tracker
  1170. */
  1171. class Piwik_Tracker_Visit_Referer
  1172. {
  1173. // @see detect*() referer methods
  1174. protected $typeRefererAnalyzed;
  1175. protected $nameRefererAnalyzed;
  1176. protected $keywordRefererAnalyzed;
  1177. protected $refererHost;
  1178. protected $refererUrl;
  1179. protected $refererUrlParse;
  1180. protected $currentUrlParse;
  1181. protected $idsite;
  1182. /**
  1183. * Returns an array containing the following information:
  1184. * - referer_type
  1185. * - direct -- absence of referer URL OR referer URL has the same host
  1186. * - site -- based on the referer URL
  1187. * - search_engine -- based on the referer URL
  1188. * - campaign -- based on campaign URL parameter
  1189. *
  1190. * - referer_name
  1191. * - ()
  1192. * - piwik.net -- site host name
  1193. * - google.fr -- search engine host name
  1194. * - adwords-search -- campaign name
  1195. *
  1196. * - referer_keyword
  1197. * - ()
  1198. * - ()
  1199. * - my keyword
  1200. * - my paid keyword
  1201. * - ()
  1202. * - ()
  1203. *
  1204. * - referer_url : the same for all the referer types
  1205. *
  1206. * @param URLs must be URL Encoded
  1207. */
  1208. public function getRefererInformation($refererUrl, $currentUrl, $idSite)
  1209. {
  1210. $this->idsite = $idSite;
  1211. // default values for the referer_* fields
  1212. $this->refererUrl = Piwik_Common::unsanitizeInputValue($refererUrl);
  1213. $this->refererUrlParse = @parse_url($this->refererUrl);
  1214. $this->currentUrlParse = @parse_url(Piwik_Common::unsanitizeInputValue($currentUrl));
  1215. $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY;
  1216. $this->nameRefererAnalyzed = '';
  1217. $this->keywordRefererAnalyzed = '';
  1218. $this->refererHost = '';
  1219. if(isset($this->refererUrlParse['host']))
  1220. {
  1221. $this->refererHost = $this->refererUrlParse['host'];
  1222. }
  1223. $refererDetected = false;
  1224. if( !empty($this->currentUrlParse['host'])
  1225. && $this->detectRefererCampaign() )
  1226. {
  1227. $refererDetected = true;
  1228. }
  1229. if(!$refererDetected)
  1230. {
  1231. if( $this->detectRefererDirectEntry()
  1232. || $this->detectRefererSearchEngine() )
  1233. {
  1234. $refererDetected = true;
  1235. }
  1236. }
  1237. if(!empty($this->refererHost)
  1238. && !$refererDetected)
  1239. {
  1240. $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_WEBSITE;
  1241. $this->nameRefererAnalyzed = mb_strtolower($this->refererHost, 'UTF-8');
  1242. }
  1243. $refererInformation = array(
  1244. 'referer_type' => $this->typeRefererAnalyzed,
  1245. 'referer_name' => $this->nameRefererAnalyzed,
  1246. 'referer_keyword' => $this->keywordRefererAnalyzed,
  1247. 'referer_url' => $this->refererUrl,
  1248. );
  1249. return $refererInformation;
  1250. }
  1251. /*
  1252. * Search engine detection
  1253. */
  1254. protected function detectRefererSearchEngine()
  1255. {
  1256. $searchEngineInformation = Piwik_Common::extractSearchEngineInformationFromUrl($this->refererUrl);
  1257. Piwik_PostEvent('Tracker.detectRefererSearchEngine', $searchEngineInformation, $this->refererUrl);
  1258. if($searchEngineInformation === false)
  1259. {
  1260. return false;
  1261. }
  1262. $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_SEARCH_ENGINE;
  1263. $this->nameRefererAnalyzed = $searchEngineInformation['name'];
  1264. $this->keywordRefererAnalyzed = $searchEngineInformation['keywords'];
  1265. return true;
  1266. }
  1267. /*
  1268. * Campaign analysis
  1269. */
  1270. protected function detectRefererCampaign()
  1271. {
  1272. if(isset($this->currentUrlParse['query']))
  1273. {
  1274. $campaignParameters = Piwik_Common::getCampaignParameters();
  1275. $campaignNames = $campaignParameters[0];
  1276. foreach($campaignNames as $campaignNameParameter)
  1277. {
  1278. $campaignName = trim(urldecode(Piwik_Common::getParameterFromQueryString($this->currentUrlParse['query'], $campaignNameParameter)));
  1279. if( !empty($campaignName))
  1280. {
  1281. break;
  1282. }
  1283. }
  1284. if(!empty($campaignName))
  1285. {
  1286. $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_CAMPAIGN;
  1287. $this->nameRefererAnalyzed = $campaignName;
  1288. $campaignKeywords = $campaignParameters[1];
  1289. foreach($campaignKeywords as $campaignKeywordParameter)
  1290. {
  1291. $campaignKeyword = Piwik_Common::getParameterFromQueryString($this->currentUrlParse['query'], $campaignKeywordParameter);
  1292. if( !empty($campaignKeyword))
  1293. {
  1294. $this->keywordRefererAnalyzed = trim(urldecode($campaignKeyword));
  1295. break;
  1296. }
  1297. }
  1298. return true;
  1299. }
  1300. }
  1301. return false;
  1302. }
  1303. /*
  1304. * We have previously tried to detect the campaign variables in the URL
  1305. * so at this stage, if the referer host is the current host,
  1306. * or if the referer host is any of the registered URL for this website,
  1307. * it is considered a direct entry
  1308. */
  1309. protected function detectRefererDirectEntry()
  1310. {
  1311. if(!empty($this->refererHost))
  1312. {
  1313. // is the referer host the current host?
  1314. if(isset($this->currentUrlParse['host']))
  1315. {
  1316. $currentHost = mb_strtolower($this->currentUrlParse['host'], 'UTF-8');
  1317. if($currentHost == mb_strtolower($this->refererHost, 'UTF-8'))
  1318. {
  1319. $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY;
  1320. return true;
  1321. }
  1322. }
  1323. if(Piwik_Tracker_Visit::isHostKnownAliasHost($this->refererHost, $this->idsite))
  1324. {
  1325. $this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY;
  1326. return true;
  1327. }
  1328. }
  1329. return false;
  1330. }
  1331. }
  1332. /**
  1333. * @package Piwik
  1334. * @subpackage Piwik_Tracker
  1335. */
  1336. class Piwik_Tracker_Visit_VisitorNotFoundInDatabase extends Exception {
  1337. }
  1338. /**
  1339. * @package Piwik
  1340. * @subpackage Piwik_Tracker
  1341. */
  1342. class Piwik_Tracker_Visit_Excluded extends Exception {
  1343. }