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