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