PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.13.5
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.13.5
5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Tracker / VisitExcluded.php
matomo / app / core / Tracker Last commit date
Db 3 years ago Handler 5 years ago TableLogAction 5 years ago Visit 5 years ago Action.php 4 years ago ActionPageview.php 5 years ago Cache.php 4 years ago Db.php 4 years ago Failures.php 4 years ago FingerprintSalt.php 4 years ago GoalManager.php 4 years ago Handler.php 5 years ago IgnoreCookie.php 4 years ago LogTable.php 5 years ago Model.php 5 years ago PageUrl.php 3 years ago Request.php 3 years ago RequestProcessor.php 5 years ago RequestSet.php 4 years ago Response.php 4 years ago ScheduledTasksRunner.php 5 years ago Settings.php 3 years ago TableLogAction.php 5 years ago TrackerCodeGenerator.php 3 years ago TrackerConfig.php 3 years ago Visit.php 4 years ago VisitExcluded.php 3 years ago VisitInterface.php 5 years ago Visitor.php 5 years ago VisitorNotFoundInDb.php 5 years ago VisitorRecognizer.php 4 years ago
VisitExcluded.php
393 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 Piwik\Cache as PiwikCache;
12 use Piwik\Common;
13 use Piwik\Container\StaticContainer;
14 use Piwik\DeviceDetector\DeviceDetectorFactory;
15 use Piwik\Exception\UnexpectedWebsiteFoundException;
16 use Matomo\Network\IP;
17 use Piwik\Piwik;
18 use Piwik\Plugins\SitesManager\SiteUrls;
19 use Piwik\Tracker\Visit\ReferrerSpamFilter;
20 use Piwik\Config;
21
22 /**
23 * This class contains the logic to exclude some visitors from being tracked as per user settings
24 */
25 class VisitExcluded
26 {
27 /**
28 * @var ReferrerSpamFilter
29 */
30 private $spamFilter;
31
32 private $siteCache = array();
33
34 public $request;
35 public $idSite;
36 public $userAgent;
37 public $ip;
38
39 /**
40 * @param Request $request
41 */
42 public function __construct(Request $request)
43 {
44 $this->spamFilter = new ReferrerSpamFilter();
45 $this->request = $request;
46
47 try {
48 $this->idSite = $request->getIdSite();
49 } catch (UnexpectedWebsiteFoundException $e){
50 // most checks will still work on a global scope and we still want to be able to test if this is a valid
51 // visit or not
52 $this->idSite = 0;
53 }
54 $userAgent = $request->getUserAgent();
55 $this->userAgent = Common::unsanitizeInputValue($userAgent);
56 $this->ip = $request->getIp();
57 }
58
59 /**
60 * Test if the current visitor is excluded from the statistics.
61 *
62 * Plugins can for example exclude visitors based on the
63 * - IP
64 * - If a given cookie is found
65 *
66 * @return bool True if the visit must not be saved, false otherwise
67 */
68 public function isExcluded()
69 {
70 $excluded = false;
71
72 if ($this->isNonHumanBot()) {
73 Common::printDebug('Search bot detected, visit excluded');
74 $excluded = true;
75 }
76
77 /*
78 * Requests built with piwik.js will contain a rec=1 parameter. This is used as
79 * an indication that the request is made by a JS enabled device. By default, Piwik
80 * doesn't track non-JS visitors.
81 */
82 if (!$excluded) {
83 $toRecord = $this->request->getParam($parameterForceRecord = 'rec');
84 if (!$toRecord) {
85 Common::printDebug(@$_SERVER['REQUEST_METHOD'] . ' parameter ' . $parameterForceRecord . ' not found in URL, request excluded');
86 $excluded = true;
87 Common::printDebug("'$parameterForceRecord' parameter not found.");
88 }
89 }
90
91 /**
92 * Triggered on every tracking request.
93 *
94 * This event can be used to tell the Tracker not to record this particular action or visit.
95 *
96 * @param bool &$excluded Whether the request should be excluded or not. Initialized
97 * to `false`. Event subscribers should set it to `true` in
98 * order to exclude the request.
99 * @param Request $request The request object which contains all of the request's information
100 *
101 */
102 Piwik::postEvent('Tracker.isExcludedVisit', array(&$excluded, $this->request));
103
104 /*
105 * Following exclude operations happen after the hook.
106 * These are of higher priority and should not be overwritten by plugins.
107 */
108
109 // Checking if in config some requests are excluded
110 if (!$excluded) {
111 $excluded = $this->request->isRequestExcluded();
112 if ($excluded) {
113 Common::printDebug("Request is excluded.");
114 }
115 }
116
117 // Checking if the Piwik ignore cookie is set
118 if (!$excluded) {
119 $excluded = $this->isIgnoreCookieFound();
120 if ($excluded) {
121 Common::printDebug("Ignore cookie found.");
122 }
123 }
124
125 // Checking for excluded IPs
126 if (!$excluded) {
127 $excluded = $this->isVisitorIpExcluded();
128 if ($excluded) {
129 Common::printDebug("IP excluded.");
130 }
131 }
132
133 // Check if user agent should be excluded
134 if (!$excluded) {
135 $excluded = $this->isUserAgentExcluded();
136 if ($excluded) {
137 Common::printDebug("User agent excluded.");
138 }
139 }
140
141 // Check if Referrer URL is a known spam
142 $generalConfig = Config::getInstance()->Tracker;
143 if ($generalConfig['enable_spam_filter']) {
144 if (!$excluded) {
145 $excluded = $this->isReferrerSpamExcluded();
146 if ($excluded) {
147 Common::printDebug("Referrer URL is listed as spam.");
148 }
149 }
150 } else {
151 Common::printDebug("Spam list is disabled.");
152 }
153
154 // Check if request URL is excluded
155 if (!$excluded) {
156 $excluded = $this->isUrlExcluded();
157 if ($excluded) {
158 Common::printDebug("Unknown URL is not allowed to track.");
159 }
160 }
161
162 if (!$excluded) {
163 if ($this->isPrefetchDetected()) {
164 $excluded = true;
165 Common::printDebug("Prefetch request detected, not a real visit so we Ignore this visit/pageview");
166 }
167 }
168
169 if ($excluded) {
170 Common::printDebug("Visitor excluded.");
171 return true;
172 }
173
174 return false;
175 }
176
177 protected function isPrefetchDetected()
178 {
179 return (isset($_SERVER["HTTP_X_PURPOSE"])
180 && in_array($_SERVER["HTTP_X_PURPOSE"], array("preview", "instant")))
181 || (isset($_SERVER['HTTP_X_MOZ'])
182 && $_SERVER['HTTP_X_MOZ'] == "prefetch");
183 }
184
185 /**
186 * Live/Bing/MSN bot and Googlebot are evolving to detect cloaked websites.
187 * As a result, these sophisticated bots exhibit characteristics of
188 * browsers (cookies enabled, executing JavaScript, etc).
189 *
190 * @see \DeviceDetector\Parser\Bot
191 *
192 * @return boolean
193 */
194 protected function isNonHumanBot()
195 {
196 $allowBots = $this->request->getParam('bots');
197
198 $deviceDetector = StaticContainer::get(DeviceDetectorFactory::class)->makeInstance($this->userAgent, $this->request->getClientHints());
199
200 return !$allowBots
201 && ($deviceDetector->isBot() || $this->isIpInRange());
202 }
203
204 private function isIpInRange()
205 {
206 $cache = PiwikCache::getTransientCache();
207
208 $ip = IP::fromBinaryIP($this->ip);
209 $key = 'VisitExcludedIsIpInRange' . $ip->toString();
210
211 if ($cache->contains($key)) {
212 $isInRanges = $cache->fetch($key);
213 } else {
214 if ($this->isChromeDataSaverUsed($ip)) {
215 $isInRanges = false;
216 } else {
217 $isInRanges = $ip->isInRanges($this->getBotIpRanges());
218 }
219
220 $cache->save($key, $isInRanges);
221 }
222
223 return $isInRanges;
224 }
225
226 public function isChromeDataSaverUsed(IP $ip)
227 {
228 // see https://github.com/piwik/piwik/issues/7733
229 return !empty($_SERVER['HTTP_VIA'])
230 && false !== strpos(strtolower($_SERVER['HTTP_VIA']), 'chrome-compression-proxy')
231 && $ip->isInRanges($this->getGoogleBotIpRanges());
232 }
233
234 protected function getBotIpRanges()
235 {
236 return array_merge($this->getGoogleBotIpRanges(), array(
237 // Live/Bing/MSN
238 '64.4.0.0/18',
239 '65.52.0.0/14',
240 '157.54.0.0/15',
241 '157.56.0.0/14',
242 '157.60.0.0/16',
243 '207.46.0.0/16',
244 '207.68.128.0/18',
245 '207.68.192.0/20',
246 '131.253.26.0/20',
247 '131.253.24.0/20',
248
249 // Yahoo
250 '72.30.198.0/20',
251 '72.30.196.0/20',
252 '98.137.207.0/20',
253 // Chinese bot hammering websites
254 '1.202.218.8'
255 ));
256 }
257
258 private function getGoogleBotIpRanges()
259 {
260 return array(
261 '216.239.32.0/19',
262 '64.233.160.0/19',
263 '66.249.80.0/20',
264 '72.14.192.0/18',
265 '209.85.128.0/17',
266 '66.102.0.0/20',
267 '74.125.0.0/16',
268 '64.18.0.0/20',
269 '207.126.144.0/20',
270 '173.194.0.0/16'
271 );
272 }
273
274 /**
275 * Looks for the ignore cookie that users can set in the Piwik admin screen.
276 * @return bool
277 */
278 protected function isIgnoreCookieFound()
279 {
280 if (IgnoreCookie::isIgnoreCookieFound()) {
281 Common::printDebug('Matomo ignore cookie was found, visit not tracked.');
282 return true;
283 }
284
285 return false;
286 }
287
288 /**
289 * Checks if the visitor ip is in the excluded list
290 *
291 * @return bool
292 */
293 protected function isVisitorIpExcluded()
294 {
295 $excludedIps = $this->getAttributes('excluded_ips', 'global_excluded_ips');
296
297 if (!empty($excludedIps)) {
298 $ip = IP::fromBinaryIP($this->ip);
299 if ($ip->isInRanges($excludedIps)) {
300 Common::printDebug('Visitor IP ' . $ip->toString() . ' is excluded from being tracked');
301 return true;
302 }
303 }
304
305 return false;
306 }
307
308 private function getAttributes($siteAttribute, $globalAttribute)
309 {
310 if (!isset($this->siteCache[$this->idSite])) {
311 $this->siteCache[$this->idSite] = array();
312 }
313 try {
314 if (empty($this->siteCache[$this->idSite])) {
315 $this->siteCache[$this->idSite] = Cache::getCacheWebsiteAttributes($this->idSite);
316 }
317 if (isset($this->siteCache[$this->idSite][$siteAttribute])) {
318 return $this->siteCache[$this->idSite][$siteAttribute];
319 }
320 } catch (UnexpectedWebsiteFoundException $e) {
321 $cached = Cache::getCacheGeneral();
322 if ($globalAttribute && isset($cached[$globalAttribute])) {
323 return $cached[$globalAttribute];
324 }
325 }
326 }
327
328 /**
329 * Checks if request URL is excluded
330 * @return bool
331 */
332 protected function isUrlExcluded()
333 {
334 $excludedUrls = $this->getAttributes('exclude_unknown_urls', null);
335 $siteUrls = $this->getAttributes('urls', null);
336
337 if (!empty($excludedUrls) && !empty($siteUrls)) {
338 $url = $this->request->getParam('url');
339 $parsedUrl = parse_url($url);
340
341 $trackingUrl = new SiteUrls();
342 $urls = $trackingUrl->groupUrlsByHost(array($this->idSite => $siteUrls));
343
344 $idSites = $trackingUrl->getIdSitesMatchingUrl($parsedUrl, $urls);
345 $isUrlExcluded = !isset($idSites) || !in_array($this->idSite, $idSites);
346
347 return $isUrlExcluded;
348 }
349
350 return false;
351 }
352
353 /**
354 * Returns true if the specified user agent should be excluded for the current site or not.
355 *
356 * Visits whose user agent string contains one of the excluded_user_agents strings for the
357 * site being tracked (or one of the global strings) will be excluded. Regular expressions
358 * are also supported.
359 *
360 * @internal param string $this ->userAgent The user agent string.
361 * @return bool
362 */
363 protected function isUserAgentExcluded(): bool
364 {
365 $excludedAgents = $this->getAttributes('excluded_user_agents', 'global_excluded_user_agents');
366
367 if (!empty($excludedAgents)) {
368 foreach ($excludedAgents as $excludedUserAgent) {
369 // if the excluded user agent string part is in this visit's user agent, this visit should be excluded
370 if (stripos($this->userAgent, $excludedUserAgent) !== false) {
371 return true;
372 }
373 // if the string is a valid regex, and the user agent matches, this visit should be excluded
374 if (@preg_match($excludedUserAgent, null) !== false) {
375 return preg_match($excludedUserAgent, $this->userAgent) ? true : false;
376 }
377 }
378 }
379
380 return false;
381 }
382
383 /**
384 * Returns true if the Referrer is a known spammer.
385 *
386 * @return bool
387 */
388 protected function isReferrerSpamExcluded()
389 {
390 return $this->spamFilter->isSpam($this->request);
391 }
392 }
393