PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 1.0.5
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v1.0.5
5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Tracker / Request.php
matomo / app / core / Tracker Last commit date
Db 6 years ago Handler 6 years ago TableLogAction 6 years ago Visit 6 years ago Action.php 6 years ago ActionPageview.php 6 years ago Cache.php 6 years ago Db.php 6 years ago Failures.php 6 years ago GoalManager.php 6 years ago Handler.php 6 years ago IgnoreCookie.php 6 years ago LogTable.php 6 years ago Model.php 6 years ago PageUrl.php 6 years ago Request.php 6 years ago RequestProcessor.php 6 years ago RequestSet.php 6 years ago Response.php 6 years ago ScheduledTasksRunner.php 6 years ago Settings.php 6 years ago TableLogAction.php 6 years ago TrackerCodeGenerator.php 6 years ago TrackerConfig.php 6 years ago Visit.php 6 years ago VisitExcluded.php 6 years ago VisitInterface.php 6 years ago Visitor.php 6 years ago VisitorNotFoundInDb.php 6 years ago VisitorRecognizer.php 6 years ago
Request.php
946 lines
1 <?php
2 /**
3 * Piwik - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik\Tracker;
10
11 use Exception;
12 use Piwik\Common;
13 use Piwik\Config;
14 use Piwik\Container\StaticContainer;
15 use Piwik\Cookie;
16 use Piwik\Exception\InvalidRequestParameterException;
17 use Piwik\Exception\UnexpectedWebsiteFoundException;
18 use Piwik\IP;
19 use Piwik\Network\IPUtils;
20 use Piwik\Piwik;
21 use Piwik\Plugins\CustomVariables\CustomVariables;
22 use Piwik\Plugins\UsersManager\UsersManager;
23 use Piwik\ProxyHttp;
24 use Piwik\Tracker;
25 use Piwik\Cache as PiwikCache;
26
27 /**
28 * The Request object holding the http parameters for this tracking request. Use getParam() to fetch a named parameter.
29 *
30 */
31 class Request
32 {
33 private $cdtCache;
34 private $idSiteCache;
35 private $paramsCache = array();
36
37 /**
38 * @var array
39 */
40 protected $params;
41 protected $rawParams;
42
43 protected $isAuthenticated = null;
44 private $isEmptyRequest = false;
45
46 protected $tokenAuth;
47
48 /**
49 * Stores plugin specific tracking request metadata. RequestProcessors can store
50 * whatever they want in this array, and other RequestProcessors can modify these
51 * values to change tracker behavior.
52 *
53 * @var string[][]
54 */
55 private $requestMetadata = array();
56
57 const UNKNOWN_RESOLUTION = 'unknown';
58
59 private $customTimestampDoesNotRequireTokenauthWhenNewerThan;
60
61 /**
62 * @param $params
63 * @param bool|string $tokenAuth
64 */
65 public function __construct($params, $tokenAuth = false)
66 {
67 if (!is_array($params)) {
68 $params = array();
69 }
70 $this->params = $params;
71 $this->rawParams = $params;
72 $this->tokenAuth = $tokenAuth;
73 $this->timestamp = time();
74 $this->isEmptyRequest = empty($params);
75 $this->customTimestampDoesNotRequireTokenauthWhenNewerThan = (int) TrackerConfig::getConfigValue('tracking_requests_require_authentication_when_custom_timestamp_newer_than');
76
77 // When the 'url' and referrer url parameter are not given, we might be in the 'Simple Image Tracker' mode.
78 // The URL can default to the Referrer, which will be in this case
79 // the URL of the page containing the Simple Image beacon
80 if (empty($this->params['urlref'])
81 && empty($this->params['url'])
82 && array_key_exists('HTTP_REFERER', $_SERVER)
83 ) {
84 $url = $_SERVER['HTTP_REFERER'];
85 if (!empty($url)) {
86 $this->params['url'] = $url;
87 }
88 }
89
90 // check for 4byte utf8 characters in all tracking params and replace them with �
91 // @TODO Remove as soon as our database tables use utf8mb4 instead of utf8
92 $this->params = $this->replaceUnsupportedUtf8Chars($this->params);
93 }
94
95 protected function replaceUnsupportedUtf8Chars($value, $key=false)
96 {
97 if (is_string($value) && preg_match('/[\x{10000}-\x{10FFFF}]/u', $value)) {
98 Common::printDebug("Unsupport character detected in $key. Replacing with \xEF\xBF\xBD");
99 return preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
100 }
101
102 if (is_array($value)) {
103 array_walk_recursive ($value, function(&$value, $key){
104 $value = $this->replaceUnsupportedUtf8Chars($value, $key);
105 });
106 }
107
108 return $value;
109 }
110
111 /**
112 * Get the params that were originally passed to the instance. These params do not contain any params that were added
113 * within this object.
114 * @return array
115 */
116 public function getRawParams()
117 {
118 return $this->rawParams;
119 }
120
121 public function getTokenAuth()
122 {
123 return $this->tokenAuth;
124 }
125
126 /**
127 * @return bool
128 */
129 public function isAuthenticated()
130 {
131 if (is_null($this->isAuthenticated)) {
132 $this->authenticateTrackingApi($this->tokenAuth);
133 }
134
135 return $this->isAuthenticated;
136 }
137
138 /**
139 * This method allows to set custom IP + server time + visitor ID, when using Tracking API.
140 * These two attributes can be only set by the Super User (passing token_auth).
141 */
142 protected function authenticateTrackingApi($tokenAuth)
143 {
144 $shouldAuthenticate = TrackerConfig::getConfigValue('tracking_requests_require_authentication');
145
146 if ($shouldAuthenticate) {
147 try {
148 $idSite = $this->getIdSite();
149 } catch (Exception $e) {
150 Common::printDebug("failed to authenticate: invalid idSite");
151 $this->isAuthenticated = false;
152 return;
153 }
154
155 if (empty($tokenAuth)) {
156 $tokenAuth = Common::getRequestVar('token_auth', false, 'string', $this->params);
157 }
158
159 $cache = PiwikCache::getTransientCache();
160 $cacheKey = 'tracker_request_authentication_' . $idSite . '_' . $tokenAuth;
161
162 if ($cache->contains($cacheKey)) {
163 Common::printDebug("token_auth is authenticated in cache!");
164 $this->isAuthenticated = $cache->fetch($cacheKey);
165 return;
166 }
167
168 try {
169 $this->isAuthenticated = self::authenticateSuperUserOrAdminOrWrite($tokenAuth, $idSite);
170 $cache->save($cacheKey, $this->isAuthenticated);
171 } catch (Exception $e) {
172 Common::printDebug("could not authenticate, caught exception: " . $e->getMessage());
173
174 $this->isAuthenticated = false;
175 }
176
177 if ($this->isAuthenticated) {
178 Common::printDebug("token_auth is authenticated!");
179 } else {
180 StaticContainer::get('Piwik\Tracker\Failures')->logFailure(Failures::FAILURE_ID_NOT_AUTHENTICATED, $this);
181 }
182 } else {
183 $this->isAuthenticated = true;
184 Common::printDebug("token_auth authentication not required");
185 }
186 }
187
188 public static function authenticateSuperUserOrAdminOrWrite($tokenAuth, $idSite)
189 {
190 if (empty($tokenAuth)) {
191 return false;
192 }
193
194 Piwik::postEvent('Request.initAuthenticationObject');
195
196 /** @var \Piwik\Auth $auth */
197 $auth = StaticContainer::get('Piwik\Auth');
198 $auth->setTokenAuth($tokenAuth);
199 $auth->setLogin(null);
200 $auth->setPassword(null);
201 $auth->setPasswordHash(null);
202 $access = $auth->authenticate();
203
204 if (!empty($access) && $access->hasSuperUserAccess()) {
205 return true;
206 }
207
208 // Now checking the list of admin token_auth cached in the Tracker config file
209 if (!empty($idSite) && $idSite > 0) {
210 $website = Cache::getCacheWebsiteAttributes($idSite);
211 $hashedToken = UsersManager::hashTrackingToken((string) $tokenAuth, $idSite);
212
213 if (array_key_exists('tracking_token_auth', $website)
214 && in_array($hashedToken, $website['tracking_token_auth'], true)) {
215 return true;
216 }
217 }
218
219 Common::printDebug("WARNING! token_auth = $tokenAuth is not valid, Super User / Admin / Write was NOT authenticated");
220
221 /**
222 * @ignore
223 * @internal
224 */
225 Piwik::postEvent('Tracker.Request.authenticate.failed');
226
227 return false;
228 }
229
230 /**
231 * @return float|int
232 */
233 public function getDaysSinceFirstVisit()
234 {
235 $cookieFirstVisitTimestamp = $this->getParam('_idts');
236
237 if (!$this->isTimestampValid($cookieFirstVisitTimestamp)) {
238 $cookieFirstVisitTimestamp = $this->getCurrentTimestamp();
239 }
240
241 $daysSinceFirstVisit = floor(($this->getCurrentTimestamp() - $cookieFirstVisitTimestamp) / 86400);
242
243 if ($daysSinceFirstVisit < 0) {
244 $daysSinceFirstVisit = 0;
245 }
246
247 return $daysSinceFirstVisit;
248 }
249
250 /**
251 * @return bool|float|int
252 */
253 public function getDaysSinceLastOrder()
254 {
255 $daysSinceLastOrder = false;
256 $lastOrderTimestamp = $this->getParam('_ects');
257
258 if ($this->isTimestampValid($lastOrderTimestamp)) {
259 $daysSinceLastOrder = round(($this->getCurrentTimestamp() - $lastOrderTimestamp) / 86400, $precision = 0);
260 if ($daysSinceLastOrder < 0) {
261 $daysSinceLastOrder = 0;
262 }
263 }
264
265 return $daysSinceLastOrder;
266 }
267
268 /**
269 * @return float|int
270 */
271 public function getDaysSinceLastVisit()
272 {
273 $daysSinceLastVisit = 0;
274 $lastVisitTimestamp = $this->getParam('_viewts');
275
276 if ($this->isTimestampValid($lastVisitTimestamp)) {
277 $daysSinceLastVisit = round(($this->getCurrentTimestamp() - $lastVisitTimestamp) / 86400, $precision = 0);
278 if ($daysSinceLastVisit < 0) {
279 $daysSinceLastVisit = 0;
280 }
281 }
282
283 return $daysSinceLastVisit;
284 }
285
286 /**
287 * @return int|mixed
288 */
289 public function getVisitCount()
290 {
291 $visitCount = $this->getParam('_idvc');
292 if ($visitCount < 1) {
293 $visitCount = 1;
294 }
295 return $visitCount;
296 }
297
298 /**
299 * Returns the language the visitor is viewing.
300 *
301 * @return string browser language code, eg. "en-gb,en;q=0.5"
302 */
303 public function getBrowserLanguage()
304 {
305 return Common::getRequestVar('lang', Common::getBrowserLanguage(), 'string', $this->params);
306 }
307
308 /**
309 * @return string
310 */
311 public function getLocalTime()
312 {
313 $localTimes = array(
314 'h' => (string)Common::getRequestVar('h', $this->getCurrentDate("H"), 'int', $this->params),
315 'i' => (string)Common::getRequestVar('m', $this->getCurrentDate("i"), 'int', $this->params),
316 's' => (string)Common::getRequestVar('s', $this->getCurrentDate("s"), 'int', $this->params)
317 );
318 if($localTimes['h'] < 0 || $localTimes['h'] > 23) {
319 $localTimes['h'] = 0;
320 }
321 if($localTimes['i'] < 0 || $localTimes['i'] > 59) {
322 $localTimes['i'] = 0;
323 }
324 if($localTimes['s'] < 0 || $localTimes['s'] > 59) {
325 $localTimes['s'] = 0;
326 }
327 foreach ($localTimes as $k => $time) {
328 if (strlen($time) == 1) {
329 $localTimes[$k] = '0' . $time;
330 }
331 }
332 $localTime = $localTimes['h'] . ':' . $localTimes['i'] . ':' . $localTimes['s'];
333 return $localTime;
334 }
335
336 /**
337 * Returns the current date in the "Y-m-d" PHP format
338 *
339 * @param string $format
340 * @return string
341 */
342 protected function getCurrentDate($format = "Y-m-d")
343 {
344 return date($format, $this->getCurrentTimestamp());
345 }
346
347 public function getGoalRevenue($defaultGoalRevenue)
348 {
349 return Common::getRequestVar('revenue', $defaultGoalRevenue, 'float', $this->params);
350 }
351
352 public function getParam($name)
353 {
354 static $supportedParams = array(
355 // Name => array( defaultValue, type )
356 '_refts' => array(0, 'int'),
357 '_ref' => array('', 'string'),
358 '_rcn' => array('', 'string'),
359 '_rck' => array('', 'string'),
360 '_idts' => array(0, 'int'),
361 '_viewts' => array(0, 'int'),
362 '_ects' => array(0, 'int'),
363 '_idvc' => array(1, 'int'),
364 'url' => array('', 'string'),
365 'urlref' => array('', 'string'),
366 'res' => array(self::UNKNOWN_RESOLUTION, 'string'),
367 'idgoal' => array(-1, 'int'),
368 'ping' => array(0, 'int'),
369
370 // other
371 'bots' => array(0, 'int'),
372 'dp' => array(0, 'int'),
373 'rec' => array(0, 'int'),
374 'new_visit' => array(0, 'int'),
375
376 // Ecommerce
377 'ec_id' => array('', 'string'),
378 'ec_st' => array(false, 'float'),
379 'ec_tx' => array(false, 'float'),
380 'ec_sh' => array(false, 'float'),
381 'ec_dt' => array(false, 'float'),
382 'ec_items' => array('', 'json'),
383
384 // Events
385 'e_c' => array('', 'string'),
386 'e_a' => array('', 'string'),
387 'e_n' => array('', 'string'),
388 'e_v' => array(false, 'float'),
389
390 // some visitor attributes can be overwritten
391 'cip' => array('', 'string'),
392 'cdt' => array('', 'string'),
393 'cid' => array('', 'string'),
394 'uid' => array('', 'string'),
395
396 // Actions / pages
397 'cs' => array('', 'string'),
398 'download' => array('', 'string'),
399 'link' => array('', 'string'),
400 'action_name' => array('', 'string'),
401 'search' => array('', 'string'),
402 'search_cat' => array('', 'string'),
403 'pv_id' => array('', 'string'),
404 'search_count' => array(-1, 'int'),
405 'gt_ms' => array(-1, 'int'),
406
407 // Content
408 'c_p' => array('', 'string'),
409 'c_n' => array('', 'string'),
410 'c_t' => array('', 'string'),
411 'c_i' => array('', 'string'),
412 );
413
414 if (isset($this->paramsCache[$name])) {
415 return $this->paramsCache[$name];
416 }
417
418 if (!isset($supportedParams[$name])) {
419 throw new Exception("Requested parameter $name is not a known Tracking API Parameter.");
420 }
421
422 $paramDefaultValue = $supportedParams[$name][0];
423 $paramType = $supportedParams[$name][1];
424
425 if ($this->hasParam($name)) {
426 $this->paramsCache[$name] = $this->replaceUnsupportedUtf8Chars(Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params), $name);
427 } else {
428 $this->paramsCache[$name] = $paramDefaultValue;
429 }
430
431 return $this->paramsCache[$name];
432 }
433
434 public function setParam($name, $value)
435 {
436 $this->params[$name] = $value;
437 unset($this->paramsCache[$name]);
438
439 if ($name === 'cdt') {
440 $this->cdtCache = null;
441 }
442 }
443
444 private function hasParam($name)
445 {
446 return isset($this->params[$name]);
447 }
448
449 public function getParams()
450 {
451 return $this->params;
452 }
453
454 public function getCurrentTimestamp()
455 {
456 if (!isset($this->cdtCache)) {
457 $this->cdtCache = $this->getCustomTimestamp();
458 }
459
460 if (!empty($this->cdtCache)) {
461 return $this->cdtCache;
462 }
463
464 return $this->timestamp;
465 }
466
467 public function setCurrentTimestamp($timestamp)
468 {
469 $this->timestamp = $timestamp;
470 }
471
472 protected function getCustomTimestamp()
473 {
474 if (!$this->hasParam('cdt')) {
475 return false;
476 }
477
478 $cdt = $this->getParam('cdt');
479
480 if (empty($cdt)) {
481 return false;
482 }
483
484 if (!is_numeric($cdt)) {
485 $cdt = strtotime($cdt);
486 }
487
488 if (!$this->isTimestampValid($cdt, $this->timestamp)) {
489 Common::printDebug(sprintf("Datetime %s is not valid", date("Y-m-d H:i:m", $cdt)));
490 return false;
491 }
492
493 // If timestamp in the past, token_auth is required
494 $timeFromNow = $this->timestamp - $cdt;
495 $isTimestampRecent = $timeFromNow < $this->customTimestampDoesNotRequireTokenauthWhenNewerThan;
496
497 if (!$isTimestampRecent) {
498 if (!$this->isAuthenticated()) {
499 $message = sprintf("Custom timestamp is %s seconds old, requires &token_auth...", $timeFromNow);
500 Common::printDebug($message);
501 Common::printDebug("WARN: Tracker API 'cdt' was used with invalid token_auth");
502 throw new InvalidRequestParameterException($message);
503 }
504 }
505
506 $cache = Tracker\Cache::getCacheGeneral();
507 if (!empty($cache['delete_logs_enable']) && !empty($cache['delete_logs_older_than'])) {
508 $scheduleInterval = $cache['delete_logs_schedule_lowest_interval'];
509 $maxLogAge = $cache['delete_logs_older_than'];
510 $logEntryCutoff = time() - (($maxLogAge + $scheduleInterval) * 60*60*24);
511 if ($cdt < $logEntryCutoff) {
512 $message = "Custom timestamp is older than the configured 'deleted old raw data' value of $maxLogAge days";
513 Common::printDebug($message);
514 throw new InvalidRequestParameterException($message);
515 }
516 }
517
518 return $cdt;
519 }
520
521 /**
522 * Returns true if the timestamp is valid ie. timestamp is sometime in the last 10 years and is not in the future.
523 *
524 * @param $time int Timestamp to test
525 * @param $now int Current timestamp
526 * @return bool
527 */
528 protected function isTimestampValid($time, $now = null)
529 {
530 if (empty($now)) {
531 $now = $this->getCurrentTimestamp();
532 }
533
534 return $time <= $now
535 && $time > $now - 20 * 365 * 86400;
536 }
537
538 /**
539 * @internal
540 * @ignore
541 */
542 public function getIdSiteUnverified()
543 {
544 $idSite = Common::getRequestVar('idsite', 0, 'int', $this->params);
545
546 /**
547 * Triggered when obtaining the ID of the site we are tracking a visit for.
548 *
549 * This event can be used to change the site ID so data is tracked for a different
550 * website.
551 *
552 * @param int &$idSite Initialized to the value of the **idsite** query parameter. If a
553 * subscriber sets this variable, the value it uses must be greater
554 * than 0.
555 * @param array $params The entire array of request parameters in the current tracking
556 * request.
557 */
558 Piwik::postEvent('Tracker.Request.getIdSite', array(&$idSite, $this->params));
559 return $idSite;
560 }
561
562 public function getIdSite()
563 {
564 if (isset($this->idSiteCache)) {
565 return $this->idSiteCache;
566 }
567
568 $idSite = $this->getIdSiteUnverified();
569
570 if ($idSite <= 0) {
571 throw new UnexpectedWebsiteFoundException('Invalid idSite: \'' . $idSite . '\'');
572 }
573
574 // check site actually exists, should throw UnexpectedWebsiteFoundException directly
575 $site = Cache::getCacheWebsiteAttributes($idSite);
576
577 if (empty($site)) {
578 // fallback just in case exception wasn't thrown...
579 throw new UnexpectedWebsiteFoundException('Invalid idSite: \'' . $idSite . '\'');
580 }
581
582 $this->idSiteCache = $idSite;
583
584 return $idSite;
585 }
586
587 public function getUserAgent()
588 {
589 $default = false;
590
591 if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
592 $default = $_SERVER['HTTP_USER_AGENT'];
593 }
594
595 return Common::getRequestVar('ua', $default, 'string', $this->params);
596 }
597
598 public function getCustomVariablesInVisitScope()
599 {
600 return $this->getCustomVariables('visit');
601 }
602
603 public function getCustomVariablesInPageScope()
604 {
605 return $this->getCustomVariables('page');
606 }
607
608 /**
609 * @deprecated since Piwik 2.10.0. Use Request::getCustomVariablesInPageScope() or Request::getCustomVariablesInVisitScope() instead.
610 * When we "remove" this method we will only set visibility to "private" and pass $parameter = _cvar|cvar as an argument instead of $scope
611 */
612 public function getCustomVariables($scope)
613 {
614 if ($scope == 'visit') {
615 $parameter = '_cvar';
616 } else {
617 $parameter = 'cvar';
618 }
619
620 $cvar = Common::getRequestVar($parameter, '', 'json', $this->params);
621 $customVar = Common::unsanitizeInputValues($cvar);
622
623 if (!is_array($customVar)) {
624 return array();
625 }
626
627 $customVariables = array();
628 $maxCustomVars = CustomVariables::getNumUsableCustomVariables();
629
630 foreach ($customVar as $id => $keyValue) {
631 $id = (int)$id;
632
633 if ($id < 1
634 || $id > $maxCustomVars
635 || count($keyValue) != 2
636 || (!is_string($keyValue[0]) && !is_numeric($keyValue[0])
637 || (!is_string($keyValue[1]) && !is_numeric($keyValue[1])))
638 ) {
639 Common::printDebug("Invalid custom variables detected (id=$id)");
640 continue;
641 }
642
643 if (strlen($keyValue[1]) == 0) {
644 $keyValue[1] = "";
645 }
646 // We keep in the URL when Custom Variable have empty names
647 // and values, as it means they can be deleted server side
648
649 $customVariables['custom_var_k' . $id] = self::truncateCustomVariable($keyValue[0]);
650 $customVariables['custom_var_v' . $id] = self::truncateCustomVariable($keyValue[1]);
651 }
652
653 return $customVariables;
654 }
655
656 public static function truncateCustomVariable($input)
657 {
658 return substr(trim($input), 0, CustomVariables::getMaxLengthCustomVariables());
659 }
660
661 protected function shouldUseThirdPartyCookie()
662 {
663 return (bool)Config::getInstance()->Tracker['use_third_party_id_cookie'];
664 }
665
666 public function getThirdPartyCookieVisitorId()
667 {
668 $cookie = $this->makeThirdPartyCookieUID();
669 $idVisitor = $cookie->get(0);
670 if ($idVisitor !== false
671 && strlen($idVisitor) == Tracker::LENGTH_HEX_ID_STRING
672 ) {
673 return $idVisitor;
674 }
675 return null;
676 }
677
678 /**
679 * Update the cookie information.
680 */
681 public function setThirdPartyCookie($idVisitor)
682 {
683 if (!$this->shouldUseThirdPartyCookie()) {
684 return;
685 }
686
687 $cookie = $this->makeThirdPartyCookieUID();
688 $idVisitor = bin2hex($idVisitor);
689 $cookie->set(0, $idVisitor);
690 if (ProxyHttp::isHttps()) {
691 $cookie->setSecure(true);
692 $cookie->save('None');
693 } else {
694 $cookie->save('Lax');
695 }
696
697 Common::printDebug(sprintf("We set the visitor ID to %s in the 3rd party cookie...", $idVisitor));
698 }
699
700 protected function makeThirdPartyCookieUID()
701 {
702 $cookie = new Cookie(
703 $this->getCookieName(),
704 $this->getCookieExpire(),
705 $this->getCookiePath());
706
707 $domain = $this->getCookieDomain();
708 if (!empty($domain)) {
709 $cookie->setDomain($domain);
710 }
711
712 Common::printDebug($cookie);
713
714 return $cookie;
715 }
716
717 protected function getCookieName()
718 {
719 return TrackerConfig::getConfigValue('cookie_name');
720 }
721
722 protected function getCookieExpire()
723 {
724 return $this->getCurrentTimestamp() + TrackerConfig::getConfigValue('cookie_expire');
725 }
726
727 protected function getCookiePath()
728 {
729 return TrackerConfig::getConfigValue('cookie_path');
730 }
731
732 protected function getCookieDomain()
733 {
734 return TrackerConfig::getConfigValue('cookie_domain');
735 }
736
737 /**
738 * Returns the ID from the request in this order:
739 * return from a given User ID,
740 * or from a Tracking API forced Visitor ID,
741 * or from a Visitor ID from 3rd party (optional) cookies,
742 * or from a given Visitor Id from 1st party?
743 *
744 * @throws Exception
745 */
746 public function getVisitorId()
747 {
748 $found = false;
749
750 // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request?
751 if (!$found) {
752 $idVisitor = $this->getForcedVisitorId();
753 if (!empty($idVisitor)) {
754 if (strlen($idVisitor) != Tracker::LENGTH_HEX_ID_STRING) {
755 throw new InvalidRequestParameterException("Visitor ID (cid) $idVisitor must be " . Tracker::LENGTH_HEX_ID_STRING . " characters long");
756 }
757 Common::printDebug("Request will be recorded for this idvisitor = " . $idVisitor);
758 $found = true;
759 }
760 }
761
762 // - If set to use 3rd party cookies for Visit ID, read the cookie
763 if (!$found) {
764 $useThirdPartyCookie = $this->shouldUseThirdPartyCookie();
765 if ($useThirdPartyCookie) {
766 $idVisitor = $this->getThirdPartyCookieVisitorId();
767 if(!empty($idVisitor)) {
768 $found = true;
769 }
770 }
771 }
772
773 // If a third party cookie was not found, we default to the first party cookie
774 if (!$found) {
775 $idVisitor = Common::getRequestVar('_id', '', 'string', $this->params);
776 $found = strlen($idVisitor) >= Tracker::LENGTH_HEX_ID_STRING;
777 }
778
779 if ($found) {
780 return $this->getVisitorIdAsBinary($idVisitor);
781 }
782
783 return false;
784 }
785
786 /**
787 * When creating a third party cookie, we want to ensure that the original value set in this 3rd party cookie
788 * sticks and is not overwritten later.
789 */
790 public function getVisitorIdForThirdPartyCookie()
791 {
792 $found = false;
793
794 // For 3rd party cookies, priority is on re-using the existing 3rd party cookie value
795 if (!$found) {
796 $useThirdPartyCookie = $this->shouldUseThirdPartyCookie();
797 if ($useThirdPartyCookie) {
798 $idVisitor = $this->getThirdPartyCookieVisitorId();
799 if(!empty($idVisitor)) {
800 $found = true;
801 }
802 }
803 }
804
805 // If a third party cookie was not found, we default to the first party cookie
806 if (!$found) {
807 $idVisitor = Common::getRequestVar('_id', '', 'string', $this->params);
808 $found = strlen($idVisitor) >= Tracker::LENGTH_HEX_ID_STRING;
809 }
810
811 if ($found) {
812 return $this->getVisitorIdAsBinary($idVisitor);
813 }
814
815 return false;
816 }
817
818
819 public function getIp()
820 {
821 return IPUtils::stringToBinaryIP($this->getIpString());
822 }
823
824 public function getForcedUserId()
825 {
826 $userId = $this->getParam('uid');
827 if (strlen($userId) > 0) {
828 return $userId;
829 }
830
831 return false;
832 }
833
834 public function getForcedVisitorId()
835 {
836 return $this->getParam('cid');
837 }
838
839 public function getPlugins()
840 {
841 static $pluginsInOrder = array('fla', 'java', 'dir', 'qt', 'realp', 'pdf', 'wma', 'gears', 'ag', 'cookie');
842 $plugins = array();
843 foreach ($pluginsInOrder as $param) {
844 $plugins[] = Common::getRequestVar($param, 0, 'int', $this->params);
845 }
846 return $plugins;
847 }
848
849 public function isEmptyRequest()
850 {
851 return $this->isEmptyRequest;
852 }
853
854 const GENERATION_TIME_MS_MAXIMUM = 3600000; // 1 hour
855
856 public function getPageGenerationTime()
857 {
858 $generationTime = $this->getParam('gt_ms');
859 if ($generationTime > 0
860 && $generationTime < self::GENERATION_TIME_MS_MAXIMUM
861 ) {
862 return (int)$generationTime;
863 }
864
865 return false;
866 }
867
868 /**
869 * @param $idVisitor
870 * @return string
871 */
872 private function truncateIdAsVisitorId($idVisitor)
873 {
874 return substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING);
875 }
876
877 /**
878 * Matches implementation of PiwikTracker::getUserIdHashed
879 *
880 * @param $userId
881 * @return string
882 */
883 public function getUserIdHashed($userId)
884 {
885 return substr(sha1($userId), 0, 16);
886 }
887
888 /**
889 * @return mixed|string
890 * @throws Exception
891 */
892 public function getIpString()
893 {
894 $cip = $this->getParam('cip');
895
896 if (empty($cip)) {
897 return IP::getIpFromHeader();
898 }
899
900 if (!$this->isAuthenticated()) {
901 Common::printDebug("WARN: Tracker API 'cip' was used with invalid token_auth");
902 return IP::getIpFromHeader();
903 }
904
905 return $cip;
906 }
907
908 /**
909 * Set a request metadata value.
910 *
911 * @param string $pluginName eg, `'Actions'`, `'Goals'`, `'YourPlugin'`
912 * @param string $key
913 * @param mixed $value
914 */
915 public function setMetadata($pluginName, $key, $value)
916 {
917 $this->requestMetadata[$pluginName][$key] = $value;
918 }
919
920 /**
921 * Get a request metadata value. Returns `null` if none exists.
922 *
923 * @param string $pluginName eg, `'Actions'`, `'Goals'`, `'YourPlugin'`
924 * @param string $key
925 * @return mixed
926 */
927 public function getMetadata($pluginName, $key)
928 {
929 return isset($this->requestMetadata[$pluginName][$key]) ? $this->requestMetadata[$pluginName][$key] : null;
930 }
931
932 /**
933 * @param $idVisitor
934 * @return bool|string
935 */
936 private function getVisitorIdAsBinary($idVisitor)
937 {
938 $truncated = $this->truncateIdAsVisitorId($idVisitor);
939 $binVisitorId = @Common::hex2bin($truncated);
940 if (!empty($binVisitorId)) {
941 return $binVisitorId;
942 }
943 return false;
944 }
945 }
946