Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Tracker.php 31KB

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