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