File manager - Edit - /home/opticamezl/www/newok/Application.tar
Back
CLI/Output/Stdout.php 0000644 00000002151 15173173554 0010460 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\CLI\Output; use Joomla\CMS\Application\CLI\CliOutput; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Output handler for writing command line output to the stdout interface * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Use the `joomla/console` package instead */ class Stdout extends CliOutput { /** * Write a string to standard output * * @param string $text The text to display. * @param boolean $nl True (default) to append a new line at the end of the output string. * * @return $this * * @codeCoverageIgnore * @since 4.0.0 */ public function out($text = '', $nl = true) { fwrite(STDOUT, $this->getProcessor()->process($text) . ($nl ? "\n" : null)); return $this; } } CLI/Output/Processor/ProcessorInterface.php 0000644 00000001470 15173173555 0014761 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\CLI\Output\Processor; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Interface for a command line output processor * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Use the `joomla/console` package instead */ interface ProcessorInterface { /** * Process the provided output into a string. * * @param string $output The string to process. * * @return string * * @since 4.0.0 */ public function process($output); } CLI/Output/Processor/ColorProcessor.php 0000644 00000011045 15173173555 0014136 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\CLI\Output\Processor; use Joomla\CMS\Application\CLI\ColorStyle; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Command line output processor supporting ANSI-colored output * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Use the `joomla/console` package instead */ class ColorProcessor implements ProcessorInterface { /** * Flag to remove color codes from the output * * @var boolean * @since 4.0.0 */ public $noColors = false; /** * Regex to match tags * * @var string * @since 4.0.0 */ protected $tagFilter = '/<([a-z=;]+)>(.*?)<\/\\1>/s'; /** * Regex used for removing color codes * * @var string * @since 4.0.0 */ protected static $stripFilter = '/<[\/]?[a-z=;]+>/'; /** * Array of ColorStyle objects * * @var ColorStyle[] * @since 4.0.0 */ protected $styles = []; /** * Class constructor * * @param boolean $noColors Defines non-colored mode on construct * * @since 4.0.0 */ public function __construct($noColors = null) { if ($noColors === null) { /* * By default windows cmd.exe and PowerShell does not support ANSI-colored output * if the variable is not set explicitly colors should be disabled on Windows */ $noColors = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); } $this->noColors = $noColors; $this->addPredefinedStyles(); } /** * Add a style. * * @param string $name The style name. * @param ColorStyle $style The color style. * * @return $this * * @since 4.0.0 */ public function addStyle($name, ColorStyle $style) { $this->styles[$name] = $style; return $this; } /** * Strip color tags from a string. * * @param string $string The string. * * @return string * * @since 4.0.0 */ public static function stripColors($string) { return preg_replace(static::$stripFilter, '', $string); } /** * Process a string. * * @param string $string The string to process. * * @return string * * @since 4.0.0 */ public function process($string) { preg_match_all($this->tagFilter, $string, $matches); if (!$matches) { return $string; } foreach ($matches[0] as $i => $m) { if (\array_key_exists($matches[1][$i], $this->styles)) { $string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], $this->styles[$matches[1][$i]]); } elseif (strpos($matches[1][$i], '=')) { // Custom format $string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], ColorStyle::fromString($matches[1][$i])); } } return $string; } /** * Replace color tags in a string. * * @param string $text The original text. * @param string $tag The matched tag. * @param string $match The match. * @param ColorStyle $style The color style to apply. * * @return mixed * * @since 4.0.0 */ private function replaceColors($text, $tag, $match, ColorStyle $style) { $replace = $this->noColors ? $match : "\033[" . $style . 'm' . $match . "\033[0m"; return str_replace('<' . $tag . '>' . $match . '</' . $tag . '>', $replace, $text); } /** * Adds predefined color styles to the ColorProcessor object * * @return $this * * @since 4.0.0 */ private function addPredefinedStyles() { $this->addStyle( 'info', new ColorStyle('green', '', ['bold']) ); $this->addStyle( 'comment', new ColorStyle('yellow', '', ['bold']) ); $this->addStyle( 'question', new ColorStyle('black', 'cyan') ); $this->addStyle( 'error', new ColorStyle('white', 'red') ); return $this; } } CLI/Output/Xml.php 0000644 00000002151 15173173555 0007737 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\CLI\Output; use Joomla\CMS\Application\CLI\CliOutput; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Output handler for writing command line output to the stdout interface * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Use the `joomla/console` package instead */ class Xml extends CliOutput { /** * Write a string to standard output. * * @param string $text The text to display. * @param boolean $nl True (default) to append a new line at the end of the output string. * * @return $this * * @since 4.0.0 * @throws \RuntimeException * @codeCoverageIgnore */ public function out($text = '', $nl = true) { fwrite(STDOUT, $text . ($nl ? "\n" : null)); return $this; } } CLI/CliInput.php 0000644 00000001444 15173173555 0007432 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\CLI; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Class CliInput * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Use the `joomla/console` package instead */ class CliInput { /** * Get a value from standard input. * * @return string The input string from standard input. * * @codeCoverageIgnore * @since 4.0.0 */ public function in() { return rtrim(fread(STDIN, 8192), "\n\r"); } } CLI/ColorStyle.php 0000644 00000013250 15173173555 0010000 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\CLI; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Class defining ANSI-color styles for command line output * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Use the `joomla/console` package instead */ final class ColorStyle { /** * Known colors * * @var array * @since 4.0.0 */ private static $knownColors = [ 'black' => 0, 'red' => 1, 'green' => 2, 'yellow' => 3, 'blue' => 4, 'magenta' => 5, 'cyan' => 6, 'white' => 7, ]; /** * Known styles * * @var array * @since 4.0.0 */ private static $knownOptions = [ 'bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, ]; /** * Foreground base value * * @var integer * @since 4.0.0 */ private static $fgBase = 30; /** * Background base value * * @var integer * @since 4.0.0 */ private static $bgBase = 40; /** * Foreground color * * @var integer * @since 4.0.0 */ private $fgColor = 0; /** * Background color * * @var integer * @since 4.0.0 */ private $bgColor = 0; /** * Array of style options * * @var array * @since 4.0.0 */ private $options = []; /** * Constructor * * @param string $fg Foreground color. * @param string $bg Background color. * @param array $options Style options. * * @since 4.0.0 * @throws \InvalidArgumentException */ public function __construct(string $fg = '', string $bg = '', array $options = []) { if ($fg) { if (\array_key_exists($fg, static::$knownColors) == false) { throw new \InvalidArgumentException( sprintf( 'Invalid foreground color "%1$s" [%2$s]', $fg, implode(', ', $this->getKnownColors()) ) ); } $this->fgColor = static::$fgBase + static::$knownColors[$fg]; } if ($bg) { if (\array_key_exists($bg, static::$knownColors) == false) { throw new \InvalidArgumentException( sprintf( 'Invalid background color "%1$s" [%2$s]', $bg, implode(', ', $this->getKnownColors()) ) ); } $this->bgColor = static::$bgBase + static::$knownColors[$bg]; } foreach ($options as $option) { if (\array_key_exists($option, static::$knownOptions) == false) { throw new \InvalidArgumentException( sprintf( 'Invalid option "%1$s" [%2$s]', $option, implode(', ', $this->getKnownOptions()) ) ); } $this->options[] = $option; } } /** * Convert to a string. * * @return string * * @since 4.0.0 */ public function __toString() { return $this->getStyle(); } /** * Create a color style from a parameter string. * * Example: fg=red;bg=blue;options=bold,blink * * @param string $string The parameter string. * * @return $this * * @since 4.0.0 * @throws \RuntimeException */ public static function fromString(string $string): self { $fg = ''; $bg = ''; $options = []; $parts = explode(';', $string); foreach ($parts as $part) { $subParts = explode('=', $part); if (\count($subParts) < 2) { continue; } switch ($subParts[0]) { case 'fg': $fg = $subParts[1]; break; case 'bg': $bg = $subParts[1]; break; case 'options': $options = explode(',', $subParts[1]); break; default: throw new \RuntimeException('Invalid option: ' . $subParts[0]); } } return new self($fg, $bg, $options); } /** * Get the translated color code. * * @return string * * @since 4.0.0 */ public function getStyle(): string { $values = []; if ($this->fgColor) { $values[] = $this->fgColor; } if ($this->bgColor) { $values[] = $this->bgColor; } foreach ($this->options as $option) { $values[] = static::$knownOptions[$option]; } return implode(';', $values); } /** * Get the known colors. * * @return string[] * * @since 4.0.0 */ public function getKnownColors(): array { return array_keys(static::$knownColors); } /** * Get the known options. * * @return string[] * * @since 4.0.0 */ public function getKnownOptions(): array { return array_keys(static::$knownOptions); } } CLI/CliOutput.php 0000644 00000004120 15173173555 0007625 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\CLI; use Joomla\CMS\Application\CLI\Output\Processor\ProcessorInterface; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Base class defining a command line output handler * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Use the `joomla/console` package instead */ abstract class CliOutput { /** * Output processing object * * @var ProcessorInterface * @since 4.0.0 */ protected $processor; /** * Constructor * * @param ProcessorInterface $processor The output processor. * * @since 4.0.0 */ public function __construct(ProcessorInterface $processor = null) { $this->setProcessor($processor ?: new Output\Processor\ColorProcessor()); } /** * Set a processor * * @param ProcessorInterface $processor The output processor. * * @return $this * * @since 4.0.0 */ public function setProcessor(ProcessorInterface $processor) { $this->processor = $processor; return $this; } /** * Get a processor * * @return ProcessorInterface * * @since 4.0.0 * @throws \RuntimeException */ public function getProcessor() { if ($this->processor) { return $this->processor; } throw new \RuntimeException('A ProcessorInterface object has not been set.'); } /** * Write a string to an output handler. * * @param string $text The text to display. * @param boolean $nl True (default) to append a new line at the end of the output string. * * @return $this * * @since 4.0.0 * @codeCoverageIgnore */ abstract public function out($text = '', $nl = true); } ApplicationHelper.php 0000644 00000013101 15173173555 0010670 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filter\OutputFilter; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Application helper functions * * @since 1.5 */ class ApplicationHelper { /** * Client information array * * @var array * @since 1.6 */ protected static $_clients = []; /** * Return the name of the request component [main component] * * @param string $default The default option * * @return string Option (e.g. com_something) * * @since 1.6 */ public static function getComponentName($default = null) { static $option; if ($option) { return $option; } $input = Factory::getApplication()->getInput(); $option = strtolower($input->get('option', '')); if (empty($option)) { $option = $default; } $input->set('option', $option); return $option; } /** * Provides a secure hash based on a seed * * @param string $seed Seed string. * * @return string A secure hash * * @since 3.2 */ public static function getHash($seed) { return md5(Factory::getApplication()->get('secret') . $seed); } /** * This method transliterates a string into a URL * safe string or returns a URL safe UTF-8 string * based on the global configuration * * @param string $string String to process * @param string $language Language to transliterate to if unicode slugs are disabled * * @return string Processed string * * @since 3.2 */ public static function stringURLSafe($string, $language = '') { if (Factory::getApplication()->get('unicodeslugs') == 1) { $output = OutputFilter::stringUrlUnicodeSlug($string); } else { if ($language === '*' || $language === '') { $languageParams = ComponentHelper::getParams('com_languages'); $language = $languageParams->get('site'); } $output = OutputFilter::stringURLSafe($string, $language); } return $output; } /** * Gets information on a specific client id. This method will be useful in * future versions when we start mapping applications in the database. * * This method will return a client information array if called * with no arguments which can be used to add custom application information. * * @param integer|string|null $id A client identifier * @param boolean $byName If true, find the client by its name * * @return \stdClass|\stdClass[]|null Object describing the client, array containing all the clients or null if $id not known * * @since 1.5 */ public static function getClientInfo($id = null, $byName = false) { // Only create the array if it is empty if (empty(self::$_clients)) { $obj = new \stdClass(); // Site Client $obj->id = 0; $obj->name = 'site'; $obj->path = JPATH_SITE; self::$_clients[0] = clone $obj; // Administrator Client $obj->id = 1; $obj->name = 'administrator'; $obj->path = JPATH_ADMINISTRATOR; self::$_clients[1] = clone $obj; // Installation Client $obj->id = 2; $obj->name = 'installation'; $obj->path = JPATH_INSTALLATION; self::$_clients[2] = clone $obj; // API Client $obj->id = 3; $obj->name = 'api'; $obj->path = JPATH_API; self::$_clients[3] = clone $obj; // CLI Client $obj->id = 4; $obj->name = 'cli'; $obj->path = JPATH_CLI; self::$_clients[4] = clone $obj; } // If no client id has been passed return the whole array if ($id === null) { return self::$_clients; } // Are we looking for client information by id or by name? if (!$byName) { if (isset(self::$_clients[$id])) { return self::$_clients[$id]; } } else { foreach (self::$_clients as $client) { if ($client->name == strtolower($id)) { return $client; } } } return null; } /** * Adds information for a client. * * @param mixed $client A client identifier either an array or object * * @return boolean True if the information is added. False on error * * @since 1.6 */ public static function addClientInfo($client) { if (\is_array($client)) { $client = (object) $client; } if (!\is_object($client)) { return false; } $info = self::getClientInfo(); if (!isset($client->id)) { $client->id = \count($info); } self::$_clients[$client->id] = clone $client; return true; } } ExtensionNamespaceMapper.php 0000644 00000001604 15173173555 0012230 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Trait for application classes which ensures the namespace mapper exists and includes it. * * @since 4.0.0 */ trait ExtensionNamespaceMapper { /** * Allows the application to load a custom or default identity. * * @return void * * @since 4.0.0 */ public function createExtensionNamespaceMap() { \JLoader::register('JNamespacePsr4Map', JPATH_LIBRARIES . '/namespacemap.php'); $extensionPsr4Loader = new \JNamespacePsr4Map(); $extensionPsr4Loader->load(); } } ConsoleApplication.php 0000644 00000047265 15173173555 0011075 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\CMS\Console; use Joomla\CMS\Extension\ExtensionManagerTrait; use Joomla\CMS\Factory; use Joomla\CMS\Language\Language; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Router\Router; use Joomla\CMS\Uri\Uri; use Joomla\CMS\Version; use Joomla\Console\Application; use Joomla\Database\DatabaseAwareTrait; use Joomla\DI\Container; use Joomla\DI\ContainerAwareTrait; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherAwareTrait; use Joomla\Event\DispatcherInterface; use Joomla\Input\Input; use Joomla\Registry\Registry; use Joomla\Session\SessionInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * The Joomla! CMS Console Application * * @since 4.0.0 */ class ConsoleApplication extends Application implements DispatcherAwareInterface, CMSApplicationInterface { use DispatcherAwareTrait; use EventAware; use IdentityAware; use ContainerAwareTrait; use ExtensionManagerTrait; use ExtensionNamespaceMapper; use DatabaseAwareTrait; /** * The input. * * @var Input * @since 4.0.0 */ protected $input = null; /** * The name of the application. * * @var string * @since 4.0.0 */ protected $name = null; /** * The application language object. * * @var Language * @since 4.0.0 */ protected $language; /** * The application message queue. * * @var array * @since 4.0.0 */ private $messages = []; /** * The application session object. * * @var SessionInterface * @since 4.0.0 */ private $session; /** * Class constructor. * * @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 DispatcherInterface $dispatcher An optional argument to provide dependency injection for the application's event dispatcher. If the * argument is a DispatcherInterface object that object will become the application's event dispatcher, * if it is null then the default event dispatcher will be created based on the application's * loadDispatcher() method. * @param Container $container Dependency injection container. * @param Language $language The language object provisioned for the application. * @param InputInterface|null $input An optional argument to provide dependency injection for the application's input object. If the * argument is an InputInterface object that object will become the application's input object, * otherwise a default input object is created. * @param OutputInterface|null $output An optional argument to provide dependency injection for the application's output object. If the * argument is an OutputInterface object that object will become the application's output object, * otherwise a default output object is created. * * @since 4.0.0 */ public function __construct( Registry $config, DispatcherInterface $dispatcher, Container $container, Language $language, ?InputInterface $input = null, ?OutputInterface $output = null ) { // Close the application if it is not executed from the command line. if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv'])) { $this->close(); } // Set up a Input object for Controllers etc to use $this->input = new \Joomla\CMS\Input\Cli(); $this->language = $language; parent::__construct($input, $output, $config); $this->setVersion(JVERSION); // Register the client name as cli $this->name = 'cli'; $this->setContainer($container); $this->setDispatcher($dispatcher); // Set the execution datetime and timestamp; $this->set('execution.datetime', gmdate('Y-m-d H:i:s')); $this->set('execution.timestamp', time()); $this->set('execution.microtimestamp', microtime(true)); // Set the current directory. $this->set('cwd', getcwd()); // Set up the environment $this->input->set('format', 'cli'); } /** * Magic method to access properties of the application. * * @param string $name The name of the property. * * @return mixed A value if the property name is valid, null otherwise. * * @since 4.0.0 * * @deprecated 4.0 will be removed in 6.0 * This is a B/C proxy for deprecated read accesses, use getInput() method instead * Example: * $app->getInput(); */ public function __get($name) { switch ($name) { case 'input': @trigger_error( 'Accessing the input property of the application is deprecated, use the getInput() method instead.', E_USER_DEPRECATED ); return $this->getInput(); default: $trace = debug_backtrace(); trigger_error( sprintf( 'Undefined property via __get(): %1$s in %2$s on line %3$s', $name, $trace[0]['file'], $trace[0]['line'] ), E_USER_NOTICE ); } } /** * Method to run the application routines. * * @return integer The exit code for the application * * @since 4.0.0 * @throws \Throwable */ protected function doExecute(): int { $exitCode = parent::doExecute(); $style = new SymfonyStyle($this->getConsoleInput(), $this->getConsoleOutput()); $methodMap = [ self::MSG_ALERT => 'error', self::MSG_CRITICAL => 'caution', self::MSG_DEBUG => 'comment', self::MSG_EMERGENCY => 'caution', self::MSG_ERROR => 'error', self::MSG_INFO => 'note', self::MSG_NOTICE => 'note', self::MSG_WARNING => 'warning', ]; // Output any enqueued messages before the app exits foreach ($this->getMessageQueue() as $type => $messages) { $method = $methodMap[$type] ?? 'comment'; $style->$method($messages); } return $exitCode; } /** * Execute the application. * * @return void * * @since 4.0.0 * @throws \Throwable */ public function execute() { // Load extension namespaces $this->createExtensionNamespaceMap(); /** * Address issues with instantiating WebApplication descendants under CLI. * * IMPORTANT! This code must be always be executed **before** the first use of * PluginHelper::importPlugin(). Some plugins will attempt to register an MVCFactory for a * component in their service provider. This will in turn try to get the SiteRouter service * for the component which tries to get an instance of SiteApplication which will fail with * a RuntimeException if the populateHttpHost() method has not already executed. */ $this->populateHttpHost(); // Import CMS plugin groups to be able to subscribe to events PluginHelper::importPlugin('system'); PluginHelper::importPlugin('console'); parent::execute(); } /** * Enqueue a system message. * * @param string $msg The message to enqueue. * @param string $type The message type. * * @return void * * @since 4.0.0 */ public function enqueueMessage($msg, $type = self::MSG_INFO) { if (!array_key_exists($type, $this->messages)) { $this->messages[$type] = []; } $this->messages[$type][] = $msg; } /** * Gets the name of the current running application. * * @return string The name of the application. * * @since 4.0.0 */ public function getName(): string { return $this->name; } /** * Get the commands which should be registered by default to the application. * * @return \Joomla\Console\Command\AbstractCommand[] * * @since 4.0.0 */ protected function getDefaultCommands(): array { return array_merge( parent::getDefaultCommands(), [ new Console\CleanCacheCommand(), new Console\CheckUpdatesCommand(), new Console\RemoveOldFilesCommand(), new Console\AddUserCommand($this->getDatabase()), new Console\AddUserToGroupCommand($this->getDatabase()), new Console\RemoveUserFromGroupCommand($this->getDatabase()), new Console\DeleteUserCommand($this->getDatabase()), new Console\ChangeUserPasswordCommand(), new Console\ListUserCommand($this->getDatabase()), ] ); } /** * Retrieve the application configuration object. * * @return Registry * * @since 4.0.0 */ public function getConfig() { return $this->config; } /** * Method to get the application input object. * * @return Input * * @since 4.0.0 */ public function getInput(): Input { return $this->input; } /** * Method to get the application language object. * * @return Language The language object * * @since 4.0.0 */ public function getLanguage() { return $this->language; } /** * Get the system message queue. * * @return array The system message queue. * * @since 4.0.0 */ public function getMessageQueue() { return $this->messages; } /** * Method to get the application session object. * * @return SessionInterface The session object * * @since 4.0.0 */ public function getSession() { return $this->session; } /** * 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 4.0.0 */ public function isClient($identifier) { return $this->getName() === $identifier; } /** * 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 4.0 will be removed in 6.0 * Will be removed without replacement. CLI will be handled by the joomla/console package instead */ public function isCli() { return true; } /** * Sets the session for the application to use, if required. * * @param SessionInterface $session A session object. * * @return $this * * @since 4.0.0 */ public function setSession(SessionInterface $session): self { $this->session = $session; return $this; } /** * Flush the media version to refresh versionable assets * * @return void * * @since 4.0.0 */ public function flushAssets() { (new Version())->refreshMediaVersion(); } /** * Get the long version string for the application. * * Overrides the parent method due to conflicting use of the getName method between the console application and * the CMS application interface. * * @return string * * @since 4.0.0 */ public function getLongVersion(): string { return sprintf('Joomla! <info>%s</info> (debug: %s)', (new Version())->getShortVersion(), (\defined('JDEBUG') && JDEBUG ? 'Yes' : 'No')); } /** * Set the name of the application. * * @param string $name The new application name. * * @return void * * @since 4.0.0 * @throws \RuntimeException because the application name cannot be changed */ public function setName(string $name): void { throw new \RuntimeException('The console application name cannot be changed'); } /** * 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 4.0.6 * * @throws \InvalidArgumentException * * @deprecated 4.3 will be removed in 6.0 * Inject the router or load it from the dependency injection container * Example: Factory::getContainer()->get(ApiRouter::class); */ public static function getRouter($name = null, array $options = []) { if (empty($name)) { throw new \InvalidArgumentException('A router name must be set in console application.'); } $options['mode'] = Factory::getApplication()->get('sef'); return Router::getInstance($name, $options); } /** * Populates the HTTP_HOST and REQUEST_URI from the URL provided in the --live-site parameter. * * If the URL provided is empty or invalid we will use the URL * https://joomla.invalid/set/by/console/application just so that the CLI application doesn't * crash when a WebApplication descendant is instantiated in it. * * This is a practical workaround for using any service depending on a WebApplication * descendant under CLI. * * Practical example: using a component's MVCFactory which instantiates the SiteRouter * service for that component which in turn relies on an instance of SiteApplication. * * @return void * @since 4.2.1 * @see https://github.com/joomla/joomla-cms/issues/38518 */ protected function populateHttpHost() { // First check for the --live-site command line option. $input = $this->getConsoleInput(); $liveSite = ''; if ($input->hasParameterOption(['--live-site', false])) { $liveSite = $input->getParameterOption(['--live-site'], ''); } // Fallback to the $live_site global configuration option in configuration.php $liveSite = $liveSite ?: $this->get('live_site', 'https://joomla.invalid/set/by/console/application'); /** * Try to use the live site URL we were given. If all else fails, fall back to * https://joomla.invalid/set/by/console/application. */ try { $uri = Uri::getInstance($liveSite); } catch (\RuntimeException $e) { $uri = Uri::getInstance('https://joomla.invalid/set/by/console/application'); } /** * Yes, this is icky but it is the only way to trick WebApplication into compliance. * * @see \Joomla\Application\AbstractWebApplication::detectRequestUri */ $_SERVER['HTTP_HOST'] = $uri->toString(['host', 'port']); $_SERVER['REQUEST_URI'] = $uri->getPath(); $_SERVER['HTTPS'] = $uri->getScheme() === 'https' ? 'on' : 'off'; } /** * Builds the default input definition. * * @return InputDefinition * * @since 4.2.1 */ protected function getDefaultInputDefinition(): InputDefinition { return new InputDefinition( [ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption( '--live-site', null, InputOption::VALUE_OPTIONAL, 'The URL to your site, e.g. https://www.example.com' ), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display the help information'), new InputOption( '--quiet', '-q', InputOption::VALUE_NONE, 'Flag indicating that all output should be silenced' ), new InputOption( '--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug' ), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Displays the application version'), new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), new InputOption( '--no-interaction', '-n', InputOption::VALUE_NONE, 'Flag to disable interacting with the user' ), ] ); } /** * 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 4.4.0 */ 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 4.4.0 */ 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; } } IdentityAware.php 0000644 00000002453 15173173555 0010046 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\CMS\User\User; use Joomla\CMS\User\UserFactoryAwareTrait; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Trait for application classes which are identity (user) aware * * @since 4.0.0 */ trait IdentityAware { use UserFactoryAwareTrait; /** * The application identity object. * * @var User * @since 4.0.0 */ protected $identity; /** * Get the application identity. * * @return User * * @since 4.0.0 */ public function getIdentity() { return $this->identity; } /** * Allows the application to load a custom or default identity. * * @param User $identity An optional identity object. If omitted, a null user object is created. * * @return $this * * @since 4.0.0 */ public function loadIdentity(User $identity = null) { $this->identity = $identity ?: $this->getUserFactory()->loadUserById(0); return $this; } } CMSApplicationInterface.php 0000644 00000010702 15173173555 0011720 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\ConfigurationAwareApplicationInterface; use Joomla\CMS\Extension\ExtensionManagerInterface; use Joomla\CMS\Language\Language; use Joomla\CMS\User\User; use Joomla\Input\Input; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Interface defining a Joomla! CMS Application class * * @since 4.0.0 * @note In Joomla 5 this interface will no longer extend EventAwareInterface * @property-read Input $input {@deprecated 4.0 will be removed in 6.0} The Joomla Input property. Deprecated in favour of getInput() */ interface CMSApplicationInterface extends ExtensionManagerInterface, ConfigurationAwareApplicationInterface, EventAwareInterface { /** * Constant defining an enqueued emergency message * * @var string * @since 4.0.0 */ public const MSG_EMERGENCY = 'emergency'; /** * Constant defining an enqueued alert message * * @var string * @since 4.0.0 */ public const MSG_ALERT = 'alert'; /** * Constant defining an enqueued critical message * * @var string * @since 4.0.0 */ public const MSG_CRITICAL = 'critical'; /** * Constant defining an enqueued error message * * @var string * @since 4.0.0 */ public const MSG_ERROR = 'error'; /** * Constant defining an enqueued warning message * * @var string * @since 4.0.0 */ public const MSG_WARNING = 'warning'; /** * Constant defining an enqueued notice message * * @var string * @since 4.0.0 */ public const MSG_NOTICE = 'notice'; /** * Constant defining an enqueued info message * * @var string * @since 4.0.0 */ public const MSG_INFO = 'info'; /** * Constant defining an enqueued debug message * * @var string * @since 4.0.0 */ public const MSG_DEBUG = 'debug'; /** * Enqueue a system message. * * @param string $msg The message to enqueue. * @param string $type The message type. * * @return void * * @since 4.0.0 */ public function enqueueMessage($msg, $type = self::MSG_INFO); /** * Get the system message queue. * * @return array The system message queue. * * @since 4.0.0 */ public function getMessageQueue(); /** * 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 4.0.0 */ public function isClient($identifier); /** * 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 4.0 will be removed in 6.0 * Will be removed without replacement. CLI will be handled by the joomla/console package instead */ public function isCli(); /** * Get the application identity. * * @return User|null A User object or null if not set. * * @since 4.0.0 */ public function getIdentity(); /** * Method to get the application input object. * * @return Input * * @since 4.0.0 */ public function getInput(): Input; /** * Method to get the application language object. * * @return Language The language object * * @since 4.0.0 */ public function getLanguage(); /** * Gets the name of the current running application. * * @return string The name of the application. * * @since 4.0.0 */ public function getName(); /** * Allows the application to load a custom or default identity. * * @param User $identity An optional identity object. If omitted, the factory user is created. * * @return $this * * @since 4.0.0 */ public function loadIdentity(User $identity = null); } EventAwareInterface.php 0000644 00000004176 15173173555 0011163 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherInterface; use Joomla\Event\Event; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Interface defining application that can trigger Joomla 3.x style events * * @since 4.0.0 * @deprecated 4.3 will be removed in 6.0 * This interface will be removed without replacement as the Joomla 3.x compatibility layer will be removed * @todo Move to combat plugin */ interface EventAwareInterface extends DispatcherAwareInterface { /** * Get the event dispatcher. * * @return DispatcherInterface * * @since 4.0.0 * @throws \UnexpectedValueException May be thrown if the dispatcher has not been set. */ public function getDispatcher(); /** * Calls all handlers associated with an event group. * * This is a legacy method, implementing old-style (Joomla! 3.x) plugin calls. It's best to go directly through the * Dispatcher and handle the returned EventInterface object instead of going through this method. This method is * deprecated and will be removed in Joomla! 5.x. * * This method will only return the 'result' argument of the event * * @param string $eventName The event name. * @param array|Event $args An array of arguments or an Event object (optional). * * @return array An array of results from each function call. Note this will be an empty array if no dispatcher is set. * * @since 4.0.0 * @throws \InvalidArgumentException * * @deprecated 4.0 will be removed in 6.0 * Use the Dispatcher method instead * Example: Factory::getApplication()->getDispatcher()->dispatch($eventName, $event); */ public function triggerEvent($eventName, $args = []); } DaemonApplication.php 0000644 00000070325 15173173555 0010667 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Input\Cli; use Joomla\CMS\Log\Log; use Joomla\Event\DispatcherInterface; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Class to turn CliApplication applications into daemons. It requires CLI and PCNTL support built into PHP. * * @link https://www.php.net/manual/en/book.pcntl.php * @link https://www.php.net/manual/en/features.commandline.php * @since 1.7.0 */ abstract class DaemonApplication extends CliApplication { /** * @var array The available POSIX signals to be caught by default. * @link https://www.php.net/manual/pcntl.constants.php * @since 1.7.0 */ protected static $signals = [ 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGIOT', 'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGPIPE', 'SIGALRM', 'SIGTERM', 'SIGSTKFLT', 'SIGCLD', 'SIGCHLD', 'SIGCONT', 'SIGTSTP', 'SIGTTIN', 'SIGTTOU', 'SIGURG', 'SIGXCPU', 'SIGXFSZ', 'SIGVTALRM', 'SIGPROF', 'SIGWINCH', 'SIGPOLL', 'SIGIO', 'SIGPWR', 'SIGSYS', 'SIGBABY', 'SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK', ]; /** * @var boolean True if the daemon is in the process of exiting. * @since 1.7.0 */ protected $exiting = false; /** * @var integer The parent process id. * @since 3.0.0 */ protected $parentId = 0; /** * @var integer The process id of the daemon. * @since 1.7.0 */ protected $processId = 0; /** * @var boolean True if the daemon is currently running. * @since 1.7.0 */ protected $running = false; /** * Class constructor. * * @param Cli $input An optional argument to provide dependency injection for the application's * input object. If the argument is a JInputCli 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 DispatcherInterface $dispatcher An optional argument to provide dependency injection for the application's * event dispatcher. If the argument is a DispatcherInterface object that object will become * the application's event dispatcher, if it is null then the default event dispatcher * will be created based on the application's loadDispatcher() method. * * @since 1.7.0 */ public function __construct(Cli $input = null, Registry $config = null, DispatcherInterface $dispatcher = null) { // Verify that the process control extension for PHP is available. if (!\defined('SIGHUP')) { Log::add('The PCNTL extension for PHP is not available.', Log::ERROR); throw new \RuntimeException('The PCNTL extension for PHP is not available.'); } // Verify that POSIX support for PHP is available. if (!\function_exists('posix_getpid')) { Log::add('The POSIX extension for PHP is not available.', Log::ERROR); throw new \RuntimeException('The POSIX extension for PHP is not available.'); } // Call the parent constructor. parent::__construct($input, $config, null, null, $dispatcher); // Set some system limits. if (\function_exists('set_time_limit')) { set_time_limit($this->config->get('max_execution_time', 0)); } if ($this->config->get('max_memory_limit') !== null) { ini_set('memory_limit', $this->config->get('max_memory_limit', '256M')); } // Flush content immediately. ob_implicit_flush(); } /** * Method to handle POSIX signals. * * @param integer $signal The received POSIX signal. * * @return void * * @since 1.7.0 * @see pcntl_signal() * @throws \RuntimeException */ public static function signal($signal) { // Log all signals sent to the daemon. Log::add('Received signal: ' . $signal, Log::DEBUG); // Let's make sure we have an application instance. if (!is_subclass_of(static::$instance, CliApplication::class)) { Log::add('Cannot find the application instance.', Log::EMERGENCY); throw new \RuntimeException('Cannot find the application instance.'); } // Fire the onReceiveSignal event. static::$instance->triggerEvent('onReceiveSignal', [$signal]); switch ($signal) { case SIGINT: case SIGTERM: // Handle shutdown tasks if (static::$instance->running && static::$instance->isActive()) { static::$instance->shutdown(); } else { static::$instance->close(); } break; case SIGHUP: // Handle restart tasks if (static::$instance->running && static::$instance->isActive()) { static::$instance->shutdown(true); } else { static::$instance->close(); } break; case SIGCHLD: // A child process has died while (static::$instance->pcntlWait($signal, WNOHANG || WUNTRACED) > 0) { usleep(1000); } break; case SIGCLD: while (static::$instance->pcntlWait($signal, WNOHANG) > 0) { $signal = static::$instance->pcntlChildExitStatus($signal); } break; default: break; } } /** * Check to see if the daemon is active. This does not assume that $this daemon is active, but * only if an instance of the application is active as a daemon. * * @return boolean True if daemon is active. * * @since 1.7.0 */ public function isActive() { // Get the process id file location for the application. $pidFile = $this->config->get('application_pid_file'); // If the process id file doesn't exist then the daemon is obviously not running. if (!is_file($pidFile)) { return false; } // Read the contents of the process id file as an integer. $fp = fopen($pidFile, 'r'); $pid = fread($fp, filesize($pidFile)); $pid = (int) $pid; fclose($fp); // Check to make sure that the process id exists as a positive integer. if (!$pid) { return false; } // Check to make sure the process is active by pinging it and ensure it responds. if (!posix_kill($pid, 0)) { // No response so remove the process id file and log the situation. @ unlink($pidFile); Log::add('The process found based on PID file was unresponsive.', Log::WARNING); return false; } return true; } /** * Load an object or array into the application configuration object. * * @param mixed $data Either an array or object to be loaded into the configuration object. * * @return DaemonApplication Instance of $this to allow chaining. * * @since 1.7.0 */ public function loadConfiguration($data) { /* * Setup some application metadata options. This is useful if we ever want to write out startup scripts * or just have some sort of information available to share about things. */ // The application author name. This string is used in generating startup scripts and has // a maximum of 50 characters. $tmp = (string) $this->config->get('author_name', 'Joomla Platform'); $this->config->set('author_name', (\strlen($tmp) > 50) ? substr($tmp, 0, 50) : $tmp); // The application author email. This string is used in generating startup scripts. $tmp = (string) $this->config->get('author_email', 'admin@joomla.org'); $this->config->set('author_email', filter_var($tmp, FILTER_VALIDATE_EMAIL)); // The application name. This string is used in generating startup scripts. $tmp = (string) $this->config->get('application_name', 'DaemonApplication'); $this->config->set('application_name', (string) preg_replace('/[^A-Z0-9_-]/i', '', $tmp)); // The application description. This string is used in generating startup scripts. $tmp = (string) $this->config->get('application_description', 'A generic Joomla Platform application.'); $this->config->set('application_description', filter_var($tmp, FILTER_SANITIZE_STRING)); /* * Setup the application path options. This defines the default executable name, executable directory, * and also the path to the daemon process id file. */ // The application executable daemon. This string is used in generating startup scripts. $tmp = (string) $this->config->get('application_executable', basename($this->input->executable)); $this->config->set('application_executable', $tmp); // The home directory of the daemon. $tmp = (string) $this->config->get('application_directory', \dirname($this->input->executable)); $this->config->set('application_directory', $tmp); // The pid file location. This defaults to a path inside the /tmp directory. $name = $this->config->get('application_name'); $tmp = (string) $this->config->get('application_pid_file', strtolower('/tmp/' . $name . '/' . $name . '.pid')); $this->config->set('application_pid_file', $tmp); /* * Setup the application identity options. It is important to remember if the default of 0 is set for * either UID or GID then changing that setting will not be attempted as there is no real way to "change" * the identity of a process from some user to root. */ // The user id under which to run the daemon. $tmp = (int) $this->config->get('application_uid', 0); $options = ['options' => ['min_range' => 0, 'max_range' => 65000]]; $this->config->set('application_uid', filter_var($tmp, FILTER_VALIDATE_INT, $options)); // The group id under which to run the daemon. $tmp = (int) $this->config->get('application_gid', 0); $options = ['options' => ['min_range' => 0, 'max_range' => 65000]]; $this->config->set('application_gid', filter_var($tmp, FILTER_VALIDATE_INT, $options)); // Option to kill the daemon if it cannot switch to the chosen identity. $tmp = (bool) $this->config->get('application_require_identity', 1); $this->config->set('application_require_identity', $tmp); /* * Setup the application runtime options. By default our execution time limit is infinite obviously * because a daemon should be constantly running unless told otherwise. The default limit for memory * usage is 256M, which admittedly is a little high, but remember it is a "limit" and PHP's memory * management leaves a bit to be desired :-) */ // The maximum execution time of the application in seconds. Zero is infinite. $tmp = $this->config->get('max_execution_time'); if ($tmp !== null) { $this->config->set('max_execution_time', (int) $tmp); } // The maximum amount of memory the application can use. $tmp = $this->config->get('max_memory_limit', '256M'); if ($tmp !== null) { $this->config->set('max_memory_limit', (string) $tmp); } return $this; } /** * Execute the daemon. * * @return void * * @since 1.7.0 */ public function execute() { // Trigger the onBeforeExecute event $this->triggerEvent('onBeforeExecute'); // Enable basic garbage collection. gc_enable(); Log::add('Starting ' . $this->name, Log::INFO); // Set off the process for becoming a daemon. if ($this->daemonize()) { // Declare ticks to start signal monitoring. When you declare ticks, PCNTL will monitor // incoming signals after each tick and call the relevant signal handler automatically. declare(ticks=1); // Start the main execution loop. while (true) { // Perform basic garbage collection. $this->gc(); // Don't completely overload the CPU. usleep(1000); // Execute the main application logic. $this->doExecute(); } } else { // We were not able to daemonize the application so log the failure and die gracefully. Log::add('Starting ' . $this->name . ' failed', Log::INFO); } // Trigger the onAfterExecute event. $this->triggerEvent('onAfterExecute'); } /** * Restart daemon process. * * @return void * * @since 1.7.0 */ public function restart() { Log::add('Stopping ' . $this->name, Log::INFO); $this->shutdown(true); } /** * Stop daemon process. * * @return void * * @since 1.7.0 */ public function stop() { Log::add('Stopping ' . $this->name, Log::INFO); $this->shutdown(); } /** * Method to change the identity of the daemon process and resources. * * @return boolean True if identity successfully changed * * @since 1.7.0 * @see posix_setuid() */ protected function changeIdentity() { // Get the group and user ids to set for the daemon. $uid = (int) $this->config->get('application_uid', 0); $gid = (int) $this->config->get('application_gid', 0); // Get the application process id file path. $file = $this->config->get('application_pid_file'); // Change the user id for the process id file if necessary. if ($uid && (fileowner($file) != $uid) && (!@ chown($file, $uid))) { Log::add('Unable to change user ownership of the process id file.', Log::ERROR); return false; } // Change the group id for the process id file if necessary. if ($gid && (filegroup($file) != $gid) && (!@ chgrp($file, $gid))) { Log::add('Unable to change group ownership of the process id file.', Log::ERROR); return false; } // Set the correct home directory for the process. if ($uid && ($info = posix_getpwuid($uid)) && is_dir($info['dir'])) { system('export HOME="' . $info['dir'] . '"'); } // Change the user id for the process necessary. if ($uid && (posix_getuid() != $uid) && (!@ posix_setuid($uid))) { Log::add('Unable to change user ownership of the process.', Log::ERROR); return false; } // Change the group id for the process necessary. if ($gid && (posix_getgid() != $gid) && (!@ posix_setgid($gid))) { Log::add('Unable to change group ownership of the process.', Log::ERROR); return false; } // Get the user and group information based on uid and gid. $user = posix_getpwuid($uid); $group = posix_getgrgid($gid); Log::add('Changed daemon identity to ' . $user['name'] . ':' . $group['name'], Log::INFO); return true; } /** * Method to put the application into the background. * * @return boolean * * @since 1.7.0 * @throws \RuntimeException */ protected function daemonize() { // Is there already an active daemon running? if ($this->isActive()) { Log::add($this->name . ' daemon is still running. Exiting the application.', Log::EMERGENCY); return false; } // Reset Process Information $this->processId = 0; $this->running = false; // Detach process! try { // Check if we should run in the foreground. if (!$this->input->get('f')) { // Detach from the terminal. $this->detach(); } else { // Setup running values. $this->exiting = false; $this->running = true; // Set the process id. $this->processId = (int) posix_getpid(); $this->parentId = $this->processId; } } catch (\RuntimeException $e) { Log::add('Unable to fork.', Log::EMERGENCY); return false; } // Verify the process id is valid. if ($this->processId < 1) { Log::add('The process id is invalid; the fork failed.', Log::EMERGENCY); return false; } // Clear the umask. @ umask(0); // Write out the process id file for concurrency management. if (!$this->writeProcessIdFile()) { Log::add('Unable to write the pid file at: ' . $this->config->get('application_pid_file'), Log::EMERGENCY); return false; } // Attempt to change the identity of user running the process. if (!$this->changeIdentity()) { // If the identity change was required then we need to return false. if ($this->config->get('application_require_identity')) { Log::add('Unable to change process owner.', Log::CRITICAL); return false; } else { Log::add('Unable to change process owner.', Log::WARNING); } } // Setup the signal handlers for the daemon. if (!$this->setupSignalHandlers()) { return false; } // Change the current working directory to the application working directory. @ chdir($this->config->get('application_directory')); return true; } /** * This is truly where the magic happens. This is where we fork the process and kill the parent * process, which is essentially what turns the application into a daemon. * * @return void * * @since 3.0.0 * @throws \RuntimeException */ protected function detach() { Log::add('Detaching the ' . $this->name . ' daemon.', Log::DEBUG); // Attempt to fork the process. $pid = $this->fork(); // If the pid is positive then we successfully forked, and can close this application. if ($pid) { // Add the log entry for debugging purposes and exit gracefully. Log::add('Ending ' . $this->name . ' parent process', Log::DEBUG); $this->close(); } else { // We are in the forked child process. // Setup some protected values. $this->exiting = false; $this->running = true; // Set the parent to self. $this->parentId = $this->processId; } } /** * Method to fork the process. * * @return integer The child process id to the parent process, zero to the child process. * * @since 1.7.0 * @throws \RuntimeException */ protected function fork() { // Attempt to fork the process. $pid = $this->pcntlFork(); // If the fork failed, throw an exception. if ($pid === -1) { throw new \RuntimeException('The process could not be forked.'); } elseif ($pid === 0) { // Update the process id for the child. $this->processId = (int) posix_getpid(); } else { // Log the fork in the parent. // Log the fork. Log::add('Process forked ' . $pid, Log::DEBUG); } // Trigger the onFork event. $this->postFork(); return $pid; } /** * Method to perform basic garbage collection and memory management in the sense of clearing the * stat cache. We will probably call this method pretty regularly in our main loop. * * @return void * * @since 1.7.0 */ protected function gc() { // Perform generic garbage collection. gc_collect_cycles(); // Clear the stat cache so it doesn't blow up memory. clearstatcache(); } /** * Method to attach the DaemonApplication signal handler to the known signals. Applications * can override these handlers by using the pcntl_signal() function and attaching a different * callback method. * * @return boolean * * @since 1.7.0 * @see pcntl_signal() */ protected function setupSignalHandlers() { // We add the error suppression for the loop because on some platforms some constants are not defined. foreach (self::$signals as $signal) { // Ignore signals that are not defined. if (!\defined($signal) || !\is_int(\constant($signal)) || (\constant($signal) === 0)) { // Define the signal to avoid notices. Log::add('Signal "' . $signal . '" not defined. Defining it as null.', Log::DEBUG); \define($signal, null); // Don't listen for signal. continue; } // Attach the signal handler for the signal. if (!$this->pcntlSignal(\constant($signal), ['DaemonApplication', 'signal'])) { Log::add(sprintf('Unable to reroute signal handler: %s', $signal), Log::EMERGENCY); return false; } } return true; } /** * Method to shut down the daemon and optionally restart it. * * @param boolean $restart True to restart the daemon on exit. * * @return void * * @since 1.7.0 */ protected function shutdown($restart = false) { // If we are already exiting, chill. if ($this->exiting) { return; } else { // If not, now we are. $this->exiting = true; } // If we aren't already daemonized then just kill the application. if (!$this->running && !$this->isActive()) { Log::add('Process was not daemonized yet, just halting current process', Log::INFO); $this->close(); } // Only read the pid for the parent file. if ($this->parentId == $this->processId) { // Read the contents of the process id file as an integer. $fp = fopen($this->config->get('application_pid_file'), 'r'); $pid = fread($fp, filesize($this->config->get('application_pid_file'))); $pid = (int) $pid; fclose($fp); // Remove the process id file. @ unlink($this->config->get('application_pid_file')); // If we are supposed to restart the daemon we need to execute the same command. if ($restart) { $this->close(exec(implode(' ', $GLOBALS['argv']) . ' > /dev/null &')); } else { // If we are not supposed to restart the daemon let's just kill -9. passthru('kill -9 ' . $pid); $this->close(); } } } /** * Method to write the process id file out to disk. * * @return boolean * * @since 1.7.0 */ protected function writeProcessIdFile() { // Verify the process id is valid. if ($this->processId < 1) { Log::add('The process id is invalid.', Log::EMERGENCY); return false; } // Get the application process id file path. $file = $this->config->get('application_pid_file'); if (empty($file)) { Log::add('The process id file path is empty.', Log::ERROR); return false; } // Make sure that the folder where we are writing the process id file exists. $folder = \dirname($file); if (!is_dir($folder) && !Folder::create($folder)) { Log::add('Unable to create directory: ' . $folder, Log::ERROR); return false; } // Write the process id file out to disk. if (!file_put_contents($file, $this->processId)) { Log::add('Unable to write process id file: ' . $file, Log::ERROR); return false; } // Make sure the permissions for the process id file are accurate. if (!chmod($file, 0644)) { Log::add('Unable to adjust permissions for the process id file: ' . $file, Log::ERROR); return false; } return true; } /** * Method to handle post-fork triggering of the onFork event. * * @return void * * @since 3.0.0 */ protected function postFork() { // Trigger the onFork event. $this->triggerEvent('onFork'); } /** * Method to return the exit code of a terminated child process. * * @param integer $status The status parameter is the status parameter supplied to a successful call to pcntl_waitpid(). * * @return integer The child process exit code. * * @see pcntl_wexitstatus() * @since 1.7.3 */ protected function pcntlChildExitStatus($status) { return pcntl_wexitstatus($status); } /** * Method to return the exit code of a terminated child process. * * @return integer On success, the PID of the child process is returned in the parent's thread * of execution, and a 0 is returned in the child's thread of execution. On * failure, a -1 will be returned in the parent's context, no child process * will be created, and a PHP error is raised. * * @see pcntl_fork() * @since 1.7.3 */ protected function pcntlFork() { return pcntl_fork(); } /** * Method to install a signal handler. * * @param integer $signal The signal number. * @param callable $handler The signal handler which may be the name of a user created function, * or method, or either of the two global constants SIG_IGN or SIG_DFL. * @param boolean $restart Specifies whether system call restarting should be used when this * signal arrives. * * @return boolean True on success. * * @see pcntl_signal() * @since 1.7.3 */ protected function pcntlSignal($signal, $handler, $restart = true) { return pcntl_signal($signal, $handler, $restart); } /** * Method to wait on or return the status of a forked child. * * @param integer &$status Status information. * @param integer $options If wait3 is available on your system (mostly BSD-style systems), * you can provide the optional options parameter. * * @return integer The process ID of the child which exited, -1 on error or zero if WNOHANG * was provided as an option (on wait3-available systems) and no child was available. * * @see pcntl_wait() * @since 1.7.3 */ protected function pcntlWait(&$status, $options = 0) { return pcntl_wait($status, $options); } } MultiFactorAuthenticationHandler.php 0000644 00000047771 15173173555 0013740 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Date\Date; use Joomla\CMS\Encrypt\Aes; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Router\Route; use Joomla\CMS\Table\User as UserTable; use Joomla\CMS\Uri\Uri; use Joomla\CMS\User\User; use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper; use Joomla\Database\DatabaseInterface; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Implements the code required for integrating with Joomla's Multi-factor Authentication. * * Please keep in mind that Joomla's MFA, like any MFA method, is designed to be user-interactive. * Moreover, it's meant to be used in an HTML- and JavaScript-aware execution environment i.e. a web * browser, web view or similar. * * If your application is designed to work non-interactively (e.g. a JSON API application) or * outside and HTML- and JavaScript-aware execution environments (e.g. CLI) you MUST NOT use this * trait. Authentication should be either implicit (e.g. CLI) or using sufficiently secure non- * interactive methods (tokens, certificates, ...). * * Regarding the Joomla CMS itself, only the SiteApplication (frontend) and AdministratorApplication * (backend) applications use this trait because of this reason. The CLI application is implicitly * authorised at the highest level, whereas the ApiApplication encourages the use of tokens for * authentication. * * @since 4.2.0 */ trait MultiFactorAuthenticationHandler { /** * Handle the redirection to the Multi-factor Authentication captive login or setup page. * * @return boolean True if we are currently handling a Multi-factor Authentication captive page. * @throws \Exception * @since 4.2.0 */ protected function isHandlingMultiFactorAuthentication(): bool { // Multi-factor Authentication checks take place only for logged in users. try { $user = $this->getIdentity() ?? null; } catch (\Exception $e) { return false; } if (!($user instanceof User) || $user->guest) { return false; } // If there is no need for a redirection I must not proceed if (!$this->needsMultiFactorAuthenticationRedirection()) { return false; } /** * Automatically migrate from legacy MFA, if needed. * * We prefer to do a user-by-user migration instead of migrating everybody on Joomla update * for practical reasons. On a site with hundreds or thousands of users the migration could * take several minutes, causing Joomla Update to time out. * * Instead, every time we are in a captive Multi-factor Authentication page (captive MFA login * or captive forced MFA setup) we spend a few milliseconds to check if a migration is * necessary. If it's necessary, we perform it. * * The captive pages don't load any content or modules, therefore the few extra milliseconds * we spend here are not a big deal. A failed all-users migration which would stop Joomla * Update dead in its tracks would, however, be a big deal (broken sites). Moreover, a * migration that has to be initiated by the site owner would also be a big deal — if they * did not know they need to do it none of their users who had previously enabled MFA would * now have it enabled! * * To paraphrase Otto von Bismarck: programming, like politics, is the art of the possible, * the attainable -- the art of the next best. */ $this->migrateFromLegacyMFA(); // We only kick in when the user has actually set up MFA or must definitely enable MFA. $userOptions = ComponentHelper::getParams('com_users'); $neverMFAUserGroups = $userOptions->get('neverMFAUserGroups', []); $forceMFAUserGroups = $userOptions->get('forceMFAUserGroups', []); $isMFADisallowed = count( array_intersect( is_array($neverMFAUserGroups) ? $neverMFAUserGroups : [], $user->getAuthorisedGroups() ) ) >= 1; $isMFAMandatory = count( array_intersect( is_array($forceMFAUserGroups) ? $forceMFAUserGroups : [], $user->getAuthorisedGroups() ) ) >= 1; $isMFADisallowed = $isMFADisallowed && !$isMFAMandatory; $isMFAPending = $this->isMultiFactorAuthenticationPending(); $session = $this->getSession(); $isNonHtml = $this->input->getCmd('format', 'html') !== 'html'; // Prevent non-interactive (non-HTML) content from being loaded until MFA is validated. if ($isMFAPending && $isNonHtml) { throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); } if ($isMFAPending && !$isMFADisallowed) { /** * Saves the current URL as the return URL if all of the following conditions apply * - It is not a URL to com_users' MFA feature itself * - A return URL does not already exist, is imperfect or external to the site * * If no return URL has been set up and the current URL is com_users' MFA feature * we will save the home page as the redirect target. */ $returnUrl = $session->get('com_users.return_url', ''); if (empty($returnUrl) || !Uri::isInternal($returnUrl)) { $returnUrl = $this->isMultiFactorAuthenticationPage() ? Uri::base() : Uri::getInstance()->toString(['scheme', 'user', 'pass', 'host', 'port', 'path', 'query', 'fragment']); $session->set('com_users.return_url', $returnUrl); } // Redirect $this->redirect(Route::_('index.php?option=com_users&view=captive', false), 307); } // If we're here someone just logged in but does not have MFA set up. Just flag him as logged in and continue. $session->set('com_users.mfa_checked', 1); // If the user is in a group that requires MFA we will redirect them to the setup page. if (!$isMFAPending && $isMFAMandatory) { // First unset the flag to make sure the redirection will apply until they conform to the mandatory MFA $session->set('com_users.mfa_checked', 0); // Now set a flag which forces rechecking MFA for this user $session->set('com_users.mandatory_mfa_setup', 1); // Then redirect them to the setup page if (!$this->isMultiFactorAuthenticationPage()) { $url = Route::_('index.php?option=com_users&view=methods', false); $this->redirect($url, 307); } } // Do I need to redirect the user to the MFA setup page after they have fully logged in? $hasRejectedMultiFactorAuthenticationSetup = $this->hasRejectedMultiFactorAuthenticationSetup() && !$isMFAMandatory; if ( !$isMFAPending && !$isMFADisallowed && ($userOptions->get('mfaredirectonlogin', 0) == 1) && !$user->guest && !$hasRejectedMultiFactorAuthenticationSetup && !empty(MfaHelper::getMfaMethods()) ) { $this->redirect( $userOptions->get('mfaredirecturl', '') ?: Route::_('index.php?option=com_users&view=methods&layout=firsttime', false) ); } return true; } /** * Does the current user need to complete MFA authentication before being allowed to access the site? * * @return boolean * @throws \Exception * @since 4.2.0 */ private function isMultiFactorAuthenticationPending(): bool { $user = $this->getIdentity(); if (empty($user) || $user->guest) { return false; } // Get the user's MFA records $records = MfaHelper::getUserMfaRecords($user->id); // No MFA Methods? Then we obviously don't need to display a Captive login page. if (count($records) < 1) { return false; } // Let's get a list of all currently active MFA Methods $mfaMethods = MfaHelper::getMfaMethods(); // If no MFA Method is active we can't really display a Captive login page. if (empty($mfaMethods)) { return false; } // Get a list of just the Method names $methodNames = []; foreach ($mfaMethods as $mfaMethod) { $methodNames[] = $mfaMethod['name']; } // Filter the records based on currently active MFA Methods foreach ($records as $record) { if (in_array($record->method, $methodNames)) { // We found an active Method. Show the Captive page. return true; } } // No viable MFA Method found. We won't show the Captive page. return false; } /** * Check whether we'll need to do a redirection to the Multi-factor Authentication captive page. * * @return boolean * @since 4.2.0 */ private function needsMultiFactorAuthenticationRedirection(): bool { $isAdmin = $this->isClient('administrator'); /** * We only kick in if the session flag is not set AND the user is not flagged for monitoring of their MFA status * * In case a user belongs to a group which requires MFA to be always enabled and they logged in without having * MFA enabled we have the recheck flag. This prevents the user from enabling and immediately disabling MFA, * circumventing the requirement for MFA. */ $session = $this->getSession(); $isMFAComplete = $session->get('com_users.mfa_checked', 0) != 0; $isMFASetupMandatory = $session->get('com_users.mandatory_mfa_setup', 0) != 0; if ($isMFAComplete && !$isMFASetupMandatory) { return false; } // Make sure we are logged in try { $user = $this->getIdentity(); } catch (\Exception $e) { // This would happen if we are in CLI or under an old Joomla! version. Either case is not supported. return false; } // The plugin only needs to kick in when you have logged in if (empty($user) || $user->guest) { return false; } // If we are in the administrator section we only kick in when the user has backend access privileges if ($isAdmin && !$user->authorise('core.login.admin')) { // @todo How exactly did you end up here if you didn't have the core.login.admin privilege to begin with?! return false; } $option = strtolower($this->input->getCmd('option', '')); $task = strtolower($this->input->getCmd('task', '')); // Allow the frontend user to log out (in case they forgot their MFA code or something) if (!$isAdmin && ($option == 'com_users') && in_array($task, ['user.logout', 'user.menulogout'])) { return false; } // Allow the backend user to log out (in case they forgot their MFA code or something) if ($isAdmin && ($option == 'com_login') && ($task == 'logout')) { return false; } // Allow the Joomla update finalisation to run if ($isAdmin && $option === 'com_joomlaupdate' && in_array($task, ['update.finalise', 'update.cleanup', 'update.finaliseconfirm'])) { return false; } // Do not redirect if we are already in a MFA management or captive page $onlyCaptive = $this->isMultiFactorAuthenticationPending() && !$isMFASetupMandatory; if ($this->isMultiFactorAuthenticationPage($onlyCaptive)) { return false; } return true; } /** * Is this a page concerning the Multi-factor Authentication feature? * * @param bool $onlyCaptive Should I only check for the MFA captive page? * * @return boolean * @since 4.2.0 */ public function isMultiFactorAuthenticationPage(bool $onlyCaptive = false): bool { $option = $this->input->get('option'); $task = $this->input->get('task'); $view = $this->input->get('view'); if ($option !== 'com_users') { return false; } $allowedViews = ['captive', 'method', 'methods', 'callback']; $allowedTasks = [ 'captive.display', 'captive.captive', 'captive.validate', 'methods.display', ]; if (!$onlyCaptive) { $allowedTasks = array_merge( $allowedTasks, [ 'method.display', 'method.add', 'method.edit', 'method.regenerateBackupCodes', 'method.delete', 'method.save', 'methods.disable', 'methods.doNotShowThisAgain', ] ); } return in_array($view, $allowedViews) || in_array($task, $allowedTasks); } /** * Does the user have a "don't show this again" flag? * * @return boolean * @since 4.2.0 */ private function hasRejectedMultiFactorAuthenticationSetup(): bool { $user = $this->getIdentity(); $profileKey = 'mfa.dontshow'; /** @var DatabaseInterface $db */ $db = Factory::getContainer()->get(DatabaseInterface::class); $query = $db->getQuery(true) ->select($db->quoteName('profile_value')) ->from($db->quoteName('#__user_profiles')) ->where($db->quoteName('user_id') . ' = :userId') ->where($db->quoteName('profile_key') . ' = :profileKey') ->bind(':userId', $user->id, ParameterType::INTEGER) ->bind(':profileKey', $profileKey); try { $result = $db->setQuery($query)->loadResult(); } catch (\Exception $e) { $result = 1; } return $result == 1; } /** * Automatically migrates a user's legacy MFA records into the new Captive MFA format. * * @return void * @since 4.2.0 */ private function migrateFromLegacyMFA(): void { $user = $this->getIdentity(); if (!($user instanceof User) || $user->guest || $user->id <= 0) { return; } /** @var DatabaseInterface $db */ $db = Factory::getContainer()->get(DatabaseInterface::class); $userTable = new UserTable($db); if (!$userTable->load($user->id) || empty($userTable->otpKey)) { return; } [$otpMethod, $otpKey] = explode(':', $userTable->otpKey, 2); $secret = $this->get('secret'); $otpKey = $this->decryptLegacyTFAString($secret, $otpKey); $otep = $this->decryptLegacyTFAString($secret, $userTable->otep); $config = @json_decode($otpKey, true); $hasConverted = true; if (!empty($config)) { switch ($otpMethod) { case 'totp': $this->getLanguage()->load('plg_multifactorauth_totp', JPATH_ADMINISTRATOR); Factory::getApplication()->bootComponent('com_users')->getMVCFactory()->createTable('Mfa', 'Administrator')->save( [ 'user_id' => $user->id, 'title' => Text::_('PLG_MULTIFACTORAUTH_TOTP_METHOD_TITLE'), 'method' => 'totp', 'default' => 0, 'created_on' => Date::getInstance()->toSql(), 'last_used' => null, 'tries' => 0, 'try_count' => null, 'options' => ['key' => $config['code']], ] ); break; case 'yubikey': $this->getLanguage()->load('plg_multifactorauth_yubikey', JPATH_ADMINISTRATOR); Factory::getApplication()->bootComponent('com_users')->getMVCFactory()->createTable('Mfa', 'Administrator')->save( [ 'user_id' => $user->id, 'title' => sprintf("%s %s", Text::_('PLG_MULTIFACTORAUTH_YUBIKEY_METHOD_TITLE'), $config['yubikey']), 'method' => 'yubikey', 'default' => 0, 'created_on' => Date::getInstance()->toSql(), 'last_used' => null, 'tries' => 0, 'try_count' => null, 'options' => ['id' => $config['yubikey']], ] ); break; default: $hasConverted = false; break; } } // Convert the emergency codes if ($hasConverted && !empty(@json_decode($otep, true))) { // Delete any other record with the same user_id and Method. $method = 'emergencycodes'; $userId = $user->id; $query = $db->getQuery(true) ->delete($db->quoteName('#__user_mfa')) ->where($db->quoteName('user_id') . ' = :user_id') ->where($db->quoteName('method') . ' = :method') ->bind(':user_id', $userId, ParameterType::INTEGER) ->bind(':method', $method); $db->setQuery($query)->execute(); // Migrate data Factory::getApplication()->bootComponent('com_users')->getMVCFactory()->createTable('Mfa', 'Administrator')->save( [ 'user_id' => $user->id, 'title' => Text::_('COM_USERS_USER_BACKUPCODES'), 'method' => 'backupcodes', 'default' => 0, 'created_on' => Date::getInstance()->toSql(), 'last_used' => null, 'tries' => 0, 'try_count' => null, 'options' => @json_decode($otep, true), ] ); } // Remove the legacy MFA $update = (object) [ 'id' => $user->id, 'otpKey' => '', 'otep' => '', ]; $db->updateObject('#__users', $update, ['id']); } /** * Tries to decrypt the legacy MFA configuration. * * @param string $secret Site's secret key * @param string $stringToDecrypt Base64-encoded and encrypted, JSON-encoded information * * @return string Decrypted, but JSON-encoded, information * * @see https://github.com/joomla/joomla-cms/pull/12497 * @since 4.2.0 */ private function decryptLegacyTFAString(string $secret, string $stringToDecrypt): string { // Is this already decrypted? try { $decrypted = @json_decode($stringToDecrypt, true); } catch (\Exception $e) { $decrypted = null; } if (!empty($decrypted)) { return $stringToDecrypt; } // No, we need to decrypt the string $aes = new Aes($secret, 256); $decrypted = $aes->decryptString($stringToDecrypt); if (!is_string($decrypted) || empty($decrypted)) { $aes->setPassword($secret, true); $decrypted = $aes->decryptString($stringToDecrypt); } if (!is_string($decrypted) || empty($decrypted)) { return ''; } // Remove the null padding added during encryption return rtrim($decrypted, "\0"); } } AdministratorApplication.php 0000644 00000041665 15173173555 0012311 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\Web\WebClient; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Input\Input; use Joomla\CMS\Language\LanguageHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Router\Router; use Joomla\CMS\Session\Session; use Joomla\CMS\Uri\Uri; use Joomla\DI\Container; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! Administrator Application class * * @since 3.2 */ class AdministratorApplication extends CMSApplication { use MultiFactorAuthenticationHandler; /** * List of allowed components for guests and users which do not have the core.login.admin privilege. * * By default we allow two core components: * * - com_login Absolutely necessary to let users log into the backend of the site. Do NOT remove! * - com_ajax Handle AJAX requests or other administrative callbacks without logging in. Required for * passwordless authentication using WebAuthn. * * @var array */ protected $allowedUnprivilegedOptions = [ 'com_login', 'com_ajax', ]; /** * 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) { // Register the application name $this->name = 'administrator'; // Register the client ID $this->clientId = 1; // Execute the parent constructor parent::__construct($input, $config, $client, $container); // Set the root in the URI based on the application name Uri::root(null, rtrim(\dirname(Uri::base(true)), '/\\')); } /** * Dispatch the application * * @param string $component The component which is being rendered. * * @return void * * @since 3.2 */ public function dispatch($component = null) { if ($component === null) { $component = $this->findOption(); } // Load the document to the API $this->loadDocument(); // Set up the params $document = Factory::getDocument(); // Register the document object with Factory Factory::$document = $document; switch ($document->getType()) { case 'html': // Get the template $template = $this->getTemplate(true); $clientId = $this->getClientId(); // Store the template and its params to the config $this->set('theme', $template->template); $this->set('themeParams', $template->params); // Add Asset registry files $wr = $document->getWebAssetManager()->getRegistry(); if ($component) { $wr->addExtensionRegistryFile($component); } if (!empty($template->parent)) { $wr->addTemplateRegistryFile($template->parent, $clientId); } $wr->addTemplateRegistryFile($template->template, $clientId); break; default: break; } $document->setTitle($this->get('sitename') . ' - ' . Text::_('JADMINISTRATION')); $document->setDescription($this->get('MetaDesc')); $document->setGenerator('Joomla! - Open Source Content Management'); $contents = ComponentHelper::renderComponent($component); $document->setBuffer($contents, 'component'); // Trigger the onAfterDispatch event. PluginHelper::importPlugin('system'); $this->triggerEvent('onAfterDispatch'); } /** * Method to run the Web application routines. * * @return void * * @since 3.2 */ protected function doExecute() { // Get the language from the (login) form or user state $login_lang = ($this->input->get('option') === 'com_login') ? $this->input->get('lang') : ''; $options = ['language' => $login_lang ?: $this->getUserState('application.lang')]; // Initialise the application $this->initialiseApp($options); // Mark afterInitialise in the profiler. JDEBUG ? $this->profiler->mark('afterInitialise') : null; // Route the application $this->route(); // Mark afterRoute in the profiler. JDEBUG ? $this->profiler->mark('afterRoute') : null; /* * Check if the user is required to reset their password * * Before $this->route(); "option" and "view" can't be safely read using: * $this->input->getCmd('option'); or $this->input->getCmd('view'); * ex: due of the sef urls */ $this->checkUserRequireReset('com_users', 'user', 'edit', 'com_users/user.edit,com_users/user.save,com_users/user.apply,com_login/logout'); // Dispatch the application $this->dispatch(); // Mark afterDispatch in the profiler. JDEBUG ? $this->profiler->mark('afterDispatch') : null; } /** * Return a reference to the 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 4.3 will be removed in 6.0 * Inject the router or load it from the dependency injection container * Example: * Factory::getContainer()->get(AdministratorRouter::class); */ public static function getRouter($name = 'administrator', array $options = []) { return parent::getRouter($name, $options); } /** * Gets the name of the current template. * * @param boolean $params True to return the template parameters * * @return string|\stdClass The name of the template if the params argument is false. The template object if the params argument is true. * * @since 3.2 * @throws \InvalidArgumentException */ public function getTemplate($params = false) { if (\is_object($this->template)) { if ($params) { return $this->template; } return $this->template->template; } $adminStyle = $this->getIdentity() ? (int) $this->getIdentity()->getParam('admin_style') : 0; $template = $this->bootComponent('templates')->getMVCFactory() ->createModel('Style', 'Administrator')->getAdminTemplate($adminStyle); $template->template = InputFilter::getInstance()->clean($template->template, 'cmd'); $template->params = new Registry($template->params); // Fallback template if ( !is_file(JPATH_THEMES . '/' . $template->template . '/index.php') && !is_file(JPATH_THEMES . '/' . $template->parent . '/index.php') ) { $this->getLogger()->error(Text::_('JERROR_ALERTNOTEMPLATE'), ['category' => 'system']); $template->params = new Registry(); $template->template = 'atum'; // Check, the data were found and if template really exists if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) { throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $template->template)); } } // Cache the result $this->template = $template; // Pass the parent template to the state $this->set('themeInherits', $template->parent); if ($params) { return $template; } return $template->template; } /** * Initialise the application. * * @param array $options An optional associative array of configuration settings. * * @return void * * @since 3.2 */ protected function initialiseApp($options = []) { $user = Factory::getUser(); // If the user is a guest we populate it with the guest user group. if ($user->guest) { $guestUsergroup = ComponentHelper::getParams('com_users')->get('guest_usergroup', 1); $user->groups = [$guestUsergroup]; } // If a language was specified it has priority, otherwise use user or default language settings if (empty($options['language'])) { $lang = $user->getParam('admin_language'); // Make sure that the user's language exists if ($lang && LanguageHelper::exists($lang)) { $options['language'] = $lang; } else { $params = ComponentHelper::getParams('com_languages'); $options['language'] = $params->get('administrator', $this->get('language', 'en-GB')); } } // One last check to make sure we have something if (!LanguageHelper::exists($options['language'])) { $lang = $this->get('language', 'en-GB'); if (LanguageHelper::exists($lang)) { $options['language'] = $lang; } else { // As a last ditch fail to english $options['language'] = 'en-GB'; } } // Finish initialisation parent::initialiseApp($options); } /** * Login authentication function * * @param array $credentials Array('username' => string, 'password' => string) * @param array $options Array('remember' => boolean) * * @return boolean True on success. * * @since 3.2 */ public function login($credentials, $options = []) { // The minimum group $options['group'] = 'Public Backend'; // Make sure users are not auto-registered $options['autoregister'] = false; // Set the application login entry point if (!\array_key_exists('entry_url', $options)) { $options['entry_url'] = Uri::base() . 'index.php?option=com_users&task=login'; } // Set the access control action to check. $options['action'] = 'core.login.admin'; $result = parent::login($credentials, $options); if (!($result instanceof \Exception)) { $lang = $this->input->getCmd('lang', ''); $lang = preg_replace('/[^A-Z-]/i', '', $lang); if ($lang) { $this->setUserState('application.lang', $lang); } $this->bootComponent('messages')->getMVCFactory() ->createModel('Messages', 'Administrator')->purge($this->getIdentity() ? $this->getIdentity()->id : 0); } return $result; } /** * Purge the jos_messages table of old messages * * @return void * * @since 3.2 * * @deprecated 4.3 will be removed in 6.0 * Purge the messages through the messages model * Example: * Factory::getApplication()->bootComponent('messages')->getMVCFactory() * ->createModel('Messages', 'Administrator')->purge(Factory::getApplication()->getIdentity()->id); */ public static function purgeMessages() { Factory::getApplication()->bootComponent('messages')->getMVCFactory() ->createModel('Messages', 'Administrator')->purge(Factory::getUser()->id); } /** * 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() { // Get the \JInput object $input = $this->input; $component = $input->getCmd('option', 'com_login'); $file = $input->getCmd('tmpl', 'index'); if ($component === 'com_login') { $file = 'login'; } $this->set('themeFile', $file . '.php'); // Safety check for when configuration.php root_user is in use. $rootUser = $this->get('root_user'); if (property_exists('\JConfig', 'root_user')) { if (Factory::getUser()->get('username') === $rootUser || Factory::getUser()->id === (string) $rootUser) { $this->enqueueMessage( Text::sprintf( 'JWARNING_REMOVE_ROOT_USER', 'index.php?option=com_config&task=application.removeroot&' . Session::getFormToken() . '=1' ), 'warning' ); } elseif (Factory::getUser()->authorise('core.admin')) { // Show this message to superusers too $this->enqueueMessage( Text::sprintf( 'JWARNING_REMOVE_ROOT_USER_ADMIN', $rootUser, 'index.php?option=com_config&task=application.removeroot&' . Session::getFormToken() . '=1' ), 'warning' ); } } parent::render(); } /** * 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 */ protected function route() { $uri = Uri::getInstance(); if ($this->get('force_ssl') >= 1 && strtolower($uri->getScheme()) !== 'https') { // Forward to https $uri->setScheme('https'); $this->redirect((string) $uri, 301); } $this->isHandlingMultiFactorAuthentication(); // Trigger the onAfterRoute event. PluginHelper::importPlugin('system'); $this->triggerEvent('onAfterRoute'); } /** * Return the application option string [main component]. * * @return string The component to access. * * @since 4.0.0 */ public function findOption(): string { /** @var self $app */ $app = Factory::getApplication(); $option = strtolower($app->getInput()->get('option', '')); $user = $app->getIdentity(); /** * Special handling for guest users and authenticated users without the Backend Login privilege. * * If the component they are trying to access is in the $this->allowedUnprivilegedOptions array we allow the * request to go through. Otherwise we force com_login to be loaded, letting the user (re)try authenticating * with a user account that has the Backend Login privilege. */ if ($user->get('guest') || !$user->authorise('core.login.admin')) { $option = in_array($option, $this->allowedUnprivilegedOptions) ? $option : 'com_login'; } /** * If no component is defined in the request we will try to load com_cpanel, the administrator Control Panel * component. This allows the /administrator URL to display something meaningful after logging in instead of an * error. */ if (empty($option)) { $option = 'com_cpanel'; } /** * Force the option to the input object. This is necessary because we might have force-changed the component in * the two if-blocks above. */ $app->getInput()->set('option', $option); return $option; } } Exception/NotAcceptable.php 0000644 00000000762 15173173555 0011740 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application\Exception; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Exception class defining a not acceptable class * * @since 4.0.0 */ class NotAcceptable extends \RuntimeException { } EventAware.php 0000644 00000007115 15173173555 0007336 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\CMS\Event\CoreEventAware; use Joomla\Event\DispatcherInterface; use Joomla\Event\Event; use Psr\Log\LoggerInterface; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Trait for application classes which dispatch events * * @since 4.0.0 */ trait EventAware { use CoreEventAware; /** * Get the event dispatcher. * * @return DispatcherInterface * * @since 4.0.0 * @throws \UnexpectedValueException May be thrown if the dispatcher has not been set. */ abstract public function getDispatcher(); /** * Get the logger. * * @return LoggerInterface * * @since 4.0.0 */ abstract public function getLogger(); /** * Registers a handler to a particular event group. * * @param string $event The event name. * @param callable $handler The handler, a function or an instance of an event object. * * @return $this * * @since 4.0.0 */ public function registerEvent($event, callable $handler) { try { $this->getDispatcher()->addListener($event, $handler); } catch (\UnexpectedValueException $e) { // No dispatcher is registered, don't throw an error (mimics old behavior) } return $this; } /** * Calls all handlers associated with an event group. * * This is a legacy method, implementing old-style (Joomla! 3.x) plugin calls. It's best to go directly through the * Dispatcher and handle the returned EventInterface object instead of going through this method. This method is * deprecated and will be removed in Joomla! 5.x. * * This method will only return the 'result' argument of the event * * @param string $eventName The event name. * @param array|Event $args An array of arguments or an Event object (optional). * * @return array An array of results from each function call. Note this will be an empty array if no dispatcher is set. * * @since 4.0.0 * @throws \InvalidArgumentException * * @deprecated 4.0 will be removed in 6.0 * Use the Dispatcher method instead * Example: Factory::getApplication()->getDispatcher()->dispatch($eventName, $event); * */ public function triggerEvent($eventName, $args = []) { try { $dispatcher = $this->getDispatcher(); } catch (\UnexpectedValueException $exception) { $this->getLogger()->error(sprintf('Dispatcher not set in %s, cannot trigger events.', \get_class($this))); return []; } if ($args instanceof Event) { $event = $args; } elseif (\is_array($args)) { $className = self::getEventClassByEventName($eventName); $event = new $className($eventName, $args); } else { throw new \InvalidArgumentException('The arguments must either be an event or an array'); } $result = $dispatcher->dispatch($eventName, $event); // @todo - There are still test cases where the result isn't defined, temporarily leave the isset check in place return !isset($result['result']) || \is_null($result['result']) ? [] : $result['result']; } } CMSWebApplicationInterface.php 0000644 00000006133 15173173555 0012361 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\SessionAwareWebApplicationInterface; use Joomla\CMS\Document\Document; use Joomla\CMS\Menu\AbstractMenu; use Joomla\CMS\Router\Router; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Interface defining a Joomla! CMS Application class for web applications. * * @since 4.0.0 */ interface CMSWebApplicationInterface extends SessionAwareWebApplicationInterface, CMSApplicationInterface { /** * Method to get the application document object. * * @return Document The document object * * @since 4.0.0 */ public function getDocument(); /** * Get the menu object. * * @param string $name The application name for the menu * @param array $options An array of options to initialise the menu with * * @return AbstractMenu|null An AbstractMenu object or null if not set. * * @since 4.0.0 */ public function getMenu($name = null, $options = []); /** * 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 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Inject the router or load it from the dependency injection container * Example: Factory::getContainer()->get($name); */ public static function getRouter($name = null, array $options = []); /** * 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 4.0.0 */ public function getUserState($key, $default = null); /** * 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 4.0.0 */ public function getUserStateFromRequest($key, $request, $default = null, $type = 'none'); /** * 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 The previous state, if one existed. Null otherwise. * * @since 4.0.0 */ public function setUserState($key, $value); } WebApplication.php 0000644 00000034727 15173173555 0010207 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\AbstractWebApplication; use Joomla\Application\Web\WebClient; use Joomla\CMS\Document\Document; use Joomla\CMS\Factory; use Joomla\CMS\Input\Input; use Joomla\CMS\Language\Language; use Joomla\CMS\Session\Session; use Joomla\CMS\Uri\Uri; use Joomla\CMS\User\User; use Joomla\CMS\Version; use Joomla\Registry\Registry; use Joomla\Session\SessionEvent; use Psr\Http\Message\ResponseInterface; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Base class for a Joomla! Web application. * * @since 2.5.0 */ abstract class WebApplication extends AbstractWebApplication { use EventAware; use IdentityAware; /** * The application component title. * * @var string * @since 4.3.0 */ public $JComponentTitle; /** * The item associations * * @var integer * @since 4.3.0 * * @deprecated 4.4.0 will be removed in 6.0 as this property is not used anymore */ public $item_associations; /** * The application document object. * * @var Document * @since 1.7.3 */ protected $document; /** * The application language object. * * @var Language * @since 1.7.3 */ protected $language; /** * The application instance. * * @var static * @since 1.7.3 */ protected static $instance; /** * 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 ResponseInterface $response An optional argument to provide dependency injection for the application's * response object. If the argument is a ResponseInterface object that object * will become the application's response object, otherwise a default response * object is created. * * @since 1.7.3 */ public function __construct(Input $input = null, Registry $config = null, WebClient $client = null, ResponseInterface $response = null) { // Ensure we have a CMS Input object otherwise the DI for \Joomla\CMS\Session\Storage\JoomlaStorage fails $input = $input ?: new Input(); parent::__construct($input, $config, $client, $response); // Set the execution datetime and timestamp; $this->set('execution.datetime', gmdate('Y-m-d H:i:s')); $this->set('execution.timestamp', time()); // Set the system URIs. $this->loadSystemUris(); } /** * Returns a reference to the global WebApplication object, only creating it if it doesn't already exist. * * This method must be invoked as: $web = WebApplication::getInstance(); * * @param string $name The name (optional) of the WebApplication class to instantiate. * * @return WebApplication * * @since 1.7.3 * @throws \RuntimeException * * @deprecated 4.0 will be removed in 6.0 * Use the application service in the DI container instead * Example: \Joomla\CMS\Factory::getContainer()->get($name) */ public static function getInstance($name = null) { // Only create the object if it doesn't exist. if (empty(static::$instance)) { if (!is_subclass_of($name, '\\Joomla\\CMS\\Application\\WebApplication')) { throw new \RuntimeException(sprintf('Unable to load application: %s', $name), 500); } static::$instance = new $name(); } return static::$instance; } /** * Execute the application. * * @return void * * @since 1.7.3 */ public function execute() { // Trigger the onBeforeExecute event. $this->triggerEvent('onBeforeExecute'); // Perform application routines. $this->doExecute(); // Trigger the onAfterExecute event. $this->triggerEvent('onAfterExecute'); // If we have an application document object, render it. if ($this->document instanceof Document) { // Trigger the onBeforeRender event. $this->triggerEvent('onBeforeRender'); // Render the application output. $this->render(); // Trigger the onAfterRender event. $this->triggerEvent('onAfterRender'); } // 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 onBeforeRespond event. $this->triggerEvent('onBeforeRespond'); // Send the application response. $this->respond(); // Trigger the onAfterRespond event. $this->triggerEvent('onAfterRespond'); } /** * 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 1.7.3 */ protected function render() { // Setup the document options. $options = [ 'template' => $this->get('theme'), 'file' => $this->get('themeFile', 'index.php'), 'params' => $this->get('themeParams'), 'templateInherits' => $this->get('themeInherits'), ]; if ($this->get('themes.base')) { $options['directory'] = $this->get('themes.base'); } else { // Fall back to constants. $options['directory'] = \defined('JPATH_THEMES') ? JPATH_THEMES : (\defined('JPATH_BASE') ? JPATH_BASE : __DIR__) . '/themes'; } // Parse the document. $this->document->parse($options); // Render the document. $data = $this->document->render($this->get('cache_enabled'), $options); // Set the application output data. $this->setBody($data); } /** * Method to get the application document object. * * @return Document The document object * * @since 1.7.3 */ public function getDocument() { return $this->document; } /** * Method to get the application language object. * * @return Language The language object * * @since 1.7.3 */ public function getLanguage() { return $this->language; } /** * Flush the media version to refresh versionable assets * * @return void * * @since 3.2 */ public function flushAssets() { (new Version())->refreshMediaVersion(); } /** * Allows the application to load a custom or default document. * * The logic and options for creating this object are adequately generic for default cases * but for many applications it will make sense to override this method and create a document, * if required, based on more specific needs. * * @param Document $document An optional document object. If omitted, the factory document is created. * * @return WebApplication This method is chainable. * * @since 1.7.3 */ public function loadDocument(Document $document = null) { $this->document = $document ?? Factory::getDocument(); return $this; } /** * Allows the application to load a custom or default language. * * The logic and options for creating this object are adequately generic for default cases * but for many applications it will make sense to override this method and create a language, * if required, based on more specific needs. * * @param Language $language An optional language object. If omitted, the factory language is created. * * @return WebApplication This method is chainable. * * @since 1.7.3 */ public function loadLanguage(Language $language = null) { $this->language = $language ?? Factory::getLanguage(); return $this; } /** * Allows the application to load a custom or default session. * * The logic and options for creating this object are adequately generic for default cases * but for many applications it will make sense to override this method and create a session, * if required, based on more specific needs. * * @param Session $session An optional session object. If omitted, the session is created. * * @return WebApplication This method is chainable. * * @since 1.7.3 * * @deprecated 4.3 will be removed in 6.0 * The session should be injected as a service. */ public function loadSession(Session $session = null) { $this->getLogger()->warning(__METHOD__ . '() is deprecated. Inject the session as a service instead.', ['category' => 'deprecated']); return $this; } /** * After the session has been started we need to populate it with some default values. * * @param SessionEvent $event Session event being triggered * * @return void * * @since 3.0.1 */ public function afterSessionStart(SessionEvent $event) { $session = $event->getSession(); if ($session->isNew()) { $session->set('registry', new Registry()); $session->set('user', new User()); } // Ensure the identity is loaded if (!$this->getIdentity()) { $this->loadIdentity($session->get('user')); } } /** * Method to load the system URI strings for the application. * * @param string $requestUri An optional request URI to use instead of detecting one from the * server environment variables. * * @return void * * @since 1.7.3 */ protected function loadSystemUris($requestUri = null) { // Set the request URI. if (!empty($requestUri)) { $this->set('uri.request', $requestUri); } else { $this->set('uri.request', $this->detectRequestUri()); } // Check to see if an explicit base URI has been set. $siteUri = trim($this->get('site_uri', '')); if ($siteUri !== '') { $uri = Uri::getInstance($siteUri); $path = $uri->toString(['path']); } else { // No explicit base URI was set so we need to detect it. // Start with the requested URI. $uri = Uri::getInstance($this->get('uri.request')); // If we are working from a CGI SAPI with the 'cgi.fix_pathinfo' directive disabled we use PHP_SELF. if (strpos(PHP_SAPI, 'cgi') !== false && !ini_get('cgi.fix_pathinfo') && !empty($_SERVER['REQUEST_URI'])) { // We aren't expecting PATH_INFO within PHP_SELF so this should work. $path = \dirname($_SERVER['PHP_SELF']); } else { // Pretty much everything else should be handled with SCRIPT_NAME. $path = \dirname($_SERVER['SCRIPT_NAME']); } } $host = $uri->toString(['scheme', 'user', 'pass', 'host', 'port']); // Check if the path includes "index.php". if (strpos($path, 'index.php') !== false) { // Remove the index.php portion of the path. $path = substr_replace($path, '', strpos($path, 'index.php'), 9); } $path = rtrim($path, '/\\'); // Set the base URI both as just a path and as the full URI. $this->set('uri.base.full', $host . $path . '/'); $this->set('uri.base.host', $host); $this->set('uri.base.path', $path . '/'); // Set the extended (non-base) part of the request URI as the route. if (stripos($this->get('uri.request'), $this->get('uri.base.full')) === 0) { $this->set('uri.route', substr_replace($this->get('uri.request'), '', 0, \strlen($this->get('uri.base.full')))); } // Get an explicitly set media URI is present. $mediaURI = trim($this->get('media_uri', '')); if ($mediaURI) { if (strpos($mediaURI, '://') !== false) { $this->set('uri.media.full', $mediaURI); $this->set('uri.media.path', $mediaURI); } else { // Normalise slashes. $mediaURI = trim($mediaURI, '/\\'); $mediaURI = !empty($mediaURI) ? '/' . $mediaURI . '/' : '/'; $this->set('uri.media.full', $this->get('uri.base.host') . $mediaURI); $this->set('uri.media.path', $mediaURI); } } else { // No explicit media URI was set, build it dynamically from the base uri. $this->set('uri.media.full', $this->get('uri.base.full') . 'media/'); $this->set('uri.media.path', $this->get('uri.base.path') . 'media/'); } } /** * Retrieve the application configuration object. * * @return Registry * * @since 4.0.0 */ public function getConfig() { return $this->config; } } ApiApplication.php 0000644 00000032776 15173173555 0010205 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\Web\WebClient; use Joomla\CMS\Access\Exception\AuthenticationFailed; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Router\ApiRouter; use Joomla\CMS\Router\Exception\RouteNotFoundException; use Joomla\CMS\Uri\Uri; use Joomla\DI\Container; use Joomla\Input\Json as JInputJson; use Joomla\Registry\Registry; use Negotiation\Accept; use Negotiation\Exception\InvalidArgument; use Negotiation\Negotiator; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! API Application class * * @since 4.0.0 */ final class ApiApplication extends CMSApplication { /** * Maps extension types to their * * @var array * @since 4.0.0 */ protected $formatMapper = []; /** * The authentication plugin type * * @var string * @since 4.0.0 */ protected $authenticationPluginType = 'api-authentication'; /** * Class constructor. * * @param JInputJson $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 4.0.0 */ public function __construct(JInputJson $input = null, Registry $config = null, WebClient $client = null, Container $container = null) { // Register the application name $this->name = 'api'; // Register the client ID $this->clientId = 3; // Execute the parent constructor parent::__construct($input, $config, $client, $container); $this->addFormatMap('application/json', 'json'); $this->addFormatMap('application/vnd.api+json', 'jsonapi'); // Set the root in the URI based on the application name Uri::root(null, str_ireplace('/' . $this->getName(), '', Uri::base(true))); } /** * Method to run the application routines. * * Most likely you will want to instantiate a controller and execute it, or perform some sort of task directly. * * @return void * * @since 4.0.0 */ protected function doExecute() { // Initialise the application $this->initialiseApp(); // Mark afterInitialise in the profiler. JDEBUG ? $this->profiler->mark('afterInitialise') : null; // Route the application $this->route(); // Mark afterApiRoute in the profiler. JDEBUG ? $this->profiler->mark('afterApiRoute') : null; // Dispatch the application $this->dispatch(); // Mark afterDispatch in the profiler. JDEBUG ? $this->profiler->mark('afterDispatch') : null; } /** * Adds a mapping from a content type to the format stored. Note the format type cannot be overwritten. * * @param string $contentHeader The content header * @param string $format The content type format * * @return void * * @since 4.0.0 */ public function addFormatMap($contentHeader, $format) { if (!\array_key_exists($contentHeader, $this->formatMapper)) { $this->formatMapper[$contentHeader] = $format; } } /** * 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 4.0.0 * * @note Rendering should be overridden to get rid of the theme files. */ protected function render() { // Render the document $this->setBody($this->document->render($this->allowCache())); } /** * Method to send the application response to the client. All headers will be sent prior to the main application output data. * * @param array $options An optional argument to enable CORS. (Temporary) * * @return void * * @since 4.0.0 */ protected function respond($options = []) { // Set the Joomla! API signature $this->setHeader('X-Powered-By', 'JoomlaAPI/1.0', true); $forceCORS = (int) $this->get('cors'); if ($forceCORS) { /** * Enable CORS (Cross-origin resource sharing) * Obtain allowed CORS origin from Global Settings. * Set to * (=all) if not set. */ $allowedOrigin = $this->get('cors_allow_origin', '*'); $this->setHeader('Access-Control-Allow-Origin', $allowedOrigin, true); $this->setHeader('Access-Control-Allow-Headers', 'Authorization'); if ($this->input->server->getString('HTTP_ORIGIN', null) !== null) { $this->setHeader('Access-Control-Allow-Origin', $this->input->server->getString('HTTP_ORIGIN'), true); $this->setHeader('Access-Control-Allow-Credentials', 'true', true); } } // Parent function can be overridden later on for debugging. parent::respond(); } /** * Gets the name of the current template. * * @param boolean $params True to return the template parameters * * @return string|\stdClass * * @since 4.0.0 */ public function getTemplate($params = false) { // The API application should not need to use a template if ($params) { $template = new \stdClass(); $template->template = 'system'; $template->params = new Registry(); $template->inheritable = 0; $template->parent = ''; return $template; } return 'system'; } /** * 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 4.0.0 */ protected function route() { $router = $this->getContainer()->get(ApiRouter::class); // Trigger the onBeforeApiRoute event. PluginHelper::importPlugin('webservices'); $this->triggerEvent('onBeforeApiRoute', [&$router, $this]); $caught404 = false; $method = $this->input->getMethod(); try { $this->handlePreflight($method, $router); $route = $router->parseApiRoute($method); } catch (RouteNotFoundException $e) { $caught404 = true; } /** * Now we have an API perform content negotiation to ensure we have a valid header. Assume if the route doesn't * tell us otherwise it uses the plain JSON API */ $priorities = ['application/vnd.api+json']; if (!$caught404 && \array_key_exists('format', $route['vars'])) { $priorities = $route['vars']['format']; } $negotiator = new Negotiator(); try { $mediaType = $negotiator->getBest($this->input->server->getString('HTTP_ACCEPT'), $priorities); } catch (InvalidArgument $e) { $mediaType = null; } // If we can't find a match bail with a 406 - Not Acceptable if ($mediaType === null) { throw new Exception\NotAcceptable('Could not match accept header', 406); } /** @var $mediaType Accept */ $format = $mediaType->getValue(); if (\array_key_exists($mediaType->getValue(), $this->formatMapper)) { $format = $this->formatMapper[$mediaType->getValue()]; } $this->input->set('format', $format); if ($caught404) { throw $e; } $this->input->set('controller', $route['controller']); $this->input->set('task', $route['task']); foreach ($route['vars'] as $key => $value) { // We inject the format directly above based on the negotiated format. We do not want the array of possible // formats provided by the plugin! if ($key === 'format') { continue; } // We inject the component key into the option parameter in global input for b/c with the other applications if ($key === 'component') { $this->input->set('option', $route['vars'][$key]); continue; } if ($this->input->getMethod() === 'POST') { $this->input->post->set($key, $value); } else { $this->input->set($key, $value); } } $this->triggerEvent('onAfterApiRoute', [$this]); if (!isset($route['vars']['public']) || $route['vars']['public'] === false) { if (!$this->login(['username' => ''], ['silent' => true, 'action' => 'core.login.api'])) { throw new AuthenticationFailed(); } } } /** * Handles preflight requests. * * @param String $method The REST verb * * @param ApiRouter $router The API Routing object * * @return void * * @since 4.0.0 */ protected function handlePreflight($method, $router) { /** * If not an OPTIONS request or CORS is not enabled, * there's nothing useful to do here. */ if ($method !== 'OPTIONS' || !(int) $this->get('cors')) { return; } // Extract routes matching current route from all known routes. $matchingRoutes = $router->getMatchingRoutes(); // Extract exposed methods from matching routes. $matchingRoutesMethods = array_unique( array_reduce( $matchingRoutes, function ($carry, $route) { return array_merge($carry, $route->getMethods()); }, [] ) ); /** * Obtain allowed CORS origin from Global Settings. * Set to * (=all) if not set. */ $allowedOrigin = $this->get('cors_allow_origin', '*'); /** * Obtain allowed CORS headers from Global Settings. * Set to sensible default if not set. */ $allowedHeaders = $this->get('cors_allow_headers', 'Content-Type,X-Joomla-Token'); /** * Obtain allowed CORS methods from Global Settings. * Set to methods exposed by current route if not set. */ $allowedMethods = $this->get('cors_allow_methods', implode(',', $matchingRoutesMethods)); // No use to go through the regular route handling hassle, // so let's simply output the headers and exit. $this->setHeader('status', '204'); $this->setHeader('Access-Control-Allow-Origin', $allowedOrigin); $this->setHeader('Access-Control-Allow-Headers', $allowedHeaders); $this->setHeader('Access-Control-Allow-Methods', $allowedMethods); $this->sendHeaders(); $this->close(); } /** * Returns the application Router object. * * @return ApiRouter * * @since 4.0.0 * * @deprecated 4.3 will be removed in 6.0 * Inject the router or load it from the dependency injection container * Example: * Factory::getContainer()->get(ApiRouter::class); * */ public function getApiRouter() { return $this->getContainer()->get(ApiRouter::class); } /** * Dispatch the application * * @param string $component The component which is being rendered. * * @return void * * @since 4.0.0 */ public function dispatch($component = null) { // Get the component if not set. if (!$component) { $component = $this->input->get('option', null); } // Load the document to the API $this->loadDocument(); // Set up the params $document = Factory::getDocument(); // Register the document object with Factory Factory::$document = $document; $contents = ComponentHelper::renderComponent($component); $document->setBuffer($contents, 'component'); // Trigger the onAfterDispatch event. PluginHelper::importPlugin('system'); $this->triggerEvent('onAfterDispatch'); } } CliApplication.php 0000644 00000026511 15173173555 0010171 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\AbstractApplication; use Joomla\CMS\Application\CLI\CliInput; use Joomla\CMS\Application\CLI\CliOutput; use Joomla\CMS\Application\CLI\Output\Stdout; use Joomla\CMS\Extension\ExtensionManagerTrait; use Joomla\CMS\Factory; use Joomla\CMS\Language\Language; use Joomla\DI\Container; use Joomla\DI\ContainerAwareTrait; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherAwareTrait; use Joomla\Event\DispatcherInterface; use Joomla\Input\Input; use Joomla\Registry\Registry; use Joomla\Session\SessionInterface; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Base class for a Joomla! command line application. * * @since 2.5.0 * * @deprecated 4.0 will be removed in 6.0 * Use the ConsoleApplication instead */ abstract class CliApplication extends AbstractApplication implements DispatcherAwareInterface, CMSApplicationInterface { use DispatcherAwareTrait; use EventAware; use IdentityAware; use ContainerAwareTrait; use ExtensionManagerTrait; use ExtensionNamespaceMapper; /** * Output object * * @var CliOutput * @since 4.0.0 */ protected $output; /** * The input. * * @var \Joomla\Input\Input * @since 4.0.0 */ protected $input = null; /** * CLI Input object * * @var CliInput * @since 4.0.0 */ protected $cliInput; /** * The application language object. * * @var Language * @since 4.0.0 */ protected $language; /** * The application message queue. * * @var array * @since 4.0.0 */ protected $messages = []; /** * The application instance. * * @var CliApplication * @since 1.7.0 */ protected static $instance; /** * Class constructor. * * @param Input $input An optional argument to provide dependency injection for the application's * input object. If the argument is a JInputCli 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 CliOutput $output The output handler. * @param CliInput $cliInput The CLI input handler. * @param DispatcherInterface $dispatcher An optional argument to provide dependency injection for the application's * event dispatcher. If the argument is a DispatcherInterface object that object will become * the application's event dispatcher, if it is null then the default event dispatcher * will be created based on the application's loadDispatcher() method. * @param Container $container Dependency injection container. * * @since 1.7.0 */ public function __construct( Input $input = null, Registry $config = null, CliOutput $output = null, CliInput $cliInput = null, DispatcherInterface $dispatcher = null, Container $container = null ) { // Close the application if we are not executed from the command line. if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv'])) { $this->close(); } $container = $container ?: Factory::getContainer(); $this->setContainer($container); $this->setDispatcher($dispatcher ?: $container->get(\Joomla\Event\DispatcherInterface::class)); if (!$container->has('session')) { $container->alias('session', 'session.cli') ->alias('JSession', 'session.cli') ->alias(\Joomla\CMS\Session\Session::class, 'session.cli') ->alias(\Joomla\Session\Session::class, 'session.cli') ->alias(\Joomla\Session\SessionInterface::class, 'session.cli'); } $this->input = new \Joomla\CMS\Input\Cli(); $this->language = Factory::getLanguage(); $this->output = $output ?: new Stdout(); $this->cliInput = $cliInput ?: new CliInput(); parent::__construct($config); // Set the current directory. $this->set('cwd', getcwd()); // Set up the environment $this->input->set('format', 'cli'); } /** * Magic method to access properties of the application. * * @param string $name The name of the property. * * @return mixed A value if the property name is valid, null otherwise. * * @since 4.0.0 * * @deprecated 4.0 will be removed in 6.0 * This is a B/C proxy for deprecated read accesses * Example: Factory::getApplication()->getInput(); */ public function __get($name) { switch ($name) { case 'input': @trigger_error( 'Accessing the input property of the application is deprecated, use the getInput() method instead.', E_USER_DEPRECATED ); return $this->getInput(); default: $trace = debug_backtrace(); trigger_error( sprintf( 'Undefined property via __get(): %1$s in %2$s on line %3$s', $name, $trace[0]['file'], $trace[0]['line'] ), E_USER_NOTICE ); } } /** * Method to get the application input object. * * @return Input * * @since 4.0.0 */ public function getInput(): Input { return $this->input; } /** * Method to get the application language object. * * @return Language The language object * * @since 4.0.0 */ public function getLanguage() { return $this->language; } /** * Returns a reference to the global CliApplication object, only creating it if it doesn't already exist. * * This method must be invoked as: $cli = CliApplication::getInstance(); * * @param string $name The name (optional) of the Application Cli class to instantiate. * * @return CliApplication * * @since 1.7.0 * * @deprecated 4.0 will be removed in 6.0 * Load the app through the container or via the Factory * Example: Factory::getContainer()->get(CliApplication::class) * * @throws \RuntimeException */ public static function getInstance($name = null) { // Only create the object if it doesn't exist. if (empty(static::$instance)) { if (!class_exists($name)) { throw new \RuntimeException(sprintf('Unable to load application: %s', $name), 500); } static::$instance = new $name(); } return static::$instance; } /** * Execute the application. * * @return void * * @since 1.7.0 */ public function execute() { $this->createExtensionNamespaceMap(); // Trigger the onBeforeExecute event $this->triggerEvent('onBeforeExecute'); // Perform application routines. $this->doExecute(); // Trigger the onAfterExecute event. $this->triggerEvent('onAfterExecute'); } /** * Get an output object. * * @return CliOutput * * @since 4.0.0 */ public function getOutput() { return $this->output; } /** * Get a CLI input object. * * @return CliInput * * @since 4.0.0 */ public function getCliInput() { return $this->cliInput; } /** * Write a string to standard output. * * @param string $text The text to display. * @param boolean $nl True (default) to append a new line at the end of the output string. * * @return $this * * @since 4.0.0 */ public function out($text = '', $nl = true) { $this->getOutput()->out($text, $nl); return $this; } /** * Get a value from standard input. * * @return string The input string from standard input. * * @codeCoverageIgnore * @since 4.0.0 */ public function in() { return $this->getCliInput()->in(); } /** * Set an output object. * * @param CliOutput $output CliOutput object * * @return $this * * @since 3.3 */ public function setOutput(CliOutput $output) { $this->output = $output; return $this; } /** * Enqueue a system message. * * @param string $msg The message to enqueue. * @param string $type The message type. * * @return void * * @since 4.0.0 */ public function enqueueMessage($msg, $type = self::MSG_INFO) { if (!\array_key_exists($type, $this->messages)) { $this->messages[$type] = []; } $this->messages[$type][] = $msg; } /** * Get the system message queue. * * @return array The system message queue. * * @since 4.0.0 */ public function getMessageQueue() { return $this->messages; } /** * 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 4.0.0 */ public function isClient($identifier) { return $identifier === 'cli'; } /** * Method to get the application session object. * * @return SessionInterface The session object * * @since 4.0.0 */ public function getSession() { return $this->container->get(SessionInterface::class); } /** * Retrieve the application configuration object. * * @return Registry * * @since 4.0.0 */ public function getConfig() { return $this->config; } /** * 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 4.0 will be removed in 6.0 * Will be removed without replacements */ public function isCli() { return true; } } CMSApplication.php 0000644 00000122432 15173173555 0010103 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org> * @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\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\LanguageFactoryInterface; 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 = []; /** * Application instances container. * * @var CmsApplication[] * @since 3.2 */ protected static $instances = []; /** * 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 = []; /** * 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 = [ '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( ['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); } // Stop 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 3.2 will be removed in 6.0 * Use get() instead * Example: Factory::getApplication()->get($varname, $default); */ public function getCfg($varname, $default = null) { try { Log::add( sprintf('%s() is deprecated and will be removed in 6.0. Use Factory->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 4.0 will be removed in 6.0 * Use the application service from the DI container instead * Example: Factory::getContainer()->get($name); */ 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 = []) { 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 = []; } 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 4.3 will be removed in 6.0 * Inject the router or load it from the dependency injection container * Example: Factory::getContainer()->get($name); */ public static function getRouter($name = null, array $options = []) { $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 string|\stdClass The name of the template if the params argument is false. The template object if the params argument is true. * * @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 = []) { // 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 = $this->getContainer()->get(LanguageFactoryInterface::class)->createLanguage($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 = []) { // 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) $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) $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', [$options]); return true; } } // Trigger onUserLoginFailure Event. $this->triggerEvent('onUserLoginFailure', [(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, ['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 = []) { // Get a user object from the Application. $user = Factory::getUser($userid); // Build the credentials array. $parameters = [ 'username' => $user->get('username'), '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', [$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', [$options]); return true; } // Trigger onUserLogoutFailure Event. $this->triggerEvent('onUserLogoutFailure', [$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 4.0 will be removed in 6.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(), ['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 The previous state, if one existed. Null otherwise. * * @since 3.2 */ public function setUserState($key, $value) { $session = $this->getSession(); $registry = $session->get('registry'); if ($registry !== null) { return $registry->set($key, $value); } return null; } /** * 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 4.0 will be removed in 6.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 will be removed in 6.0 * Will be removed without replacements */ protected function isTwoFactorAuthenticationRequired(): bool { return false; } /** * No longer used * * @return boolean * * @since 4.0.0 * * @throws \Exception * * @deprecated 4.2 will be removed in 6.0 * Will be removed without replacements */ 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; } } SiteApplication.php 0000644 00000071760 15173173555 0010374 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\Web\WebClient; use Joomla\CMS\Cache\CacheControllerFactoryAwareTrait; use Joomla\CMS\Cache\Controller\OutputController; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Input\Input; use Joomla\CMS\Language\LanguageHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\Pathway\Pathway; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Router\Route; use Joomla\CMS\Router\SiteRouter; use Joomla\CMS\Uri\Uri; use Joomla\DI\Container; use Joomla\Registry\Registry; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! Site Application class * * @since 3.2 */ final class SiteApplication extends CMSApplication { use CacheControllerFactoryAwareTrait; use MultiFactorAuthenticationHandler; /** * Option to filter by language * * @var boolean * @since 4.0.0 */ protected $language_filter = false; /** * Option to detect language by the browser * * @var boolean * @since 4.0.0 */ protected $detect_browser = false; /** * The registered URL parameters. * * @var object * @since 4.3.0 */ public $registeredurlparams; /** * 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) { // Register the application name $this->name = 'site'; // Register the client ID $this->clientId = 0; // Execute the parent constructor parent::__construct($input, $config, $client, $container); } /** * Check if the user can access the application * * @param integer $itemid The item ID to check authorisation for * * @return void * * @since 3.2 * * @throws \Exception When you are not authorised to view the home page menu item */ protected function authorise($itemid) { $menus = $this->getMenu(); $user = Factory::getUser(); if (!$menus->authorise($itemid)) { if ($user->get('id') == 0) { // Set the data $this->setUserState('users.login.form.data', ['return' => Uri::getInstance()->toString()]); $url = Route::_('index.php?option=com_users&view=login', false); $this->enqueueMessage(Text::_('JGLOBAL_YOU_MUST_LOGIN_FIRST'), 'error'); $this->redirect($url); } else { // Get the home page menu item $home_item = $menus->getDefault($this->getLanguage()->getTag()); // If we are already in the homepage raise an exception if ($menus->getActive()->id == $home_item->id) { throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403); } // Otherwise redirect to the homepage and show an error $this->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error'); $this->redirect(Route::_('index.php?Itemid=' . $home_item->id, false)); } } } /** * Dispatch the application * * @param string $component The component which is being rendered. * * @return void * * @since 3.2 */ public function dispatch($component = null) { // Get the component if not set. if (!$component) { $component = $this->input->getCmd('option', null); } // Load the document to the API $this->loadDocument(); // Set up the params $document = $this->getDocument(); $params = $this->getParams(); // Register the document object with Factory Factory::$document = $document; switch ($document->getType()) { case 'html': // Set up the language LanguageHelper::getLanguages('lang_code'); // Set metadata $document->setMetaData('rights', $this->get('MetaRights')); // Get the template $template = $this->getTemplate(true); // Store the template and its params to the config $this->set('theme', $template->template); $this->set('themeParams', $template->params); // Add Asset registry files $wr = $document->getWebAssetManager()->getRegistry(); if ($component) { $wr->addExtensionRegistryFile($component); } if ($template->parent) { $wr->addTemplateRegistryFile($template->parent, $this->getClientId()); } $wr->addTemplateRegistryFile($template->template, $this->getClientId()); break; case 'feed': $document->setBase(htmlspecialchars(Uri::current())); break; } $document->setTitle($params->get('page_title')); $document->setDescription($params->get('page_description')); // Add version number or not based on global configuration if ($this->get('MetaVersion', 0)) { $document->setGenerator('Joomla! - Open Source Content Management - Version ' . JVERSION); } else { $document->setGenerator('Joomla! - Open Source Content Management'); } $contents = ComponentHelper::renderComponent($component); $document->setBuffer($contents, 'component'); // Trigger the onAfterDispatch event. PluginHelper::importPlugin('system'); $this->triggerEvent('onAfterDispatch'); } /** * Method to run the Web application routines. * * @return void * * @since 3.2 */ protected function doExecute() { // Initialise the application $this->initialiseApp(); // Mark afterInitialise in the profiler. JDEBUG ? $this->profiler->mark('afterInitialise') : null; // Route the application $this->route(); // Mark afterRoute in the profiler. JDEBUG ? $this->profiler->mark('afterRoute') : null; if (!$this->isHandlingMultiFactorAuthentication()) { /* * Check if the user is required to reset their password * * Before $this->route(); "option" and "view" can't be safely read using: * $this->input->getCmd('option'); or $this->input->getCmd('view'); * ex: due of the sef urls */ $this->checkUserRequireReset('com_users', 'profile', 'edit', 'com_users/profile.save,com_users/profile.apply,com_users/user.logout'); } // Dispatch the application $this->dispatch(); // Mark afterDispatch in the profiler. JDEBUG ? $this->profiler->mark('afterDispatch') : null; } /** * Return the current state of the detect browser option. * * @return boolean * * @since 3.2 */ public function getDetectBrowser() { return $this->detect_browser; } /** * Return the current state of the language filter. * * @return boolean * * @since 3.2 */ public function getLanguageFilter() { return $this->language_filter; } /** * Get the application parameters * * @param string $option The component option * * @return Registry The parameters object * * @since 3.2 */ public function getParams($option = null) { static $params = []; $hash = '__default'; if (!empty($option)) { $hash = $option; } if (!isset($params[$hash])) { // Get component parameters if (!$option) { $option = $this->input->getCmd('option', null); } // Get new instance of component global parameters $params[$hash] = clone ComponentHelper::getParams($option); // Get menu parameters $menus = $this->getMenu(); $menu = $menus->getActive(); // Get language $lang_code = $this->getLanguage()->getTag(); $languages = LanguageHelper::getLanguages('lang_code'); $title = $this->get('sitename'); if (isset($languages[$lang_code]) && $languages[$lang_code]->metadesc) { $description = $languages[$lang_code]->metadesc; } else { $description = $this->get('MetaDesc'); } $rights = $this->get('MetaRights'); $robots = $this->get('robots'); // Retrieve com_menu global settings $temp = clone ComponentHelper::getParams('com_menus'); // Lets cascade the parameters if we have menu item parameters if (\is_object($menu)) { // Get show_page_heading from com_menu global settings $params[$hash]->def('show_page_heading', $temp->get('show_page_heading')); $params[$hash]->merge($menu->getParams()); $title = $menu->title; } else { // Merge com_menu global settings $params[$hash]->merge($temp); // If supplied, use page title $title = $temp->get('page_title', $title); } $params[$hash]->def('page_title', $title); $params[$hash]->def('page_description', $description); $params[$hash]->def('page_rights', $rights); $params[$hash]->def('robots', $robots); } return $params[$hash]; } /** * Return a reference to the Pathway object. * * @param string $name The name of the application. * @param array $options An optional associative array of configuration settings. * * @return Pathway A Pathway object * * @since 3.2 */ public function getPathway($name = 'site', $options = []) { return parent::getPathway($name, $options); } /** * Return a reference to the Router object. * * @param string $name The name of the application. * @param array $options An optional associative array of configuration settings. * * @return \Joomla\CMS\Router\Router * * @since 3.2 * * @deprecated 4.3 will be removed in 6.0 * Inject the router or load it from the dependency injection container * Example: Factory::getContainer()->get(SiteRouter::class); */ public static function getRouter($name = 'site', array $options = []) { return parent::getRouter($name, $options); } /** * Gets the name of the current template. * * @param boolean $params True to return the template parameters * * @return string|\stdClass The name of the template if the params argument is false. The template object if the params argument is true. * * @since 3.2 * @throws \InvalidArgumentException */ public function getTemplate($params = false) { if (\is_object($this->template)) { if ($this->template->parent) { if (!is_file(JPATH_THEMES . '/' . $this->template->template . '/index.php')) { if (!is_file(JPATH_THEMES . '/' . $this->template->parent . '/index.php')) { throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $this->template->template)); } } } elseif (!is_file(JPATH_THEMES . '/' . $this->template->template . '/index.php')) { throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $this->template->template)); } if ($params) { return $this->template; } return $this->template->template; } // Get the id of the active menu item $menu = $this->getMenu(); $item = $menu->getActive(); if (!$item) { $item = $menu->getItem($this->input->getInt('Itemid', null)); } $id = 0; if (\is_object($item)) { // Valid item retrieved $id = $item->template_style_id; } $tid = $this->input->getUint('templateStyle', 0); if (is_numeric($tid) && (int) $tid > 0) { $id = (int) $tid; } /** @var OutputController $cache */ $cache = $this->getCacheControllerFactory()->createCacheController('output', ['defaultgroup' => 'com_templates']); if ($this->getLanguageFilter()) { $tag = $this->getLanguage()->getTag(); } else { $tag = ''; } $cacheId = 'templates0' . $tag; if ($cache->contains($cacheId)) { $templates = $cache->get($cacheId); } else { $templates = $this->bootComponent('templates')->getMVCFactory() ->createModel('Style', 'Administrator')->getSiteTemplates(); foreach ($templates as &$template) { // Create home element if ($template->home == 1 && !isset($template_home) || $this->getLanguageFilter() && $template->home == $tag) { $template_home = clone $template; } $template->params = new Registry($template->params); } // Unset the $template reference to the last $templates[n] item cycled in the foreach above to avoid editing it later unset($template); // Add home element, after loop to avoid double execution if (isset($template_home)) { $template_home->params = new Registry($template_home->params); $templates[0] = $template_home; } $cache->store($templates, $cacheId); } if (isset($templates[$id])) { $template = $templates[$id]; } else { $template = $templates[0]; } // Allows for overriding the active template from the request $template_override = $this->input->getCmd('template', ''); // Only set template override if it is a valid template (= it exists and is enabled) if (!empty($template_override)) { if (is_file(JPATH_THEMES . '/' . $template_override . '/index.php')) { foreach ($templates as $tmpl) { if ($tmpl->template === $template_override) { $template = $tmpl; break; } } } } // Need to filter the default value as well $template->template = InputFilter::getInstance()->clean($template->template, 'cmd'); // Fallback template if (!empty($template->parent)) { if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) { if (!is_file(JPATH_THEMES . '/' . $template->parent . '/index.php')) { $this->enqueueMessage(Text::_('JERROR_ALERTNOTEMPLATE'), 'error'); // Try to find data for 'cassiopeia' template $original_tmpl = $template->template; foreach ($templates as $tmpl) { if ($tmpl->template === 'cassiopeia') { $template = $tmpl; break; } } // Check, the data were found and if template really exists if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) { throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $original_tmpl)); } } } } elseif (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) { $this->enqueueMessage(Text::_('JERROR_ALERTNOTEMPLATE'), 'error'); // Try to find data for 'cassiopeia' template $original_tmpl = $template->template; foreach ($templates as $tmpl) { if ($tmpl->template === 'cassiopeia') { $template = $tmpl; break; } } // Check, the data were found and if template really exists if (!is_file(JPATH_THEMES . '/' . $template->template . '/index.php')) { throw new \InvalidArgumentException(Text::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $original_tmpl)); } } // Cache the result $this->template = $template; if ($params) { return $template; } return $template->template; } /** * Initialise the application. * * @param array $options An optional associative array of configuration settings. * * @return void * * @since 3.2 */ protected function initialiseApp($options = []) { $user = Factory::getUser(); // If the user is a guest we populate it with the guest user group. if ($user->guest) { $guestUsergroup = ComponentHelper::getParams('com_users')->get('guest_usergroup', 1); $user->groups = [$guestUsergroup]; } if ($plugin = PluginHelper::getPlugin('system', 'languagefilter')) { $pluginParams = new Registry($plugin->params); $this->setLanguageFilter(true); $this->setDetectBrowser($pluginParams->get('detect_browser', 1) == 1); } if (empty($options['language'])) { // Detect the specified language $lang = $this->input->getString('language', null); // Make sure that the user's language exists if ($lang && LanguageHelper::exists($lang)) { $options['language'] = $lang; } } if (empty($options['language']) && $this->getLanguageFilter()) { // Detect cookie language $lang = $this->input->cookie->get(md5($this->get('secret') . 'language'), null, 'string'); // Make sure that the user's language exists if ($lang && LanguageHelper::exists($lang)) { $options['language'] = $lang; } } if (empty($options['language'])) { // Detect user language $lang = $user->getParam('language'); // Make sure that the user's language exists if ($lang && LanguageHelper::exists($lang)) { $options['language'] = $lang; } } if (empty($options['language']) && $this->getDetectBrowser()) { // Detect browser language $lang = LanguageHelper::detectLanguage(); // Make sure that the user's language exists if ($lang && LanguageHelper::exists($lang)) { $options['language'] = $lang; } } if (empty($options['language'])) { // Detect default language $params = ComponentHelper::getParams('com_languages'); $options['language'] = $params->get('site', $this->get('language', 'en-GB')); } // One last check to make sure we have something if (!LanguageHelper::exists($options['language'])) { $lang = $this->config->get('language', 'en-GB'); if (LanguageHelper::exists($lang)) { $options['language'] = $lang; } else { // As a last ditch fail to english $options['language'] = 'en-GB'; } } // Finish initialisation parent::initialiseApp($options); } /** * Load the library language files for the application * * @return void * * @since 3.6.3 */ protected function loadLibraryLanguage() { /* * Try the lib_joomla file in the current language (without allowing the loading of the file in the default language) * Fallback to the default language if necessary */ $this->getLanguage()->load('lib_joomla', JPATH_SITE) || $this->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR); } /** * Login authentication function * * @param array $credentials Array('username' => string, 'password' => string) * @param array $options Array('remember' => boolean) * * @return boolean True on success. * * @since 3.2 */ public function login($credentials, $options = []) { // Set the application login entry point if (!\array_key_exists('entry_url', $options)) { $options['entry_url'] = Uri::base() . 'index.php?option=com_users&task=user.login'; } // Set the access control action to check. $options['action'] = 'core.login.site'; return parent::login($credentials, $options); } /** * 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() { switch ($this->document->getType()) { case 'feed': // No special processing for feeds break; case 'html': default: $template = $this->getTemplate(true); $file = $this->input->get('tmpl', 'index'); if ($file === 'offline' && !$this->get('offline')) { $this->set('themeFile', 'index.php'); } if ($this->get('offline') && !Factory::getUser()->authorise('core.login.offline')) { $this->setUserState('users.login.form.data', ['return' => Uri::getInstance()->toString()]); $this->set('themeFile', 'offline.php'); $this->setHeader('Status', '503 Service Temporarily Unavailable', 'true'); } if (!is_dir(JPATH_THEMES . '/' . $template->template) && !$this->get('offline')) { $this->set('themeFile', 'component.php'); } // Ensure themeFile is set by now if ($this->get('themeFile') == '') { $this->set('themeFile', $file . '.php'); } // Pass the parent template to the state $this->set('themeInherits', $template->parent); break; } parent::render(); } /** * 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 */ protected function route() { // Get the full request URI. $uri = clone Uri::getInstance(); // It is not possible to inject the SiteRouter as it requires a SiteApplication // and we would end in an infinite loop $result = $this->getContainer()->get(SiteRouter::class)->parse($uri, true); $active = $this->getMenu()->getActive(); if ( $active !== null && $active->type === 'alias' && $active->getParams()->get('alias_redirect') && \in_array($this->input->getMethod(), ['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'); $Itemid = $this->input->getInt('Itemid', null); $this->authorise($Itemid); } /** * Set the current state of the detect browser option. * * @param boolean $state The new state of the detect browser option * * @return boolean The previous state * * @since 3.2 */ public function setDetectBrowser($state = false) { $old = $this->getDetectBrowser(); $this->detect_browser = $state; return $old; } /** * Set the current state of the language filter. * * @param boolean $state The new state of the language filter * * @return boolean The previous state * * @since 3.2 */ public function setLanguageFilter($state = false) { $old = $this->getLanguageFilter(); $this->language_filter = $state; return $old; } /** * Overrides the default template that would be used * * @param \stdClass|string $template The template name or definition * @param mixed $styleParams The template style parameters * * @return void * * @since 3.2 */ public function setTemplate($template, $styleParams = null) { if (is_object($template)) { $templateName = empty($template->template) ? '' : $template->template; $templateInheritable = empty($template->inheritable) ? 0 : $template->inheritable; $templateParent = empty($template->parent) ? '' : $template->parent; $templateParams = empty($template->params) ? $styleParams : $template->params; } else { $templateName = $template; $templateInheritable = 0; $templateParent = ''; $templateParams = $styleParams; } if (is_dir(JPATH_THEMES . '/' . $templateName)) { $this->template = new \stdClass(); $this->template->template = $templateName; if ($templateParams instanceof Registry) { $this->template->params = $templateParams; } else { $this->template->params = new Registry($templateParams); } $this->template->inheritable = $templateInheritable; $this->template->parent = $templateParent; // Store the template and its params to the config $this->set('theme', $this->template->template); $this->set('themeParams', $this->template->params); } } } BaseApplication.php 0000644 00000004027 15173173555 0010332 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Application; use Joomla\Application\AbstractApplication; use Joomla\CMS\Input\Input; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherAwareTrait; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla Platform Base Application Class * * @property-read Input $input The application input object * * @since 3.0.0 * * @deprecated 4.3 will be removed in 6.0 * Application classes should directly be based on \Joomla\Application\AbstractApplication * don't use this class anymore */ abstract class BaseApplication extends AbstractApplication implements DispatcherAwareInterface { use DispatcherAwareTrait; use EventAware; use IdentityAware; /** * 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. * * @since 3.0.0 */ public function __construct(Input $input = null, Registry $config = null) { $this->input = $input instanceof Input ? $input : new Input(); $this->config = $config instanceof Registry ? $config : new Registry(); $this->initialise(); } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Generation time: 0 |
proxy
|
phpinfo
|
Settings