1504 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1504 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * @package   FOF
 | |
|  * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 | |
|  * @license   GNU General Public License version 3, or later
 | |
|  */
 | |
| 
 | |
| namespace FOF40\Platform\Joomla;
 | |
| 
 | |
| defined('_JEXEC') || die;
 | |
| 
 | |
| use ActionlogsModelActionlog;
 | |
| use DateTime;
 | |
| use DateTimeZone;
 | |
| use Exception;
 | |
| use FOF40\Container\Container;
 | |
| use FOF40\Date\Date;
 | |
| use FOF40\Date\DateDecorator;
 | |
| use FOF40\Input\Input;
 | |
| use FOF40\Platform\Base\Platform as BasePlatform;
 | |
| use InvalidArgumentException;
 | |
| use JDatabaseDriver;
 | |
| use JEventDispatcher;
 | |
| use Joomla\CMS\Application\ApplicationHelper;
 | |
| use Joomla\CMS\Application\CliApplication;
 | |
| use Joomla\CMS\Application\CliApplication as JApplicationCli;
 | |
| use Joomla\CMS\Application\ConsoleApplication;
 | |
| use Joomla\CMS\Authentication\Authentication;
 | |
| use Joomla\CMS\Authentication\AuthenticationResponse;
 | |
| use Joomla\CMS\Cache\Cache;
 | |
| use Joomla\CMS\Document\Document;
 | |
| use Joomla\CMS\Document\HtmlDocument;
 | |
| use Joomla\CMS\Factory as JoomlaFactory;
 | |
| use Joomla\CMS\Language\Language;
 | |
| use Joomla\CMS\Log\Log;
 | |
| use Joomla\CMS\MVC\Model\BaseDatabaseModel;
 | |
| use Joomla\CMS\Plugin\PluginHelper;
 | |
| use Joomla\CMS\Session\Session;
 | |
| use Joomla\CMS\Uri\Uri;
 | |
| use Joomla\CMS\User\User;
 | |
| use Joomla\CMS\User\UserFactoryInterface;
 | |
| use Joomla\CMS\User\UserHelper;
 | |
| use Joomla\CMS\Version as JoomlaVersion;
 | |
| use Joomla\Event\Event;
 | |
| use Joomla\Registry\Registry;
 | |
| 
 | |
| /**
 | |
|  * Part of the FOF Platform Abstraction Layer.
 | |
|  *
 | |
|  * This implements the platform class for Joomla! 3 and Joomla! 4
 | |
|  *
 | |
|  * @since    2.1
 | |
|  */
 | |
| class Platform extends BasePlatform
 | |
| {
 | |
| 	/**
 | |
| 	 * Is this a CLI application?
 | |
| 	 *
 | |
| 	 * @var   bool
 | |
| 	 */
 | |
| 	protected static $isCLI;
 | |
| 
 | |
| 	/**
 | |
| 	 * Is this an administrator application?
 | |
| 	 *
 | |
| 	 * @var   bool
 | |
| 	 */
 | |
| 	protected static $isAdmin;
 | |
| 
 | |
| 	/**
 | |
| 	 * Is this an API application?
 | |
| 	 *
 | |
| 	 * @var   bool
 | |
| 	 */
 | |
| 	protected static $isApi;
 | |
| 
 | |
| 	/**
 | |
| 	 * A fake session storage for CLI apps. Since CLI applications cannot have a session we are using a Registry object
 | |
| 	 * we manage internally.
 | |
| 	 *
 | |
| 	 * @var   Registry
 | |
| 	 */
 | |
| 	protected static $fakeSession;
 | |
| 
 | |
| 	/**
 | |
| 	 * The table and table field cache object, used to speed up database access
 | |
| 	 *
 | |
| 	 * @var  Registry|null
 | |
| 	 */
 | |
| 	private $_cache;
 | |
| 
 | |
| 	/**
 | |
| 	 * Public constructor.
 | |
| 	 *
 | |
| 	 * Overridden to cater for CLI applications not having access to a session object.
 | |
| 	 *
 | |
| 	 * @param   Container  $c  The component container
 | |
| 	 */
 | |
| 	public function __construct(Container $c)
 | |
| 	{
 | |
| 		parent::__construct($c);
 | |
| 
 | |
| 		if ($this->isCli())
 | |
| 		{
 | |
| 			self::$fakeSession = new Registry();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Checks if the current script is run inside a valid CMS execution
 | |
| 	 *
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public function checkExecution(): bool
 | |
| 	{
 | |
| 		return defined('_JEXEC');
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Raises an error, using the logic requested by the CMS (PHP Exception or dedicated class)
 | |
| 	 *
 | |
| 	 * @param   integer  $code
 | |
| 	 * @param   string   $message
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 *
 | |
| 	 * @throws  Exception
 | |
| 	 *
 | |
| 	 * @deprecated 5.0 Use showErrorPage with a real exception instead
 | |
| 	 */
 | |
| 	public function raiseError(int $code, string $message): void
 | |
| 	{
 | |
| 		$this->showErrorPage(new Exception($message, $code));
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns absolute path to directories used by the containing CMS/application.
 | |
| 	 *
 | |
| 	 * The return is a table with the following key:
 | |
| 	 * * root    Path to the site root
 | |
| 	 * * public  Path to the public area of the site
 | |
| 	 * * admin   Path to the administrative area of the site
 | |
| 	 * * api     Path to the API application area of the site
 | |
| 	 * * tmp     Path to the temp directory
 | |
| 	 * * log     Path to the log directory
 | |
| 	 *
 | |
| 	 * @return  array  A hash array with keys root, public, admin, tmp and log.
 | |
| 	 */
 | |
| 	public function getPlatformBaseDirs(): array
 | |
| 	{
 | |
| 		return [
 | |
| 			'root'   => JPATH_ROOT,
 | |
| 			'public' => JPATH_SITE,
 | |
| 			'media'  => JPATH_SITE . '/media',
 | |
| 			'admin'  => JPATH_ADMINISTRATOR,
 | |
| 			'api'    => defined('JPATH_API') ? JPATH_API : (JPATH_ROOT . '/api'),
 | |
| 			'tmp'    => JoomlaFactory::getConfig()->get('tmp_path'),
 | |
| 			'log'    => JoomlaFactory::getConfig()->get('log_path'),
 | |
| 		];
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns the base (root) directories for a given component, i.e the application
 | |
| 	 * which is running inside our main application (CMS, web app).
 | |
| 	 *
 | |
| 	 * The return is a table with the following keys:
 | |
| 	 * * main    The normal location of component files. For a back-end Joomla!
 | |
| 	 *          component this is the administrator/components/com_example
 | |
| 	 *          directory.
 | |
| 	 * * alt    The alternate location of component files. For a back-end
 | |
| 	 *          Joomla! component this is the front-end directory, e.g.
 | |
| 	 *          components/com_example
 | |
| 	 * * site    The location of the component files serving the public part of
 | |
| 	 *          the application.
 | |
| 	 * * admin    The location of the component files serving the administrative
 | |
| 	 *          part of the application.
 | |
| 	 * * api    The location of the component files serving the API part of the application
 | |
| 	 *
 | |
| 	 * All paths MUST be absolute. All paths MAY be the same if the
 | |
| 	 * platform doesn't make a distinction between public and private parts,
 | |
| 	 * or when the component does not provide both a public and private part.
 | |
| 	 * All of the directories MUST be defined and non-empty.
 | |
| 	 *
 | |
| 	 * @param   string  $component  The name of the component. For Joomla! this
 | |
| 	 *                              is something like "com_example"
 | |
| 	 *
 | |
| 	 * @return  array  A hash array with keys main, alt, site and admin.
 | |
| 	 */
 | |
| 	public function getComponentBaseDirs(string $component): array
 | |
| 	{
 | |
| 		if (!$this->isBackend())
 | |
| 		{
 | |
| 			$mainPath = JPATH_SITE . '/components/' . $component;
 | |
| 			$altPath  = JPATH_ADMINISTRATOR . '/components/' . $component;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			$mainPath = JPATH_ADMINISTRATOR . '/components/' . $component;
 | |
| 			$altPath  = JPATH_SITE . '/components/' . $component;
 | |
| 		}
 | |
| 
 | |
| 		return [
 | |
| 			'main'  => $mainPath,
 | |
| 			'alt'   => $altPath,
 | |
| 			'site'  => JPATH_SITE . '/components/' . $component,
 | |
| 			'admin' => JPATH_ADMINISTRATOR . '/components/' . $component,
 | |
| 			'api'   => (defined('JPATH_API') ? JPATH_API : (JPATH_ROOT . '/api')) . '/components/' . $component,
 | |
| 		];
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns the application's template name
 | |
| 	 *
 | |
| 	 * @param   null|array  $params  An optional associative array of configuration settings
 | |
| 	 *
 | |
| 	 * @return  string  The template name. "system" is the fallback.
 | |
| 	 */
 | |
| 	public function getTemplate(?array $params = null): string
 | |
| 	{
 | |
| 		try
 | |
| 		{
 | |
| 			return JoomlaFactory::getApplication()->getTemplate($params ?? false);
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			return 'system';
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get application-specific suffixes to use with template paths. This allows
 | |
| 	 * you to look for view template overrides based on the application version.
 | |
| 	 *
 | |
| 	 * @return  array  A plain array of suffixes to try in template names
 | |
| 	 */
 | |
| 	public function getTemplateSuffixes(): array
 | |
| 	{
 | |
| 		$jversion     = new JoomlaVersion;
 | |
| 		$versionParts = explode('.', $jversion->getShortVersion());
 | |
| 		$majorVersion = array_shift($versionParts);
 | |
| 
 | |
| 		return [
 | |
| 			'.j' . str_replace('.', '', $jversion->getHelpVersion()),
 | |
| 			'.j' . $majorVersion,
 | |
| 		];
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Return the absolute path to the application's template overrides
 | |
| 	 * directory for a specific component. We will use it to look for template
 | |
| 	 * files instead of the regular component directories. If the application
 | |
| 	 * does not have such a thing as template overrides return an empty string.
 | |
| 	 *
 | |
| 	 * @param   string  $component  The name of the component for which to fetch the overrides
 | |
| 	 * @param   bool    $absolute   Should I return an absolute or relative path?
 | |
| 	 *
 | |
| 	 * @return  string  The path to the template overrides directory
 | |
| 	 */
 | |
| 	public function getTemplateOverridePath(string $component, bool $absolute = true): string
 | |
| 	{
 | |
| 		if (!$this->isCli())
 | |
| 		{
 | |
| 			if ($absolute)
 | |
| 			{
 | |
| 				$path = JPATH_THEMES . '/';
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				$path = $this->isBackend() ? 'administrator/templates/' : 'templates/';
 | |
| 			}
 | |
| 
 | |
| 			$directory = (substr($component, 0, 7) == 'media:/') ? ('media/' . substr($component, 7)) : ('html/' . $component);
 | |
| 
 | |
| 			$path .= $this->getTemplate() .
 | |
| 				'/' . $directory;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			$path = '';
 | |
| 		}
 | |
| 
 | |
| 		return $path;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Load the translation files for a given component.
 | |
| 	 *
 | |
| 	 * @param   string  $component  The name of the component, e.g. "com_example"
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	public function loadTranslations(string $component): void
 | |
| 	{
 | |
| 		$paths = $this->isBackend() ? [JPATH_ROOT, JPATH_ADMINISTRATOR] : [JPATH_ADMINISTRATOR, JPATH_ROOT];
 | |
| 
 | |
| 		$jlang = $this->getLanguage();
 | |
| 		$jlang->load($component, $paths[0], 'en-GB', true);
 | |
| 		$jlang->load($component, $paths[0], null, true);
 | |
| 		$jlang->load($component, $paths[1], 'en-GB', true);
 | |
| 		$jlang->load($component, $paths[1], null, true);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * By default FOF will only use the Controller's onBefore* methods to
 | |
| 	 * perform user authorisation. In some cases, like the Joomla! back-end,
 | |
| 	 * you also need to perform component-wide user authorisation in the
 | |
| 	 * Dispatcher. This method MUST implement this authorisation check. If you
 | |
| 	 * do not need this in your platform, please always return true.
 | |
| 	 *
 | |
| 	 * @param   string  $component  The name of the component.
 | |
| 	 *
 | |
| 	 * @return  bool  True to allow loading the component, false to halt loading
 | |
| 	 */
 | |
| 	public function authorizeAdmin(string $component): bool
 | |
| 	{
 | |
| 		if ($this->isBackend())
 | |
| 		{
 | |
| 			// Master access check for the back-end, Joomla! 1.6 style.
 | |
| 			$user = $this->getUser();
 | |
| 
 | |
| 			if (!$user->authorise('core.manage', $component)
 | |
| 				&& !$user->authorise('core.admin', $component)
 | |
| 			)
 | |
| 			{
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns a user object.
 | |
| 	 *
 | |
| 	 * @param   integer  $id  The user ID to load. Skip or use null to retrieve
 | |
| 	 *                        the object for the currently logged in user.
 | |
| 	 *
 | |
| 	 * @return  User  The User object for the specified user
 | |
| 	 */
 | |
| 	public function getUser(?int $id = null): User
 | |
| 	{
 | |
| 		/**
 | |
| 		 * If I'm in CLI I need load the User directly, otherwise JoomlaFactory will check the session (which doesn't exist
 | |
| 		 * in CLI)
 | |
| 		 */
 | |
| 		if ($this->isCli())
 | |
| 		{
 | |
| 			if ($id)
 | |
| 			{
 | |
| 				return User::getInstance($id) ?? new User();
 | |
| 			}
 | |
| 
 | |
| 			return new User();
 | |
| 		}
 | |
| 
 | |
| 		// Joomla 3
 | |
| 		if (version_compare(JVERSION, '3.999.999', 'lt'))
 | |
| 		{
 | |
| 			return JoomlaFactory::getUser($id) ?? new User();
 | |
| 		}
 | |
| 
 | |
| 		// Joomla 4
 | |
| 		if (is_null($id))
 | |
| 		{
 | |
| 			return JoomlaFactory::getApplication()->getIdentity() ?? new User();
 | |
| 		}
 | |
| 
 | |
| 		return JoomlaFactory::getContainer()->get(UserFactoryInterface::class)->loadUserById($id) ?? new User();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns the Document object which handles this component's response. You
 | |
| 	 * may also return null and FOF will a. try to figure out the output type by
 | |
| 	 * examining the "format" input parameter (or fall back to "html") and b.
 | |
| 	 * FOF will not attempt to load CSS and Javascript files (as it doesn't make
 | |
| 	 * sense if there's no Document to handle them).
 | |
| 	 *
 | |
| 	 * @return  Document|null
 | |
| 	 */
 | |
| 	public function getDocument(): ?Document
 | |
| 	{
 | |
| 		$document = null;
 | |
| 
 | |
| 		if (!$this->isCli())
 | |
| 		{
 | |
| 			try
 | |
| 			{
 | |
| 				$document = JoomlaFactory::getDocument();
 | |
| 			}
 | |
| 			catch (Exception $exc)
 | |
| 			{
 | |
| 				$document = null;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return $document;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns an object to handle dates
 | |
| 	 *
 | |
| 	 * @param   mixed                     $time      The initial time
 | |
| 	 * @param   DateTimeZone|string|null  $tzOffset  The timezone offset
 | |
| 	 * @param   bool                      $locale    Should I try to load a specific class for current language?
 | |
| 	 *
 | |
| 	 * @return  Date object
 | |
| 	 */
 | |
| 	public function getDate(?string $time = 'now', $tzOffset = null, $locale = true): Date
 | |
| 	{
 | |
| 		$time = $time ?? $this->getDbo()->getNullDate() ?? 'now';
 | |
| 
 | |
| 		if (!is_string($time) && (!is_object($time) || !($time instanceof DateTime)))
 | |
| 		{
 | |
| 			throw new InvalidArgumentException(sprintf('%s::%s -- $time expects a string or a DateTime object', __CLASS__, __METHOD__));
 | |
| 		}
 | |
| 
 | |
| 		if ($locale)
 | |
| 		{
 | |
| 			// Work around a bug in Joomla! 3.7.0.
 | |
| 			if ($time == 'now')
 | |
| 			{
 | |
| 				$time = time();
 | |
| 			}
 | |
| 
 | |
| 			$coreObject = JoomlaFactory::getDate($time, $tzOffset);
 | |
| 
 | |
| 			return new DateDecorator($coreObject);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			return new Date($time, $tzOffset);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Return the Language instance of the CMS/application
 | |
| 	 *
 | |
| 	 * @return Language
 | |
| 	 */
 | |
| 	public function getLanguage(): Language
 | |
| 	{
 | |
| 		return JoomlaFactory::getLanguage();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns the database driver object of the CMS/application
 | |
| 	 *
 | |
| 	 * @return JDatabaseDriver
 | |
| 	 */
 | |
| 	public function getDbo(): JDatabaseDriver
 | |
| 	{
 | |
| 		return JoomlaFactory::getDbo();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * This method will try retrieving a variable from the request (input) data.
 | |
| 	 * If it doesn't exist it will be loaded from the user state, typically
 | |
| 	 * stored in the session. If it doesn't exist there either, the $default
 | |
| 	 * value will be used. If $setUserState is set to true, the retrieved
 | |
| 	 * variable will be stored in the user session.
 | |
| 	 *
 | |
| 	 * @param   string  $key           The user state key for the variable
 | |
| 	 * @param   string  $request       The request variable name for the variable
 | |
| 	 * @param   Input   $input         The Input object with the request (input) data
 | |
| 	 * @param   mixed   $default       The default value. Default: null
 | |
| 	 * @param   string  $type          The filter type for the variable data. Default: none (no filtering)
 | |
| 	 * @param   bool    $setUserState  Should I set the user state with the fetched value?
 | |
| 	 *
 | |
| 	 * @return  mixed  The value of the variable
 | |
| 	 */
 | |
| 	public function getUserStateFromRequest(string $key, string $request, Input $input, $default = null, string $type = 'none', bool $setUserState = true)
 | |
| 	{
 | |
| 		if ($this->isCli())
 | |
| 		{
 | |
| 			$ret = $input->get($request, $default, $type);
 | |
| 
 | |
| 			if ($ret === $default)
 | |
| 			{
 | |
| 				$input->set($request, $ret);
 | |
| 			}
 | |
| 
 | |
| 			return $ret;
 | |
| 		}
 | |
| 
 | |
| 		try
 | |
| 		{
 | |
| 			$app = JoomlaFactory::getApplication();
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			$app = null;
 | |
| 		}
 | |
| 
 | |
| 		$old_state = (!is_null($app) && method_exists($app, 'getUserState')) ? $app->getUserState($key, $default) : null;
 | |
| 
 | |
| 		$cur_state = (!is_null($old_state)) ? $old_state : $default;
 | |
| 		$new_state = $input->get($request, null, $type);
 | |
| 
 | |
| 		// Save the new value only if it was set in this request
 | |
| 		if ($setUserState)
 | |
| 		{
 | |
| 			if ($new_state !== null)
 | |
| 			{
 | |
| 				$app->setUserState($key, $new_state);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				$new_state = $cur_state;
 | |
| 			}
 | |
| 		}
 | |
| 		elseif (is_null($new_state))
 | |
| 		{
 | |
| 			$new_state = $cur_state;
 | |
| 		}
 | |
| 
 | |
| 		return $new_state;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Load plugins of a specific type. Obviously this seems to only be required
 | |
| 	 * in the Joomla! CMS.
 | |
| 	 *
 | |
| 	 * @param   string  $type  The type of the plugins to be loaded
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 *
 | |
| 	 * @codeCoverageIgnore
 | |
| 	 * @see PlatformInterface::importPlugin()
 | |
| 	 *
 | |
| 	 */
 | |
| 	public function importPlugin(string $type): void
 | |
| 	{
 | |
| 		// Should I actually run the plugins?
 | |
| 		$runPlugins = $this->isAllowPluginsInCli() || !$this->isCli();
 | |
| 
 | |
| 		if ($runPlugins)
 | |
| 		{
 | |
| 			PluginHelper::importPlugin($type);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Execute plugins (system-level triggers) and fetch back an array with
 | |
| 	 * their return values.
 | |
| 	 *
 | |
| 	 * @param   string  $event  The event (trigger) name, e.g. onBeforeScratchMyEar
 | |
| 	 * @param   array   $data   A hash array of data sent to the plugins as part of the trigger
 | |
| 	 *
 | |
| 	 * @return  array  A simple array containing the results of the plugins triggered
 | |
| 	 */
 | |
| 	public function runPlugins(string $event, array $data = []): array
 | |
| 	{
 | |
| 		// Should I actually run the plugins?
 | |
| 		$runPlugins = $this->isAllowPluginsInCli() || !$this->isCli();
 | |
| 
 | |
| 		if ($runPlugins)
 | |
| 		{
 | |
| 			if (class_exists('JEventDispatcher'))
 | |
| 			{
 | |
| 				return JEventDispatcher::getInstance()->trigger($event, $data);
 | |
| 			}
 | |
| 
 | |
| 			// If there's no JEventDispatcher try getting JApplication
 | |
| 			try
 | |
| 			{
 | |
| 				$app = JoomlaFactory::getApplication();
 | |
| 			}
 | |
| 			catch (Exception $e)
 | |
| 			{
 | |
| 				// If I can't get JApplication I cannot run the plugins.
 | |
| 				return [];
 | |
| 			}
 | |
| 
 | |
| 			// Joomla 3 and 4 have triggerEvent
 | |
| 			if (method_exists($app, 'triggerEvent'))
 | |
| 			{
 | |
| 				return $app->triggerEvent($event, $data);
 | |
| 			}
 | |
| 
 | |
| 			// Joomla 5 (and possibly some 4.x versions) don't have triggerEvent. Go through the Events dispatcher.
 | |
| 			if (method_exists($app, 'getDispatcher') && class_exists('Joomla\Event\Event'))
 | |
| 			{
 | |
| 				try
 | |
| 				{
 | |
| 					$dispatcher = $app->getDispatcher();
 | |
| 				}
 | |
| 				catch (\UnexpectedValueException $exception)
 | |
| 				{
 | |
| 					return [];
 | |
| 				}
 | |
| 
 | |
| 				if ($data instanceof Event)
 | |
| 				{
 | |
| 					$eventObject = $data;
 | |
| 				}
 | |
| 				elseif (\is_array($data))
 | |
| 				{
 | |
| 					$eventObject = new Event($event, $data);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					throw new \InvalidArgumentException('The plugin data must either be an event or an array');
 | |
| 				}
 | |
| 
 | |
| 				$result = $dispatcher->dispatch($event, $eventObject);
 | |
| 
 | |
| 				return !isset($result['result']) || \is_null($result['result']) ? [] : $result['result'];
 | |
| 			}
 | |
| 
 | |
| 			// No viable way to run the plugins :(
 | |
| 			return [];
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			return [];
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Perform an ACL check. Please note that FOF uses by default the Joomla!
 | |
| 	 * CMS convention for ACL privileges, e.g core.edit for the edit privilege.
 | |
| 	 * If your platform uses different conventions you'll have to override the
 | |
| 	 * FOF defaults using fof.xml or by specialising the controller.
 | |
| 	 *
 | |
| 	 * @param   string       $action     The ACL privilege to check, e.g. core.edit
 | |
| 	 * @param   string|null  $assetname  The asset name to check, typically the component's name
 | |
| 	 *
 | |
| 	 * @return  bool  True if the user is allowed this action
 | |
| 	 */
 | |
| 	public function authorise(string $action, ?string $assetname = null): bool
 | |
| 	{
 | |
| 		if ($this->isCli())
 | |
| 		{
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		$ret = JoomlaFactory::getUser()->authorise($action, $assetname);
 | |
| 
 | |
| 		// Work around Joomla returning null instead of false in some cases.
 | |
| 		return (bool) $ret;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Is this the administrative section of the component?
 | |
| 	 *
 | |
| 	 * @return  bool
 | |
| 	 */
 | |
| 	public function isBackend(): bool
 | |
| 	{
 | |
| 		[$isCli, $isAdmin, $isApi] = $this->isCliAdminApi();
 | |
| 
 | |
| 		return $isAdmin && !$isCli && !$isApi;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Is this the public section of the component?
 | |
| 	 *
 | |
| 	 * @param   bool  $strict  True to only confirm if we're under the 'site' client. False to confirm if we're under
 | |
| 	 *                         either 'site' or 'api' client (both are front-end access). The default is false which
 | |
| 	 *                         causes the method to return true when the application is either 'client' (HTML frontend)
 | |
| 	 *                         or 'api' (JSON frontend).
 | |
| 	 *
 | |
| 	 * @return  bool
 | |
| 	 */
 | |
| 	public function isFrontend(bool $strict = false): bool
 | |
| 	{
 | |
| 		[$isCli, $isAdmin, $isApi] = $this->isCliAdminApi();
 | |
| 
 | |
| 		if ($strict)
 | |
| 		{
 | |
| 			return !$isAdmin && !$isCli && !$isApi;
 | |
| 		}
 | |
| 
 | |
| 		return !$isAdmin && !$isCli;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Is this a component running in a CLI application?
 | |
| 	 *
 | |
| 	 * @return  bool
 | |
| 	 */
 | |
| 	public function isCli(): bool
 | |
| 	{
 | |
| 		[$isCli, $isAdmin, $isApi] = $this->isCliAdminApi();
 | |
| 
 | |
| 		return !$isAdmin && !$isApi && $isCli;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Is this a component running under the API application?
 | |
| 	 *
 | |
| 	 * @return  bool
 | |
| 	 */
 | |
| 	public function isApi(): bool
 | |
| 	{
 | |
| 		[$isCli, $isAdmin, $isApi] = $this->isCliAdminApi();
 | |
| 
 | |
| 		return $isApi && !$isAdmin && !$isCli;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Is the global FOF cache enabled?
 | |
| 	 *
 | |
| 	 * @return  bool
 | |
| 	 */
 | |
| 	public function isGlobalFOFCacheEnabled(): bool
 | |
| 	{
 | |
| 		return !(defined('JDEBUG') && JDEBUG);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Retrieves data from the cache. This is supposed to be used for system-side
 | |
| 	 * FOF data, not application data.
 | |
| 	 *
 | |
| 	 * @param   string       $key      The key of the data to retrieve
 | |
| 	 * @param   string|null  $default  The default value to return if the key is not found or the cache is not populated
 | |
| 	 *
 | |
| 	 * @return  string|null  The cached value
 | |
| 	 */
 | |
| 	public function getCache(string $key, ?string $default = null): ?string
 | |
| 	{
 | |
| 		$registry = $this->getCacheObject();
 | |
| 
 | |
| 		return $registry->get($key, $default);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Saves something to the cache. This is supposed to be used for system-wide
 | |
| 	 * FOF data, not application data.
 | |
| 	 *
 | |
| 	 * @param   string  $key      The key of the data to save
 | |
| 	 * @param   string  $content  The actual data to save
 | |
| 	 *
 | |
| 	 * @return  bool  True on success
 | |
| 	 */
 | |
| 	public function setCache(string $key, string $content): bool
 | |
| 	{
 | |
| 		$registry = $this->getCacheObject();
 | |
| 
 | |
| 		$registry->set($key, $content);
 | |
| 
 | |
| 		return $this->saveCache();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Clears the cache of system-wide FOF data. You are supposed to call this in
 | |
| 	 * your components' installation script post-installation and post-upgrade
 | |
| 	 * methods or whenever you are modifying the structure of database tables
 | |
| 	 * accessed by FOF. Please note that FOF's cache never expires and is not
 | |
| 	 * purged by Joomla!. You MUST use this method to manually purge the cache.
 | |
| 	 *
 | |
| 	 * @return  bool  True on success
 | |
| 	 */
 | |
| 	public function clearCache(): bool
 | |
| 	{
 | |
| 		$false = false;
 | |
| 		$cache = JoomlaFactory::getCache('fof', '');
 | |
| 
 | |
| 		return $cache->store($false, 'cache', 'fof');
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns an object that holds the configuration of the current site.
 | |
| 	 *
 | |
| 	 * @return  Registry
 | |
| 	 *
 | |
| 	 * @codeCoverageIgnore
 | |
| 	 */
 | |
| 	public function getConfig(): Registry
 | |
| 	{
 | |
| 		return JoomlaFactory::getConfig();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * logs in a user
 | |
| 	 *
 | |
| 	 * @param   array  $authInfo  Authentication information
 | |
| 	 *
 | |
| 	 * @return  bool  True on success
 | |
| 	 */
 | |
| 	public function loginUser(array $authInfo): bool
 | |
| 	{
 | |
| 		$options = ['remember' => false];
 | |
| 
 | |
| 		$response         = new AuthenticationResponse();
 | |
| 		$response->type   = 'fof';
 | |
| 		$response->status = Authentication::STATUS_FAILURE;
 | |
| 
 | |
| 		if (isset($authInfo['username']))
 | |
| 		{
 | |
| 			$authenticate = Authentication::getInstance();
 | |
| 			$response     = $authenticate->authenticate($authInfo, $options);
 | |
| 		}
 | |
| 
 | |
| 		// Use our own authentication handler, onFOFUserAuthenticate, as a fallback
 | |
| 		if ($response->status != Authentication::STATUS_SUCCESS)
 | |
| 		{
 | |
| 			$this->container->platform->importPlugin('user');
 | |
| 			$this->container->platform->importPlugin('fof');
 | |
| 			$pluginResults = $this->container->platform->runPlugins('onFOFUserAuthenticate', [$authInfo, $options]);
 | |
| 
 | |
| 			/**
 | |
| 			 * Loop through all plugin results until we find a successful login. On failure we fall back to Joomla's
 | |
| 			 * previous authentication response.
 | |
| 			 */
 | |
| 			foreach ($pluginResults as $result)
 | |
| 			{
 | |
| 				if (empty($result))
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				if (!is_object($result) || !($result instanceof AuthenticationResponse))
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				if ($result->status != Authentication::STATUS_SUCCESS)
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				$response = $result;
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// User failed to authenticate: maybe he enabled two factor authentication?
 | |
| 		// Let's try again "manually", skipping the check vs two factor auth
 | |
| 		// Due the big mess with encryption algorithms and libraries, we are doing this extra check only
 | |
| 		// if we're in Joomla 2.5.18+ or 3.2.1+
 | |
| 		if ($response->status != Authentication::STATUS_SUCCESS && method_exists('\Joomla\CMS\User\UserHelper', 'verifyPassword'))
 | |
| 		{
 | |
| 			$db     = JoomlaFactory::getDbo();
 | |
| 			$query  = $db->getQuery(true)
 | |
| 				->select($db->qn(['id', 'password']))
 | |
| 				->from('#__users')
 | |
| 				->where('username=' . $db->quote($authInfo['username']));
 | |
| 			$result = $db->setQuery($query)->loadObject();
 | |
| 
 | |
| 			if ($result)
 | |
| 			{
 | |
| 				$match = UserHelper::verifyPassword($authInfo['password'], $result->password, $result->id);
 | |
| 
 | |
| 				if ($match === true)
 | |
| 				{
 | |
| 					// Bring this in line with the rest of the system
 | |
| 					$user               = $this->getUser($result->id);
 | |
| 					$response->email    = $user->email;
 | |
| 					$response->fullname = $user->name;
 | |
| 
 | |
| 					$response->language = $this->isBackend() ? $user->getParam('admin_language') : $user->getParam('language');
 | |
| 
 | |
| 					$response->status        = Authentication::STATUS_SUCCESS;
 | |
| 					$response->error_message = '';
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if ($response->status == Authentication::STATUS_SUCCESS)
 | |
| 		{
 | |
| 			$this->importPlugin('user');
 | |
| 			$results = $this->runPlugins('onLoginUser', [(array) $response, $options]);
 | |
| 
 | |
| 			unset($results); // Just to make phpStorm happy
 | |
| 
 | |
| 			$userid = UserHelper::getUserId($response->username);
 | |
| 			$user   = $this->getUser($userid);
 | |
| 
 | |
| 			$session = $this->container->session;
 | |
| 			$session->set('user', $user);
 | |
| 
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * logs out a user
 | |
| 	 *
 | |
| 	 * @return  bool  True on success
 | |
| 	 */
 | |
| 	public function logoutUser(): bool
 | |
| 	{
 | |
| 		try
 | |
| 		{
 | |
| 			$app = JoomlaFactory::getApplication();
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		$user       = $this->getUser();
 | |
| 		$options    = ['remember' => false];
 | |
| 		$parameters = [
 | |
| 			'username' => $user->username,
 | |
| 			'id'       => $user->id,
 | |
| 		];
 | |
| 
 | |
| 		// Set clientid in the options array if it hasn't been set already and shared sessions are not enabled.
 | |
| 		if (!$app->get('shared_session', '0'))
 | |
| 		{
 | |
| 			$options['clientid'] = $app->getClientId();
 | |
| 		}
 | |
| 
 | |
| 		$ret = $app->triggerEvent('onUserLogout', [$parameters, $options]);
 | |
| 
 | |
| 		return !in_array(false, $ret, true);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Add a log file for FOF
 | |
| 	 *
 | |
| 	 * @param   string  $file
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	public function logAddLogger($file): void
 | |
| 	{
 | |
| 		Log::addLogger(['text_file' => $file], Log::ALL, ['fof']);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Logs a deprecated practice. In Joomla! this results in the $message being output in the
 | |
| 	 * deprecated log file, found in your site's log directory.
 | |
| 	 *
 | |
| 	 * @param   string  $message  The deprecated practice log message
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	public function logDeprecated(string $message): void
 | |
| 	{
 | |
| 		Log::add($message, Log::WARNING, 'deprecated');
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Adds a message to the application's debug log
 | |
| 	 *
 | |
| 	 * @param   string  $message
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 *
 | |
| 	 * @codeCoverageIgnore
 | |
| 	 */
 | |
| 	public function logDebug(string $message): void
 | |
| 	{
 | |
| 		Log::add($message, Log::DEBUG, 'fof');
 | |
| 	}
 | |
| 
 | |
| 	/** @inheritDoc */
 | |
| 	public function logUserAction($title, string $logText, string $extension, User $user = null): void
 | |
| 	{
 | |
| 		if (!is_string($title) && !is_array($title))
 | |
| 		{
 | |
| 			throw new InvalidArgumentException(sprintf('%s::%s -- $title expects a string or an array', __CLASS__, __METHOD__));
 | |
| 		}
 | |
| 
 | |
| 		static $joomlaModelAdded = false;
 | |
| 
 | |
| 		// User Actions Log is available only under Joomla 3.9+
 | |
| 		if (version_compare(JVERSION, '3.9', 'lt'))
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// Do not perform logging if we're under CLI. Even if we _could_ have a logged user in CLI, ActionlogsModelActionlog
 | |
| 		// model always uses JoomlaFactory to fetch the current user, fetching data from the session. This means that under the CLI
 | |
| 		// (where there is no session) such session is started, causing warnings because usually output was already started before
 | |
| 		if ($this->isCli())
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// Include required Joomla Model
 | |
| 		if (!$joomlaModelAdded)
 | |
| 		{
 | |
| 			BaseDatabaseModel::addIncludePath(JPATH_ROOT . '/administrator/components/com_actionlogs/models', 'ActionlogsModel');
 | |
| 			$joomlaModelAdded = true;
 | |
| 		}
 | |
| 
 | |
| 		$user = $this->getUser();
 | |
| 
 | |
| 		// No log for guest users
 | |
| 		if ($user->guest)
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		$message = [
 | |
| 			'title'       => $title,
 | |
| 			'username'    => $user->username,
 | |
| 			'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
 | |
| 		];
 | |
| 
 | |
| 		if (is_array($title))
 | |
| 		{
 | |
| 			unset ($message['title']);
 | |
| 
 | |
| 			$message = array_merge($message, $title);
 | |
| 		}
 | |
| 
 | |
| 		/** @var ActionlogsModelActionlog $model * */
 | |
| 		try
 | |
| 		{
 | |
| 			$model = BaseDatabaseModel::getInstance('Actionlog', 'ActionlogsModel');
 | |
| 			$model->addLog([$message], $logText, $extension, $user->id);
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			// Ignore any error
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns the root URI for the request.
 | |
| 	 *
 | |
| 	 * @param   bool         $pathonly  If false, prepend the scheme, host and port information. Default is false.
 | |
| 	 * @param   string|null  $path      The path
 | |
| 	 *
 | |
| 	 * @return  string  The root URI string.
 | |
| 	 *
 | |
| 	 * @codeCoverageIgnore
 | |
| 	 */
 | |
| 	public function URIroot(bool $pathonly = false, ?string $path = null): string
 | |
| 	{
 | |
| 		return Uri::root($pathonly, $path);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns the base URI for the request.
 | |
| 	 *
 | |
| 	 * @param   bool  $pathonly  If false, prepend the scheme, host and port information. Default is false.
 | |
| 	 *
 | |
| 	 * @return  string  The base URI string
 | |
| 	 */
 | |
| 	public function URIbase(bool $pathonly = false): string
 | |
| 	{
 | |
| 		return Uri::base($pathonly);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Method to set a response header.  If the replace flag is set then all headers
 | |
| 	 * with the given name will be replaced by the new one (only if the current platform supports header caching)
 | |
| 	 *
 | |
| 	 * @param   string  $name     The name of the header to set.
 | |
| 	 * @param   string  $value    The value of the header to set.
 | |
| 	 * @param   bool    $replace  True to replace any headers with the same name.
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 *
 | |
| 	 * @codeCoverageIgnore
 | |
| 	 */
 | |
| 	public function setHeader(string $name, string $value, bool $replace = false): void
 | |
| 	{
 | |
| 		try
 | |
| 		{
 | |
| 			JoomlaFactory::getApplication()->setHeader($name, $value, $replace);
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * In platforms that perform header caching, send all headers.
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 *
 | |
| 	 * @codeCoverageIgnore
 | |
| 	 */
 | |
| 	public function sendHeaders(): void
 | |
| 	{
 | |
| 		try
 | |
| 		{
 | |
| 			JoomlaFactory::getApplication()->sendHeaders();
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Immediately terminate the containing application's execution
 | |
| 	 *
 | |
| 	 * @param   int  $code  The result code which should be returned by the application
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	public function closeApplication(int $code = 0): void
 | |
| 	{
 | |
| 		// Necessary workaround for broken System - Page Cache plugin in Joomla! 3.7.0
 | |
| 		$this->bugfixJoomlaCachePlugin();
 | |
| 
 | |
| 		try
 | |
| 		{
 | |
| 			JoomlaFactory::getApplication()->close($code);
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			exit($code);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Perform a redirection to a different page, optionally enqueuing a message for the user.
 | |
| 	 *
 | |
| 	 * @param   string  $url     The URL to redirect to
 | |
| 	 * @param   int     $status  (optional) The HTTP redirection status code, default 303 (See Other)
 | |
| 	 * @param   string  $msg     (optional) A message to enqueue
 | |
| 	 * @param   string  $type    (optional) The message type, e.g. 'message' (default), 'warning' or 'error'.
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	public function redirect(string $url, int $status = 301, ?string $msg = null, string $type = 'message'): void
 | |
| 	{
 | |
| 		// Necessary workaround for broken System - Page Cache plugin in Joomla! 3.7.0
 | |
| 		$this->bugfixJoomlaCachePlugin();
 | |
| 
 | |
| 		try
 | |
| 		{
 | |
| 			$app = JoomlaFactory::getApplication();
 | |
| 		}
 | |
| 		catch (Exception $e)
 | |
| 		{
 | |
| 			die(sprintf('Please go to <a href="%s">%1$s</a>', $url));
 | |
| 		}
 | |
| 
 | |
| 		if (!empty($msg))
 | |
| 		{
 | |
| 			if (empty($type))
 | |
| 			{
 | |
| 				$type = 'message';
 | |
| 			}
 | |
| 
 | |
| 			$app->enqueueMessage($msg, $type);
 | |
| 		}
 | |
| 
 | |
| 		// Joomla 4: redirecting to index.php in the backend takes you to the frontend. I need to address that.
 | |
| 		$isJoomla4   = version_compare(JVERSION, '3.999.999', 'gt');
 | |
| 		$isBareIndex = substr($url, 0, 9) === 'index.php';
 | |
| 
 | |
| 		if ($isJoomla4 && $isBareIndex && $this->isBackend())
 | |
| 		{
 | |
| 			$givenUri = new Uri($url);
 | |
| 			$newUri   = new Uri(Uri::base());
 | |
| 
 | |
| 			$newUri->setQuery($givenUri->getQuery());
 | |
| 
 | |
| 			if ($givenUri->getFragment())
 | |
| 			{
 | |
| 				$newUri->setFragment($givenUri->getFragment());
 | |
| 			}
 | |
| 
 | |
| 			$url = $newUri->toString();
 | |
| 		}
 | |
| 
 | |
| 		// Finally, do the redirection
 | |
| 		$app->redirect($url, $status);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Handle an exception in a way that results to an error page. We use this under Joomla! to work around a bug in
 | |
| 	 * Joomla! 3.7 which results in error pages leading to white pages because Joomla's System - Page Cache plugin is
 | |
| 	 * broken.
 | |
| 	 *
 | |
| 	 * @param   Exception  $exception  The exception to handle
 | |
| 	 *
 | |
| 	 * @throws  Exception  We rethrow the exception
 | |
| 	 */
 | |
| 	public function showErrorPage(Exception $exception): void
 | |
| 	{
 | |
| 		// Necessary workaround for broken System - Page Cache plugin in Joomla! 3.7.0
 | |
| 		$this->bugfixJoomlaCachePlugin();
 | |
| 
 | |
| 		throw $exception;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Set a variable in the user session
 | |
| 	 *
 | |
| 	 * @param   string       $name       The name of the variable to set
 | |
| 	 * @param   string|null  $value      (optional) The value to set it to, default is null
 | |
| 	 * @param   string       $namespace  (optional) The variable's namespace e.g. the component name. Default: 'default'
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	public function setSessionVar(string $name, $value = null, string $namespace = 'default'): void
 | |
| 	{
 | |
| 		// CLI
 | |
| 		if ($this->isCli() && !class_exists('FOFApplicationCLI'))
 | |
| 		{
 | |
| 			static::$fakeSession->set("$namespace.$name", $value);
 | |
| 
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// Joomla 3
 | |
| 		if (version_compare(JVERSION, '3.9999.9999', 'le'))
 | |
| 		{
 | |
| 			$this->container->session->set($name, $value, $namespace);
 | |
| 		}
 | |
| 
 | |
| 		// Joomla 4
 | |
| 		if (empty($namespace))
 | |
| 		{
 | |
| 			$this->container->session->set($name, $value);
 | |
| 
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		$registry = $this->container->session->get('registry');
 | |
| 
 | |
| 		if (is_null($registry))
 | |
| 		{
 | |
| 			$registry = new Registry();
 | |
| 
 | |
| 			$this->container->session->set('registry', $registry);
 | |
| 		}
 | |
| 
 | |
| 		$registry->set($namespace . '.' . $name, $value);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get a variable from the user session
 | |
| 	 *
 | |
| 	 * @param   string  $name       The name of the variable to set
 | |
| 	 * @param   string  $default    (optional) The default value to return if the variable does not exit, default: null
 | |
| 	 * @param   string  $namespace  (optional) The variable's namespace e.g. the component name. Default: 'default'
 | |
| 	 *
 | |
| 	 * @return  mixed
 | |
| 	 */
 | |
| 	public function getSessionVar(string $name, $default = null, $namespace = 'default')
 | |
| 	{
 | |
| 		// CLI
 | |
| 		if ($this->isCli() && !class_exists('FOFApplicationCLI'))
 | |
| 		{
 | |
| 			return static::$fakeSession->get("$namespace.$name", $default);
 | |
| 		}
 | |
| 
 | |
| 		// Joomla 3
 | |
| 		if (version_compare(JVERSION, '3.9999.9999', 'le'))
 | |
| 		{
 | |
| 			return $this->container->session->get($name, $default, $namespace);
 | |
| 		}
 | |
| 
 | |
| 		// Joomla 4
 | |
| 		if (empty($namespace))
 | |
| 		{
 | |
| 			return $this->container->session->get($name, $default);
 | |
| 		}
 | |
| 
 | |
| 		$registry = $this->container->session->get('registry');
 | |
| 
 | |
| 		if (is_null($registry))
 | |
| 		{
 | |
| 			$registry = new Registry();
 | |
| 
 | |
| 			$this->container->session->set('registry', $registry);
 | |
| 		}
 | |
| 
 | |
| 		return $registry->get($namespace . '.' . $name, $default);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Unset a variable from the user session
 | |
| 	 *
 | |
| 	 * @param   string  $name       The name of the variable to unset
 | |
| 	 * @param   string  $namespace  (optional) The variable's namespace e.g. the component name. Default: 'default'
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	public function unsetSessionVar(string $name, string $namespace = 'default'): void
 | |
| 	{
 | |
| 		$this->setSessionVar($name, null, $namespace);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Return the session token. Two types of tokens can be returned:
 | |
| 	 *
 | |
| 	 * Session token ($formToken == false): Used for anti-spam protection of forms. This is specific to a session
 | |
| 	 *   object.
 | |
| 	 *
 | |
| 	 * Form token ($formToken == true): A secure hash of the user ID with the session token. Both the session and the
 | |
| 	 *   user are fetched from the application container.
 | |
| 	 *
 | |
| 	 * @param   bool  $formToken  Should I return a form token?
 | |
| 	 * @param   bool  $forceNew   Should I force the creation of a new token?
 | |
| 	 *
 | |
| 	 * @return  mixed
 | |
| 	 */
 | |
| 	public function getToken(bool $formToken = false, bool $forceNew = false): string
 | |
| 	{
 | |
| 		// For CLI apps we implement our own fake token system
 | |
| 		if ($this->isCli())
 | |
| 		{
 | |
| 			$token = $this->getSessionVar('session.token');
 | |
| 
 | |
| 			// Create a token
 | |
| 			if (is_null($token) || $forceNew)
 | |
| 			{
 | |
| 				$token = UserHelper::genRandomPassword(32);
 | |
| 				$this->setSessionVar('session.token', $token);
 | |
| 			}
 | |
| 
 | |
| 			if (!$formToken)
 | |
| 			{
 | |
| 				return $token;
 | |
| 			}
 | |
| 
 | |
| 			$user = $this->getUser();
 | |
| 
 | |
| 			return ApplicationHelper::getHash($user->id . $token);
 | |
| 		}
 | |
| 
 | |
| 		// Web application, go through the regular Joomla! API.
 | |
| 		if ($formToken)
 | |
| 		{
 | |
| 			return Session::getFormToken($forceNew);
 | |
| 		}
 | |
| 
 | |
| 		return $this->container->session->getToken($forceNew);
 | |
| 	}
 | |
| 
 | |
| 	/** @inheritDoc */
 | |
| 	public function addScriptOptions($key, $value, $merge = true)
 | |
| 	{
 | |
| 		/** @var HtmlDocument $document */
 | |
| 		$document = $this->getDocument();
 | |
| 
 | |
| 		if (!method_exists($document, 'addScriptOptions'))
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		$document->addScriptOptions($key, $value, $merge);
 | |
| 	}
 | |
| 
 | |
| 	/** @inheritDoc */
 | |
| 	public function getScriptOptions($key = null)
 | |
| 	{
 | |
| 		/** @var HtmlDocument $document */
 | |
| 		$document = $this->getDocument();
 | |
| 
 | |
| 		if (!method_exists($document, 'getScriptOptions'))
 | |
| 		{
 | |
| 			return [];
 | |
| 		}
 | |
| 
 | |
| 		return $document->getScriptOptions($key);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Main function to detect if we're running in a CLI environment, if we're admin or if it's an API application
 | |
| 	 *
 | |
| 	 * @return  array  isCLI and isAdmin. It's not an associative array, so we can use list().
 | |
| 	 */
 | |
| 	protected function isCliAdminApi(): array
 | |
| 	{
 | |
| 		if (is_null(static::$isCLI) && is_null(static::$isAdmin))
 | |
| 		{
 | |
| 			static::$isCLI   = false;
 | |
| 			static::$isAdmin = false;
 | |
| 			static::$isApi   = false;
 | |
| 
 | |
| 			try
 | |
| 			{
 | |
| 				if (is_null(JoomlaFactory::$application))
 | |
| 				{
 | |
| 					static::$isCLI   = true;
 | |
| 					static::$isAdmin = false;
 | |
| 
 | |
| 					return [static::$isCLI, static::$isAdmin, static::$isApi];
 | |
| 				}
 | |
| 
 | |
| 				$app           = JoomlaFactory::getApplication();
 | |
| 				static::$isCLI = $app instanceof Exception || $app instanceof CliApplication;
 | |
| 
 | |
| 				if (class_exists('Joomla\CMS\Application\CliApplication'))
 | |
| 				{
 | |
| 					static::$isCLI = static::$isCLI || $app instanceof JApplicationCli;
 | |
| 				}
 | |
| 
 | |
| 				if (class_exists('Joomla\CMS\Application\ConsoleApplication'))
 | |
| 				{
 | |
| 					static::$isCLI = static::$isCLI || ($app instanceof ConsoleApplication);
 | |
| 				}
 | |
| 			}
 | |
| 			catch (Exception $e)
 | |
| 			{
 | |
| 				static::$isCLI = true;
 | |
| 			}
 | |
| 
 | |
| 			if (static::$isCLI)
 | |
| 			{
 | |
| 				return [static::$isCLI, static::$isAdmin, static::$isApi];
 | |
| 			}
 | |
| 
 | |
| 			try
 | |
| 			{
 | |
| 				$app = JoomlaFactory::getApplication();
 | |
| 			}
 | |
| 			catch (Exception $e)
 | |
| 			{
 | |
| 				return [static::$isCLI, static::$isAdmin, static::$isApi];
 | |
| 			}
 | |
| 
 | |
| 			if (method_exists($app, 'isAdmin'))
 | |
| 			{
 | |
| 				static::$isAdmin = $app->isAdmin();
 | |
| 			}
 | |
| 			elseif (method_exists($app, 'isClient'))
 | |
| 			{
 | |
| 				static::$isAdmin = $app->isClient('administrator');
 | |
| 				static::$isApi   = $app->isClient('api');
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return [static::$isCLI, static::$isAdmin, static::$isApi];
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets a reference to the cache object, loading it from the disk if
 | |
| 	 * needed.
 | |
| 	 *
 | |
| 	 * @param   bool  $force  Should I forcibly reload the registry?
 | |
| 	 *
 | |
| 	 * @return  Registry
 | |
| 	 */
 | |
| 	private function &getCacheObject(bool $force = false): Registry
 | |
| 	{
 | |
| 		// Check if we have to load the cache file or we are forced to do that
 | |
| 		if (is_null($this->_cache) || $force)
 | |
| 		{
 | |
| 			// Try to get data from Joomla!'s cache
 | |
| 			$cache        = JoomlaFactory::getCache('fof', '');
 | |
| 			$this->_cache = $cache->get('cache', 'fof');
 | |
| 
 | |
| 			$isRegistry = is_object($this->_cache);
 | |
| 
 | |
| 			if ($isRegistry)
 | |
| 			{
 | |
| 				$isRegistry = $this->_cache instanceof Registry;
 | |
| 			}
 | |
| 
 | |
| 			if (!$isRegistry)
 | |
| 			{
 | |
| 				// Create a new Registry object
 | |
| 				$this->_cache = new Registry();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return $this->_cache;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Save the cache object back to disk
 | |
| 	 *
 | |
| 	 * @return  bool  True on success
 | |
| 	 */
 | |
| 	private function saveCache(): bool
 | |
| 	{
 | |
| 		// Get the Registry object of our cached data
 | |
| 		$registry = $this->getCacheObject();
 | |
| 
 | |
| 		$cache = JoomlaFactory::getCache('fof', '');
 | |
| 
 | |
| 		return $cache->store($registry, 'cache', 'fof');
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Joomla! 3.7 has a broken System - Page Cache plugin. When this plugin is enabled it FORCES the caching of all
 | |
| 	 * pages as soon as Joomla! starts loading, before the plugin has a chance to request to not be cached. Event worse,
 | |
| 	 * in case of a redirection, it doesn't try to remove the cache lock. This means that the next request will be
 | |
| 	 * treated as though the result of the page should be cached. Since there is NO cache content for the page Joomla!
 | |
| 	 * returns an empty response with a 200 OK header. This will, of course, get in the way of every single attempt to
 | |
| 	 * perform a redirection in the frontend of the site.
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	private function bugfixJoomlaCachePlugin(): void
 | |
| 	{
 | |
| 		// Only do something when the System - Cache plugin is activated
 | |
| 		if (!class_exists('PlgSystemCache'))
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// Forcibly uncache the current request
 | |
| 		$options = [
 | |
| 			'defaultgroup' => 'page',
 | |
| 			'browsercache' => false,
 | |
| 			'caching'      => false,
 | |
| 		];
 | |
| 
 | |
| 		$cache_key = Uri::getInstance()->toString();
 | |
| 		Cache::getInstance('page', $options)->cache->remove($cache_key, 'page');
 | |
| 	}
 | |
| }
 |