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