diff --git a/administrator/components/com_users/src/Model/CaptiveModel.php b/administrator/components/com_users/src/Model/CaptiveModel.php index 8b5a21e70b22a..41193e7a40a0a 100644 --- a/administrator/components/com_users/src/Model/CaptiveModel.php +++ b/administrator/components/com_users/src/Model/CaptiveModel.php @@ -13,6 +13,7 @@ use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Date\Date; +use Joomla\CMS\Event\Module; use Joomla\CMS\Event\MultiFactor\Captive; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; @@ -21,7 +22,6 @@ use Joomla\Component\Users\Administrator\DataShape\CaptiveRenderOptions; use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper; use Joomla\Component\Users\Administrator\Table\MfaTable; -use Joomla\Event\Event; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -334,24 +334,22 @@ public function getMethodImage(string $name): string * the way this event is handled, taking its return into account. For now, we just abuse the mutable event * properties - a feature of the event objects we discussed in the Joomla! 4 Working Group back in August 2015. * - * @param Event $event The Joomla! event object + * @param Module\AfterModuleListEvent $event The Joomla! event object * * @return void * @throws \Exception * * @since 4.2.0 */ - public function onAfterModuleList(Event $event): void + public function onAfterModuleList(Module\AfterModuleListEvent $event): void { - $modules = $event->getArgument(0); + $modules = $event->getModules(); if (empty($modules)) { return; } $this->filterModules($modules); - - $event->setArgument(0, $modules); } /** diff --git a/libraries/src/Document/Renderer/Html/ModulesRenderer.php b/libraries/src/Document/Renderer/Html/ModulesRenderer.php index dda43423a7956..465819e649474 100644 --- a/libraries/src/Document/Renderer/Html/ModulesRenderer.php +++ b/libraries/src/Document/Renderer/Html/ModulesRenderer.php @@ -10,9 +10,11 @@ namespace Joomla\CMS\Document\Renderer\Html; use Joomla\CMS\Document\DocumentRenderer; +use Joomla\CMS\Event\Module; use Joomla\CMS\Factory; use Joomla\CMS\Helper\ModuleHelper; use Joomla\CMS\Layout\LayoutHelper; +use Joomla\CMS\Proxy\ArrayProxy; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -57,7 +59,13 @@ public function render($position, $params = [], $content = null) $buffer .= $moduleHtml; } - $app->triggerEvent('onAfterRenderModules', [&$buffer, &$params]); + // Dispatch onAfterRenderModules event + $event = new Module\AfterRenderModulesEvent('onAfterRenderModules', [ + 'subject' => $buffer, + 'attributes' => new ArrayProxy($params), + ]); + $app->getDispatcher()->dispatch('onAfterRenderModules', $event); + $buffer = $event->getContent(); return $buffer; } diff --git a/libraries/src/Event/CoreEventAware.php b/libraries/src/Event/CoreEventAware.php index e16396f18674f..ceafb0c1dd4c6 100644 --- a/libraries/src/Event/CoreEventAware.php +++ b/libraries/src/Event/CoreEventAware.php @@ -118,6 +118,13 @@ trait CoreEventAware 'onContentChangeState' => Model\AfterChangeStateEvent::class, 'onCategoryChangeState' => Model\AfterCategoryChangeStateEvent::class, 'onBeforeBatch' => Model\BeforeBatchEvent::class, + // Modules + 'onRenderModule' => Module\BeforeRenderModuleEvent::class, + 'onAfterRenderModule' => Module\AfterRenderModuleEvent::class, + 'onAfterRenderModules' => Module\AfterRenderModulesEvent::class, + 'onPrepareModuleList' => Module\PrepareModuleListEvent::class, + 'onAfterModuleList' => Module\AfterModuleListEvent::class, + 'onAfterCleanModuleList' => Module\AfterCleanModuleListEvent::class, // Extension and Installer 'onExtensionBeforeInstall' => Extension\BeforeInstallEvent::class, 'onExtensionAfterInstall' => Extension\AfterInstallEvent::class, diff --git a/libraries/src/Event/Module/AfterCleanModuleListEvent.php b/libraries/src/Event/Module/AfterCleanModuleListEvent.php new file mode 100644 index 0000000000000..1f15f081855e9 --- /dev/null +++ b/libraries/src/Event/Module/AfterCleanModuleListEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events. + * Example: + * new AfterCleanModuleListEvent('onEventName', ['subject' => $modules]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterCleanModuleListEvent extends ModuleListEvent +{ +} diff --git a/libraries/src/Event/Module/AfterModuleListEvent.php b/libraries/src/Event/Module/AfterModuleListEvent.php new file mode 100644 index 0000000000000..164a67410ebfe --- /dev/null +++ b/libraries/src/Event/Module/AfterModuleListEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events. + * Example: + * new AfterModuleListEvent('onEventName', ['subject' => $modules]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterModuleListEvent extends ModuleListEvent +{ +} diff --git a/libraries/src/Event/Module/AfterRenderModuleEvent.php b/libraries/src/Event/Module/AfterRenderModuleEvent.php new file mode 100644 index 0000000000000..04838eff8d531 --- /dev/null +++ b/libraries/src/Event/Module/AfterRenderModuleEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events. + * Example: + * new AfterRenderModuleEvent('onEventName', ['subject' => $module, 'attributes' => $attrs]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterRenderModuleEvent extends RenderModuleEvent +{ +} diff --git a/libraries/src/Event/Module/AfterRenderModulesEvent.php b/libraries/src/Event/Module/AfterRenderModulesEvent.php new file mode 100644 index 0000000000000..eb0a7d2813e80 --- /dev/null +++ b/libraries/src/Event/Module/AfterRenderModulesEvent.php @@ -0,0 +1,121 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events. + * Example: + * new AfterRenderModulesEvent('onEventName', ['subject' => $content, 'attributes' => $attrs]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterRenderModulesEvent extends ModuleEvent +{ + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = ['subject', 'attributes']; + + /** + * Constructor. + * + * @param string $name The event name. + * @param array $arguments The event arguments. + * + * @throws \BadMethodCallException + * + * @since __DEPLOY_VERSION__ + */ + public function __construct($name, array $arguments = []) + { + parent::__construct($name, $arguments); + + if (!\array_key_exists('attributes', $this->arguments)) { + throw new \BadMethodCallException("Argument 'attributes' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the subject argument. + * + * @param string $value The value to set + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(string $value): string + { + return $value; + } + + /** + * Setter for the attributes argument. + * + * @param array|\ArrayAccess $value The value to set + * + * @return array|\ArrayAccess + * + * @since __DEPLOY_VERSION__ + */ + protected function setAttributes(array|\ArrayAccess $value): array|\ArrayAccess + { + return $value; + } + + /** + * Getter for the content. + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + public function getContent(): string + { + return $this->arguments['subject']; + } + + /** + * Setter for the content. + * + * @param string $value The value to set + * + * @return self + * + * @since __DEPLOY_VERSION__ + */ + public function setContent(string $value): self + { + $this->arguments['subject'] = $value; + + return $this; + } + + /** + * Getter for the attributes argument. + * + * @return array|\ArrayAccess + * + * @since __DEPLOY_VERSION__ + */ + public function getAttributes(): array|\ArrayAccess + { + return $this->arguments['attributes']; + } +} diff --git a/libraries/src/Event/Module/BeforeRenderModuleEvent.php b/libraries/src/Event/Module/BeforeRenderModuleEvent.php new file mode 100644 index 0000000000000..988a6703252fb --- /dev/null +++ b/libraries/src/Event/Module/BeforeRenderModuleEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events. + * Example: + * new BeforeRenderModuleEvent('onEventName', ['subject' => $module, 'attributes' => $attrs]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeRenderModuleEvent extends RenderModuleEvent +{ +} diff --git a/libraries/src/Event/Module/ModuleEvent.php b/libraries/src/Event/Module/ModuleEvent.php new file mode 100644 index 0000000000000..37078c3f3b4a6 --- /dev/null +++ b/libraries/src/Event/Module/ModuleEvent.php @@ -0,0 +1,61 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +use Joomla\CMS\Event\AbstractImmutableEvent; +use Joomla\CMS\Event\ReshapeArgumentsAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Base class for Module events + * + * @since __DEPLOY_VERSION__ + */ +abstract class ModuleEvent extends AbstractImmutableEvent +{ + use ReshapeArgumentsAware; + + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = []; + + /** + * Constructor. + * + * @param string $name The event name. + * @param array $arguments The event arguments. + * + * @throws \BadMethodCallException + * + * @since __DEPLOY_VERSION__ + */ + public function __construct($name, array $arguments = []) + { + // Reshape the arguments array to preserve b/c with legacy listeners + if ($this->legacyArgumentsOrder) { + $arguments = $this->reshapeArguments($arguments, $this->legacyArgumentsOrder); + } + + if (!\array_key_exists('subject', $arguments)) { + throw new \BadMethodCallException("Argument 'subject' of event {$name} is required but has not been provided"); + } + + parent::__construct($name, $arguments); + } +} diff --git a/libraries/src/Event/Module/ModuleListEvent.php b/libraries/src/Event/Module/ModuleListEvent.php new file mode 100644 index 0000000000000..ff18ceaf774c7 --- /dev/null +++ b/libraries/src/Event/Module/ModuleListEvent.php @@ -0,0 +1,58 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events + * + * @since __DEPLOY_VERSION__ + */ +abstract class ModuleListEvent extends ModuleEvent +{ + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = ['subject']; + + /** + * Setter for the subject argument. + * + * @param array|\ArrayAccess $value The value to set + * + * @return array|\ArrayAccess + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(array|\ArrayAccess $value): array|\ArrayAccess + { + return $value; + } + + /** + * Getter for the subject argument. + * + * @return array|\ArrayAccess + * + * @since __DEPLOY_VERSION__ + */ + public function getModules(): array|\ArrayAccess + { + return $this->arguments['subject']; + } +} diff --git a/libraries/src/Event/Module/PrepareModuleListEvent.php b/libraries/src/Event/Module/PrepareModuleListEvent.php new file mode 100644 index 0000000000000..e21677dba3a13 --- /dev/null +++ b/libraries/src/Event/Module/PrepareModuleListEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events. + * Example: + * new AfterModuleListEvent('onEventName', ['subject' => $modules]); + * + * @since __DEPLOY_VERSION__ + */ +class PrepareModuleListEvent extends ModuleListEvent +{ +} diff --git a/libraries/src/Event/Module/RenderModuleEvent.php b/libraries/src/Event/Module/RenderModuleEvent.php new file mode 100644 index 0000000000000..15c8f00935e3d --- /dev/null +++ b/libraries/src/Event/Module/RenderModuleEvent.php @@ -0,0 +1,103 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Module; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Module events + * + * @since __DEPLOY_VERSION__ + */ +abstract class RenderModuleEvent extends ModuleEvent +{ + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = ['subject', 'attributes']; + + /** + * Constructor. + * + * @param string $name The event name. + * @param array $arguments The event arguments. + * + * @throws \BadMethodCallException + * + * @since __DEPLOY_VERSION__ + */ + public function __construct($name, array $arguments = []) + { + parent::__construct($name, $arguments); + + if (!\array_key_exists('attributes', $this->arguments)) { + throw new \BadMethodCallException("Argument 'attributes' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the subject argument. + * + * @param object $value The value to set + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(object $value): object + { + return $value; + } + + /** + * Setter for the attributes argument. + * + * @param array|\ArrayAccess $value The value to set + * + * @return array|\ArrayAccess + * + * @since __DEPLOY_VERSION__ + */ + protected function setAttributes(array|\ArrayAccess $value): array|\ArrayAccess + { + return $value; + } + + /** + * Getter for the subject argument. + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + public function getModule(): object + { + return $this->arguments['subject']; + } + + /** + * Getter for the attributes argument. + * + * @return array|\ArrayAccess + * + * @since __DEPLOY_VERSION__ + */ + public function getAttributes(): array|\ArrayAccess + { + return $this->arguments['attributes']; + } +} diff --git a/libraries/src/Helper/ModuleHelper.php b/libraries/src/Helper/ModuleHelper.php index 1dabda01caf7b..93b2b3a01df43 100644 --- a/libraries/src/Helper/ModuleHelper.php +++ b/libraries/src/Helper/ModuleHelper.php @@ -12,6 +12,7 @@ use Joomla\CMS\Cache\CacheControllerFactoryInterface; use Joomla\CMS\Cache\Controller\CallbackController; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\Module; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Language\LanguageHelper; @@ -19,6 +20,7 @@ use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Log\Log; use Joomla\CMS\Profiler\Profiler; +use Joomla\CMS\Proxy\ArrayProxy; use Joomla\Database\ParameterType; use Joomla\Filesystem\Path; use Joomla\Registry\Registry; @@ -174,6 +176,7 @@ public static function renderModule($module, $attribs = []) // Set scope to component name $app->scope = $module->module; + $dispatcher = $app->getDispatcher(); // Get the template $template = $app->getTemplate(); @@ -206,9 +209,13 @@ public static function renderModule($module, $attribs = []) $module->style = $attribs['style']; // If the $module is nulled it will return an empty content, otherwise it will render the module normally. - $app->triggerEvent('onRenderModule', [&$module, &$attribs]); + $attrProxy = new ArrayProxy($attribs); + $dispatcher->dispatch('onRenderModule', new Module\BeforeRenderModuleEvent('onRenderModule', [ + 'subject' => $module, + 'attributes' => $attrProxy, + ])); - if ($module === null || !isset($module->content)) { + if (!isset($module->content)) { return ''; } @@ -232,7 +239,10 @@ public static function renderModule($module, $attribs = []) // Revert the scope $app->scope = $scope; - $app->triggerEvent('onAfterRenderModule', [&$module, &$attribs]); + $dispatcher->dispatch('onAfterRenderModule', new Module\AfterRenderModuleEvent('onAfterRenderModule', [ + 'subject' => $module, + 'attributes' => $attrProxy, + ])); if (JDEBUG) { Profiler::getInstance('Application')->mark('afterRenderModule ' . $module->module . ' (' . $module->title . ')'); @@ -362,22 +372,27 @@ protected static function &load() return $modules; } - $app = Factory::getApplication(); - - $modules = null; + $dispatcher = Factory::getApplication()->getDispatcher(); + $modules = []; - $app->triggerEvent('onPrepareModuleList', [&$modules]); + $dispatcher->dispatch('onPrepareModuleList', new Module\PrepareModuleListEvent('onPrepareModuleList', [ + 'subject' => new ArrayProxy($modules), + ])); // If the onPrepareModuleList event returns an array of modules, then ignore the default module list creation - if (!\is_array($modules)) { + if (!$modules) { $modules = static::getModuleList(); } - $app->triggerEvent('onAfterModuleList', [&$modules]); + $dispatcher->dispatch('onAfterModuleList', new Module\AfterModuleListEvent('onAfterModuleList', [ + 'subject' => new ArrayProxy($modules), + ])); $modules = static::cleanModuleList($modules); - $app->triggerEvent('onAfterCleanModuleList', [&$modules]); + $dispatcher->dispatch('onAfterCleanModuleList', new Module\AfterCleanModuleListEvent('onAfterCleanModuleList', [ + 'subject' => new ArrayProxy($modules), + ])); return $modules; }