PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Tracker.php

https://github.com/CodeYellowBV/piwik
PHP | 915 lines | 583 code | 136 blank | 196 comment | 93 complexity | 9b27a8b691073307a1eb208ab04e6c60 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;
  10. use Exception;
  11. use Piwik\Plugins\PrivacyManager\Config as PrivacyManagerConfig;
  12. use Piwik\Tracker\Cache;
  13. use Piwik\Tracker\Db\DbException;
  14. use Piwik\Tracker\Db\Mysqli;
  15. use Piwik\Tracker\Db\Pdo\Mysql;
  16. use Piwik\Tracker\Request;
  17. use Piwik\Tracker\Visit;
  18. use Piwik\Tracker\VisitInterface;
  19. /**
  20. * Class used by the logging script piwik.php called by the javascript tag.
  21. * Handles the visitor & his/her actions on the website, saves the data in the DB,
  22. * saves information in the cookie, etc.
  23. *
  24. * We try to include as little files as possible (no dependency on 3rd party modules).
  25. *
  26. */
  27. class Tracker
  28. {
  29. protected $stateValid = self::STATE_NOTHING_TO_NOTICE;
  30. /**
  31. * @var Db
  32. */
  33. protected static $db = null;
  34. const STATE_NOTHING_TO_NOTICE = 1;
  35. const STATE_LOGGING_DISABLE = 10;
  36. const STATE_EMPTY_REQUEST = 11;
  37. const STATE_NOSCRIPT_REQUEST = 13;
  38. // We use hex ID that are 16 chars in length, ie. 64 bits IDs
  39. const LENGTH_HEX_ID_STRING = 16;
  40. const LENGTH_BINARY_ID = 8;
  41. static protected $forcedDateTime = null;
  42. static protected $forcedIpString = null;
  43. static protected $forcedVisitorId = null;
  44. static protected $pluginsNotToLoad = array();
  45. static protected $pluginsToLoad = array();
  46. /**
  47. * The set of visits to track.
  48. *
  49. * @var array
  50. */
  51. private $requests = array();
  52. /**
  53. * The token auth supplied with a bulk visits POST.
  54. *
  55. * @var string
  56. */
  57. private $tokenAuth = null;
  58. /**
  59. * Whether we're currently using bulk tracking or not.
  60. *
  61. * @var bool
  62. */
  63. private $usingBulkTracking = false;
  64. /**
  65. * The number of requests that have been successfully logged.
  66. *
  67. * @var int
  68. */
  69. private $countOfLoggedRequests = 0;
  70. protected function outputAccessControlHeaders()
  71. {
  72. $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
  73. if ($requestMethod !== 'GET') {
  74. $origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*';
  75. Common::sendHeader('Access-Control-Allow-Origin: ' . $origin);
  76. Common::sendHeader('Access-Control-Allow-Credentials: true');
  77. }
  78. }
  79. public function clear()
  80. {
  81. self::$forcedIpString = null;
  82. self::$forcedDateTime = null;
  83. self::$forcedVisitorId = null;
  84. $this->stateValid = self::STATE_NOTHING_TO_NOTICE;
  85. }
  86. public static function setForceIp($ipString)
  87. {
  88. self::$forcedIpString = $ipString;
  89. }
  90. public static function setForceDateTime($dateTime)
  91. {
  92. self::$forcedDateTime = $dateTime;
  93. }
  94. public static function setForceVisitorId($visitorId)
  95. {
  96. self::$forcedVisitorId = $visitorId;
  97. }
  98. /**
  99. * Do not load the specified plugins (used during testing, to disable Provider plugin)
  100. * @param array $plugins
  101. */
  102. static public function setPluginsNotToLoad($plugins)
  103. {
  104. self::$pluginsNotToLoad = $plugins;
  105. }
  106. /**
  107. * Get list of plugins to not load
  108. *
  109. * @return array
  110. */
  111. static public function getPluginsNotToLoad()
  112. {
  113. return self::$pluginsNotToLoad;
  114. }
  115. /**
  116. * Update Tracker config
  117. *
  118. * @param string $name Setting name
  119. * @param mixed $value Value
  120. */
  121. static private function updateTrackerConfig($name, $value)
  122. {
  123. $section = Config::getInstance()->Tracker;
  124. $section[$name] = $value;
  125. Config::getInstance()->Tracker = $section;
  126. }
  127. protected function initRequests($args)
  128. {
  129. $rawData = self::getRawBulkRequest();
  130. if (!empty($rawData)) {
  131. $this->usingBulkTracking = strpos($rawData, '"requests"') || strpos($rawData, "'requests'");
  132. if ($this->usingBulkTracking) {
  133. return $this->authenticateBulkTrackingRequests($rawData);
  134. }
  135. }
  136. // Not using bulk tracking
  137. $this->requests = $args ? $args : (!empty($_GET) || !empty($_POST) ? array($_GET + $_POST) : array());
  138. }
  139. private static function getRequestsArrayFromBulkRequest($rawData)
  140. {
  141. $rawData = trim($rawData);
  142. $rawData = Common::sanitizeLineBreaks($rawData);
  143. // POST data can be array of string URLs or array of arrays w/ visit info
  144. $jsonData = json_decode($rawData, $assoc = true);
  145. $tokenAuth = Common::getRequestVar('token_auth', false, 'string', $jsonData);
  146. $requests = array();
  147. if (isset($jsonData['requests'])) {
  148. $requests = $jsonData['requests'];
  149. }
  150. return array( $requests, $tokenAuth);
  151. }
  152. private function isBulkTrackingRequireTokenAuth()
  153. {
  154. return !empty(Config::getInstance()->Tracker['bulk_requests_require_authentication']);
  155. }
  156. private function authenticateBulkTrackingRequests($rawData)
  157. {
  158. list($this->requests, $tokenAuth) = $this->getRequestsArrayFromBulkRequest($rawData);
  159. $bulkTrackingRequireTokenAuth = $this->isBulkTrackingRequireTokenAuth();
  160. if($bulkTrackingRequireTokenAuth) {
  161. if(empty($tokenAuth)) {
  162. throw new Exception("token_auth must be specified when using Bulk Tracking Import. "
  163. . " See <a href='http://developer.piwik.org/api-reference/tracking-api'>Tracking Doc</a>");
  164. }
  165. }
  166. if (!empty($this->requests)) {
  167. foreach ($this->requests as &$request) {
  168. // if a string is sent, we assume its a URL and try to parse it
  169. if (is_string($request)) {
  170. $params = array();
  171. $url = @parse_url($request);
  172. if (!empty($url)) {
  173. @parse_str($url['query'], $params);
  174. $request = $params;
  175. }
  176. }
  177. $requestObj = new Request($request, $tokenAuth);
  178. $this->loadTrackerPlugins($requestObj);
  179. if($bulkTrackingRequireTokenAuth
  180. && !$requestObj->isAuthenticated()) {
  181. throw new Exception(sprintf("token_auth specified does not have Admin permission for idsite=%s", $requestObj->getIdSite()));
  182. }
  183. $request = $requestObj;
  184. }
  185. }
  186. return $tokenAuth;
  187. }
  188. /**
  189. * Main - tracks the visit/action
  190. *
  191. * @param array $args Optional Request Array
  192. */
  193. public function main($args = null)
  194. {
  195. try {
  196. $tokenAuth = $this->initRequests($args);
  197. } catch (Exception $ex) {
  198. $this->exitWithException($ex, true);
  199. }
  200. $this->initOutputBuffer();
  201. if (!empty($this->requests)) {
  202. $this->beginTransaction();
  203. try {
  204. foreach ($this->requests as $params) {
  205. $isAuthenticated = $this->trackRequest($params, $tokenAuth);
  206. }
  207. $this->runScheduledTasksIfAllowed($isAuthenticated);
  208. $this->commitTransaction();
  209. } catch(DbException $e) {
  210. Common::printDebug($e->getMessage());
  211. $this->rollbackTransaction();
  212. }
  213. } else {
  214. $this->handleEmptyRequest(new Request($_GET + $_POST));
  215. }
  216. $this->end();
  217. $this->flushOutputBuffer();
  218. }
  219. protected function initOutputBuffer()
  220. {
  221. ob_start();
  222. }
  223. protected function flushOutputBuffer()
  224. {
  225. ob_end_flush();
  226. }
  227. protected function getOutputBuffer()
  228. {
  229. return ob_get_contents();
  230. }
  231. protected function beginTransaction()
  232. {
  233. $this->transactionId = null;
  234. if(!$this->shouldUseTransactions()) {
  235. return;
  236. }
  237. $this->transactionId = self::getDatabase()->beginTransaction();
  238. }
  239. protected function commitTransaction()
  240. {
  241. if(empty($this->transactionId)) {
  242. return;
  243. }
  244. self::getDatabase()->commit($this->transactionId);
  245. }
  246. protected function rollbackTransaction()
  247. {
  248. if(empty($this->transactionId)) {
  249. return;
  250. }
  251. self::getDatabase()->rollback($this->transactionId);
  252. }
  253. /**
  254. * @return bool
  255. */
  256. protected function shouldUseTransactions()
  257. {
  258. $isBulkRequest = count($this->requests) > 1;
  259. return $isBulkRequest && $this->isTransactionSupported();
  260. }
  261. /**
  262. * @return bool
  263. */
  264. protected function isTransactionSupported()
  265. {
  266. return (bool) Config::getInstance()->Tracker['bulk_requests_use_transaction'];
  267. }
  268. protected function shouldRunScheduledTasks()
  269. {
  270. // don't run scheduled tasks in CLI mode from Tracker, this is the case
  271. // where we bulk load logs & don't want to lose time with tasks
  272. return !Common::isPhpCliMode()
  273. && $this->getState() != self::STATE_LOGGING_DISABLE;
  274. }
  275. /**
  276. * Tracker requests will automatically trigger the Scheduled tasks.
  277. * This is useful for users who don't setup the cron,
  278. * but still want daily/weekly/monthly PDF reports emailed automatically.
  279. *
  280. * This is similar to calling the API CoreAdminHome.runScheduledTasks
  281. */
  282. protected static function runScheduledTasks()
  283. {
  284. $now = time();
  285. // Currently, there are no hourly tasks. When there are some,
  286. // this could be too aggressive minimum interval (some hours would be skipped in case of low traffic)
  287. $minimumInterval = Config::getInstance()->Tracker['scheduled_tasks_min_interval'];
  288. // If the user disabled browser archiving, he has already setup a cron
  289. // To avoid parallel requests triggering the Scheduled Tasks,
  290. // Get last time tasks started executing
  291. $cache = Cache::getCacheGeneral();
  292. if ($minimumInterval <= 0
  293. || empty($cache['isBrowserTriggerEnabled'])
  294. ) {
  295. Common::printDebug("-> Scheduled tasks not running in Tracker: Browser archiving is disabled.");
  296. return;
  297. }
  298. $nextRunTime = $cache['lastTrackerCronRun'] + $minimumInterval;
  299. if ((isset($GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS']) && $GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS'])
  300. || $cache['lastTrackerCronRun'] === false
  301. || $nextRunTime < $now
  302. ) {
  303. $cache['lastTrackerCronRun'] = $now;
  304. Cache::setCacheGeneral($cache);
  305. self::initCorePiwikInTrackerMode();
  306. Option::set('lastTrackerCronRun', $cache['lastTrackerCronRun']);
  307. Common::printDebug('-> Scheduled Tasks: Starting...');
  308. // save current user privilege and temporarily assume Super User privilege
  309. $isSuperUser = Piwik::hasUserSuperUserAccess();
  310. // Scheduled tasks assume Super User is running
  311. Piwik::setUserHasSuperUserAccess();
  312. // While each plugins should ensure that necessary languages are loaded,
  313. // we ensure English translations at least are loaded
  314. Translate::loadEnglishTranslation();
  315. ob_start();
  316. CronArchive::$url = SettingsPiwik::getPiwikUrl();
  317. $cronArchive = new CronArchive();
  318. $cronArchive->runScheduledTasksInTrackerMode();
  319. $resultTasks = ob_get_contents();
  320. ob_clean();
  321. // restore original user privilege
  322. Piwik::setUserHasSuperUserAccess($isSuperUser);
  323. foreach (explode('</pre>', $resultTasks) as $resultTask) {
  324. Common::printDebug(str_replace('<pre>', '', $resultTask));
  325. }
  326. Common::printDebug('Finished Scheduled Tasks.');
  327. } else {
  328. Common::printDebug("-> Scheduled tasks not triggered.");
  329. }
  330. Common::printDebug("Next run will be from: " . date('Y-m-d H:i:s', $nextRunTime) . ' UTC');
  331. }
  332. static public $initTrackerMode = false;
  333. /**
  334. * Used to initialize core Piwik components on a piwik.php request
  335. * Eg. when cache is missed and we will be calling some APIs to generate cache
  336. */
  337. static public function initCorePiwikInTrackerMode()
  338. {
  339. if (SettingsServer::isTrackerApiRequest()
  340. && self::$initTrackerMode === false
  341. ) {
  342. self::$initTrackerMode = true;
  343. require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
  344. require_once PIWIK_INCLUDE_PATH . '/core/Option.php';
  345. $access = Access::getInstance();
  346. $config = Config::getInstance();
  347. try {
  348. Db::get();
  349. } catch (Exception $e) {
  350. Db::createDatabaseObject();
  351. }
  352. \Piwik\Plugin\Manager::getInstance()->loadCorePluginsDuringTracker();
  353. }
  354. }
  355. /**
  356. * Echos an error message & other information, then exits.
  357. *
  358. * @param Exception $e
  359. * @param bool $authenticated
  360. */
  361. protected function exitWithException($e, $authenticated = false)
  362. {
  363. if ($this->usingBulkTracking) {
  364. // when doing bulk tracking we return JSON so the caller will know how many succeeded
  365. $result = array(
  366. 'status' => 'error',
  367. 'tracked' => $this->countOfLoggedRequests
  368. );
  369. // send error when in debug mode or when authenticated (which happens when doing log importing,
  370. if ((isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG'])
  371. || $authenticated
  372. ) {
  373. $result['message'] = $this->getMessageFromException($e);
  374. }
  375. Common::sendHeader('Content-Type: application/json');
  376. echo Common::json_encode($result);
  377. exit;
  378. }
  379. if (isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) {
  380. Common::sendHeader('Content-Type: text/html; charset=utf-8');
  381. $trailer = '<span style="color: #888888">Backtrace:<br /><pre>' . $e->getTraceAsString() . '</pre></span>';
  382. $headerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/simpleLayoutHeader.tpl');
  383. $footerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/simpleLayoutFooter.tpl');
  384. $headerPage = str_replace('{$HTML_TITLE}', 'Piwik &rsaquo; Error', $headerPage);
  385. echo $headerPage . '<p>' . $this->getMessageFromException($e) . '</p>' . $trailer . $footerPage;
  386. } // If not debug, but running authenticated (eg. during log import) then we display raw errors
  387. elseif ($authenticated) {
  388. Common::sendHeader('Content-Type: text/html; charset=utf-8');
  389. echo $this->getMessageFromException($e);
  390. } else {
  391. $this->outputTransparentGif();
  392. }
  393. exit;
  394. }
  395. /**
  396. * Returns the date in the "Y-m-d H:i:s" PHP format
  397. *
  398. * @param int $timestamp
  399. * @return string
  400. */
  401. public static function getDatetimeFromTimestamp($timestamp)
  402. {
  403. return date("Y-m-d H:i:s", $timestamp);
  404. }
  405. /**
  406. * Initialization
  407. */
  408. protected function init(Request $request)
  409. {
  410. $this->loadTrackerPlugins($request);
  411. $this->handleTrackingApi($request);
  412. $this->handleDisabledTracker();
  413. $this->handleEmptyRequest($request);
  414. Common::printDebug("Current datetime: " . date("Y-m-d H:i:s", $request->getCurrentTimestamp()));
  415. }
  416. /**
  417. * Cleanup
  418. */
  419. protected function end()
  420. {
  421. if ($this->usingBulkTracking) {
  422. $result = array(
  423. 'status' => 'success',
  424. 'tracked' => $this->countOfLoggedRequests
  425. );
  426. Common::sendHeader('Content-Type: application/json');
  427. echo Common::json_encode($result);
  428. exit;
  429. }
  430. switch ($this->getState()) {
  431. case self::STATE_LOGGING_DISABLE:
  432. $this->outputTransparentGif();
  433. Common::printDebug("Logging disabled, display transparent logo");
  434. break;
  435. case self::STATE_EMPTY_REQUEST:
  436. Common::printDebug("Empty request => Piwik page");
  437. echo "<a href='/'>Piwik</a> is a free/libre web <a href='http://piwik.org'>analytics</a> that lets you keep control of your data.";
  438. break;
  439. case self::STATE_NOSCRIPT_REQUEST:
  440. case self::STATE_NOTHING_TO_NOTICE:
  441. default:
  442. $this->outputTransparentGif();
  443. Common::printDebug("Nothing to notice => default behaviour");
  444. break;
  445. }
  446. Common::printDebug("End of the page.");
  447. if ($GLOBALS['PIWIK_TRACKER_DEBUG'] === true) {
  448. if (isset(self::$db)) {
  449. self::$db->recordProfiling();
  450. Profiler::displayDbTrackerProfile(self::$db);
  451. }
  452. }
  453. self::disconnectDatabase();
  454. }
  455. /**
  456. * Factory to create database objects
  457. *
  458. * @param array $configDb Database configuration
  459. * @throws Exception
  460. * @return \Piwik\Tracker\Db\Mysqli|\Piwik\Tracker\Db\Pdo\Mysql
  461. */
  462. public static function factory($configDb)
  463. {
  464. /**
  465. * Triggered before a connection to the database is established by the Tracker.
  466. *
  467. * This event can be used to change the database connection settings used by the Tracker.
  468. *
  469. * @param array $dbInfos Reference to an array containing database connection info,
  470. * including:
  471. *
  472. * - **host**: The host name or IP address to the MySQL database.
  473. * - **username**: The username to use when connecting to the
  474. * database.
  475. * - **password**: The password to use when connecting to the
  476. * database.
  477. * - **dbname**: The name of the Piwik MySQL database.
  478. * - **port**: The MySQL database port to use.
  479. * - **adapter**: either `'PDO_MYSQL'` or `'MYSQLI'`
  480. * - **type**: The MySQL engine to use, for instance 'InnoDB'
  481. */
  482. Piwik::postEvent('Tracker.getDatabaseConfig', array(&$configDb));
  483. switch ($configDb['adapter']) {
  484. case 'PDO\MYSQL':
  485. case 'PDO_MYSQL': // old format pre Piwik 2
  486. require_once PIWIK_INCLUDE_PATH . '/core/Tracker/Db/Pdo/Mysql.php';
  487. return new Mysql($configDb);
  488. case 'MYSQLI':
  489. require_once PIWIK_INCLUDE_PATH . '/core/Tracker/Db/Mysqli.php';
  490. return new Mysqli($configDb);
  491. }
  492. throw new Exception('Unsupported database adapter ' . $configDb['adapter']);
  493. }
  494. public static function connectPiwikTrackerDb()
  495. {
  496. $db = null;
  497. $configDb = Config::getInstance()->database;
  498. if (!isset($configDb['port'])) {
  499. // before 0.2.4 there is no port specified in config file
  500. $configDb['port'] = '3306';
  501. }
  502. $db = Tracker::factory($configDb);
  503. $db->connect();
  504. return $db;
  505. }
  506. protected static function connectDatabaseIfNotConnected()
  507. {
  508. if (!is_null(self::$db)) {
  509. return;
  510. }
  511. try {
  512. self::$db = self::connectPiwikTrackerDb();
  513. } catch (Exception $e) {
  514. throw new DbException($e->getMessage(), $e->getCode());
  515. }
  516. }
  517. /**
  518. * @return Db
  519. */
  520. public static function getDatabase()
  521. {
  522. self::connectDatabaseIfNotConnected();
  523. return self::$db;
  524. }
  525. public static function disconnectDatabase()
  526. {
  527. if (isset(self::$db)) {
  528. self::$db->disconnect();
  529. self::$db = null;
  530. }
  531. }
  532. /**
  533. * Returns the Tracker_Visit object.
  534. * This method can be overwritten to use a different Tracker_Visit object
  535. *
  536. * @throws Exception
  537. * @return \Piwik\Tracker\Visit
  538. */
  539. protected function getNewVisitObject()
  540. {
  541. $visit = null;
  542. /**
  543. * Triggered before a new **visit tracking object** is created. Subscribers to this
  544. * event can force the use of a custom visit tracking object that extends from
  545. * {@link Piwik\Tracker\VisitInterface}.
  546. *
  547. * @param \Piwik\Tracker\VisitInterface &$visit Initialized to null, but can be set to
  548. * a new visit object. If it isn't modified
  549. * Piwik uses the default class.
  550. */
  551. Piwik::postEvent('Tracker.makeNewVisitObject', array(&$visit));
  552. if (is_null($visit)) {
  553. $visit = new Visit();
  554. } elseif (!($visit instanceof VisitInterface)) {
  555. throw new Exception("The Visit object set in the plugin must implement VisitInterface");
  556. }
  557. return $visit;
  558. }
  559. protected function outputTransparentGif()
  560. {
  561. if (isset($GLOBALS['PIWIK_TRACKER_DEBUG'])
  562. && $GLOBALS['PIWIK_TRACKER_DEBUG']
  563. ) {
  564. return;
  565. }
  566. if (strlen($this->getOutputBuffer()) > 0) {
  567. // If there was an error during tracker, return so errors can be flushed
  568. return;
  569. }
  570. $transGifBase64 = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
  571. Common::sendHeader('Content-Type: image/gif');
  572. $this->outputAccessControlHeaders();
  573. print(base64_decode($transGifBase64));
  574. }
  575. protected function isVisitValid()
  576. {
  577. return $this->stateValid !== self::STATE_LOGGING_DISABLE
  578. && $this->stateValid !== self::STATE_EMPTY_REQUEST;
  579. }
  580. protected function getState()
  581. {
  582. return $this->stateValid;
  583. }
  584. protected function setState($value)
  585. {
  586. $this->stateValid = $value;
  587. }
  588. protected function loadTrackerPlugins(Request $request)
  589. {
  590. // Adding &dp=1 will disable the provider plugin, if token_auth is used (used to speed up bulk imports)
  591. $disableProvider = $request->getParam('dp');
  592. if (!empty($disableProvider)) {
  593. Tracker::setPluginsNotToLoad(array('Provider'));
  594. }
  595. try {
  596. $pluginsTracker = \Piwik\Plugin\Manager::getInstance()->loadTrackerPlugins();
  597. Common::printDebug("Loading plugins: { " . implode(",", $pluginsTracker) . " }");
  598. } catch (Exception $e) {
  599. Common::printDebug("ERROR: " . $e->getMessage());
  600. }
  601. }
  602. protected function handleEmptyRequest(Request $request)
  603. {
  604. $countParameters = $request->getParamsCount();
  605. if ($countParameters == 0) {
  606. $this->setState(self::STATE_EMPTY_REQUEST);
  607. }
  608. if ($countParameters == 1) {
  609. $this->setState(self::STATE_NOSCRIPT_REQUEST);
  610. }
  611. }
  612. protected function handleDisabledTracker()
  613. {
  614. $saveStats = Config::getInstance()->Tracker['record_statistics'];
  615. if ($saveStats == 0) {
  616. $this->setState(self::STATE_LOGGING_DISABLE);
  617. }
  618. }
  619. protected function getTokenAuth()
  620. {
  621. if (!is_null($this->tokenAuth)) {
  622. return $this->tokenAuth;
  623. }
  624. return Common::getRequestVar('token_auth', false);
  625. }
  626. /**
  627. * This method allows to set custom IP + server time + visitor ID, when using Tracking API.
  628. * These two attributes can be only set by the Super User (passing token_auth).
  629. */
  630. protected function handleTrackingApi(Request $request)
  631. {
  632. if (!$request->isAuthenticated()) {
  633. return;
  634. }
  635. // Custom IP to use for this visitor
  636. $customIp = $request->getParam('cip');
  637. if (!empty($customIp)) {
  638. $this->setForceIp($customIp);
  639. }
  640. // Custom server date time to use
  641. $customDatetime = $request->getParam('cdt');
  642. if (!empty($customDatetime)) {
  643. $this->setForceDateTime($customDatetime);
  644. }
  645. // Forced Visitor ID to record the visit / action
  646. $customVisitorId = $request->getParam('cid');
  647. if (!empty($customVisitorId)) {
  648. $this->setForceVisitorId($customVisitorId);
  649. }
  650. }
  651. public static function setTestEnvironment($args = null, $requestMethod = null)
  652. {
  653. if (is_null($args)) {
  654. $postData = self::getRequestsArrayFromBulkRequest(self::getRawBulkRequest());
  655. $args = $_GET + $postData;
  656. }
  657. if (is_null($requestMethod) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
  658. $requestMethod = $_SERVER['REQUEST_METHOD'];
  659. } else if (is_null($requestMethod)) {
  660. $requestMethod = 'GET';
  661. }
  662. // Do not run scheduled tasks during tests
  663. self::updateTrackerConfig('scheduled_tasks_min_interval', 0);
  664. // if nothing found in _GET/_POST and we're doing a POST, assume bulk request. in which case,
  665. // we have to bypass authentication
  666. if (empty($args) && $requestMethod == 'POST') {
  667. self::updateTrackerConfig('tracking_requests_require_authentication', 0);
  668. }
  669. // Tests can force the use of 3rd party cookie for ID visitor
  670. if (Common::getRequestVar('forceUseThirdPartyCookie', false, null, $args) == 1) {
  671. self::updateTrackerConfig('use_third_party_id_cookie', 1);
  672. }
  673. // Tests using window_look_back_for_visitor
  674. if (Common::getRequestVar('forceLargeWindowLookBackForVisitor', false, null, $args) == 1
  675. // also look for this in bulk requests (see fake_logs_replay.log)
  676. || strpos( json_encode($args, true), '"forceLargeWindowLookBackForVisitor":"1"' ) !== false) {
  677. self::updateTrackerConfig('window_look_back_for_visitor', 2678400);
  678. }
  679. // Tests can force the enabling of IP anonymization
  680. if (Common::getRequestVar('forceIpAnonymization', false, null, $args) == 1) {
  681. self::connectDatabaseIfNotConnected();
  682. $privacyConfig = new PrivacyManagerConfig();
  683. $privacyConfig->ipAddressMaskLength = 2;
  684. \Piwik\Plugins\PrivacyManager\IPAnonymizer::activate();
  685. }
  686. // Custom IP to use for this visitor
  687. $customIp = Common::getRequestVar('cip', false, null, $args);
  688. if (!empty($customIp)) {
  689. self::setForceIp($customIp);
  690. }
  691. // Custom server date time to use
  692. $customDatetime = Common::getRequestVar('cdt', false, null, $args);
  693. if (!empty($customDatetime)) {
  694. self::setForceDateTime($customDatetime);
  695. }
  696. // Custom visitor id
  697. $customVisitorId = Common::getRequestVar('cid', false, null, $args);
  698. if (!empty($customVisitorId)) {
  699. self::setForceVisitorId($customVisitorId);
  700. }
  701. $pluginsDisabled = array('Provider');
  702. // Disable provider plugin, because it is so slow to do many reverse ip lookups
  703. self::setPluginsNotToLoad($pluginsDisabled);
  704. }
  705. /**
  706. * Gets the error message to output when a tracking request fails.
  707. *
  708. * @param Exception $e
  709. * @return string
  710. */
  711. private function getMessageFromException($e)
  712. {
  713. // Note: duplicated from FormDatabaseSetup.isAccessDenied
  714. // Avoid leaking the username/db name when access denied
  715. if ($e->getCode() == 1044 || $e->getCode() == 42000) {
  716. return "Error while connecting to the Piwik database - please check your credentials in config/config.ini.php file";
  717. } else {
  718. return $e->getMessage();
  719. }
  720. }
  721. /**
  722. * @param $params
  723. * @param $tokenAuth
  724. * @return array
  725. */
  726. protected function trackRequest($params, $tokenAuth)
  727. {
  728. if ($params instanceof Request) {
  729. $request = $params;
  730. } else {
  731. $request = new Request($params, $tokenAuth);
  732. }
  733. $this->init($request);
  734. $isAuthenticated = $request->isAuthenticated();
  735. try {
  736. if ($this->isVisitValid()) {
  737. $visit = $this->getNewVisitObject();
  738. $request->setForcedVisitorId(self::$forcedVisitorId);
  739. $request->setForceDateTime(self::$forcedDateTime);
  740. $request->setForceIp(self::$forcedIpString);
  741. $visit->setRequest($request);
  742. $visit->handle();
  743. } else {
  744. Common::printDebug("The request is invalid: empty request, or maybe tracking is disabled in the config.ini.php via record_statistics=0");
  745. }
  746. } catch (DbException $e) {
  747. Common::printDebug("Exception: " . $e->getMessage());
  748. $this->exitWithException($e, $isAuthenticated);
  749. } catch (Exception $e) {
  750. $this->exitWithException($e, $isAuthenticated);
  751. }
  752. $this->clear();
  753. // increment successfully logged request count. make sure to do this after try-catch,
  754. // since an excluded visit is considered 'successfully logged'
  755. ++$this->countOfLoggedRequests;
  756. return $isAuthenticated;
  757. }
  758. protected function runScheduledTasksIfAllowed($isAuthenticated)
  759. {
  760. // Do not run schedule task if we are importing logs
  761. // or doing custom tracking (as it could slow down)
  762. try {
  763. if (!$isAuthenticated
  764. && $this->shouldRunScheduledTasks()
  765. ) {
  766. self::runScheduledTasks();
  767. }
  768. } catch (Exception $e) {
  769. $this->exitWithException($e);
  770. }
  771. }
  772. /**
  773. * @return string
  774. */
  775. protected static function getRawBulkRequest()
  776. {
  777. return file_get_contents("php://input");
  778. }
  779. }