Skip to content
Extraits de code Groupes Projets
FrontController.php 22,7 ko
Newer Older
  • Learn to ignore specific revisions
  •  * Piwik - free/libre analytics platform
    
    robocoder's avatar
    robocoder a validé
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
    
    use Exception;
    use Piwik\API\Request;
    use Piwik\API\ResponseBuilder;
    
    use Piwik\Exceptions\HtmlMessageException;
    
    use Piwik\Http\Router;
    
    use Piwik\Plugin\Controller;
    
    use Piwik\Plugins\CoreAdminHome\CustomLogo;
    
    use Piwik\Session;
    
    use Piwik\Plugins\CoreHome\Controller as CoreHomeController;
    
     * This singleton dispatches requests to the appropriate plugin Controller.
    
     * Piwik uses this class for all requests that go through **index.php**. Plugins can
    
     * use it to call controller actions of other plugins.
    
     * **Forwarding controller requests**
    
     *     public function myConfiguredRealtimeMap()
     *     {
     *         $_GET['changeVisitAlpha'] = false;
     *         $_GET['removeOldVisits'] = false;
     *         $_GET['showFooterMessage'] = false;
    
     *         return FrontController::getInstance()->dispatch('UserCountryMap', 'realtimeMap');
    
     * **Using other plugin controller actions**
    
     *     public function myPopupWithRealtimeMap()
     *     {
     *         $_GET['changeVisitAlpha'] = false;
     *         $_GET['removeOldVisits'] = false;
     *         $_GET['showFooterMessage'] = false;
     *         $realtimeMap = FrontController::getInstance()->fetchDispatch('UserCountryMap', 'realtimeMap');
    
     *         $view = new View('@MyPlugin/myPopupWithRealtimeMap.twig');
     *         $view->realtimeMap = $realtimeMap;
    
     *         return $realtimeMap->render();
    
     * For a detailed explanation, see the documentation [here](http://piwik.org/docs/plugins/framework-overview).
    
     * @method static \Piwik\FrontController getInstance()
    
    class FrontController extends Singleton
    
    mattab's avatar
    mattab a validé
        const DEFAULT_MODULE = 'CoreHome';
    
    mattab's avatar
    mattab a validé
    
    
        /**
         * Set to false and the Front Controller will not dispatch the request
         *
         * @var bool
         */
    
        public static $enableDispatch = true;
    
         * Executes the requested plugin controller method.
    
         * @throws Exception|\Piwik\PluginDeactivatedException in case the plugin doesn't exist, the action doesn't exist,
         *                                                     there is not enough permission, etc.
    
         * @param string $module The name of the plugin whose controller to execute, eg, `'UserCountryMap'`.
    
         * @param string $action The controller method name, eg, `'realtimeMap'`.
         * @param array $parameters Array of parameters to pass to the controller method.
         * @return void|mixed The returned value of the call. This is the output of the controller method.
    
         */
        public function dispatch($module = null, $action = null, $parameters = null)
        {
            if (self::$enableDispatch === false) {
                return;
            }
    
    
            $filter = new Router();
    
            $redirection = $filter->filterUrl(Url::getCurrentUrl());
    
            if ($redirection !== null) {
                Url::redirectToUrl($redirection);
    
                $result = $this->doDispatch($module, $action, $parameters);
    
            } catch (NoAccessException $exception) {
    
                Log::debug($exception);
    
                 * Triggered when a user with insufficient access permissions tries to view some resource.
    
                 * This event can be used to customize the error that occurs when a user is denied access
                 * (for example, displaying an error message, redirecting to a page other than login, etc.).
    
                 * @param \Piwik\NoAccessException $exception The exception that was caught.
    
                Piwik::postEvent('User.isNotAuthorized', array($exception), $pending = true);
    
        protected function makeController($module, $action, &$parameters)
    
        {
            $controllerClassName = $this->getClassNameController($module);
    
    
            // TRY TO FIND ACTION IN CONTROLLER
            if (class_exists($controllerClassName)) {
    
                $class = $this->getClassNameController($module);
                /** @var $controller Controller */
                $controller = new $class;
    
                $controllerAction = $action;
                if ($controllerAction === false) {
                    $controllerAction = $controller->getDefaultAction();
                }
    
                if (is_callable(array($controller, $controllerAction))) {
    
                    return array($controller, $controllerAction);
    
                    $this->triggerControllerActionNotFoundError($module, $controllerAction);
    
            }
    
            // TRY TO FIND ACTION IN WIDGET
            $widget = Widgets::factory($module, $action);
    
            if (!empty($widget)) {
    
                $parameters['widgetModule'] = $module;
                $parameters['widgetMethod'] = $action;
    
                return array(new CoreHomeController(), 'renderWidget');
            }
    
            // TRY TO FIND ACTION IN REPORT
            $report = Report::factory($module, $action);
    
            if (!empty($report)) {
    
    
                $parameters['reportModule'] = $module;
    
                $parameters['reportAction'] = $action;
    
                return array(new CoreHomeController(), 'renderReportWidget');
            }
    
    
    mattab's avatar
    mattab a validé
            if (!empty($action) && Report::PREFIX_ACTION_IN_MENU === substr($action, 0, strlen(Report
                ::PREFIX_ACTION_IN_MENU))) {
    
                $reportAction = lcfirst(substr($action, 4)); // menuGetPageUrls => getPageUrls
                $report       = Report::factory($module, $reportAction);
    
                if (!empty($report)) {
                    $parameters['reportModule'] = $module;
                    $parameters['reportAction'] = $reportAction;
    
                    return array(new CoreHomeController(), 'renderReportMenu');
                }
    
            $this->triggerControllerActionNotFoundError($module, $action);
        }
    
        protected function triggerControllerActionNotFoundError($module, $action)
        {
            throw new Exception("Action '$action' not found in the module '$module'.");
    
        protected function getClassNameController($module)
        {
            return "\\Piwik\\Plugins\\$module\\Controller";
        }
    
    
         * Executes the requested plugin controller method and returns the data, capturing anything the
         * method `echo`s.
    
         * _Note: If the plugin controller returns something, the return value is returned instead
         * of whatever is in the output buffer._
    
         * @param string $module The name of the plugin whose controller to execute, eg, `'UserCountryMap'`.
         * @param string $action The controller action name, eg, `'realtimeMap'`.
         * @param array $parameters Array of parameters to pass to the controller action method.
         * @return string The `echo`'d data or the return value of the controller action.
    
        public function fetchDispatch($module = null, $actionName = null, $parameters = null)
    
            $output = $this->dispatch($module, $actionName, $parameters);
    
            // if nothing returned we try to load something that was printed on the screen
            if (empty($output)) {
                $output = ob_get_contents();
    
            } else {
                // if something was returned, flush output buffer as it is meant to be written to the screen
                ob_flush();
    
            }
            ob_end_clean();
            return $output;
        }
    
        /**
         * Called at the end of the page generation
         */
    
        public function __destruct()
    
                    && !SettingsServer::isTrackerApiRequest()
                ) {
    
                    // in tracker mode Piwik\Tracker\Db\Pdo\Mysql does currently not implement profiling
    
    Benaka Moorthi's avatar
    Benaka Moorthi a validé
                    Profiler::displayDbProfileReport();
                    Profiler::printQueryCount();
                }
    
                Log::verbose($e);
    
            }
        }
    
        // Should we show exceptions messages directly rather than display an html error page?
        public static function shouldRethrowException()
        {
            // If we are in no dispatch mode, eg. a script reusing Piwik libs,
            // then we should return the exception directly, rather than trigger the event "bad config file"
            // which load the HTML page of the installer with the error.
            return (defined('PIWIK_ENABLE_DISPATCH') && !PIWIK_ENABLE_DISPATCH)
    
            || Common::isPhpCliMode()
            || SettingsServer::isArchivePhpTriggered();
    
    Christian Raue's avatar
    Christian Raue a validé
        public static function setUpSafeMode()
    
        {
            register_shutdown_function(array('\\Piwik\\FrontController','triggerSafeModeWhenError'));
        }
    
    
    Christian Raue's avatar
    Christian Raue a validé
        public static function triggerSafeModeWhenError()
    
        {
            $lastError = error_get_last();
            if (!empty($lastError) && $lastError['type'] == E_ERROR) {
                $controller = FrontController::getInstance();
                $controller->init();
                $message = $controller->dispatch('CorePluginsAdmin', 'safemode', array($lastError));
    
                echo $message;
            }
        }
    
    
        /**
         * Loads the config file and assign to the global registry
    
    sgiehl's avatar
    sgiehl a validé
         * This is overridden in tests to ensure test config file is used
         *
         * @return Exception
    
    Christian Raue's avatar
    Christian Raue a validé
        public static function createConfigObject()
    
                Config::getInstance()->database; // access property to check if the local file exists
    
            } catch (Exception $exception) {
    
                Log::debug($exception);
    
                 * Triggered when the configuration file cannot be found or read, which usually
                 * means Piwik is not installed yet.
    
                 * This event can be used to start the installation process or to display a custom error message.
    
                 * @param Exception $exception The exception that was thrown by `Config::getInstance()`.
    
                Piwik::postEvent('Config.NoConfigurationFile', array($exception), $pending = true);
    
                $exceptionToThrow = $exception;
    
            }
            return $exceptionToThrow;
        }
    
        /**
         * Must be called before dispatch()
         * - checks that directories are writable,
         * - loads the configuration file,
         * - loads the plugin,
         * - inits the DB connection,
         * - etc.
    
    sgiehl's avatar
    sgiehl a validé
         *
    
    sgiehl's avatar
    sgiehl a validé
         * @return void
    
        public function init()
    
        {
            static $initialized = false;
            if ($initialized) {
                return;
            }
            $initialized = true;
    
    
            Registry::set('timer', new Timer);
    
            $directoriesToCheck = array(
                '/tmp/',
                '/tmp/assets/',
                '/tmp/cache/',
                '/tmp/logs/',
                '/tmp/tcpdf/',
                '/tmp/templates_c/',
            );
    
            Translate::loadEnglishTranslation();
    
            Filechecks::dieIfDirectoriesNotWritable($directoriesToCheck);
    
            $exceptionToThrow = self::createConfigObject();
    
            $this->handleMaintenanceMode();
            $this->handleProfiler();
            $this->handleSSLRedirection();
    
            Plugin\Manager::getInstance()->loadPluginTranslations('en');
            Plugin\Manager::getInstance()->loadActivatedPlugins();
    
            if ($exceptionToThrow) {
                throw $exceptionToThrow;
            }
    
            // try to connect to the database
            try {
                Db::createDatabaseObject();
                Db::fetchAll("SELECT DATABASE()");
            } catch (Exception $exception) {
                if (self::shouldRethrowException()) {
    
                    throw $exception;
    
                 * Triggered when Piwik cannot connect to the database.
    
                 * This event can be used to start the installation process or to display a custom error
                 * message.
    
                 * @param Exception $exception The exception thrown from creating and testing the database
                 *                             connection.
    
                Piwik::postEvent('Db.cannotConnectToDb', array($exception), $pending = true);
    
            // try to get an option (to check if data can be queried)
            try {
                Option::get('TestingIfDatabaseConnectionWorked');
            } catch (Exception $exception) {
                if (self::shouldRethrowException()) {
                    throw $exception;
    
                 * Triggered when Piwik cannot access database data.
    
                 * This event can be used to start the installation process or to display a custom error
                 * message.
    
                 * @param Exception $exception The exception thrown from trying to get an option value.
    
                Piwik::postEvent('Config.badConfigurationFile', array($exception), $pending = true);
    
            // Init the Access object, so that eg. core/Updates/* can enforce Super User and use some APIs
            Access::getInstance();
    
            /**
             * Triggered just after the platform is initialized and plugins are loaded.
             *
             * This event can be used to do early initialization.
             *
             * _Note: At this point the user is not authenticated yet._
             */
            Piwik::postEvent('Request.dispatchCoreAndPluginUpdatesScreen');
    
            \Piwik\Plugin\Manager::getInstance()->installLoadedPlugins();
    
            // ensure the current Piwik URL is known for later use
            if (method_exists('Piwik\SettingsPiwik', 'getPiwikUrl')) {
                SettingsPiwik::getPiwikUrl();
    
    
            /**
             * Triggered before the user is authenticated, when the global authentication object
             * should be created.
             *
             * Plugins that provide their own authentication implementation should use this event
             * to set the global authentication object (which must derive from {@link Piwik\Auth}).
             *
             * **Example**
             *
             *     Piwik::addAction('Request.initAuthenticationObject', function() {
             *         Piwik\Registry::set('auth', new MyAuthImplementation());
             *     });
             */
            Piwik::postEvent('Request.initAuthenticationObject');
            try {
                $authAdapter = Registry::get('auth');
            } catch (Exception $e) {
    
                throw new HtmlMessageException("Authentication object cannot be found in the Registry. Maybe the Login plugin is not activated?
    
                                <br />You can activate the plugin by adding:<br />
                                <code>Plugins[] = Login</code><br />
                                under the <code>[Plugins]</code> section in your config/config.ini.php");
            }
            Access::getInstance()->reloadAccess($authAdapter);
    
            // Force the auth to use the token_auth if specified, so that embed dashboard
            // and all other non widgetized controller methods works fine
            if (Common::getRequestVar('token_auth', false, 'string') !== false) {
                Request::reloadAuthUsingTokenAuth();
    
            SettingsServer::raiseMemoryLimitIfNecessary();
    
            Translate::reloadLanguage();
            \Piwik\Plugin\Manager::getInstance()->postLoadPlugins();
    
            /**
             * Triggered after the platform is initialized and after the user has been authenticated, but
             * before the platform has handled the request.
             *
             * Piwik uses this event to check for updates to Piwik.
             */
            Piwik::postEvent('Platform.initialized');
    
        protected function prepareDispatch($module, $action, $parameters)
        {
            if (is_null($module)) {
    
    mattab's avatar
    mattab a validé
                $module = Common::getRequestVar('module', self::DEFAULT_MODULE, 'string');
    
            }
    
            if (is_null($action)) {
                $action = Common::getRequestVar('action', false);
            }
    
    
            if (SettingsPiwik::isPiwikInstalled()
    
                && ($module !== 'API' || ($action && $action !== 'index'))
            ) {
                Session::start();
            }
    
            if (is_null($parameters)) {
                $parameters = array();
            }
    
            if (!ctype_alnum($module)) {
                throw new Exception("Invalid module name '$module'");
            }
    
            $module = Request::renameModule($module);
    
            if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated($module)) {
                throw new PluginDeactivatedException($module);
            }
    
    
            return array($module, $action, $parameters);
    
        protected function handleMaintenanceMode()
        {
    
            if (Config::getInstance()->General['maintenance_mode'] == 1
    
    mattab's avatar
    mattab a validé
                && !Common::isPhpCliMode()
    
                $format = Common::getRequestVar('format', '');
    
    
                $message = "Piwik is in scheduled maintenance. Please come back later."
                    . " The administrator can disable maintenance by editing the file piwik/config/config.ini.php and removing the following: "
                    . " maintenance_mode=1 ";
    
                if (Config::getInstance()->Tracker['record_statistics'] == 0) {
    
                    $message .= ' and record_statistics=0';
                }
    
                $exception = new Exception($message);
                // extend explain how to re-enable
                // show error message when record stats = 0
                if (empty($format)) {
                    throw $exception;
                }
    
                $response = new ResponseBuilder($format);
    
                echo $response->getResponseException($exception);
                exit;
            }
        }
    
        protected function handleSSLRedirection()
        {
    
            // Specifically disable for the opt out iframe
    
            if (Piwik::getModule() == 'CoreAdminHome' && Piwik::getAction() == 'optOut') {
    
            // Disable Https for VisitorGenerator
    
            if (Piwik::getModule() == 'VisitorGenerator') {
    
            // Only enable this feature after Piwik is already installed
    
            if (!SettingsPiwik::isPiwikInstalled()) {
    
            // proceed only when force_ssl = 1
    
            if (!SettingsPiwik::isHttpsForced()) {
    
                return;
            }
            Url::redirectToHttps();
    
    mattab's avatar
    mattab a validé
        private function handleProfiler()
        {
            if (!empty($_GET['xhprof'])) {
    
                $mainRun = $_GET['xhprof'] == 1; // core:archive command sets xhprof=2
    
    mattab's avatar
    mattab a validé
                Profiler::setupProfilerXHProf($mainRun);
            }
        }
    
    
        /**
         * @param $module
         * @param $action
         * @param $parameters
         * @return mixed
         */
        private function doDispatch($module, $action, $parameters)
        {
            list($module, $action, $parameters) = $this->prepareDispatch($module, $action, $parameters);
    
            /**
             * Triggered directly before controller actions are dispatched.
             *
             * This event can be used to modify the parameters passed to one or more controller actions
             * and can be used to change the controller action being dispatched to.
             *
             * @param string &$module The name of the plugin being dispatched to.
             * @param string &$action The name of the controller method being dispatched to.
             * @param array &$parameters The arguments passed to the controller action.
             */
            Piwik::postEvent('Request.dispatch', array(&$module, &$action, &$parameters));
    
    
            list($controller, $actionToCall) = $this->makeController($module, $action, $parameters);
    
    
            /**
             * Triggered directly before controller actions are dispatched.
             *
             * This event exists for convenience and is triggered directly after the {@hook Request.dispatch}
             * event is triggered.
             *
             * It can be used to do the same things as the {@hook Request.dispatch} event, but for one controller
             * action only. Using this event will result in a little less code than {@hook Request.dispatch}.
             *
             * @param array &$parameters The arguments passed to the controller action.
             */
            Piwik::postEvent(sprintf('Controller.%s.%s', $module, $action), array(&$parameters));
    
    
            $result = call_user_func_array(array($controller, $actionToCall), $parameters);
    
    
            /**
             * Triggered after a controller action is successfully called.
             *
             * This event exists for convenience and is triggered immediately before the {@hook Request.dispatch.end}
             * event is triggered.
             *
             * It can be used to do the same things as the {@hook Request.dispatch.end} event, but for one
             * controller action only. Using this event will result in a little less code than
             * {@hook Request.dispatch.end}.
             *
             * @param mixed &$result The result of the controller action.
             * @param array $parameters The arguments passed to the controller action.
             */
            Piwik::postEvent(sprintf('Controller.%s.%s.end', $module, $action), array(&$result, $parameters));
    
            /**
             * Triggered after a controller action is successfully called.
             *
             * This event can be used to modify controller action output (if any) before the output is returned.
             *
             * @param mixed &$result The controller action result.
             * @param array $parameters The arguments passed to the controller action.
             */
    
            Piwik::postEvent('Request.dispatch.end', array(&$result, $module, $action, $parameters));
    
    
        /**
         * Returns HTML that displays an exception's error message (and possibly stack trace).
         * The result of this method is echo'd by dispatch.php.
         *
         * @param Exception $ex The exception to use when generating the error page's HTML.
         * @return string The HTML to echo.
         */
        public function getErrorResponse(Exception $ex)
        {
            $debugTrace = $ex->getTraceAsString();
    
    
            if (method_exists($ex, 'getHtmlMessage')) {
                $message = $ex->getHtmlMessage();
    
            } else {
                $message = Common::sanitizeInputValue($ex->getMessage());
            }
    
    
            $logo = new CustomLogo();
    
    
            $logoHeaderUrl = false;
            $logoFaviconUrl = false;
            try {
                $logoHeaderUrl = $logo->getHeaderLogoUrl();
                $logoFaviconUrl = $logo->getPathUserFavicon();
            } catch (Exception $ex) {
                Log::debug($ex);
            }
    
            $result = Piwik_GetErrorMessagePage($message, $debugTrace, true, true, $logoHeaderUrl, $logoFaviconUrl);
    
    
            /**
             * Triggered before a Piwik error page is displayed to the user.
             *
             * This event can be used to modify the content of the error page that is displayed when
             * an exception is caught.
             *
             * @param string &$result The HTML of the error page.
             * @param Exception $ex The Exception displayed in the error page.
             */
            Piwik::postEvent('FrontController.modifyErrorPage', array(&$result, $ex));
    
            return $result;