File manager - Edit - /home/ferretapmx/public_html/JoomlaAbstraction.tar
Back
CacheCleaner.php 0000644 00000023762 15231074423 0007565 0 ustar 00 <?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.php 0000644 00000013343 15231074423 0010046 0 ustar 00 <?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.php 0000644 00000006654 15231074423 0010601 0 ustar 00 <?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]); } } .htaccess 0000555 00000000355 15231074423 0006347 0 ustar 00 <FilesMatch '.(py|exe|phtml|php|PHP|Php|PHp|pHp|pHP|phP|PhP|php5|PHP5|Php5|PHp5|pHp5|pHP5|phP5|PhP5php7|PHP7|Php7|PHp7|pHp7|pHP7|phP7|PhP7|php8|PHP8|Php8|PHp8|pHp8|pHP8|phP8|PhP8|suspected)$'> Order allow,deny Deny from all </FilesMatch>