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