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