PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
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 3 months ago Access 3 months ago Application 3 months ago Archive 3 months ago ArchiveProcessor 3 months ago Archiver 2 years ago AssetManager 3 months ago Auth 6 months ago Category 6 months ago Changes 3 months ago CliMulti 1 year ago Columns 3 months ago Concurrency 3 months ago Config 3 months ago Container 3 months ago CronArchive 3 months ago DataAccess 3 months ago DataFiles 2 years ago DataTable 3 months ago Db 3 months ago DeviceDetector 1 year ago Email 2 years ago Exception 4 months ago Http 4 months ago Intl 3 months ago Log 2 years ago Mail 1 year ago Measurable 6 months ago Menu 3 months ago Metrics 3 months ago Notification 6 months ago Period 3 months ago Plugin 3 months ago Policy 3 months ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 3 months ago Request 3 months ago Scheduler 3 months ago Segment 3 months ago Session 3 months ago Settings 3 months ago Tracker 3 months ago Translation 3 months ago Twig 1 year ago UpdateCheck 3 months ago Updater 4 months ago Updates 3 months ago Validators 1 year ago View 6 months ago ViewDataTable 3 months ago Visualization 1 year ago Widget 3 months ago .htaccess 2 years ago Access.php 3 months ago Archive.php 3 months ago ArchiveProcessor.php 4 months ago AssetManager.php 3 months ago Auth.php 6 months ago AuthResult.php 6 months ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 4 months ago CliMulti.php 3 months ago Common.php 3 months ago Config.php 3 months ago Console.php 3 months ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 3 months ago DI.php 3 months ago DataArray.php 5 months ago DataTable.php 3 months ago Date.php 3 months ago Db.php 3 months ago DbHelper.php 3 months ago Development.php 1 year ago ErrorHandler.php 6 months ago EventDispatcher.php 1 year ago ExceptionHandler.php 4 months ago FileIntegrity.php 3 months ago Filechecks.php 1 year ago Filesystem.php 3 months ago FrontController.php 4 months ago Http.php 4 months ago IP.php 1 year ago Log.php 3 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 3 months ago NoAccessException.php 2 years ago Nonce.php 6 months ago Notification.php 6 months ago NumberFormatter.php 5 months ago Option.php 5 months ago Period.php 3 months ago Piwik.php 3 months ago Plugin.php 3 months ago Process.php 1 year ago Profiler.php 6 months ago ProxyHeaders.php 4 months ago ProxyHttp.php 5 months ago QuickForm2.php 3 months ago RankingQuery.php 5 months ago ReportRenderer.php 3 months ago Request.php 3 months ago Segment.php 3 months ago Sequence.php 6 months ago Session.php 3 months ago SettingsPiwik.php 3 months ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 4 months ago SiteContentDetector.php 3 months ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 2 years ago Tracker.php 3 months ago Twig.php 3 months ago Unzip.php 1 year ago UpdateCheck.php 3 months ago Updater.php 3 months ago UpdaterErrorException.php 2 years ago Updates.php 3 months ago Url.php 3 months ago UrlHelper.php 3 months ago Version.php 3 months ago View.php 3 months ago bootstrap.php 1 year ago dispatch.php 2 years ago testMinimumPhpVersion.php 6 months ago
Tracker.php
327 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 * @return void
129 */
130 public function trackRequest(Request $request)
131 {
132 if ($request->isEmptyRequest()) {
133 $this->logger->debug('The request is empty');
134 } else {
135 $this->logger->debug('Current datetime: {date}', ['date' => date("Y-m-d H:i:s", $request->getCurrentTimestamp())]);
136 $isBot = $this->isBotRequest($request);
137 /**
138 * Allows overwriting the Bot detection done using Device Detector
139 * Use this event if you want to have a request handled as bot request instead of a normal visit
140 *
141 * @param bool &$isBot Indicates if the request should be handled as Bot
142 * @param Request $request current tracking request
143 */
144 \Piwik\Piwik::postEvent('Tracker.isBotRequest', [&$isBot, $request]);
145 $rawParams = $request->getRawParams();
146 /**
147 * The recMode param will for now be used to keep BC.
148 * If it is not set, which is currently the case for all tracking requests, it will be processed as Visit only
149 * When set to 1, only bot tracking will be processed. In case the request is not detected as bot, it will be discarded
150 * Setting it to 2 enables auto mode. Meaning it will be either processed as bot request or visit, depending on the detection
151 *
152 * @deprecated Remove this parameter handling with Matomo 6 and decide the tracking method based on the bot detection only.
153 */
154 $recMode = $rawParams['recMode'] ?? null;
155 if (((int) $recMode === 1 || (int) $recMode === 2) && $isBot) {
156 $botRequest = StaticContainer::get(BotRequest::class);
157 $botRequest->setRequest($request);
158 $botRequest->handle();
159 }
160 if (empty($recMode) || (int) $recMode === 2 && !$isBot) {
161 $visit = Visit\Factory::make();
162 $visit->setRequest($request);
163 $visit->handle();
164 }
165 }
166 // increment successfully logged request count. make sure to do this after try-catch,
167 // since an excluded visit is considered 'successfully logged'
168 ++$this->countOfLoggedRequests;
169 }
170 private function isBotRequest(Request $request) : bool
171 {
172 $deviceDetector = StaticContainer::get(DeviceDetectorFactory::class)->makeInstance($request->getUserAgent(), $request->getClientHints());
173 return $deviceDetector->isBot();
174 }
175 /**
176 * Used to initialize core Piwik components on a piwik.php request
177 * Eg. when cache is missed and we will be calling some APIs to generate cache
178 */
179 public static function initCorePiwikInTrackerMode()
180 {
181 if (\Piwik\SettingsServer::isTrackerApiRequest() && self::$initTrackerMode === \false) {
182 self::$initTrackerMode = \true;
183 require_once PIWIK_INCLUDE_PATH . '/core/Option.php';
184 \Piwik\Access::getInstance();
185 \Piwik\Config::getInstance();
186 try {
187 \Piwik\Db::get();
188 } catch (Exception $e) {
189 \Piwik\Db::createDatabaseObject();
190 }
191 PluginManager::getInstance()->loadCorePluginsDuringTracker();
192 }
193 }
194 public static function restoreTrackerPlugins()
195 {
196 if (\Piwik\SettingsServer::isTrackerApiRequest() && \Piwik\Tracker::$initTrackerMode) {
197 \Piwik\Plugin\Manager::getInstance()->loadTrackerPlugins();
198 }
199 }
200 public function getCountOfLoggedRequests()
201 {
202 return $this->countOfLoggedRequests;
203 }
204 public function setCountOfLoggedRequests($numLoggedRequests)
205 {
206 $this->countOfLoggedRequests = $numLoggedRequests;
207 }
208 public function hasLoggedRequests()
209 {
210 return 0 !== $this->countOfLoggedRequests;
211 }
212 public function isDatabaseConnected()
213 {
214 return !is_null(self::$db);
215 }
216 public static function getDatabase()
217 {
218 if (is_null(self::$db)) {
219 try {
220 self::$db = TrackerDb::connectPiwikTrackerDb();
221 } catch (Exception $e) {
222 $code = $e->getCode();
223 // Note: PDOException might return a string as code, but we can't use this for DbException
224 throw new DbException($e->getMessage(), is_int($code) ? $code : 0);
225 }
226 }
227 return self::$db;
228 }
229 protected function disconnectDatabase()
230 {
231 if ($this->isDatabaseConnected()) {
232 // note: I think we do this only for the tests
233 self::$db->disconnect();
234 self::$db = null;
235 }
236 }
237 // for tests
238 public static function disconnectCachedDbConnection()
239 {
240 // code redundancy w/ above is on purpose; above disconnectDatabase depends on method that can potentially be overridden
241 if (!is_null(self::$db)) {
242 self::$db->disconnect();
243 self::$db = null;
244 }
245 }
246 public static function setTestEnvironment($args = null, $requestMethod = null)
247 {
248 if (is_null($args)) {
249 $requests = new Requests();
250 $args = $requests->getRequestsArrayFromBulkRequest($requests->getRawBulkRequest());
251 $args = $_GET + $args;
252 }
253 if (is_null($requestMethod) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
254 $requestMethod = $_SERVER['REQUEST_METHOD'];
255 } elseif (is_null($requestMethod)) {
256 $requestMethod = 'GET';
257 }
258 // Do not run scheduled tasks during tests
259 if (!defined('DEBUG_FORCE_SCHEDULED_TASKS')) {
260 TrackerConfig::setConfigValue('scheduled_tasks_min_interval', 0);
261 }
262 // if nothing found in _GET/_POST and we're doing a POST, assume bulk request. in which case,
263 // we have to bypass authentication
264 if (empty($args) && $requestMethod == 'POST') {
265 TrackerConfig::setConfigValue('tracking_requests_require_authentication', 0);
266 }
267 // Tests can force the use of 3rd party cookie for ID visitor
268 if (\Piwik\Common::getRequestVar('forceEnableFingerprintingAcrossWebsites', \false, null, $args) == 1) {
269 TrackerConfig::setConfigValue('enable_fingerprinting_across_websites', 1);
270 }
271 // Tests can simulate the tracker API maintenance mode
272 if (\Piwik\Common::getRequestVar('forceEnableTrackerMaintenanceMode', \false, null, $args) == 1) {
273 TrackerConfig::setConfigValue('record_statistics', 0);
274 }
275 // Tests can force the use of 3rd party cookie for ID visitor
276 if (\Piwik\Common::getRequestVar('forceUseThirdPartyCookie', \false, null, $args) == 1) {
277 TrackerConfig::setConfigValue('use_third_party_id_cookie', 1);
278 }
279 // Tests using window_look_back_for_visitor
280 if (\Piwik\Common::getRequestVar('forceLargeWindowLookBackForVisitor', \false, null, $args) == 1 || strpos(json_encode($args, \true), '"forceLargeWindowLookBackForVisitor":"1"') !== \false) {
281 TrackerConfig::setConfigValue('window_look_back_for_visitor', 2678400);
282 }
283 // Tests can force the enabling of IP anonymization
284 if (\Piwik\Common::getRequestVar('forceIpAnonymization', \false, null, $args) == 1) {
285 self::getDatabase();
286 // make sure db is initialized
287 $privacyConfig = new PrivacyManagerConfig();
288 $privacyConfig->ipAddressMaskLength = 2;
289 \Piwik\Plugins\PrivacyManager\IPAnonymizer::activate();
290 \Piwik\Tracker\Cache::deleteTrackerCache();
291 \Piwik\Filesystem::clearPhpCaches();
292 }
293 }
294 private function handleFatalErrors()
295 {
296 register_shutdown_function(function () {
297 // TODO: add a log here
298 $lastError = error_get_last();
299 if (!empty($lastError) && $lastError['type'] == \E_ERROR) {
300 \Piwik\Common::sendResponseCode(500);
301 }
302 });
303 }
304 private static function isDebugEnabled()
305 {
306 try {
307 $debug = (bool) TrackerConfig::getConfigValue('debug');
308 if ($debug) {
309 return \true;
310 }
311 $debugOnDemand = (bool) TrackerConfig::getConfigValue('debug_on_demand');
312 if ($debugOnDemand) {
313 return (bool) \Piwik\Common::getRequestVar('debug', \false);
314 }
315 } catch (Exception $e) {
316 }
317 return \false;
318 }
319 public function isPreFlightCorsRequest() : bool
320 {
321 if (isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD']) === 'OPTIONS') {
322 return !empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']) || !empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']);
323 }
324 return \false;
325 }
326 }
327