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