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