Your IP : 216.73.217.68


Current Path : /home/w/u/e/wuectly/www/03cbe/
Upload File :
Current File : /home/w/u/e/wuectly/www/03cbe/JoomlaAbstraction.tar

CacheCleaner.php000060400000023762152455515700007571 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\JoomlaAbstraction;

defined('_JEXEC') || die;

use Exception;
use FOF40\Container\Container;
use Joomla\Application\AbstractApplication;
use Joomla\Application\ConfigurationAwareApplicationInterface;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\CMS\Cache\Controller\CallbackController;
use Joomla\CMS\Cache\Exception\CacheExceptionInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\Registry\Registry;
use Throwable;

/**
 * A utility class to help you quickly clean the Joomla! cache
 */
class CacheCleaner
{
	/**
	 * Clears the com_modules and com_plugins cache. You need to call this whenever you alter the publish state or
	 * parameters of a module or plugin from your code.
	 *
	 * @return  void
	 */
	public static function clearPluginsAndModulesCache()
	{
		self::clearPluginsCache();
		self::clearModulesCache();
	}

	/**
	 * Clears the com_plugins cache. You need to call this whenever you alter the publish state or parameters of a
	 * plugin from your code.
	 *
	 * @return  void
	 */
	public static function clearPluginsCache()
	{
		self::clearCacheGroups(['com_plugins'], [0, 1]);
	}

	/**
	 * Clears the com_modules cache. You need to call this whenever you alter the publish state or parameters of a
	 * module from your code.
	 *
	 * @return  void
	 */
	public static function clearModulesCache()
	{
		self::clearCacheGroups(['com_modules'], [0, 1]);
	}

	/**
	 * Clears the specified cache groups.
	 *
	 * @param   array        $clearGroups   Which cache groups to clear. Usually this is com_yourcomponent to clear
	 *                                      your component's cache.
	 * @param   array        $cacheClients  Which cache clients to clear. 0 is the back-end, 1 is the front-end. If you
	 *                                      do not specify anything, both cache clients will be cleared.
	 * @param   string|null  $event         An event to run upon trying to clear the cache. Empty string to disable. If
	 *                                      NULL and the group is "com_content" I will trigger onContentCleanCache.
	 *
	 * @return  void
	 * @throws  Exception
	 */
	public static function clearCacheGroups(array $clearGroups, array $cacheClients = [
		0, 1,
	], ?string $event = null): void
	{
		// Early return on nonsensical input
		if (empty($clearGroups) || empty($cacheClients))
		{
			return;
		}

		// Make sure I have an application object
		try
		{
			$app = Factory::getApplication();
		}
		catch (Exception $e)
		{
			return;
		}

		// If there's no application object things will break; let's get outta here.
		if (!is_object($app))
		{
			return;
		}

		$isJoomla4 = version_compare(JVERSION, '3.9999.9999', 'gt');

		// Loop all groups to clean
		foreach ($clearGroups as $group)
		{
			// Groups must be non-empty strings
			if (empty($group) || !is_string($group))
			{
				continue;
			}

			// Loop all clients (applications)
			foreach ($cacheClients as $client_id)
			{
				$client_id = (int) ($client_id ?? 0);

				$options = $isJoomla4
					? self::clearCacheGroupJoomla4($group, $client_id, $app)
					: self::clearCacheGroupJoomla3($group, $client_id, $app);

				// Do not call any events if I failed to clean the cache using the core Joomla API
				if (!($options['result'] ?? false))
				{
					return;
				}

				/**
				 * If you're cleaning com_content and you have passed no event name I will use onContentCleanCache.
				 */
				if ($group === 'com_content')
				{
					$cacheCleaningEvent = $event ?: 'onContentCleanCache';
				}

				/**
				 * Call Joomla's cache cleaning plugin event (e.g. onContentCleanCache) as well.
				 *
				 * @see BaseDatabaseModel::cleanCache()
				 */
				if (empty($cacheCleaningEvent))
				{
					continue;
				}

				$fakeContainer = Container::getInstance('com_FOOBAR');
				$fakeContainer->platform->runPlugins($cacheCleaningEvent, $options);
			}
		}
	}

	/**
	 * Clean a cache group on Joomla 3
	 *
	 * @param   string  $group      The cache to clean, e.g. com_content
	 * @param   int     $client_id  The application ID for which the cache will be cleaned
	 * @param   object  $app        The current CMS application. DO NOT TYPEHINT MORE SPECIFICALLY!
	 *
	 * @return  array Cache controller options, including cleaning result
	 * @throws  Exception
	 */
	private static function clearCacheGroupJoomla3(string $group, int $client_id, object $app): array
	{
		$options = [
			'defaultgroup' => $group,
			'cachebase'    => ($client_id) ? self::getAppConfigParam($app, 'cache_path', JPATH_SITE . '/cache') : JPATH_ADMINISTRATOR . '/cache',
			'result'       => true,
		];

		try
		{
			$cache = Cache::getInstance('callback', $options);
			/** @noinspection PhpUndefinedMethodInspection Available via __call(), not tagged in Joomla core */
			$cache->clean();
		}
		catch (Throwable $e)
		{
			$options['result'] = false;
		}

		return $options;
	}

	/**
	 * Clean a cache group on Joomla 4
	 *
	 * @param   string  $group      The cache to clean, e.g. com_content
	 * @param   int     $client_id  The application ID for which the cache will be cleaned
	 * @param   object  $app        The current CMS application. DO NOT TYPEHINT MORE SPECIFICALLY!
	 *
	 * @return  array Cache controller options, including cleaning result
	 * @throws  Exception
	 */
	private static function clearCacheGroupJoomla4(string $group, int $client_id, object $app): array
	{
		// Get the default cache folder. Start by using the JPATH_CACHE constant.
		$cacheBaseDefault = JPATH_CACHE;
		$appClientId      = 0;

		if (method_exists($app, 'getClientId'))
		{
			$appClientId = $app->getClientId();
		}

		// -- If we are asked to clean cache on the other side of the application we need to find a new cache base
		if ($client_id != $appClientId)
		{
			$cacheBaseDefault = (($client_id) ? JPATH_SITE : JPATH_ADMINISTRATOR) . '/cache';
		}

		// Get the cache controller's options
		$options = [
			'defaultgroup' => $group,
			'cachebase'    => self::getAppConfigParam($app, 'cache_path', $cacheBaseDefault),
			'result'       => true,
		];

		try
		{
			$container = Factory::getContainer();

			if (empty($container))
			{
				throw new \RuntimeException('Cannot get Joomla 4 application container');
			}

			/** @var CacheControllerFactoryInterface $cacheControllerFactory */
			$cacheControllerFactory   = $container->get('cache.controller.factory');

			if (empty($cacheControllerFactory))
			{
				throw new \RuntimeException('Cannot get Joomla 4 cache controller factory');
			}

			/** @var CallbackController $cache */
			$cache = $cacheControllerFactory->createCacheController('callback', $options);

			if (empty($cache) || !property_exists($cache, 'cache') || !method_exists($cache->cache, 'clean'))
			{
				throw new \RuntimeException('Cannot get Joomla 4 cache controller');
			}

			$cache->cache->clean();
		}
		catch (CacheExceptionInterface $exception)
		{
			$options['result'] = false;
		}
		catch (Throwable $e)
		{
			$options['result'] = false;
		}

		return $options;
	}

	private static function getAppConfigParam(?object $app, string $key, $default = null)
	{
		/**
		 * Any kind of Joomla CMS, Web, API or CLI application extends from AbstractApplication and has the get()
		 * method to return application configuration parameters.
		 */
		if (is_object($app) && ($app instanceof AbstractApplication))
		{
			return $app->get($key, $default);
		}

		/**
		 * A custom application may instead implement the Joomla\Application\ConfigurationAwareApplicationInterface
		 * interface (Joomla 4+), in whihc case it has the get() method to return application configuration parameters.
		 */
		if (is_object($app)
			&& interface_exists('Joomla\Application\ConfigurationAwareApplicationInterface', true)
			&& ($app instanceof ConfigurationAwareApplicationInterface))
		{
			return $app->get($key, $default);
		}

		/**
		 * A Joomla 3 custom application may simply implement the get() method without implementing an interface.
		 */
		if (is_object($app) && method_exists($app, 'get'))
		{
			return $app->get($key, $default);
		}

		/**
		 * At this point the $app variable is not an object or is something I can't use. Does the Joomla Factory still
		 * has the legacy static method getConfig() to get the application configuration? If so, use it.
		 */
		if (method_exists(Factory::class, 'getConfig'))
		{
			try
			{
				$jConfig = Factory::getConfig();

				if (is_object($jConfig) && ($jConfig instanceof Registry))
				{
					$jConfig->get($key, $default);
				}
			}
			catch (Throwable $e)
			{
				/**
				 * Factory tries to go through the application object. It might fail if there is a custom application
				 * which doesn't implement the interfaces Factory expects. In this case we get a Fatal Error whcih we
				 * can trap and fall through to the next if-block.
				 */
			}
		}

		/**
		 * When we are here all hope is nearly lost. We have to do a crude approximation of Joomla Factory's code to
		 * create an application configuration Registry object and retrieve the configuration values. This will work as
		 * long as the JConfig class (defined in configuration.php) has been loaded.
		 */
		$configPath = defined('JPATH_CONFIGURATION') ? JPATH_CONFIGURATION :
			(defined('JPATH_ROOT') ? JPATH_ROOT : null);
		$configPath = $configPath ?? (__DIR__ . '/../../..');
		$configFile = $configPath . '/configuration.php';

		if (!class_exists('JConfig') && @file_exists($configFile) && @is_file($configFile) && @is_readable($configFile))
		{
			require_once $configFile;
		}

		if (class_exists('JConfig'))
		{
			try
			{
				$jConfig      = new Registry();
				$configObject = new \JConfig();
				$jConfig->loadObject($configObject);

				return $jConfig->get($key, $default);
			}
			catch (Throwable $e)
			{
				return $default;
			}
		}

		/**
		 * All hope is lost. I can't find the application configuration. I am returning the default value and hope stuff
		 * won't break spectacularly...
		 */
		return $default;
	}
}
DynamicGroups.php000060400000013343152455515700010052 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\JoomlaAbstraction;

defined('_JEXEC') || die;

use FOF40\Container\Container;

/**
 * Dynamic user to user group assignment.
 *
 * This class allows you to add / remove the currently logged in user to a user group without writing the information to
 * the database. This is useful when you want to allow core and third party code to allow or prohibit display of
 * information and / or taking actions based on a condition controlled in your code.
 */
class DynamicGroups
{
	/**
	 * Add the current user to a user group just for this page load.
	 *
	 * @param   int  $groupID  The group ID to add the current user into.
	 *
	 * @return  void
	 */
	public static function addGroup(int $groupID): void
	{
		self::addRemoveGroup($groupID, true);
		self::cleanUpUserObjectCache();
	}

	/**
	 * Remove the current user from a user group just for this page load.
	 *
	 * @param   int  $groupID  The group ID to remove the current user from.
	 *
	 * @return  void
	 */
	public static function removeGroup(int $groupID): void
	{
		self::addRemoveGroup($groupID, false);
		self::cleanUpUserObjectCache();
	}

	/**
	 * Internal function to add or remove the current user from a user group just for this page load.
	 *
	 * @param   int   $groupID  The group ID to add / remove the current user from.
	 * @param   bool  $add      Add (true) or remove (false) the user?
	 *
	 * @return  void
	 */
	protected static function addRemoveGroup(int $groupID, bool $add): void
	{
		// Get a fake container (we need it for its platform interface)
		$container = Container::getInstance('com_FOOBAR');

		/**
		 * Make sure that Joomla has retrieved the user's groups from the database.
		 *
		 * By going through the User object's getAuthorisedGroups we force Joomla to go through Access::getGroupsByUser
		 * which retrieves the information from the database and caches it into the Access helper class.
		 */
		$container->platform->getUser()->getAuthorisedGroups();
		$container->platform->getUser($container->platform->getUser()->id)->getAuthorisedGroups();

		/**
		 * Now we can get a Reflection object into Joomla's Access helper class and manipulate its groupsByUser cache.
		 */
		$className = 'Joomla\\CMS\\Access\\Access';

		try
		{
			$reflectedAccess = new \ReflectionClass($className);
		}
		catch (\ReflectionException $e)
		{
			// This should never happen!
			$container->platform->logDebug('Cannot locate the Joomla\\CMS\\Access\\Access class. Is your Joomla installation broken or too old / too new?');

			return;
		}

		$groupsByUser = $reflectedAccess->getProperty('groupsByUser');
		$groupsByUser->setAccessible(true);
		$rawGroupsByUser = $groupsByUser->getValue();

		/**
		 * Next up, we need to manipulate the keys of the cache which contain user to user group assignments.
		 *
		 * $rawGroupsByUser (Access::$groupsByUser) stored the group ownership as userID:recursive e.g. 0:1 for the
		 * default user, recursive. We need to deal with four keys: 0:1, 0:0, myID:1 and myID:0
		 */
		$user = $container->platform->getUser();
		$keys = ['0:1', '0:0', $user->id . ':1', $user->id . ':0'];

		foreach ($keys as $key)
		{
			if (!array_key_exists($key, $rawGroupsByUser))
			{
				continue;
			}

			$groups = $rawGroupsByUser[$key];

			if ($add)
			{
				if (in_array($groupID, $groups))
				{
					continue;
				}

				$groups[] = $groupID;
			}
			else
			{
				if (!in_array($groupID, $groups))
				{
					continue;
				}

				$removeKey = array_search($groupID, $groups);
				unset($groups[$removeKey]);
			}

			$rawGroupsByUser[$key] = $groups;
		}

		// We can commit our changes back to the cache property and make it publicly inaccessible again.
		$groupsByUser->setValue(null, $rawGroupsByUser);
		$groupsByUser->setAccessible(false);

		/**
		 * We are not done. Caching user groups is only one aspect of Joomla access management. Joomla also caches the
		 * identities, i.e. the user group assignment per user, in a different cache. We need to reset it to for our
		 * user.
		 *
		 * Do note that we CAN NOT use clearStatics since that also clears the user group assignment which we assigned
		 * dynamically. Therefore calling it would destroy our work so far.
		 */
		$refProperty = $reflectedAccess->getProperty('identities');
		$refProperty->setAccessible(true);
		$identities = $refProperty->getValue();

		$keys = array($user->id, 0);

		foreach ($keys as $key)
		{
			if (!array_key_exists($key, $identities))
			{
				continue;
			}

			unset($identities[$key]);
		}

		$refProperty->setValue(null, $identities);
		$refProperty->setAccessible(false);
	}

	/**
	 * Clean up the current user's authenticated groups cache.
	 *
	 * @return  void
	 */
	protected static function cleanUpUserObjectCache(): void
	{
		// Get a fake container (we need it for its platform interface)
		$container = Container::getInstance('com_FOOBAR');

		$user          = $container->platform->getUser();
		$reflectedUser = new \ReflectionObject($user);

		// Clear the user group cache
		$refProperty   = $reflectedUser->getProperty('_authGroups');
		$refProperty->setAccessible(true);
		$refProperty->setValue($user, array());
		$refProperty->setAccessible(false);

		// Clear the view access level cache
		$refProperty   = $reflectedUser->getProperty('_authLevels');
		$refProperty->setAccessible(true);
		$refProperty->setValue($user, array());
		$refProperty->setAccessible(false);

		// Clear the authenticated actions cache. I haven't seen it used anywhere but it's there, so...
		$refProperty   = $reflectedUser->getProperty('_authActions');
		$refProperty->setAccessible(true);
		$refProperty->setValue($user, array());
		$refProperty->setAccessible(false);
	}
}ComponentVersion.php000060400000006654152455515700010605 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\JoomlaAbstraction;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Factory as JoomlaFactory;
use SimpleXMLElement;

/**
 * Retrieve the version of a component from the cached XML manifest or, if it's not present, the version recorded in the
 * database.
 */
abstract class ComponentVersion
{
	/**
	 * A cache with the version numbers of components
	 *
	 * @var   array
	 *
	 * @since 3.1.5
	 */
	private static $version = array();

	/**
	 * Get a component's version. The XML manifest on disk will be tried first. If it's not there or does not have a
	 * version string the manifest cache in the database is tried. If that fails a fake version number will be returned.
	 *
	 * @param   string  $component  The name of the component, e.g. com_foobar
	 *
	 * @return  string  The version string
	 *
	 * @since   3.1.5
	 */
	public static function getFor(string $component): string
	{
		if (!isset(self::$version[$component]))
		{
			self::$version[$component] = null;
		}

		if (is_null(self::$version[$component]))
		{
			self::$version[$component] = self::getVersionFromManifest($component);
		}

		if (is_null(self::$version[$component]))
		{
			self::$version[$component] = self::getVersionFromDatabase($component);
		}

		if (is_null(self::$version[$component]))
		{
			self::$version[$component] = 'dev-' . str_replace(' ', '_', microtime(false));
		}

		return self::$version[$component];
	}

	/**
	 * Get a component's version from the manifest cache in the database
	 *
	 * @param   string  $component  The component's bname
	 *
	 * @return  string|null  The component version or null if none is defined
	 *
	 * @since   3.1.5
	 */
	private static function getVersionFromDatabase(string $component): ?string
	{
		$db      = JoomlaFactory::getDbo();
		$query   = $db->getQuery(true)
			->select($db->qn('manifest_cache'))
			->from($db->qn('#__extensions'))
			->where($db->qn('element') . ' = ' . $db->q($component))
			->where($db->qn('type') . ' = ' . $db->q('component'));

		try
		{
			$json = $db->setQuery($query)->loadResult();
		}
		catch (Exception $e)
		{
			return null;
		}

		if (empty($json))
		{
			return null;
		}

		$options = json_decode($json, true);

		if (empty($options))
		{
			return null;
		}

		if (!isset($options['version']))
		{
			return null;
		}

		return $options['version'];
	}

	/**
	 * Get a component's version from the manifest file on disk. IMPORTANT! The manifest for com_something must be named
	 * something.xml.
	 *
	 * @param   string  $component  The component's bname
	 *
	 * @return  string  The component version or null if none is defined
	 *
	 * @since   1.2.0
	 */
	private static function getVersionFromManifest(string $component): ?string
	{
		$bareComponent = str_replace('com_', '', $component);
		$file = JPATH_ADMINISTRATOR . '/components/' . $component . '/' . $bareComponent . '.xml';

		if (!is_file($file) || !is_readable($file))
		{
			return null;
		}

		$data = @file_get_contents($file);

		if (empty($data))
		{
			return null;
		}

		try
		{
			$xml = new SimpleXMLElement($data, LIBXML_COMPACT | LIBXML_NONET | LIBXML_ERR_NONE);
		}
		catch (Exception $e)
		{
			return null;
		}

		$versionNode = $xml->xpath('/extension/version');

		if (empty($versionNode))
		{
			return null;
		}

		return (string)($versionNode[0]);
	}
}