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 / FrontController.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
FrontController.php
701 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\API\Request;
13 use Piwik\Exception\PluginNotFoundException;
14 use Piwik\Http\HttpCodeException;
15 use Piwik\Request\AuthenticationToken;
16 use Piwik\Config\GeneralConfig;
17 use Piwik\Container\StaticContainer;
18 use Piwik\DataTable\Manager;
19 use Piwik\Exception\AuthenticationFailedException;
20 use Piwik\Exception\DatabaseSchemaIsNewerThanCodebaseException;
21 use Piwik\Exception\PluginDeactivatedException;
22 use Piwik\Exception\PluginRequiresInternetException;
23 use Piwik\Exception\StylesheetLessCompileException;
24 use Piwik\Http\ControllerResolver;
25 use Piwik\Http\Router;
26 use Piwik\Plugins\CoreAdminHome\CustomLogo;
27 use Piwik\Session\SessionAuth;
28 use Piwik\Session\SessionInitializer;
29 use Piwik\Log\LoggerInterface;
30 /**
31 * This singleton dispatches requests to the appropriate plugin Controller.
32 *
33 * Piwik uses this class for all requests that go through **index.php**. Plugins can
34 * use it to call controller actions of other plugins.
35 *
36 * ### Examples
37 *
38 * **Forwarding controller requests**
39 *
40 * public function myConfiguredRealtimeMap()
41 * {
42 * $_GET['changeVisitAlpha'] = false;
43 * $_GET['removeOldVisits'] = false;
44 * $_GET['showFooterMessage'] = false;
45 * return FrontController::getInstance()->dispatch('UserCountryMap', 'realtimeMap');
46 * }
47 *
48 * **Using other plugin controller actions**
49 *
50 * public function myPopupWithRealtimeMap()
51 * {
52 * $_GET['changeVisitAlpha'] = false;
53 * $_GET['removeOldVisits'] = false;
54 * $_GET['showFooterMessage'] = false;
55 * $realtimeMap = FrontController::getInstance()->dispatch('UserCountryMap', 'realtimeMap');
56 *
57 * $view = new View('@MyPlugin/myPopupWithRealtimeMap.twig');
58 * $view->realtimeMap = $realtimeMap;
59 * return $realtimeMap->render();
60 * }
61 *
62 * For a detailed explanation, see the documentation [here](https://developer.matomo.org/guides/how-piwik-works).
63 *
64 * @method static \Piwik\FrontController getInstance()
65 */
66 class FrontController extends \Piwik\Singleton
67 {
68 public const DEFAULT_MODULE = 'CoreHome';
69 public const DEFAULT_LOGIN = 'anonymous';
70 public const DEFAULT_TOKEN_AUTH = 'anonymous';
71 private const SESSION_TIMEOUT_COOKIE_NAME = 'matomo_session_timed_out';
72 // public for tests
73 public static $requestId = null;
74 /**
75 * Set to false and the Front Controller will not dispatch the request
76 *
77 * @var bool
78 */
79 public static $enableDispatch = \true;
80 /**
81 * @var bool
82 */
83 private $initialized = \false;
84 /**
85 * @param $lastError
86 * @return string
87 * @throws AuthenticationFailedException
88 * @throws Exception
89 */
90 private static function generateSafeModeOutputFromError($lastError)
91 {
92 \Piwik\Common::sendResponseCode(500);
93 $controller = \Piwik\FrontController::getInstance();
94 try {
95 $controller->init();
96 $message = $controller->dispatch('CorePluginsAdmin', 'safemode', array($lastError));
97 } catch (Exception $e) {
98 // may fail in safe mode (eg. global.ini.php not found)
99 $message = sprintf("Matomo encountered an error: %s (which lead to: %s)", $lastError['message'], $e->getMessage());
100 }
101 return $message;
102 }
103 /**
104 * @param Exception $e
105 * @return string
106 */
107 public static function generateSafeModeOutputFromException($e)
108 {
109 if ($e instanceof HttpCodeException && $e->getCode() >= 400 && $e->getCode() < 500) {
110 StaticContainer::get(LoggerInterface::class)->debug('Uncaught client error: {exception}', ['exception' => $e, 'ignoreInScreenWriter' => \true]);
111 } else {
112 StaticContainer::get(LoggerInterface::class)->error('Uncaught exception: {exception}', ['exception' => $e, 'ignoreInScreenWriter' => \true]);
113 }
114 $error = array('message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine());
115 if (isset(self::$requestId)) {
116 $error['request_id'] = self::$requestId;
117 }
118 $error['backtrace'] = ' on ' . $error['file'] . '(' . $error['line'] . ")\n";
119 $error['backtrace'] .= $e->getTraceAsString();
120 $exception = $e;
121 while ($exception = $exception->getPrevious()) {
122 $error['backtrace'] .= "\ncaused by: " . $exception->getMessage();
123 $error['backtrace'] .= ' on ' . $exception->getFile() . '(' . $exception->getLine() . ")\n";
124 $error['backtrace'] .= $exception->getTraceAsString();
125 }
126 return self::generateSafeModeOutputFromError($error);
127 }
128 /**
129 * Executes the requested plugin controller method.
130 *
131 * @throws Exception|\Piwik\Exception\PluginDeactivatedException in case the plugin doesn't exist, the action doesn't exist,
132 * there is not enough permission, etc.
133 *
134 * @param string $module The name of the plugin whose controller to execute, eg, `'UserCountryMap'`.
135 * @param string $action The controller method name, eg, `'realtimeMap'`.
136 * @param array $parameters Array of parameters to pass to the controller method.
137 * @return void|mixed The returned value of the call. This is the output of the controller method.
138 * @api
139 */
140 public function dispatch($module = null, $action = null, $parameters = null)
141 {
142 if (self::$enableDispatch === \false) {
143 return;
144 }
145 $filter = new Router();
146 $redirection = $filter->filterUrl(\Piwik\Url::getCurrentUrl());
147 if ($redirection !== null) {
148 \Piwik\Url::redirectToUrl($redirection);
149 return;
150 }
151 try {
152 $result = $this->doDispatch($module, $action, $parameters);
153 return $result;
154 } catch (\Piwik\NoAccessException $exception) {
155 \Piwik\Log::debug($exception);
156 /**
157 * Triggered when a user with insufficient access permissions tries to view some resource.
158 *
159 * This event can be used to customize the error that occurs when a user is denied access
160 * (for example, displaying an error message, redirecting to a page other than login, etc.).
161 *
162 * @param \Piwik\NoAccessException $exception The exception that was caught.
163 */
164 \Piwik\Piwik::postEvent('User.isNotAuthorized', array($exception), $pending = \true);
165 } catch (\Matomo\Dependencies\Twig\Error\RuntimeError $e) {
166 if ($e->getPrevious() && !$e->getPrevious() instanceof \Matomo\Dependencies\Twig\Error\RuntimeError) {
167 // a regular exception unrelated to twig was triggered while rendering an a view, for example as part of a triggered event
168 // we want to ensure to show the regular error message response instead of the safemode as it's likely wrong user input
169 throw $e;
170 } else {
171 echo $this->generateSafeModeOutputFromException($e);
172 exit;
173 }
174 } catch (StylesheetLessCompileException $e) {
175 echo $this->generateSafeModeOutputFromException($e);
176 exit;
177 } catch (\Error $e) {
178 echo $this->generateSafeModeOutputFromException($e);
179 exit;
180 }
181 }
182 /**
183 * Executes the requested plugin controller method and returns the data, capturing anything the
184 * method `echo`s.
185 *
186 * _Note: If the plugin controller returns something, the return value is returned instead
187 * of whatever is in the output buffer._
188 *
189 * @param string $module The name of the plugin whose controller to execute, eg, `'UserCountryMap'`.
190 * @param string $actionName The controller action name, eg, `'realtimeMap'`.
191 * @param array $parameters Array of parameters to pass to the controller action method.
192 * @return string The `echo`'d data or the return value of the controller action.
193 */
194 public function fetchDispatch($module = null, $actionName = null, $parameters = null)
195 {
196 ob_start();
197 $output = $this->dispatch($module, $actionName, $parameters);
198 // if nothing returned we try to load something that was printed on the screen
199 if (empty($output)) {
200 $output = ob_get_contents();
201 } else {
202 // if something was returned, flush output buffer as it is meant to be written to the screen
203 ob_flush();
204 }
205 ob_end_clean();
206 return $output;
207 }
208 /**
209 * Called at the end of the page generation
210 */
211 public function __destruct()
212 {
213 try {
214 if (class_exists('Piwik\\Profiler') && !\Piwik\SettingsServer::isTrackerApiRequest()) {
215 // in tracker mode Piwik\Tracker\Db\Pdo\Mysql does currently not implement profiling
216 \Piwik\Profiler::displayDbProfileReport();
217 \Piwik\Profiler::printQueryCount();
218 }
219 } catch (Exception $e) {
220 \Piwik\Log::debug($e);
221 }
222 }
223 // Should we show exceptions messages directly rather than display an html error page?
224 public static function shouldRethrowException()
225 {
226 // If we are in no dispatch mode, eg. a script reusing Piwik libs,
227 // then we should return the exception directly, rather than trigger the event "bad config file"
228 // which load the HTML page of the installer with the error.
229 return defined('PIWIK_ENABLE_DISPATCH') && !PIWIK_ENABLE_DISPATCH || \Piwik\Common::isPhpCliMode() || \Piwik\SettingsServer::isArchivePhpTriggered();
230 }
231 public static function setUpSafeMode()
232 {
233 register_shutdown_function(array('\\Piwik\\FrontController', 'triggerSafeModeWhenError'));
234 }
235 public static function triggerSafeModeWhenError()
236 {
237 Manager::getInstance()->deleteAll();
238 $lastError = error_get_last();
239 if (!empty($lastError) && isset(self::$requestId)) {
240 $lastError['request_id'] = self::$requestId;
241 }
242 if (!empty($lastError) && $lastError['type'] == \E_ERROR) {
243 $lastError['backtrace'] = ' on ' . $lastError['file'] . '(' . $lastError['line'] . ")\n" . \Piwik\ErrorHandler::getFatalErrorPartialBacktrace();
244 StaticContainer::get(LoggerInterface::class)->error('Fatal error encountered: {exception}', ['exception' => $lastError, 'ignoreInScreenWriter' => \true]);
245 $message = self::generateSafeModeOutputFromError($lastError);
246 echo $message;
247 }
248 }
249 /**
250 * Must be called before dispatch()
251 * - checks that directories are writable,
252 * - loads the configuration file,
253 * - loads the plugin,
254 * - inits the DB connection,
255 * - etc.
256 *
257 * @throws Exception
258 * @return void
259 */
260 public function init()
261 {
262 if ($this->initialized) {
263 return;
264 }
265 self::setRequestIdHeader();
266 $this->initialized = \true;
267 $tmpPath = StaticContainer::get('path.tmp');
268 $directoriesToCheck = array($tmpPath, $tmpPath . '/assets/', $tmpPath . '/cache/', $tmpPath . '/logs/', $tmpPath . '/tcpdf/', StaticContainer::get('path.tmp.templates'));
269 \Piwik\Filechecks::dieIfDirectoriesNotWritable($directoriesToCheck);
270 $this->handleMaintenanceMode();
271 $this->handleProfiler();
272 $this->handleSSLRedirection();
273 \Piwik\Plugin\Manager::getInstance()->loadPluginTranslations();
274 \Piwik\Plugin\Manager::getInstance()->loadActivatedPlugins();
275 // try to connect to the database
276 try {
277 \Piwik\Db::createDatabaseObject();
278 \Piwik\Db::fetchAll("SELECT DATABASE()");
279 } catch (Exception $exception) {
280 if (self::shouldRethrowException()) {
281 throw $exception;
282 }
283 \Piwik\Log::debug($exception);
284 /**
285 * Triggered when Piwik cannot connect to the database.
286 *
287 * This event can be used to start the installation process or to display a custom error
288 * message.
289 *
290 * @param Exception $exception The exception thrown from creating and testing the database
291 * connection.
292 */
293 \Piwik\Piwik::postEvent('Db.cannotConnectToDb', array($exception), $pending = \true);
294 throw $exception;
295 }
296 // try to get an option (to check if data can be queried)
297 try {
298 \Piwik\Option::get('TestingIfDatabaseConnectionWorked');
299 } catch (Exception $exception) {
300 if (self::shouldRethrowException()) {
301 throw $exception;
302 }
303 \Piwik\Log::debug($exception);
304 /**
305 * Triggered when Piwik cannot access database data.
306 *
307 * This event can be used to start the installation process or to display a custom error
308 * message.
309 *
310 * @param Exception $exception The exception thrown from trying to get an option value.
311 */
312 \Piwik\Piwik::postEvent('Config.badConfigurationFile', array($exception), $pending = \true);
313 throw $exception;
314 }
315 // Init the Access object, so that eg. core/Updates/* can enforce Super User and use some APIs
316 \Piwik\Access::getInstance();
317 /**
318 * Triggered just after the platform is initialized and plugins are loaded.
319 *
320 * This event can be used to do early initialization.
321 *
322 * _Note: At this point the user is not authenticated yet._
323 */
324 \Piwik\Piwik::postEvent('Request.dispatchCoreAndPluginUpdatesScreen');
325 $this->throwIfPiwikVersionIsOlderThanDBSchema();
326 $module = \Piwik\Piwik::getModule();
327 $action = \Piwik\Piwik::getAction();
328 if (empty($module) || empty($action) || $module !== 'Installation' || !in_array($action, array('getInstallationCss', 'getInstallationJs'))) {
329 \Piwik\Plugin\Manager::getInstance()->installLoadedPlugins();
330 }
331 // ensure the current Piwik URL is known for later use
332 if (method_exists('Piwik\\SettingsPiwik', 'getPiwikUrl')) {
333 \Piwik\SettingsPiwik::getPiwikUrl();
334 }
335 $loggedIn = \false;
336 //move this up unsupported Browser do not create session
337 if ($this->isSupportedBrowserCheckNeeded()) {
338 \Piwik\SupportedBrowser::checkIfBrowserSupported();
339 }
340 // don't use sessionauth in cli mode
341 // try authenticating w/ session first...
342 $sessionAuth = $this->makeSessionAuthenticator();
343 if ($sessionAuth) {
344 $loggedIn = \Piwik\Access::getInstance()->reloadAccess($sessionAuth);
345 if (!$loggedIn && $sessionAuth->wasSessionExpired()) {
346 \Piwik\Access::getInstance()->setSessionExpired(\true);
347 }
348 }
349 // ... if session auth fails try normal auth (which will login the anonymous user)
350 if (!$loggedIn) {
351 $authAdapter = $this->makeAuthenticator();
352 $success = \Piwik\Access::getInstance()->reloadAccess($authAdapter);
353 if ($success && \Piwik\Piwik::isUserIsAnonymous() && $authAdapter->getLogin() === 'anonymous' && \Piwik\Piwik::isUserHasSomeViewAccess() && \Piwik\Session::isSessionStarted() && \Piwik\Session::isWritable()) {
354 // usually the session would be started when someone logs in using login controller. But in this
355 // case we need to init session here for anoynymous users
356 $init = StaticContainer::get(SessionInitializer::class);
357 $init->initSession($authAdapter);
358 }
359 } else {
360 $this->makeAuthenticator($sessionAuth);
361 // Piwik\Auth must be set to the correct Login plugin
362 }
363 $this->consumeSessionTimeoutCookie();
364 $this->sendSessionTimedOutHeaderIfNeeded();
365 // Force the auth to use the token_auth if specified, so that embed dashboard
366 // and all other non widgetized controller methods works fine
367 if (StaticContainer::get(AuthenticationToken::class)->getAuthToken() !== '' && Request::shouldReloadAuthUsingTokenAuth(null)) {
368 Request::reloadAuthUsingTokenAuth();
369 Request::checkTokenAuthIsNotLimited($module, $action);
370 }
371 \Piwik\SettingsServer::raiseMemoryLimitIfNecessary();
372 \Piwik\Plugin\Manager::getInstance()->postLoadPlugins();
373 /**
374 * Triggered after the platform is initialized and after the user has been authenticated, but
375 * before the platform has handled the request.
376 *
377 * Piwik uses this event to check for updates to Piwik.
378 */
379 \Piwik\Piwik::postEvent('Platform.initialized');
380 }
381 protected function prepareDispatch($module, $action, $parameters)
382 {
383 if (is_null($module)) {
384 $module = \Piwik\Common::getRequestVar('module', self::DEFAULT_MODULE, 'string');
385 }
386 if (is_null($action)) {
387 $action = \Piwik\Common::getRequestVar('action', \false);
388 if ($action !== \false) {
389 // If a value was provided, check it has the correct type.
390 $action = \Piwik\Common::getRequestVar('action', null, 'string');
391 }
392 }
393 if (\Piwik\Session::isSessionStarted()) {
394 $this->closeSessionEarlyForFasterUI();
395 }
396 if (is_null($parameters)) {
397 $parameters = array();
398 }
399 if (!ctype_alnum($module)) {
400 throw new Exception("Invalid module name '{$module}'");
401 }
402 [$module, $action] = Request::getRenamedModuleAndAction($module, $action);
403 if (!\Piwik\SettingsPiwik::isInternetEnabled() && \Piwik\Plugin\Manager::getInstance()->doesPluginRequireInternetConnection($module)) {
404 throw new PluginRequiresInternetException($module);
405 }
406 if (!\Piwik\Plugin\Manager::getInstance()->isPluginInFilesystem($module)) {
407 throw new PluginNotFoundException($module);
408 }
409 if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated($module)) {
410 throw new PluginDeactivatedException($module);
411 }
412 return array($module, $action, $parameters);
413 }
414 protected function handleMaintenanceMode()
415 {
416 if (GeneralConfig::getConfigValue('maintenance_mode') != 1 || \Piwik\Common::isPhpCliMode()) {
417 return;
418 }
419 // as request matomo behind load balancer should not return 503. https://github.com/matomo-org/matomo/issues/18054
420 if (GeneralConfig::getConfigValue('multi_server_environment') != 1) {
421 \Piwik\Common::sendResponseCode(503);
422 }
423 $logoUrl = 'plugins/Morpheus/images/logo.svg';
424 $faviconUrl = 'plugins/CoreHome/images/favicon.png';
425 try {
426 $logo = new CustomLogo();
427 if ($logo->hasSVGLogo()) {
428 $logoUrl = $logo->getSVGLogoUrl();
429 } else {
430 $logoUrl = $logo->getHeaderLogoUrl();
431 }
432 $faviconUrl = $logo->getPathUserFavicon();
433 } catch (Exception $ex) {
434 }
435 $recordStatistics = \Piwik\Config::getInstance()->Tracker['record_statistics'];
436 $trackMessage = '';
437 if ($recordStatistics) {
438 $trackMessage = 'Your analytics data will continue to be tracked as normal.';
439 } else {
440 $trackMessage = 'While the maintenance mode is active, data tracking is disabled.';
441 }
442 $page = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/maintenance.tpl');
443 $page = str_replace('%logoUrl%', $logoUrl, $page);
444 $page = str_replace('%faviconUrl%', $faviconUrl, $page);
445 $page = str_replace('%piwikTitle%', \Piwik\Piwik::getRandomTitle(), $page);
446 $page = str_replace('%trackMessage%', $trackMessage, $page);
447 echo $page;
448 exit;
449 }
450 protected function handleSSLRedirection()
451 {
452 // Specifically disable for the opt out iframe
453 if (\Piwik\Piwik::getModule() == 'CoreAdminHome' && (\Piwik\Piwik::getAction() == 'optOut' || \Piwik\Piwik::getAction() == 'optOutJS')) {
454 return;
455 }
456 // Disable Https for VisitorGenerator
457 if (\Piwik\Piwik::getModule() == 'VisitorGenerator') {
458 return;
459 }
460 if (\Piwik\Common::isPhpCliMode()) {
461 return;
462 }
463 // proceed only when force_ssl = 1
464 if (!\Piwik\SettingsPiwik::isHttpsForced()) {
465 return;
466 }
467 // TODO: remove in Matomo 6 - avoid update redirect loops before proxy_scheme_headers migration runs.
468 if (\Piwik\Piwik::getModule() === 'CoreUpdater' && \Piwik\ProxyHeaders::getProtocolInformation() !== null) {
469 return;
470 }
471 \Piwik\Url::redirectToHttps();
472 }
473 private function closeSessionEarlyForFasterUI()
474 {
475 $isDashboardReferrer = !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'module=CoreHome&action=index') !== \false;
476 $isAllWebsitesReferrer = !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'module=MultiSites&action=index') !== \false;
477 if ($isDashboardReferrer && StaticContainer::get(AuthenticationToken::class)->wasTokenAuthProvidedSecurely() && \Piwik\Common::getRequestVar('widget', 0, 'int') === 1) {
478 \Piwik\Session::close();
479 }
480 if (($isDashboardReferrer || $isAllWebsitesReferrer) && \Piwik\Common::getRequestVar('viewDataTable', '', 'string') === 'sparkline') {
481 \Piwik\Session::close();
482 }
483 }
484 private function handleProfiler()
485 {
486 $profilerEnabled = \Piwik\Config::getInstance()->Debug['enable_php_profiler'] == 1;
487 if (!$profilerEnabled) {
488 return;
489 }
490 if (!empty($_GET['xhprof'])) {
491 $mainRun = $_GET['xhprof'] == 1;
492 // core:archive command sets xhprof=2
493 \Piwik\Profiler::setupProfilerXHProf($mainRun);
494 }
495 }
496 /**
497 * @param $module
498 * @param $action
499 * @param $parameters
500 * @return mixed
501 */
502 private function doDispatch($module, $action, $parameters)
503 {
504 [$module, $action, $parameters] = $this->prepareDispatch($module, $action, $parameters);
505 /**
506 * Triggered directly before controller actions are dispatched.
507 *
508 * This event can be used to modify the parameters passed to one or more controller actions
509 * and can be used to change the controller action being dispatched to.
510 *
511 * @param string &$module The name of the plugin being dispatched to.
512 * @param string &$action The name of the controller method being dispatched to.
513 * @param array &$parameters The arguments passed to the controller action.
514 */
515 \Piwik\Piwik::postEvent('Request.dispatch', array(&$module, &$action, &$parameters));
516 /** @var ControllerResolver $controllerResolver */
517 $controllerResolver = StaticContainer::get('Piwik\\Http\\ControllerResolver');
518 $controller = $controllerResolver->getController($module, $action, $parameters);
519 /**
520 * Triggered directly before controller actions are dispatched.
521 *
522 * This event exists for convenience and is triggered directly after the {@hook Request.dispatch}
523 * event is triggered.
524 *
525 * It can be used to do the same things as the {@hook Request.dispatch} event, but for one controller
526 * action only. Using this event will result in a little less code than {@hook Request.dispatch}.
527 *
528 * @param array &$parameters The arguments passed to the controller action.
529 */
530 \Piwik\Piwik::postEvent(sprintf('Controller.%s.%s', $module, $action), array(&$parameters));
531 $result = call_user_func_array($controller, $parameters);
532 /**
533 * Triggered after a controller action is successfully called.
534 *
535 * This event exists for convenience and is triggered immediately before the {@hook Request.dispatch.end}
536 * event is triggered.
537 *
538 * It can be used to do the same things as the {@hook Request.dispatch.end} event, but for one
539 * controller action only. Using this event will result in a little less code than
540 * {@hook Request.dispatch.end}.
541 *
542 * @param mixed &$result The result of the controller action.
543 * @param array $parameters The arguments passed to the controller action.
544 */
545 \Piwik\Piwik::postEvent(sprintf('Controller.%s.%s.end', $module, $action), array(&$result, $parameters));
546 /**
547 * Triggered after a controller action is successfully called.
548 *
549 * This event can be used to modify controller action output (if any) before the output is returned.
550 *
551 * @param mixed &$result The controller action result.
552 * @param array $parameters The arguments passed to the controller action.
553 */
554 \Piwik\Piwik::postEvent('Request.dispatch.end', array(&$result, $module, $action, $parameters));
555 return $result;
556 }
557 /**
558 * This method ensures that Piwik Platform cannot be running when using a NEWER database.
559 */
560 private function throwIfPiwikVersionIsOlderThanDBSchema()
561 {
562 // When developing this situation happens often when switching branches
563 if (\Piwik\Development::isEnabled()) {
564 return;
565 }
566 if (!StaticContainer::get('EnableDbVersionCheck')) {
567 return;
568 }
569 $updater = new \Piwik\Updater();
570 $dbSchemaVersion = $updater->getCurrentComponentVersion('core');
571 $current = \Piwik\Version::VERSION;
572 if (-1 === version_compare($current, $dbSchemaVersion)) {
573 $messages = array(
574 \Piwik\Piwik::translate('General_ExceptionDatabaseVersionNewerThanCodebase', array($current, $dbSchemaVersion)),
575 \Piwik\Piwik::translate('General_ExceptionDatabaseVersionNewerThanCodebaseWait'),
576 // we cannot fill in the Super User emails as we are failing before Authentication was ready
577 \Piwik\Piwik::translate('General_ExceptionContactSupportGeneric', array('', '')),
578 );
579 throw new DatabaseSchemaIsNewerThanCodebaseException(implode(" ", $messages));
580 }
581 }
582 private function makeSessionAuthenticator()
583 {
584 if (\Piwik\Common::isPhpClimode() && !defined('PIWIK_TEST_MODE')) {
585 // don't use the session auth during CLI requests
586 return null;
587 }
588 $token = StaticContainer::get(AuthenticationToken::class);
589 if ($token->getAuthToken() !== '' && !$token->isSessionToken()) {
590 return null;
591 }
592 $module = \Piwik\Common::getRequestVar('module', self::DEFAULT_MODULE, 'string');
593 $action = \Piwik\Common::getRequestVar('action', \false);
594 // the session must be started before using the session authenticator,
595 // so we do it here, if this is not an API request.
596 if (\Piwik\SettingsPiwik::isMatomoInstalled() && ($module !== 'API' || $action && $action !== 'index') && !($module === 'CoreAdminHome' && $action === 'optOutJS')) {
597 /**
598 * @ignore
599 */
600 \Piwik\Piwik::postEvent('Session.beforeSessionStart');
601 \Piwik\Session::start();
602 return StaticContainer::get(SessionAuth::class);
603 }
604 return null;
605 }
606 private function makeAuthenticator(?SessionAuth $auth = null)
607 {
608 /**
609 * Triggered before the user is authenticated, when the global authentication object
610 * should be created.
611 *
612 * Plugins that provide their own authentication implementation should use this event
613 * to set the global authentication object (which must derive from {@link Piwik\Auth}).
614 *
615 * **Example**
616 *
617 * Piwik::addAction('Request.initAuthenticationObject', function() {
618 * StaticContainer::getContainer()->set('Piwik\Auth', new MyAuthImplementation());
619 * });
620 */
621 \Piwik\Piwik::postEvent('Request.initAuthenticationObject');
622 try {
623 $authAdapter = StaticContainer::get('Piwik\\Auth');
624 } catch (Exception $e) {
625 $message = "Authentication object cannot be found in the container. Maybe the Login plugin is not activated?\n <br />You can activate the plugin by adding:<br />\n <code>Plugins[] = Login</code><br />\n under the <code>[Plugins]</code> section in your config/config.ini.php";
626 $ex = new AuthenticationFailedException($message);
627 $ex->setIsHtmlMessage();
628 throw $ex;
629 }
630 if ($auth) {
631 $authAdapter->setLogin($auth->getLogin());
632 $authAdapter->setTokenAuth($auth->getTokenAuth());
633 } else {
634 $authAdapter->setLogin(self::DEFAULT_LOGIN);
635 $authAdapter->setTokenAuth(self::DEFAULT_TOKEN_AUTH);
636 }
637 return $authAdapter;
638 }
639 public static function getUniqueRequestId()
640 {
641 if (self::$requestId === null) {
642 self::$requestId = substr(\Piwik\Common::generateUniqId(), 0, 5);
643 }
644 return self::$requestId;
645 }
646 private static function setRequestIdHeader()
647 {
648 $requestId = self::getUniqueRequestId();
649 \Piwik\Common::sendHeader("X-Matomo-Request-Id: {$requestId}");
650 }
651 private function consumeSessionTimeoutCookie() : void
652 {
653 $cookie = new \Piwik\Cookie(self::SESSION_TIMEOUT_COOKIE_NAME);
654 if (!$cookie->isCookieFound()) {
655 return;
656 }
657 $cookie->delete();
658 if (\Piwik\Piwik::isUserIsAnonymous()) {
659 \Piwik\Access::getInstance()->setSessionExpired(\true);
660 }
661 }
662 private function isSupportedBrowserCheckNeeded()
663 {
664 if (defined('PIWIK_ENABLE_DISPATCH') && !PIWIK_ENABLE_DISPATCH) {
665 return \false;
666 }
667 $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
668 if ($userAgent === '') {
669 return \false;
670 }
671 $isTestMode = defined('PIWIK_TEST_MODE') && PIWIK_TEST_MODE;
672 if (!$isTestMode && \Piwik\Common::isPhpCliMode() === \true) {
673 return \false;
674 }
675 if (\Piwik\Piwik::getModule() === 'API' && (empty(\Piwik\Piwik::getAction()) || \Piwik\Piwik::getAction() === 'index' || \Piwik\Piwik::getAction() === 'glossary')) {
676 return \false;
677 }
678 if (\Piwik\Piwik::getModule() === 'Widgetize') {
679 return \true;
680 }
681 $generalConfig = \Piwik\Config::getInstance()->General;
682 if ($generalConfig['enable_framed_pages'] == '1' || $generalConfig['enable_framed_settings'] == '1') {
683 return \true;
684 }
685 if (StaticContainer::get(AuthenticationToken::class)->getAuthToken() !== '') {
686 return \true;
687 }
688 if (\Piwik\Piwik::isUserIsAnonymous()) {
689 return \true;
690 }
691 return \false;
692 }
693 private function sendSessionTimedOutHeaderIfNeeded()
694 {
695 if (!\Piwik\Access::getInstance()->wasSessionExpired()) {
696 return;
697 }
698 \Piwik\Common::sendHeader('X-Matomo-Session-Timed-Out: 1');
699 }
700 }
701