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