shell bypass 403
<?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2024 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'
);
}
}