PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/core/Tracker/Request.php

https://github.com/CodeYellowBV/piwik
PHP | 573 lines | 417 code | 67 blank | 89 comment | 62 complexity | db9b138ff45922cfbfd578e662080dbb MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\Tracker;
  10. use Exception;
  11. use Piwik\Common;
  12. use Piwik\Config;
  13. use Piwik\Cookie;
  14. use Piwik\IP;
  15. use Piwik\Piwik;
  16. use Piwik\Plugins\CustomVariables\CustomVariables;
  17. use Piwik\Registry;
  18. use Piwik\Tracker;
  19. /**
  20. * The Request object holding the http parameters for this tracking request. Use getParam() to fetch a named parameter.
  21. *
  22. */
  23. class Request
  24. {
  25. /**
  26. * @var array
  27. */
  28. protected $params;
  29. protected $forcedVisitorId = false;
  30. protected $isAuthenticated = null;
  31. protected $tokenAuth;
  32. const UNKNOWN_RESOLUTION = 'unknown';
  33. /**
  34. * @param $params
  35. * @param bool|string $tokenAuth
  36. */
  37. public function __construct($params, $tokenAuth = false)
  38. {
  39. if (!is_array($params)) {
  40. $params = array();
  41. }
  42. $this->params = $params;
  43. $this->tokenAuth = $tokenAuth;
  44. $this->timestamp = time();
  45. $this->enforcedIp = false;
  46. // When the 'url' and referrer url parameter are not given, we might be in the 'Simple Image Tracker' mode.
  47. // The URL can default to the Referrer, which will be in this case
  48. // the URL of the page containing the Simple Image beacon
  49. if (empty($this->params['urlref'])
  50. && empty($this->params['url'])
  51. ) {
  52. $url = @$_SERVER['HTTP_REFERER'];
  53. if (!empty($url)) {
  54. $this->params['url'] = $url;
  55. }
  56. }
  57. }
  58. /**
  59. * @return bool
  60. */
  61. public function isAuthenticated()
  62. {
  63. if (is_null($this->isAuthenticated)) {
  64. $this->authenticateTrackingApi($this->tokenAuth);
  65. }
  66. return $this->isAuthenticated;
  67. }
  68. /**
  69. * This method allows to set custom IP + server time + visitor ID, when using Tracking API.
  70. * These two attributes can be only set by the Super User (passing token_auth).
  71. */
  72. protected function authenticateTrackingApi($tokenAuthFromBulkRequest)
  73. {
  74. $shouldAuthenticate = Config::getInstance()->Tracker['tracking_requests_require_authentication'];
  75. if ($shouldAuthenticate) {
  76. $tokenAuth = $tokenAuthFromBulkRequest ? $tokenAuthFromBulkRequest : Common::getRequestVar('token_auth', false, 'string', $this->params);
  77. try {
  78. $idSite = $this->getIdSite();
  79. $this->isAuthenticated = $this->authenticateSuperUserOrAdmin($tokenAuth, $idSite);
  80. } catch (Exception $e) {
  81. $this->isAuthenticated = false;
  82. }
  83. if (!$this->isAuthenticated) {
  84. return;
  85. }
  86. Common::printDebug("token_auth is authenticated!");
  87. } else {
  88. $this->isAuthenticated = true;
  89. Common::printDebug("token_auth authentication not required");
  90. }
  91. }
  92. public static function authenticateSuperUserOrAdmin($tokenAuth, $idSite)
  93. {
  94. if (empty($tokenAuth)) {
  95. return false;
  96. }
  97. Piwik::postEvent('Request.initAuthenticationObject');
  98. /** @var \Piwik\Auth $auth */
  99. $auth = Registry::get('auth');
  100. $auth->setTokenAuth($tokenAuth);
  101. $auth->setLogin(null);
  102. $access = $auth->authenticate();
  103. if (!empty($access) && $access->hasSuperUserAccess()) {
  104. return true;
  105. }
  106. // Now checking the list of admin token_auth cached in the Tracker config file
  107. if (!empty($idSite) && $idSite > 0) {
  108. $website = Cache::getCacheWebsiteAttributes($idSite);
  109. if (array_key_exists('admin_token_auth', $website) && in_array($tokenAuth, $website['admin_token_auth'])) {
  110. return true;
  111. }
  112. }
  113. Common::printDebug("WARNING! token_auth = $tokenAuth is not valid, Super User / Admin was NOT authenticated");
  114. return false;
  115. }
  116. /**
  117. * @return float|int
  118. */
  119. public function getDaysSinceFirstVisit()
  120. {
  121. $cookieFirstVisitTimestamp = $this->getParam('_idts');
  122. if (!$this->isTimestampValid($cookieFirstVisitTimestamp)) {
  123. $cookieFirstVisitTimestamp = $this->getCurrentTimestamp();
  124. }
  125. $daysSinceFirstVisit = round(($this->getCurrentTimestamp() - $cookieFirstVisitTimestamp) / 86400, $precision = 0);
  126. if ($daysSinceFirstVisit < 0) {
  127. $daysSinceFirstVisit = 0;
  128. }
  129. return $daysSinceFirstVisit;
  130. }
  131. /**
  132. * @return bool|float|int
  133. */
  134. public function getDaysSinceLastOrder()
  135. {
  136. $daysSinceLastOrder = false;
  137. $lastOrderTimestamp = $this->getParam('_ects');
  138. if ($this->isTimestampValid($lastOrderTimestamp)) {
  139. $daysSinceLastOrder = round(($this->getCurrentTimestamp() - $lastOrderTimestamp) / 86400, $precision = 0);
  140. if ($daysSinceLastOrder < 0) {
  141. $daysSinceLastOrder = 0;
  142. }
  143. }
  144. return $daysSinceLastOrder;
  145. }
  146. /**
  147. * @return float|int
  148. */
  149. public function getDaysSinceLastVisit()
  150. {
  151. $daysSinceLastVisit = 0;
  152. $lastVisitTimestamp = $this->getParam('_viewts');
  153. if ($this->isTimestampValid($lastVisitTimestamp)) {
  154. $daysSinceLastVisit = round(($this->getCurrentTimestamp() - $lastVisitTimestamp) / 86400, $precision = 0);
  155. if ($daysSinceLastVisit < 0) {
  156. $daysSinceLastVisit = 0;
  157. }
  158. }
  159. return $daysSinceLastVisit;
  160. }
  161. /**
  162. * @return int|mixed
  163. */
  164. public function getVisitCount()
  165. {
  166. $visitCount = $this->getParam('_idvc');
  167. if ($visitCount < 1) {
  168. $visitCount = 1;
  169. }
  170. return $visitCount;
  171. }
  172. /**
  173. * Returns the language the visitor is viewing.
  174. *
  175. * @return string browser language code, eg. "en-gb,en;q=0.5"
  176. */
  177. public function getBrowserLanguage()
  178. {
  179. return Common::getRequestVar('lang', Common::getBrowserLanguage(), 'string', $this->params);
  180. }
  181. /**
  182. * @return string
  183. */
  184. public function getLocalTime()
  185. {
  186. $localTimes = array(
  187. 'h' => (string)Common::getRequestVar('h', $this->getCurrentDate("H"), 'int', $this->params),
  188. 'i' => (string)Common::getRequestVar('m', $this->getCurrentDate("i"), 'int', $this->params),
  189. 's' => (string)Common::getRequestVar('s', $this->getCurrentDate("s"), 'int', $this->params)
  190. );
  191. foreach ($localTimes as $k => $time) {
  192. if (strlen($time) == 1) {
  193. $localTimes[$k] = '0' . $time;
  194. }
  195. }
  196. $localTime = $localTimes['h'] . ':' . $localTimes['i'] . ':' . $localTimes['s'];
  197. return $localTime;
  198. }
  199. /**
  200. * Returns the current date in the "Y-m-d" PHP format
  201. *
  202. * @param string $format
  203. * @return string
  204. */
  205. protected function getCurrentDate($format = "Y-m-d")
  206. {
  207. return date($format, $this->getCurrentTimestamp());
  208. }
  209. public function getGoalRevenue($defaultGoalRevenue)
  210. {
  211. return Common::getRequestVar('revenue', $defaultGoalRevenue, 'float', $this->params);
  212. }
  213. public function getParam($name)
  214. {
  215. static $supportedParams = array(
  216. // Name => array( defaultValue, type )
  217. '_refts' => array(0, 'int'),
  218. '_ref' => array('', 'string'),
  219. '_rcn' => array('', 'string'),
  220. '_rck' => array('', 'string'),
  221. '_idts' => array(0, 'int'),
  222. '_viewts' => array(0, 'int'),
  223. '_ects' => array(0, 'int'),
  224. '_idvc' => array(1, 'int'),
  225. 'url' => array('', 'string'),
  226. 'urlref' => array('', 'string'),
  227. 'res' => array(self::UNKNOWN_RESOLUTION, 'string'),
  228. 'idgoal' => array(-1, 'int'),
  229. // other
  230. 'bots' => array(0, 'int'),
  231. 'dp' => array(0, 'int'),
  232. 'rec' => array(false, 'int'),
  233. 'new_visit' => array(0, 'int'),
  234. // Ecommerce
  235. 'ec_id' => array(false, 'string'),
  236. 'ec_st' => array(false, 'float'),
  237. 'ec_tx' => array(false, 'float'),
  238. 'ec_sh' => array(false, 'float'),
  239. 'ec_dt' => array(false, 'float'),
  240. 'ec_items' => array('', 'string'),
  241. // Events
  242. 'e_c' => array(false, 'string'),
  243. 'e_a' => array(false, 'string'),
  244. 'e_n' => array(false, 'string'),
  245. 'e_v' => array(false, 'float'),
  246. // some visitor attributes can be overwritten
  247. 'cip' => array(false, 'string'),
  248. 'cdt' => array(false, 'string'),
  249. 'cid' => array(false, 'string'),
  250. // Actions / pages
  251. 'cs' => array(false, 'string'),
  252. 'download' => array('', 'string'),
  253. 'link' => array('', 'string'),
  254. 'action_name' => array('', 'string'),
  255. 'search' => array('', 'string'),
  256. 'search_cat' => array(false, 'string'),
  257. 'search_count' => array(-1, 'int'),
  258. 'gt_ms' => array(-1, 'int'),
  259. );
  260. if (!isset($supportedParams[$name])) {
  261. throw new Exception("Requested parameter $name is not a known Tracking API Parameter.");
  262. }
  263. $paramDefaultValue = $supportedParams[$name][0];
  264. $paramType = $supportedParams[$name][1];
  265. $value = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params);
  266. return $value;
  267. }
  268. public function getCurrentTimestamp()
  269. {
  270. return $this->timestamp;
  271. }
  272. protected function isTimestampValid($time)
  273. {
  274. return $time <= $this->getCurrentTimestamp()
  275. && $time > $this->getCurrentTimestamp() - 10 * 365 * 86400;
  276. }
  277. public function getIdSite()
  278. {
  279. $idSite = Common::getRequestVar('idsite', 0, 'int', $this->params);
  280. /**
  281. * Triggered when obtaining the ID of the site we are tracking a visit for.
  282. *
  283. * This event can be used to change the site ID so data is tracked for a different
  284. * website.
  285. *
  286. * @param int &$idSite Initialized to the value of the **idsite** query parameter. If a
  287. * subscriber sets this variable, the value it uses must be greater
  288. * than 0.
  289. * @param array $params The entire array of request parameters in the current tracking
  290. * request.
  291. */
  292. Piwik::postEvent('Tracker.Request.getIdSite', array(&$idSite, $this->params));
  293. if ($idSite <= 0) {
  294. throw new Exception('Invalid idSite: \'' . $idSite . '\'');
  295. }
  296. return $idSite;
  297. }
  298. public function getUserAgent()
  299. {
  300. $default = @$_SERVER['HTTP_USER_AGENT'];
  301. return Common::getRequestVar('ua', is_null($default) ? false : $default, 'string', $this->params);
  302. }
  303. public function getCustomVariables($scope)
  304. {
  305. if ($scope == 'visit') {
  306. $parameter = '_cvar';
  307. } else {
  308. $parameter = 'cvar';
  309. }
  310. $customVar = Common::unsanitizeInputValues(Common::getRequestVar($parameter, '', 'json', $this->params));
  311. if (!is_array($customVar)) {
  312. return array();
  313. }
  314. $customVariables = array();
  315. $maxCustomVars = CustomVariables::getMaxCustomVariables();
  316. foreach ($customVar as $id => $keyValue) {
  317. $id = (int)$id;
  318. if ($id < 1
  319. || $id > $maxCustomVars
  320. || count($keyValue) != 2
  321. || (!is_string($keyValue[0]) && !is_numeric($keyValue[0]))
  322. ) {
  323. Common::printDebug("Invalid custom variables detected (id=$id)");
  324. continue;
  325. }
  326. if (strlen($keyValue[1]) == 0) {
  327. $keyValue[1] = "";
  328. }
  329. // We keep in the URL when Custom Variable have empty names
  330. // and values, as it means they can be deleted server side
  331. $key = self::truncateCustomVariable($keyValue[0]);
  332. $value = self::truncateCustomVariable($keyValue[1]);
  333. $customVariables['custom_var_k' . $id] = $key;
  334. $customVariables['custom_var_v' . $id] = $value;
  335. }
  336. return $customVariables;
  337. }
  338. public static function truncateCustomVariable($input)
  339. {
  340. return substr(trim($input), 0, CustomVariables::getMaxLengthCustomVariables());
  341. }
  342. protected function shouldUseThirdPartyCookie()
  343. {
  344. return (bool)Config::getInstance()->Tracker['use_third_party_id_cookie'];
  345. }
  346. /**
  347. * Update the cookie information.
  348. */
  349. public function setThirdPartyCookie($idVisitor)
  350. {
  351. if (!$this->shouldUseThirdPartyCookie()) {
  352. return;
  353. }
  354. Common::printDebug("We manage the cookie...");
  355. $cookie = $this->makeThirdPartyCookie();
  356. // idcookie has been generated in handleNewVisit or we simply propagate the old value
  357. $cookie->set(0, bin2hex($idVisitor));
  358. $cookie->save();
  359. }
  360. protected function makeThirdPartyCookie()
  361. {
  362. $cookie = new Cookie(
  363. $this->getCookieName(),
  364. $this->getCookieExpire(),
  365. $this->getCookiePath());
  366. Common::printDebug($cookie);
  367. return $cookie;
  368. }
  369. protected function getCookieName()
  370. {
  371. return Config::getInstance()->Tracker['cookie_name'];
  372. }
  373. protected function getCookieExpire()
  374. {
  375. return $this->getCurrentTimestamp() + Config::getInstance()->Tracker['cookie_expire'];
  376. }
  377. protected function getCookiePath()
  378. {
  379. return Config::getInstance()->Tracker['cookie_path'];
  380. }
  381. /**
  382. * Is the request for a known VisitorId, based on 1st party, 3rd party (optional) cookies or Tracking API forced Visitor ID
  383. * @throws Exception
  384. */
  385. public function getVisitorId()
  386. {
  387. $found = false;
  388. // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request?
  389. $idVisitor = $this->getForcedVisitorId();
  390. if (!empty($idVisitor)) {
  391. if (strlen($idVisitor) != Tracker::LENGTH_HEX_ID_STRING) {
  392. throw new Exception("Visitor ID (cid) $idVisitor must be " . Tracker::LENGTH_HEX_ID_STRING . " characters long");
  393. }
  394. Common::printDebug("Request will be recorded for this idvisitor = " . $idVisitor);
  395. $found = true;
  396. }
  397. // - If set to use 3rd party cookies for Visit ID, read the cookie
  398. if (!$found) {
  399. // - By default, reads the first party cookie ID
  400. $useThirdPartyCookie = $this->shouldUseThirdPartyCookie();
  401. if ($useThirdPartyCookie) {
  402. $cookie = $this->makeThirdPartyCookie();
  403. $idVisitor = $cookie->get(0);
  404. if ($idVisitor !== false
  405. && strlen($idVisitor) == Tracker::LENGTH_HEX_ID_STRING
  406. ) {
  407. $found = true;
  408. }
  409. }
  410. }
  411. // If a third party cookie was not found, we default to the first party cookie
  412. if (!$found) {
  413. $idVisitor = Common::getRequestVar('_id', '', 'string', $this->params);
  414. $found = strlen($idVisitor) >= Tracker::LENGTH_HEX_ID_STRING;
  415. }
  416. if ($found) {
  417. $truncated = substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING);
  418. $binVisitorId = @Common::hex2bin($truncated);
  419. if (!empty($binVisitorId)) {
  420. return $binVisitorId;
  421. }
  422. }
  423. return false;
  424. }
  425. public function getIp()
  426. {
  427. if (!empty($this->enforcedIp)) {
  428. $ipString = $this->enforcedIp;
  429. } else {
  430. $ipString = IP::getIpFromHeader();
  431. }
  432. $ip = IP::P2N($ipString);
  433. return $ip;
  434. }
  435. public function setForceIp($ip)
  436. {
  437. if (!empty($ip)) {
  438. $this->enforcedIp = $ip;
  439. }
  440. }
  441. public function setForceDateTime($dateTime)
  442. {
  443. if (!is_numeric($dateTime)) {
  444. $dateTime = strtotime($dateTime);
  445. }
  446. if (!empty($dateTime)) {
  447. $this->timestamp = $dateTime;
  448. }
  449. }
  450. public function setForcedVisitorId($visitorId)
  451. {
  452. if (!empty($visitorId)) {
  453. $this->forcedVisitorId = $visitorId;
  454. }
  455. }
  456. public function getForcedVisitorId()
  457. {
  458. return $this->forcedVisitorId;
  459. }
  460. public function overrideLocation(&$visitorInfo)
  461. {
  462. if (!$this->isAuthenticated()) {
  463. return;
  464. }
  465. // check for location override query parameters (ie, lat, long, country, region, city)
  466. static $locationOverrideParams = array(
  467. 'country' => array('string', 'location_country'),
  468. 'region' => array('string', 'location_region'),
  469. 'city' => array('string', 'location_city'),
  470. 'lat' => array('float', 'location_latitude'),
  471. 'long' => array('float', 'location_longitude'),
  472. );
  473. foreach ($locationOverrideParams as $queryParamName => $info) {
  474. list($type, $visitorInfoKey) = $info;
  475. $value = Common::getRequestVar($queryParamName, false, $type, $this->params);
  476. if (!empty($value)) {
  477. $visitorInfo[$visitorInfoKey] = $value;
  478. }
  479. }
  480. return;
  481. }
  482. public function getPlugins()
  483. {
  484. static $pluginsInOrder = array('fla', 'java', 'dir', 'qt', 'realp', 'pdf', 'wma', 'gears', 'ag', 'cookie');
  485. $plugins = array();
  486. foreach ($pluginsInOrder as $param) {
  487. $plugins[] = Common::getRequestVar($param, 0, 'int', $this->params);
  488. }
  489. return $plugins;
  490. }
  491. public function getParamsCount()
  492. {
  493. return count($this->params);
  494. }
  495. const GENERATION_TIME_MS_MAXIMUM = 3600000; // 1 hour
  496. public function getPageGenerationTime()
  497. {
  498. $generationTime = $this->getParam('gt_ms');
  499. if ($generationTime > 0
  500. && $generationTime < self::GENERATION_TIME_MS_MAXIMUM
  501. ) {
  502. return (int)$generationTime;
  503. }
  504. return false;
  505. }
  506. }