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