* @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\SessionAwareWebApplicationTrait; use Joomla\Application\Web\WebClient; use Joomla\CMS\Authentication\Authentication; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Event\AbstractEvent; use Joomla\CMS\Event\ErrorEvent; use Joomla\CMS\Exception\ExceptionHandler; use Joomla\CMS\Extension\ExtensionManagerTrait; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Input\Input; use Joomla\CMS\Language\Language; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\Menu\AbstractMenu; use Joomla\CMS\Menu\MenuFactoryInterface; use Joomla\CMS\Pathway\Pathway; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Profiler\Profiler; use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Router; use Joomla\CMS\Session\MetadataManager; use Joomla\CMS\Session\Session; use Joomla\CMS\Uri\Uri; use Joomla\DI\Container; use Joomla\DI\ContainerAwareInterface; use Joomla\DI\ContainerAwareTrait; use Joomla\Registry\Registry; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! CMS Application class * * @since 3.2 */ abstract class CMSApplication extends WebApplication implements ContainerAwareInterface, CMSWebApplicationInterface { use ContainerAwareTrait; use ExtensionManagerTrait; use ExtensionNamespaceMapper; use SessionAwareWebApplicationTrait; /** * Array of options for the \JDocument object * * @var array * @since 3.2 */ protected $docOptions = array(); /** * Application instances container. * * @var CmsApplication[] * @since 3.2 */ protected static $instances = array(); /** * The scope of the application. * * @var string * @since 3.2 */ public $scope = null; /** * The client identifier. * * @var integer * @since 4.0.0 */ protected $clientId = null; /** * The application message queue. * * @var array * @since 4.0.0 */ protected $messageQueue = array(); /** * The name of the application. * * @var string * @since 4.0.0 */ protected $name = null; /** * The profiler instance * * @var Profiler * @since 3.2 */ protected $profiler = null; /** * Currently active template * * @var object * @since 3.2 */ protected $template = null; /** * The pathway object * * @var Pathway * @since 4.0.0 */ protected $pathway = null; /** * The authentication plugin type * * @var string * @since 4.0.0 */ protected $authenticationPluginType = 'authentication'; /** * Menu instances container. * * @var AbstractMenu[] * @since 4.2.0 */ protected $menus = []; /** * The menu factory * * @var MenuFactoryInterface * * @since 4.2.0 */ private $menuFactory; /** * Class constructor. * * @param Input $input An optional argument to provide dependency injection for the application's input * object. If the argument is a JInput object that object will become the * application's input object, otherwise a default input object is created. * @param Registry $config An optional argument to provide dependency injection for the application's config * object. If the argument is a Registry object that object will become the * application's config object, otherwise a default config object is created. * @param WebClient $client An optional argument to provide dependency injection for the application's client * object. If the argument is a WebClient object that object will become the * application's client object, otherwise a default client object is created. * @param Container $container Dependency injection container. * * @since 3.2 */ public function __construct(Input $input = null, Registry $config = null, WebClient $client = null, Container $container = null) { $container = $container ?: new Container(); $this->setContainer($container); parent::__construct($input, $config, $client); // If JDEBUG is defined, load the profiler instance if (\defined('JDEBUG') && JDEBUG) { $this->profiler = Profiler::getInstance('Application'); } // Enable sessions by default. if ($this->config->get('session') === null) { $this->config->set('session', true); } // Set the session default name. if ($this->config->get('session_name') === null) { $this->config->set('session_name', $this->getName()); } } /** * Checks the user session. * * If the session record doesn't exist, initialise it. * If session is new, create session variables * * @return void * * @since 3.2 * @throws \RuntimeException */ public function checkSession() { $this->getContainer()->get(MetadataManager::class)->createOrUpdateRecord($this->getSession(), $this->getIdentity()); } /** * Enqueue a system message. * * @param string $msg The message to enqueue. * @param string $type The message type. Default is message. * * @return void * * @since 3.2 */ public function enqueueMessage($msg, $type = self::MSG_INFO) { // Don't add empty messages. if ($msg === null || trim($msg) === '') { return; } $inputFilter = InputFilter::getInstance( [], [], InputFilter::ONLY_BLOCK_DEFINED_TAGS, InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES ); // Build the message array and apply the HTML InputFilter with the default blacklist to the message $message = array( 'message' => $inputFilter->clean($msg, 'html'), 'type' => $inputFilter->clean(strtolower($type), 'cmd'), ); // For empty queue, if messages exists in the session, enqueue them first. $messages = $this->getMessageQueue(); if (!\in_array($message, $this->messageQueue)) { // Enqueue the message. $this->messageQueue[] = $message; } } /** * Ensure several core system input variables are not arrays. * * @return void * * @since 3.9 */ private function sanityCheckSystemVariables() { $input = $this->input; // Get invalid input variables $invalidInputVariables = array_filter( array('option', 'view', 'format', 'lang', 'Itemid', 'template', 'templateStyle', 'task'), function ($systemVariable) use ($input) { return $input->exists($systemVariable) && is_array($input->getRaw($systemVariable)); } ); // Unset invalid system variables foreach ($invalidInputVariables as $systemVariable) { $input->set($systemVariable, null); } // Abort when there are invalid variables if ($invalidInputVariables) { throw new \RuntimeException('Invalid input, aborting application.'); } } /** * Execute the application. * * @return void * * @since 3.2 */ public function execute() { try { $this->sanityCheckSystemVariables(); $this->setupLogging(); $this->createExtensionNamespaceMap(); // Perform application routines. $this->doExecute(); // If we have an application document object, render it. if ($this->document instanceof \Joomla\CMS\Document\Document) { // Render the application output. $this->render(); } // If gzip compression is enabled in configuration and the server is compliant, compress the output. if ($this->get('gzip') && !ini_get('zlib.output_compression') && ini_get('output_handler') !== 'ob_gzhandler') { $this->compress(); // Trigger the onAfterCompress event. $this->triggerEvent('onAfterCompress'); } } catch (\Throwable $throwable) { /** @var ErrorEvent $event */ $event = AbstractEvent::create( 'onError', [ 'subject' => $throwable, 'eventClass' => ErrorEvent::class, 'application' => $this, ] ); // Trigger the onError event. $this->triggerEvent('onError', $event); ExceptionHandler::handleException($event->getError()); } // Trigger the onBeforeRespond event. $this->getDispatcher()->dispatch('onBeforeRespond'); // Send the application response. $this->respond(); // Trigger the onAfterRespond event. $this->getDispatcher()->dispatch('onAfterRespond'); } /** * Check if the user is required to reset their password. * * If the user is required to reset their password will be redirected to the page that manage the password reset. * * @param string $option The option that manage the password reset * @param string $view The view that manage the password reset * @param string $layout The layout of the view that manage the password reset * @param string $tasks Permitted tasks * * @return void * * @throws \Exception */ protected function checkUserRequireReset($option, $view, $layout, $tasks) { if (Factory::getUser()->get('requireReset', 0)) { $redirect = false; /* * By default user profile edit page is used. * That page allows you to change more than just the password and might not be the desired behavior. * This allows a developer to override the page that manage the password reset. * (can be configured using the file: configuration.php, or if extended, through the global configuration form) */ $name = $this->getName(); if ($this->get($name . '_reset_password_override', 0)) { $option = $this->get($name . '_reset_password_option', ''); $view = $this->get($name . '_reset_password_view', ''); $layout = $this->get($name . '_reset_password_layout', ''); $tasks = $this->get($name . '_reset_password_tasks', ''); } $task = $this->input->getCmd('task', ''); // Check task or option/view/layout if (!empty($task)) { $tasks = explode(',', $tasks); // Check full task version "option/task" if (array_search($this->input->getCmd('option', '') . '/' . $task, $tasks) === false) { // Check short task version, must be on the same option of the view if ($this->input->getCmd('option', '') !== $option || array_search($task, $tasks) === false) { // Not permitted task $redirect = true; } } } else { if ( $this->input->getCmd('option', '') !== $option || $this->input->getCmd('view', '') !== $view || $this->input->getCmd('layout', '') !== $layout ) { // Requested a different option/view/layout $redirect = true; } } if ($redirect) { // Redirect to the profile edit page $this->enqueueMessage(Text::_('JGLOBAL_PASSWORD_RESET_REQUIRED'), 'notice'); $url = Route::_('index.php?option=' . $option . '&view=' . $view . '&layout=' . $layout, false); // In the administrator we need a different URL if (strtolower($name) === 'administrator') { $user = Factory::getApplication()->getIdentity(); $url = Route::_('index.php?option=' . $option . '&task=' . $view . '.' . $layout . '&id=' . $user->id, false); } $this->redirect($url); } } } /** * Gets a configuration value. * * @param string $varname The name of the value to get. * @param string $default Default value to return * * @return mixed The user state. * * @since 3.2 * @deprecated 5.0 Use get() instead */ public function getCfg($varname, $default = null) { try { Log::add( sprintf('%s() is deprecated and will be removed in 5.0. Use JFactory->getApplication()->get() instead.', __METHOD__), Log::WARNING, 'deprecated' ); } catch (\RuntimeException $exception) { // Informational log only } return $this->get($varname, $default); } /** * Gets the client id of the current running application. * * @return integer A client identifier. * * @since 3.2 */ public function getClientId() { return $this->clientId; } /** * Returns a reference to the global CmsApplication object, only creating it if it doesn't already exist. * * This method must be invoked as: $web = CmsApplication::getInstance(); * * @param string $name The name (optional) of the CmsApplication class to instantiate. * @param string $prefix The class name prefix of the object. * @param Container $container An optional dependency injection container to inject into the application. * * @return CmsApplication * * @since 3.2 * @throws \RuntimeException * @deprecated 5.0 Use \Joomla\CMS\Factory::getContainer()->get($name) instead */ public static function getInstance($name = null, $prefix = '\JApplication', Container $container = null) { if (empty(static::$instances[$name])) { // Create a CmsApplication object. $classname = $prefix . ucfirst($name); if (!$container) { $container = Factory::getContainer(); } if ($container->has($classname)) { static::$instances[$name] = $container->get($classname); } elseif (class_exists($classname)) { // @todo This creates an implicit hard requirement on the ApplicationCms constructor static::$instances[$name] = new $classname(null, null, null, $container); } else { throw new \RuntimeException(Text::sprintf('JLIB_APPLICATION_ERROR_APPLICATION_LOAD', $name), 500); } static::$instances[$name]->loadIdentity(Factory::getUser()); } return static::$instances[$name]; } /** * Returns the application \JMenu object. * * @param string $name The name of the application/client. * @param array $options An optional associative array of configuration settings. * * @return AbstractMenu * * @since 3.2 */ public function getMenu($name = null, $options = array()) { if (!isset($name)) { $name = $this->getName(); } // Inject this application object into the \JMenu tree if one isn't already specified if (!isset($options['app'])) { $options['app'] = $this; } if (array_key_exists($name, $this->menus)) { return $this->menus[$name]; } if ($this->menuFactory === null) { @trigger_error('Menu factory must be set in 5.0', E_USER_DEPRECATED); $this->menuFactory = $this->getContainer()->get(MenuFactoryInterface::class); } $this->menus[$name] = $this->menuFactory->createMenu($name, $options); // Make sure the abstract menu has the instance too, is needed for BC and will be removed with version 5 AbstractMenu::$instances[$name] = $this->menus[$name]; return $this->menus[$name]; } /** * Get the system message queue. * * @param boolean $clear Clear the messages currently attached to the application object * * @return array The system message queue. * * @since 3.2 */ public function getMessageQueue($clear = false) { // For empty queue, if messages exists in the session, enqueue them. if (!\count($this->messageQueue)) { $sessionQueue = $this->getSession()->get('application.queue', []); if ($sessionQueue) { $this->messageQueue = $sessionQueue; $this->getSession()->set('application.queue', []); } } $messageQueue = $this->messageQueue; if ($clear) { $this->messageQueue = array(); } return $messageQueue; } /** * Gets the name of the current running application. * * @return string The name of the application. * * @since 3.2 */ public function getName() { return $this->name; } /** * Returns the application Pathway object. * * @return Pathway * * @since 3.2 */ public function getPathway() { if (!$this->pathway) { $resourceName = ucfirst($this->getName()) . 'Pathway'; if (!$this->getContainer()->has($resourceName)) { throw new \RuntimeException( Text::sprintf('JLIB_APPLICATION_ERROR_PATHWAY_LOAD', $this->getName()), 500 ); } $this->pathway = $this->getContainer()->get($resourceName); } return $this->pathway; } /** * Returns the application Router object. * * @param string $name The name of the application. * @param array $options An optional associative array of configuration settings. * * @return Router * * @since 3.2 * * @deprecated 5.0 Inject the router or load it from the dependency injection container */ public static function getRouter($name = null, array $options = array()) { $app = Factory::getApplication(); if (!isset($name)) { $name = $app->getName(); } $options['mode'] = $app->get('sef'); return Router::getInstance($name, $options); } /** * Gets the name of the current template. * * @param boolean $params An optional associative array of configuration settings * * @return mixed System is the fallback. * * @since 3.2 */ public function getTemplate($params = false) { if ($params) { $template = new \stdClass(); $template->template = 'system'; $template->params = new Registry(); $template->inheritable = 0; $template->parent = ''; return $template; } return 'system'; } /** * Gets a user state. * * @param string $key The path of the state. * @param mixed $default Optional default value, returned if the internal value is null. * * @return mixed The user state or null. * * @since 3.2 */ public function getUserState($key, $default = null) { $registry = $this->getSession()->get('registry'); if ($registry !== null) { return $registry->get($key, $default); } return $default; } /** * Gets the value of a user state variable. * * @param string $key The key of the user state variable. * @param string $request The name of the variable passed in a request. * @param string $default The default value for the variable if not found. Optional. * @param string $type Filter for the variable, for valid values see {@link InputFilter::clean()}. Optional. * * @return mixed The request user state. * * @since 3.2 */ public function getUserStateFromRequest($key, $request, $default = null, $type = 'none') { $cur_state = $this->getUserState($key, $default); $new_state = $this->input->get($request, null, $type); if ($new_state === null) { return $cur_state; } // Save the new value only if it was set in this request. $this->setUserState($key, $new_state); return $new_state; } /** * Initialise the application. * * @param array $options An optional associative array of configuration settings. * * @return void * * @since 3.2 */ protected function initialiseApp($options = array()) { // Check that we were given a language in the array (since by default may be blank). if (isset($options['language'])) { $this->set('language', $options['language']); } // Build our language object $lang = Language::getInstance($this->get('language'), $this->get('debug_lang')); // Load the language to the API $this->loadLanguage($lang); // Register the language object with Factory Factory::$language = $this->getLanguage(); // Load the library language files $this->loadLibraryLanguage(); // Set user specific editor. $user = Factory::getUser(); $editor = $user->getParam('editor', $this->get('editor')); if (!PluginHelper::isEnabled('editors', $editor)) { $editor = $this->get('editor'); if (!PluginHelper::isEnabled('editors', $editor)) { $editor = 'none'; } } $this->set('editor', $editor); // Load the behaviour plugins PluginHelper::importPlugin('behaviour'); // Trigger the onAfterInitialise event. PluginHelper::importPlugin('system'); $this->triggerEvent('onAfterInitialise'); } /** * Checks if HTTPS is forced in the client configuration. * * @param integer $clientId An optional client id (defaults to current application client). * * @return boolean True if is forced for the client, false otherwise. * * @since 3.7.3 */ public function isHttpsForced($clientId = null) { $clientId = (int) ($clientId !== null ? $clientId : $this->getClientId()); $forceSsl = (int) $this->get('force_ssl'); if ($clientId === 0 && $forceSsl === 2) { return true; } if ($clientId === 1 && $forceSsl >= 1) { return true; } return false; } /** * Check the client interface by name. * * @param string $identifier String identifier for the application interface * * @return boolean True if this application is of the given type client interface. * * @since 3.7.0 */ public function isClient($identifier) { return $this->getName() === $identifier; } /** * Load the library language files for the application * * @return void * * @since 3.6.3 */ protected function loadLibraryLanguage() { $this->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR); } /** * Login authentication function. * * Username and encoded password are passed the onUserLogin event which * is responsible for the user validation. A successful validation updates * the current session record with the user's details. * * Username and encoded password are sent as credentials (along with other * possibilities) to each observer (authentication plugin) for user * validation. Successful validation will update the current session with * the user details. * * @param array $credentials Array('username' => string, 'password' => string) * @param array $options Array('remember' => boolean) * * @return boolean|\Exception True on success, false if failed or silent handling is configured, or a \Exception object on authentication error. * * @since 3.2 */ public function login($credentials, $options = array()) { // Get the global Authentication object. $authenticate = Authentication::getInstance($this->authenticationPluginType); $response = $authenticate->authenticate($credentials, $options); // Import the user plugin group. PluginHelper::importPlugin('user'); if ($response->status === Authentication::STATUS_SUCCESS) { /* * Validate that the user should be able to login (different to being authenticated). * This permits authentication plugins blocking the user. */ $authorisations = $authenticate->authorise($response, $options); $denied_states = Authentication::STATUS_EXPIRED | Authentication::STATUS_DENIED; foreach ($authorisations as $authorisation) { if ((int) $authorisation->status & $denied_states) { // Trigger onUserAuthorisationFailure Event. $this->triggerEvent('onUserAuthorisationFailure', array((array) $authorisation)); // If silent is set, just return false. if (isset($options['silent']) && $options['silent']) { return false; } // Return the error. switch ($authorisation->status) { case Authentication::STATUS_EXPIRED: Factory::getApplication()->enqueueMessage(Text::_('JLIB_LOGIN_EXPIRED'), 'error'); return false; case Authentication::STATUS_DENIED: Factory::getApplication()->enqueueMessage(Text::_('JLIB_LOGIN_DENIED'), 'error'); return false; default: Factory::getApplication()->enqueueMessage(Text::_('JLIB_LOGIN_AUTHORISATION'), 'error'); return false; } } } // OK, the credentials are authenticated and user is authorised. Let's fire the onLogin event. $results = $this->triggerEvent('onUserLogin', array((array) $response, $options)); /* * If any of the user plugins did not successfully complete the login routine * then the whole method fails. * * Any errors raised should be done in the plugin as this provides the ability * to provide much more information about why the routine may have failed. */ $user = Factory::getUser(); if ($response->type === 'Cookie') { $user->set('cookieLogin', true); } if (\in_array(false, $results, true) == false) { $options['user'] = $user; $options['responseType'] = $response->type; // The user is successfully logged in. Run the after login events $this->triggerEvent('onUserAfterLogin', array($options)); return true; } } // Trigger onUserLoginFailure Event. $this->triggerEvent('onUserLoginFailure', array((array) $response)); // If silent is set, just return false. if (isset($options['silent']) && $options['silent']) { return false; } // If status is success, any error will have been raised by the user plugin if ($response->status !== Authentication::STATUS_SUCCESS) { $this->getLogger()->warning($response->error_message, array('category' => 'jerror')); } return false; } /** * Logout authentication function. * * Passed the current user information to the onUserLogout event and reverts the current * session record back to 'anonymous' parameters. * If any of the authentication plugins did not successfully complete * the logout routine then the whole method fails. Any errors raised * should be done in the plugin as this provides the ability to give * much more information about why the routine may have failed. * * @param integer $userid The user to load - Can be an integer or string - If string, it is converted to ID automatically * @param array $options Array('clientid' => array of client id's) * * @return boolean True on success * * @since 3.2 */ public function logout($userid = null, $options = array()) { // Get a user object from the Application. $user = Factory::getUser($userid); // Build the credentials array. $parameters['username'] = $user->get('username'); $parameters['id'] = $user->get('id'); // Set clientid in the options array if it hasn't been set already and shared sessions are not enabled. if (!$this->get('shared_session', '0') && !isset($options['clientid'])) { $options['clientid'] = $this->getClientId(); } // Import the user plugin group. PluginHelper::importPlugin('user'); // OK, the credentials are built. Lets fire the onLogout event. $results = $this->triggerEvent('onUserLogout', array($parameters, $options)); // Check if any of the plugins failed. If none did, success. if (!\in_array(false, $results, true)) { $options['username'] = $user->get('username'); $this->triggerEvent('onUserAfterLogout', array($options)); return true; } // Trigger onUserLogoutFailure Event. $this->triggerEvent('onUserLogoutFailure', array($parameters)); return false; } /** * Redirect to another URL. * * If the headers have not been sent the redirect will be accomplished using a "301 Moved Permanently" * or "303 See Other" code in the header pointing to the new location. If the headers have already been * sent this will be accomplished using a JavaScript statement. * * @param string $url The URL to redirect to. Can only be http/https URL * @param integer $status The HTTP 1.1 status code to be provided. 303 is assumed by default. * * @return void * * @since 3.2 */ public function redirect($url, $status = 303) { // Persist messages if they exist. if (\count($this->messageQueue)) { $this->getSession()->set('application.queue', $this->messageQueue); } // Hand over processing to the parent now parent::redirect($url, $status); } /** * Rendering is the process of pushing the document buffers into the template * placeholders, retrieving data from the document and pushing it into * the application response buffer. * * @return void * * @since 3.2 */ protected function render() { // Setup the document options. $this->docOptions['template'] = $this->get('theme'); $this->docOptions['file'] = $this->get('themeFile', 'index.php'); $this->docOptions['params'] = $this->get('themeParams'); $this->docOptions['csp_nonce'] = $this->get('csp_nonce'); $this->docOptions['templateInherits'] = $this->get('themeInherits'); if ($this->get('themes.base')) { $this->docOptions['directory'] = $this->get('themes.base'); } else { // Fall back to constants. $this->docOptions['directory'] = \defined('JPATH_THEMES') ? JPATH_THEMES : (\defined('JPATH_BASE') ? JPATH_BASE : __DIR__) . '/themes'; } // Parse the document. $this->document->parse($this->docOptions); // Trigger the onBeforeRender event. PluginHelper::importPlugin('system'); $this->triggerEvent('onBeforeRender'); $caching = false; if ($this->isClient('site') && $this->get('caching') && $this->get('caching', 2) == 2 && !Factory::getUser()->get('id')) { $caching = true; } // Render the document. $data = $this->document->render($caching, $this->docOptions); // Set the application output data. $this->setBody($data); // Trigger the onAfterRender event. $this->triggerEvent('onAfterRender'); // Mark afterRender in the profiler. JDEBUG ? $this->profiler->mark('afterRender') : null; } /** * Route the application. * * Routing is the process of examining the request environment to determine which * component should receive the request. The component optional parameters * are then set in the request object to be processed when the application is being * dispatched. * * @return void * * @since 3.2 * * @deprecated 5.0 Implement the route functionality in the extending class, this here will be removed without replacement */ protected function route() { // Get the full request URI. $uri = clone Uri::getInstance(); $router = static::getRouter(); $result = $router->parse($uri, true); $active = $this->getMenu()->getActive(); if ( $active !== null && $active->type === 'alias' && $active->getParams()->get('alias_redirect') && \in_array($this->input->getMethod(), array('GET', 'HEAD'), true) ) { $item = $this->getMenu()->getItem($active->getParams()->get('aliasoptions')); if ($item !== null) { $oldUri = clone Uri::getInstance(); if ($oldUri->getVar('Itemid') == $active->id) { $oldUri->setVar('Itemid', $item->id); } $base = Uri::base(true); $oldPath = StringHelper::strtolower(substr($oldUri->getPath(), \strlen($base) + 1)); $activePathPrefix = StringHelper::strtolower($active->route); $position = strpos($oldPath, $activePathPrefix); if ($position !== false) { $oldUri->setPath($base . '/' . substr_replace($oldPath, $item->route, $position, \strlen($activePathPrefix))); $this->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00 GMT', true); $this->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true); $this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate', false); $this->sendHeaders(); $this->redirect((string) $oldUri, 301); } } } foreach ($result as $key => $value) { $this->input->def($key, $value); } // Trigger the onAfterRoute event. PluginHelper::importPlugin('system'); $this->triggerEvent('onAfterRoute'); } /** * Sets the value of a user state variable. * * @param string $key The path of the state. * @param mixed $value The value of the variable. * * @return mixed|void The previous state, if one existed. * * @since 3.2 */ public function setUserState($key, $value) { $session = $this->getSession(); $registry = $session->get('registry'); if ($registry !== null) { return $registry->set($key, $value); } } /** * Sends all headers prior to returning the string * * @param boolean $compress If true, compress the data * * @return string * * @since 3.2 */ public function toString($compress = false) { // Don't compress something if the server is going to do it anyway. Waste of time. if ($compress && !ini_get('zlib.output_compression') && ini_get('output_handler') !== 'ob_gzhandler') { $this->compress(); } if ($this->allowCache() === false) { $this->setHeader('Cache-Control', 'no-cache', false); } $this->sendHeaders(); return $this->getBody(); } /** * Method to determine a hash for anti-spoofing variable names * * @param boolean $forceNew If true, force a new token to be created * * @return string Hashed var name * * @since 4.0.0 */ public function getFormToken($forceNew = false) { /** @var Session $session */ $session = $this->getSession(); return $session->getFormToken($forceNew); } /** * Checks for a form token in the request. * * Use in conjunction with getFormToken. * * @param string $method The request method in which to look for the token key. * * @return boolean True if found and valid, false otherwise. * * @since 4.0.0 */ public function checkToken($method = 'post') { /** @var Session $session */ $session = $this->getSession(); return $session->checkToken($method); } /** * Flag if the application instance is a CLI or web based application. * * Helper function, you should use the native PHP functions to detect if it is a CLI application. * * @return boolean * * @since 4.0.0 * @deprecated 5.0 Will be removed without replacements */ public function isCli() { return false; } /** * No longer used * * @return boolean * * @since 4.0.0 * * @throws \Exception * @deprecated 4.2.0 Will be removed in 5.0 without replacement. */ protected function isTwoFactorAuthenticationRequired(): bool { return false; } /** * No longer used * * @return boolean * * @since 4.0.0 * * @throws \Exception * @deprecated 4.2.0 Will be removed in 5.0 without replacement. */ private function hasUserConfiguredTwoFactorAuthentication(): bool { return false; } /** * Setup logging functionality. * * @return void * * @since 4.0.0 */ private function setupLogging(): void { // Add InMemory logger that will collect all log entries to allow to display them later by extensions if ($this->get('debug')) { Log::addLogger(['logger' => 'inmemory']); } // Log the deprecated API. if ($this->get('log_deprecated')) { Log::addLogger(['text_file' => 'deprecated.php'], Log::ALL, ['deprecated']); } // We only log errors unless Site Debug is enabled $logLevels = Log::ERROR | Log::CRITICAL | Log::ALERT | Log::EMERGENCY; if ($this->get('debug')) { $logLevels = Log::ALL; } Log::addLogger(['text_file' => 'joomla_core_errors.php'], $logLevels, ['system']); // Log everything (except deprecated APIs, these are logged separately with the option above). if ($this->get('log_everything')) { Log::addLogger(['text_file' => 'everything.php'], Log::ALL, ['deprecated', 'deprecation-notes', 'databasequery'], true); } if ($this->get('log_categories')) { $priority = 0; foreach ($this->get('log_priorities', ['all']) as $p) { $const = '\\Joomla\\CMS\\Log\\Log::' . strtoupper($p); if (defined($const)) { $priority |= constant($const); } } // Split into an array at any character other than alphabet, numbers, _, ., or - $categories = preg_split('/[^\w.-]+/', $this->get('log_categories', ''), -1, PREG_SPLIT_NO_EMPTY); $mode = (bool) $this->get('log_category_mode', false); if (!$categories) { return; } Log::addLogger(['text_file' => 'custom-logging.php'], $priority, $categories, $mode); } } /** * Sets the internal menu factory. * * @param MenuFactoryInterface $menuFactory The menu factory * * @return void * * @since 4.2.0 */ public function setMenuFactory(MenuFactoryInterface $menuFactory): void { $this->menuFactory = $menuFactory; } }