PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
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 1 month ago Access 3 months ago Application 1 month ago Archive 1 month ago ArchiveProcessor 1 month ago Archiver 2 years ago AssetManager 1 month ago Auth 6 months ago Category 6 months ago Changes 1 month ago CliMulti 1 year ago Columns 1 month ago Concurrency 1 month ago Config 1 month ago Container 1 month ago CronArchive 3 months ago DataAccess 1 month ago DataFiles 2 years ago DataTable 2 weeks ago Db 2 weeks 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 1 month ago Metrics 3 months ago Notification 6 months ago Period 1 month ago Plugin 2 weeks ago Policy 1 month ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 3 months ago Request 3 months ago Scheduler 1 month ago Segment 1 month ago Session 2 weeks ago Settings 1 month ago Tracker 2 weeks ago Translation 1 month ago Twig 1 year ago UpdateCheck 3 months ago Updater 1 month ago Updates 3 days ago Validators 1 year ago View 1 month ago ViewDataTable 2 weeks ago Visualization 1 year ago Widget 1 month ago .htaccess 2 years ago Access.php 1 month ago Archive.php 1 month ago ArchiveProcessor.php 1 month ago AssetManager.php 1 month 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 1 month ago Common.php 2 weeks ago Config.php 1 month ago Console.php 3 months ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 1 month ago DI.php 3 months ago DataArray.php 1 month ago DataTable.php 1 month ago Date.php 1 month ago Db.php 1 month ago DbHelper.php 1 month ago Development.php 1 year ago ErrorHandler.php 6 months ago EventDispatcher.php 1 month ago ExceptionHandler.php 4 months ago FileIntegrity.php 1 month ago Filechecks.php 1 year ago Filesystem.php 1 month ago FrontController.php 4 months ago Http.php 1 month ago IP.php 1 year ago Log.php 3 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 1 month ago NoAccessException.php 2 years ago Nonce.php 6 months ago Notification.php 1 month ago NumberFormatter.php 5 months ago Option.php 5 months ago Period.php 1 month ago Piwik.php 1 month ago Plugin.php 1 month ago Process.php 1 month ago Profiler.php 6 months ago ProxyHeaders.php 4 months ago ProxyHttp.php 5 months ago QuickForm2.php 3 months ago RankingQuery.php 1 month ago ReportRenderer.php 1 month ago Request.php 1 month ago Segment.php 1 month ago Sequence.php 6 months ago Session.php 2 weeks ago SettingsPiwik.php 1 month ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 1 month ago SiteContentDetector.php 1 month ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 1 month ago Tracker.php 1 month ago Twig.php 1 month ago Unzip.php 1 year ago UpdateCheck.php 1 month ago Updater.php 1 month ago UpdaterErrorException.php 2 years ago Updates.php 3 months ago Url.php 3 months ago UrlHelper.php 1 month ago Version.php 3 days ago View.php 1 month 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 = TrackerConfig::getBoolConfigValue('debug', \false);
308 if ($debug) {
309 return \true;
310 }
311 $debugOnDemand = TrackerConfig::getBoolConfigValue('debug_on_demand', \false);
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