PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.7.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.7.0
5.13.0 5.12.1 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.php
matomo / app / core Last commit date
API 6 months ago Access 8 months ago Application 6 months ago Archive 7 months ago ArchiveProcessor 6 months ago Archiver 2 years ago AssetManager 6 months ago Auth 8 months ago Category 8 months ago Changes 8 months ago CliMulti 1 year ago Columns 8 months ago Concurrency 8 months ago Config 8 months ago Container 8 months ago CronArchive 8 months ago DataAccess 7 months ago DataFiles 2 years ago DataTable 8 months ago Db 6 months ago DeviceDetector 1 year ago Email 2 years ago Exception 6 months ago Http 6 months ago Intl 8 months ago Log 2 years ago Mail 1 year ago Measurable 8 months ago Menu 8 months ago Metrics 8 months ago Notification 8 months ago Period 8 months ago Plugin 6 months ago Policy 6 months ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 1 year ago Request 8 months ago Scheduler 8 months ago Segment 6 months ago Session 6 months ago Settings 6 months ago Tracker 6 months ago Translation 1 year ago Twig 1 year ago UpdateCheck 8 months ago Updater 6 months ago Updates 6 months ago Validators 1 year ago View 8 months ago ViewDataTable 8 months ago Visualization 1 year ago Widget 1 year ago .htaccess 2 years ago Access.php 6 months ago Archive.php 8 months ago ArchiveProcessor.php 6 months ago AssetManager.php 1 year ago Auth.php 8 months ago AuthResult.php 8 months ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 6 months ago CliMulti.php 8 months ago Common.php 8 months ago Config.php 6 months ago Console.php 1 year ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 8 months ago DI.php 1 year ago DataArray.php 7 months ago DataTable.php 6 months ago Date.php 6 months ago Db.php 1 year ago DbHelper.php 8 months ago Development.php 1 year ago ErrorHandler.php 8 months ago EventDispatcher.php 1 year ago ExceptionHandler.php 6 months ago FileIntegrity.php 1 year ago Filechecks.php 1 year ago Filesystem.php 1 year ago FrontController.php 6 months ago Http.php 6 months ago IP.php 1 year ago Log.php 8 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 8 months ago NoAccessException.php 2 years ago Nonce.php 8 months ago Notification.php 8 months ago NumberFormatter.php 7 months ago Option.php 7 months ago Period.php 7 months ago Piwik.php 8 months ago Plugin.php 1 year ago Process.php 1 year ago Profiler.php 8 months ago ProxyHeaders.php 6 months ago ProxyHttp.php 7 months ago QuickForm2.php 1 year ago RankingQuery.php 7 months ago ReportRenderer.php 1 year ago Request.php 1 year ago Segment.php 8 months ago Sequence.php 8 months ago Session.php 1 year ago SettingsPiwik.php 1 year ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 6 months ago SiteContentDetector.php 1 year ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 2 years ago Tracker.php 6 months ago Twig.php 8 months ago Unzip.php 1 year ago UpdateCheck.php 1 year ago Updater.php 1 year ago UpdaterErrorException.php 2 years ago Updates.php 1 year ago Url.php 6 months ago UrlHelper.php 1 year ago Version.php 6 months ago View.php 8 months ago bootstrap.php 1 year ago dispatch.php 2 years ago testMinimumPhpVersion.php 8 months ago
Tracker.php
328 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;
10
11 use Exception;
12 use Piwik\Container\StaticContainer;
13 use Piwik\DeviceDetector\DeviceDetectorFactory;
14 use Piwik\Plugins\BulkTracking\Tracker\Requests;
15 use Piwik\Plugins\PrivacyManager\Config as PrivacyManagerConfig;
16 use Piwik\Tracker\BotRequest;
17 use Piwik\Tracker\Db as TrackerDb;
18 use Piwik\Tracker\Db\DbException;
19 use Piwik\Tracker\Handler;
20 use Piwik\Tracker\Request;
21 use Piwik\Tracker\RequestSet;
22 use Piwik\Tracker\TrackerConfig;
23 use Piwik\Tracker\Visit;
24 use Piwik\Plugin\Manager as PluginManager;
25 use Piwik\Log\LoggerInterface;
26 /**
27 * Class used by the logging script piwik.php called by the javascript tag.
28 * Handles the visitor and their actions on the website, saves the data in the DB,
29 * saves information in the cookie, etc.
30 *
31 * We try to include as little files as possible (no dependency on 3rd party modules).
32 */
33 class Tracker
34 {
35 /**
36 * @var Db
37 */
38 private static $db = null;
39 // We use hex ID that are 16 chars in length, ie. 64 bits IDs
40 public const LENGTH_HEX_ID_STRING = 16;
41 public const LENGTH_BINARY_ID = 8;
42 public static $initTrackerMode = \false;
43 private $countOfLoggedRequests = 0;
44 protected $isInstalled = null;
45 /**
46 * @var LoggerInterface
47 */
48 private $logger;
49 public function __construct()
50 {
51 $this->logger = StaticContainer::get(LoggerInterface::class);
52 }
53 public function isDebugModeEnabled()
54 {
55 return array_key_exists('PIWIK_TRACKER_DEBUG', $GLOBALS) && $GLOBALS['PIWIK_TRACKER_DEBUG'] === \true;
56 }
57 public function shouldRecordStatistics()
58 {
59 $record = TrackerConfig::getConfigValue('record_statistics') != 0;
60 if (!$record) {
61 $this->logger->debug('Tracking is disabled in the config.ini.php via record_statistics=0');
62 }
63 return $record && $this->isInstalled();
64 }
65 public static function loadTrackerEnvironment()
66 {
67 \Piwik\SettingsServer::setIsTrackerApiRequest();
68 if (empty($GLOBALS['PIWIK_TRACKER_DEBUG'])) {
69 $GLOBALS['PIWIK_TRACKER_DEBUG'] = self::isDebugEnabled();
70 }
71 if (!empty($GLOBALS['PIWIK_TRACKER_DEBUG']) && !\Piwik\Common::isPhpCliMode()) {
72 \Piwik\Common::sendHeader('Content-Type: text/plain');
73 }
74 PluginManager::getInstance()->loadTrackerPlugins();
75 }
76 private function init()
77 {
78 $this->handleFatalErrors();
79 if ($this->isDebugModeEnabled()) {
80 \Piwik\ErrorHandler::registerErrorHandler();
81 \Piwik\ExceptionHandler::setUp();
82 $this->logger->debug("Debug enabled - Input parameters: {params}", ['params' => var_export($_GET + $_POST, \true)]);
83 }
84 }
85 public function isInstalled()
86 {
87 if (is_null($this->isInstalled)) {
88 $this->isInstalled = \Piwik\SettingsPiwik::isMatomoInstalled();
89 }
90 return $this->isInstalled;
91 }
92 public function main(Handler $handler, RequestSet $requestSet)
93 {
94 try {
95 $this->init();
96 if ($this->isPreFlightCorsRequest()) {
97 \Piwik\Common::sendHeader('Access-Control-Allow-Methods: GET, POST');
98 \Piwik\Common::sendHeader('Access-Control-Allow-Headers: *');
99 \Piwik\Common::sendHeader('Access-Control-Allow-Origin: *');
100 \Piwik\Common::sendResponseCode(204);
101 $this->logger->debug("Tracker detected preflight CORS request. Skipping...");
102 return null;
103 }
104 $handler->init($this, $requestSet);
105 $this->track($handler, $requestSet);
106 } catch (Exception $e) {
107 $this->logger->debug("Tracker encountered an exception: {ex}", [$e]);
108 $handler->onException($this, $requestSet, $e);
109 }
110 \Piwik\Piwik::postEvent('Tracker.end');
111 $response = $handler->finish($this, $requestSet);
112 $this->disconnectDatabase();
113 return $response;
114 }
115 public function track(Handler $handler, RequestSet $requestSet)
116 {
117 if (!$this->shouldRecordStatistics()) {
118 return;
119 }
120 $requestSet->initRequestsAndTokenAuth();
121 if ($requestSet->hasRequests()) {
122 $handler->onStartTrackRequests($this, $requestSet);
123 $handler->process($this, $requestSet);
124 $handler->onAllRequestsTracked($this, $requestSet);
125 }
126 }
127 /**
128 * @param Request $request
129 * @return void
130 */
131 public function trackRequest(Request $request)
132 {
133 if ($request->isEmptyRequest()) {
134 $this->logger->debug('The request is empty');
135 } else {
136 $this->logger->debug('Current datetime: {date}', ['date' => date("Y-m-d H:i:s", $request->getCurrentTimestamp())]);
137 $isBot = $this->isBotRequest($request);
138 /**
139 * Allows overwriting the Bot detection done using Device Detector
140 * Use this event if you want to have a request handled as bot request instead of a normal visit
141 *
142 * @param bool &$isBot Indicates if the request should be handled as Bot
143 * @param Request $request current tracking request
144 */
145 \Piwik\Piwik::postEvent('Tracker.isBotRequest', [&$isBot, $request]);
146 $rawParams = $request->getRawParams();
147 /**
148 * The recMode param will for now be used to keep BC.
149 * If it is not set, which is currently the case for all tracking requests, it will be processed as Visit only
150 * When set to 1, only bot tracking will be processed. In case the request is not detected as bot, it will be discarded
151 * Setting it to 2 enables auto mode. Meaning it will be either processed as bot request or visit, depending on the detection
152 *
153 * @deprecated Remove this parameter handling with Matomo 6 and decide the tracking method based on the bot detection only.
154 */
155 $recMode = $rawParams['recMode'] ?? null;
156 if (((int) $recMode === 1 || (int) $recMode === 2) && $isBot) {
157 $botRequest = StaticContainer::get(BotRequest::class);
158 $botRequest->setRequest($request);
159 $botRequest->handle();
160 }
161 if (empty($recMode) || (int) $recMode === 2 && !$isBot) {
162 $visit = Visit\Factory::make();
163 $visit->setRequest($request);
164 $visit->handle();
165 }
166 }
167 // increment successfully logged request count. make sure to do this after try-catch,
168 // since an excluded visit is considered 'successfully logged'
169 ++$this->countOfLoggedRequests;
170 }
171 private function isBotRequest(Request $request) : bool
172 {
173 $deviceDetector = StaticContainer::get(DeviceDetectorFactory::class)->makeInstance($request->getUserAgent(), $request->getClientHints());
174 return $deviceDetector->isBot();
175 }
176 /**
177 * Used to initialize core Piwik components on a piwik.php request
178 * Eg. when cache is missed and we will be calling some APIs to generate cache
179 */
180 public static function initCorePiwikInTrackerMode()
181 {
182 if (\Piwik\SettingsServer::isTrackerApiRequest() && self::$initTrackerMode === \false) {
183 self::$initTrackerMode = \true;
184 require_once PIWIK_INCLUDE_PATH . '/core/Option.php';
185 \Piwik\Access::getInstance();
186 \Piwik\Config::getInstance();
187 try {
188 \Piwik\Db::get();
189 } catch (Exception $e) {
190 \Piwik\Db::createDatabaseObject();
191 }
192 PluginManager::getInstance()->loadCorePluginsDuringTracker();
193 }
194 }
195 public static function restoreTrackerPlugins()
196 {
197 if (\Piwik\SettingsServer::isTrackerApiRequest() && \Piwik\Tracker::$initTrackerMode) {
198 \Piwik\Plugin\Manager::getInstance()->loadTrackerPlugins();
199 }
200 }
201 public function getCountOfLoggedRequests()
202 {
203 return $this->countOfLoggedRequests;
204 }
205 public function setCountOfLoggedRequests($numLoggedRequests)
206 {
207 $this->countOfLoggedRequests = $numLoggedRequests;
208 }
209 public function hasLoggedRequests()
210 {
211 return 0 !== $this->countOfLoggedRequests;
212 }
213 public function isDatabaseConnected()
214 {
215 return !is_null(self::$db);
216 }
217 public static function getDatabase()
218 {
219 if (is_null(self::$db)) {
220 try {
221 self::$db = TrackerDb::connectPiwikTrackerDb();
222 } catch (Exception $e) {
223 $code = $e->getCode();
224 // Note: PDOException might return a string as code, but we can't use this for DbException
225 throw new DbException($e->getMessage(), is_int($code) ? $code : 0);
226 }
227 }
228 return self::$db;
229 }
230 protected function disconnectDatabase()
231 {
232 if ($this->isDatabaseConnected()) {
233 // note: I think we do this only for the tests
234 self::$db->disconnect();
235 self::$db = null;
236 }
237 }
238 // for tests
239 public static function disconnectCachedDbConnection()
240 {
241 // code redundancy w/ above is on purpose; above disconnectDatabase depends on method that can potentially be overridden
242 if (!is_null(self::$db)) {
243 self::$db->disconnect();
244 self::$db = null;
245 }
246 }
247 public static function setTestEnvironment($args = null, $requestMethod = null)
248 {
249 if (is_null($args)) {
250 $requests = new Requests();
251 $args = $requests->getRequestsArrayFromBulkRequest($requests->getRawBulkRequest());
252 $args = $_GET + $args;
253 }
254 if (is_null($requestMethod) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
255 $requestMethod = $_SERVER['REQUEST_METHOD'];
256 } elseif (is_null($requestMethod)) {
257 $requestMethod = 'GET';
258 }
259 // Do not run scheduled tasks during tests
260 if (!defined('DEBUG_FORCE_SCHEDULED_TASKS')) {
261 TrackerConfig::setConfigValue('scheduled_tasks_min_interval', 0);
262 }
263 // if nothing found in _GET/_POST and we're doing a POST, assume bulk request. in which case,
264 // we have to bypass authentication
265 if (empty($args) && $requestMethod == 'POST') {
266 TrackerConfig::setConfigValue('tracking_requests_require_authentication', 0);
267 }
268 // Tests can force the use of 3rd party cookie for ID visitor
269 if (\Piwik\Common::getRequestVar('forceEnableFingerprintingAcrossWebsites', \false, null, $args) == 1) {
270 TrackerConfig::setConfigValue('enable_fingerprinting_across_websites', 1);
271 }
272 // Tests can simulate the tracker API maintenance mode
273 if (\Piwik\Common::getRequestVar('forceEnableTrackerMaintenanceMode', \false, null, $args) == 1) {
274 TrackerConfig::setConfigValue('record_statistics', 0);
275 }
276 // Tests can force the use of 3rd party cookie for ID visitor
277 if (\Piwik\Common::getRequestVar('forceUseThirdPartyCookie', \false, null, $args) == 1) {
278 TrackerConfig::setConfigValue('use_third_party_id_cookie', 1);
279 }
280 // Tests using window_look_back_for_visitor
281 if (\Piwik\Common::getRequestVar('forceLargeWindowLookBackForVisitor', \false, null, $args) == 1 || strpos(json_encode($args, \true), '"forceLargeWindowLookBackForVisitor":"1"') !== \false) {
282 TrackerConfig::setConfigValue('window_look_back_for_visitor', 2678400);
283 }
284 // Tests can force the enabling of IP anonymization
285 if (\Piwik\Common::getRequestVar('forceIpAnonymization', \false, null, $args) == 1) {
286 self::getDatabase();
287 // make sure db is initialized
288 $privacyConfig = new PrivacyManagerConfig();
289 $privacyConfig->ipAddressMaskLength = 2;
290 \Piwik\Plugins\PrivacyManager\IPAnonymizer::activate();
291 \Piwik\Tracker\Cache::deleteTrackerCache();
292 \Piwik\Filesystem::clearPhpCaches();
293 }
294 }
295 private function handleFatalErrors()
296 {
297 register_shutdown_function(function () {
298 // TODO: add a log here
299 $lastError = error_get_last();
300 if (!empty($lastError) && $lastError['type'] == \E_ERROR) {
301 \Piwik\Common::sendResponseCode(500);
302 }
303 });
304 }
305 private static function isDebugEnabled()
306 {
307 try {
308 $debug = (bool) TrackerConfig::getConfigValue('debug');
309 if ($debug) {
310 return \true;
311 }
312 $debugOnDemand = (bool) TrackerConfig::getConfigValue('debug_on_demand');
313 if ($debugOnDemand) {
314 return (bool) \Piwik\Common::getRequestVar('debug', \false);
315 }
316 } catch (Exception $e) {
317 }
318 return \false;
319 }
320 public function isPreFlightCorsRequest() : bool
321 {
322 if (isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD']) === 'OPTIONS') {
323 return !empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']) || !empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']);
324 }
325 return \false;
326 }
327 }
328