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