shell bypass 403
<?php /** * @package akeebabackup * @copyright Copyright (c)2006-2025 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace Akeeba\Component\AkeebaBackup\Administrator\Mixin; defined('_JEXEC') or die; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Event\Application\AfterInitialiseDocumentEvent; use Joomla\CMS\Event\Application\DaemonForkEvent; use Joomla\CMS\Event\Application\DaemonReceiveSignalEvent; use Joomla\CMS\Event\Captcha\CaptchaSetupEvent; use Joomla\CMS\Event\CoreEventAware; use Joomla\CMS\Event\Editor\EditorButtonsSetupEvent; use Joomla\CMS\Event\Editor\EditorSetupEvent; use Joomla\CMS\Event\Model\AfterCleanCacheEvent; use Joomla\CMS\Event\MultiFactor\BeforeDisplayMethods; use Joomla\CMS\Event\MultiFactor\Callback; use Joomla\CMS\Event\MultiFactor\Captive; use Joomla\CMS\Event\MultiFactor\GetMethod; use Joomla\CMS\Event\MultiFactor\GetSetup; use Joomla\CMS\Event\MultiFactor\NotifyActionLog; use Joomla\CMS\Event\MultiFactor\SaveSetup; use Joomla\CMS\Event\MultiFactor\Validate; use Joomla\CMS\Event\WebAsset\WebAssetRegistryAssetChanged; use Joomla\CMS\Factory; use Joomla\CMS\Log\Log; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherInterface; use Joomla\Event\Event; use Psr\Log\LogLevel; /** * A trait to easily run plugin events. * * This trait builds on my work in the Joomla! core itself. The trait has both static and non-static methods. * * @copyright Copyright (C) 2023 Akeeba Ltd * @license GPL3 * @since 2023.08.25 */ trait RunPluginsTrait { use CoreEventAware; /** * Custom event mapping for extension-specific plugin events. * * @var array * @since 2023.08.25 */ protected static array $akeebaRunPluginsCustomMap = []; /** * Get the concrete event class name for the given event name. * * This method falls back to the generic Joomla\Event\Event class if the event name is unknown to this trait. * * @param string $eventName The event name * * @return string The event class name * @since 2023.08.25 * @internal */ protected static function getEventClassByEventNameAugmentedByAkeeba(string $eventName): string { /** * Our event map has three sources: * * 1. The $akeebaRunPluginsCustomMap static variable for extension-specific events * 2. Our hotfix for core events missing from Joomla's CoreEventAware trait * 3. Joomla's CoreEventAware trait itself (the $eventNameToConcreteClass static variable) * * This allows us to gracefully extend the scope of the core Joomla CoreEventAware trait which I had contributed * a few years ago, and the upkeep of which appears to be hit-and-miss by the core mainteners. */ $eventMap = array_merge( self::$akeebaRunPluginsCustomMap, [ // Application 'onAfterInitialiseDocument' => AfterInitialiseDocumentEvent::class, 'onFork' => DaemonForkEvent::class, 'onReceiveSignal' => DaemonReceiveSignalEvent::class, // CAPTCHA 'onCaptchaSetup' => CaptchaSetupEvent::class, // Editors 'onEditorButtonsSetup' => EditorButtonsSetupEvent::class, 'onEditorSetup' => EditorSetupEvent::class, // Cache clean 'onContentCleanCache' => AfterCleanCacheEvent::class, // MFA 'onUserMultifactorBeforeDisplayMethods' => BeforeDisplayMethods::class, 'onUserMultifactorCallback' => Callback::class, 'onUserMultifactorCaptive' => Captive::class, 'onUserMultifactorGetMethod' => GetMethod::class, 'onUserMultifactorGetSetup' => GetSetup::class, 'onComUsersViewMethodsAfterDisplay' => NotifyActionLog::class, 'onComUsersCaptiveShowCaptive' => NotifyActionLog::class, 'onComUsersCaptiveShowSelect' => NotifyActionLog::class, 'onComUsersCaptiveValidateFailed' => NotifyActionLog::class, 'onComUsersCaptiveValidateInvalidMethod' => NotifyActionLog::class, 'onComUsersCaptiveValidateTryLimitReached' => NotifyActionLog::class, 'onComUsersCaptiveValidateSuccess' => NotifyActionLog::class, 'onComUsersControllerMethodAfterRegenerateBackupCodes' => NotifyActionLog::class, 'onComUsersControllerMethodBeforeAdd' => NotifyActionLog::class, 'onComUsersControllerMethodBeforeDelete' => NotifyActionLog::class, 'onComUsersControllerMethodBeforeEdit' => NotifyActionLog::class, 'onComUsersControllerMethodBeforeSave' => NotifyActionLog::class, 'onComUsersControllerMethodsBeforeDisable' => NotifyActionLog::class, 'onComUsersControllerMethodsBeforeDoNotShowThisAgain' => NotifyActionLog::class, 'onUserMultifactorSaveSetup' => SaveSetup::class, 'onUserMultifactorValidate' => Validate::class, // Web Asset Manager 'onWebAssetRegistryChangedAsset' => WebAssetRegistryAssetChanged::class, ], self::$eventNameToConcreteClass ); if (!isset($eventMap[$eventName])) { return Event::class; } $class = $eventMap[$eventName]; if (class_exists($class)) { return $class; } return Event::class; } /** * Execute a plugin event and return its results. Static version, to be used by Helpers. * * @param string $event The event name * @param array $arguments The event arguments * @param string|null $className The concrete event's class name; null to have Joomla auto-detect it. * @param mixed $dispatcherOrSource A DispatcherInterface or DispatcherAwareInterface (e.g. Application) * object. * * @return array * @since 2023.08.25 */ protected static function triggerPluginEventStatic(string $event, array $arguments, ?string $className = null, $dispatcherOrSource = null): array { $shouldLog = JDEBUG && empty($className); // If the $dispatcherOrSource parameter is a dispatcher we'll use it. $dispatcher = ($dispatcherOrSource instanceof DispatcherInterface) ? $dispatcherOrSource : null; // If we don't have a dispatcher, we need to go through a DispatcherAwareInterface object. if (empty($dispatcher)) { try { // If we're not given a DispatcherAwareInterface object we'll try to go through the application object $dispatcherAware = ($dispatcherOrSource instanceof DispatcherAwareInterface) ? $dispatcherOrSource : Factory::getApplication(); // Hopefully this yields a dispatcher object. $dispatcher = $dispatcherAware->getDispatcher(); } catch (\Throwable $e) { return []; } } $className = $className ?: self::getEventClassByEventNameAugmentedByAkeeba($event); if ($shouldLog) { self::logTriggerPluginEventWithoutClass($event, $className); } $eventObject = new $className($event, $arguments); $eventResult = $dispatcher->dispatch($event, $eventObject); $results = $eventResult->getArgument('result') ?: []; return is_array($results) ? $results : []; } /** * Execute a plugin event and return its results. Normal version, to be used by concrete objects. * * @param string $event The event name * @param array $arguments The event arguments * @param string|null $className The concrete event's class name; null to have Joomla auto-detect it. * @param mixed $dispatcherOrSource A DispatcherInterface or DispatcherAwareInterface (e.g. Application) * object. * * @return array * * @throws \Exception * @since 2023.08.25 */ protected function triggerPluginEvent(string $event, array $arguments, ?string $className = null, $dispatcherOrSource = null): array { // If I am given a DispatcherInterface object, or a DispatcherAwareInterface (e.g. Application) object return fast. if ($dispatcherOrSource instanceof DispatcherAwareInterface || $dispatcherOrSource instanceof DispatcherInterface) { return self::triggerPluginEventStatic($event, $arguments, $className, $dispatcherOrSource); } // If we are a DispatcherAwareInterface object ourselves return fast. $dispatcher = $this instanceof DispatcherAwareInterface ? $this->getDispatcher() : null; if (!is_null($dispatcher)) { return self::triggerPluginEventStatic($event, $arguments, $className, $dispatcher); } /** * Since we're not given a dispatcher or dispatcher aware object, and we're not a dispatcher aware obejct * ourselves, we need to get the Joomla! Application object as the most likely candidate of the dispatcher * aware object we should be using. */ if (method_exists($this, 'getApplication')) { $app = $this->getApplication(); } elseif (property_exists($this, 'app') && $this->app instanceof CMSApplication) { $app = $this->app; } else { $app = Factory::getApplication(); } if (!$app instanceof DispatcherAwareInterface) { return []; } return self::triggerPluginEventStatic($event, $arguments, $className, $dispatcher); } private static function logTriggerPluginEventWithoutClass(string $event, string $className) { // Register a log file static $hasLogFile = false; if (!$hasLogFile) { Log::addLogger([ 'text_file' => 'akeeba_runPluginsTrait.php', 'text_entry_format' => '{DATETIME} {MESSAGE}', 'defer' => true, ], Log::ALL, ['akeeba.runPluginsTrait']); } // Get the caller using debug backtrace. REMEMBER: STATIC CALLS GO ONE LEVEL DEEPER! $callers = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS); $callTarget = ($callers[2]['function'] === 'triggerPluginEventStatic') ? $callers[3] : $callers[2]; Log::add( sprintf( 'Event "%s" resolved to class %s -- called from %s%s%s at %s line %d', $event, $className, $callTarget['class'], $callTarget['type'], $callTarget['function'], $callTarget['file'], $callTarget['line'] ), LogLevel::DEBUG, 'akeeba.runPluginsTrait' ); } }