shell bypass 403
<?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; } }