diff --git a/administrator/components/com_categories/src/Model/CategoryModel.php b/administrator/components/com_categories/src/Model/CategoryModel.php index 45fab45040c51..af5c51ac3be0c 100644 --- a/administrator/components/com_categories/src/Model/CategoryModel.php +++ b/administrator/components/com_categories/src/Model/CategoryModel.php @@ -14,6 +14,7 @@ use Joomla\CMS\Association\AssociationServiceInterface; use Joomla\CMS\Categories\CategoryServiceInterface; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\Model\AfterCategoryChangeStateEvent; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Helper\TagsHelper; @@ -740,10 +741,14 @@ public function publish(&$pks, $value = 1) $extension = Factory::getApplication()->getInput()->get('extension'); // Include the content plugins for the change of category state event. - PluginHelper::importPlugin('content'); + PluginHelper::importPlugin('content', null, true, $this->getDispatcher()); // Trigger the onCategoryChangeState event. - Factory::getApplication()->triggerEvent('onCategoryChangeState', [$extension, $pks, $value]); + $this->getDispatcher()->dispatch('onCategoryChangeState', new AfterCategoryChangeStateEvent('onCategoryChangeState', [ + 'context' => $extension, + 'subject' => $pks, + 'value' => $value, + ])); return true; } diff --git a/administrator/components/com_mails/src/Controller/TemplateController.php b/administrator/components/com_mails/src/Controller/TemplateController.php index 88d8a570ff295..d3ac98f40f2f5 100644 --- a/administrator/components/com_mails/src/Controller/TemplateController.php +++ b/administrator/components/com_mails/src/Controller/TemplateController.php @@ -11,7 +11,7 @@ namespace Joomla\Component\Mails\Administrator\Controller; use Joomla\CMS\Application\CMSApplication; -use Joomla\CMS\Factory; +use Joomla\CMS\Event\Model; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\FormController; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; @@ -192,9 +192,13 @@ public function save($key = null, $urlVar = null) // Send an object which can be modified through the plugin event $objData = (object) $data; - $this->app->triggerEvent( + $this->getDispatcher()->dispatch( 'onContentNormaliseRequestData', - [$this->option . '.' . $this->context, $objData, $form] + new Model\NormaliseRequestDataEvent('onContentNormaliseRequestData', [ + 'context' => $this->option . '.' . $this->context, + 'data' => $objData, + 'subject' => $form, + ]) ); $data = (array) $objData; diff --git a/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php b/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php index 0a81adf39ff3c..2de696411a005 100644 --- a/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php +++ b/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php @@ -10,6 +10,7 @@ namespace Joomla\Component\Scheduler\Administrator\Traits; +use Joomla\CMS\Event\Model; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; @@ -97,8 +98,8 @@ protected function endRoutine(ExecuteTaskEvent $event, int $exitCode): void * `onContentPrepareForm` event through {@see SubscriberInterface::getSubscribedEvents()} and will take care * of injecting the fields without additional logic in the plugin class. * - * @param EventInterface|Form $context The onContentPrepareForm event or the Form object. - * @param mixed $data The form data, required when $context is a {@see Form} instance. + * @param Model\PrepareFormEvent|Form $context The onContentPrepareForm event or the Form object. + * @param mixed $data The form data, required when $context is a {@see Form} instance. * * @return boolean True if the form was successfully enhanced or the context was not relevant. * @@ -107,10 +108,9 @@ protected function endRoutine(ExecuteTaskEvent $event, int $exitCode): void */ public function enhanceTaskItemForm($context, $data = null): bool { - if ($context instanceof EventInterface) { - /** @var Form $form */ - $form = $context->getArgument('0'); - $data = $context->getArgument('1'); + if ($context instanceof Model\PrepareFormEvent) { + $form = $context->getForm(); + $data = $context->getData(); } elseif ($context instanceof Form) { $form = $context; } else { diff --git a/components/com_content/src/View/Article/HtmlView.php b/components/com_content/src/View/Article/HtmlView.php index 8872787c79087..262352c31b496 100644 --- a/components/com_content/src/View/Article/HtmlView.php +++ b/components/com_content/src/View/Article/HtmlView.php @@ -11,6 +11,7 @@ namespace Joomla\Component\Content\Site\View\Article; use Joomla\CMS\Categories\Categories; +use Joomla\CMS\Event\Content; use Joomla\CMS\Factory; use Joomla\CMS\Helper\TagsHelper; use Joomla\CMS\Language\Associations; @@ -23,7 +24,6 @@ use Joomla\CMS\Uri\Uri; use Joomla\Component\Content\Site\Helper\AssociationHelper; use Joomla\Component\Content\Site\Helper\RouteHelper; -use Joomla\Event\Event; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -173,7 +173,7 @@ public function display($tpl = null) } } - $offset = $this->state->get('list.offset'); + $offset = (int) $this->state->get('list.offset'); // Check the view access to the article (the model has already computed the values). if ($item->params->get('access-view') == false && ($item->params->get('show_noauth', '0') == '0')) { @@ -223,18 +223,31 @@ public function display($tpl = null) } // Process the content plugins. - PluginHelper::importPlugin('content'); - $this->dispatchEvent(new Event('onContentPrepare', ['com_content.article', &$item, &$item->params, $offset])); - - $item->event = new \stdClass(); - $results = Factory::getApplication()->triggerEvent('onContentAfterTitle', ['com_content.article', &$item, &$item->params, $offset]); - $item->event->afterDisplayTitle = trim(implode("\n", $results)); - - $results = Factory::getApplication()->triggerEvent('onContentBeforeDisplay', ['com_content.article', &$item, &$item->params, $offset]); - $item->event->beforeDisplayContent = trim(implode("\n", $results)); - - $results = Factory::getApplication()->triggerEvent('onContentAfterDisplay', ['com_content.article', &$item, &$item->params, $offset]); - $item->event->afterDisplayContent = trim(implode("\n", $results)); + PluginHelper::importPlugin('content', null, true, $this->getDispatcher()); + + $contentEventArguments = [ + 'context' => 'com_content.article', + 'subject' => $item, + 'params' => $item->params, + 'page' => $offset, + ]; + + $this->dispatchEvent(new Content\ContentPrepareEvent('onContentPrepare', $contentEventArguments)); + + // Extra content from events + $item->event = new \stdClass(); + $contentEvents = [ + 'afterDisplayTitle' => new Content\AfterTitleEvent('onContentAfterTitle', $contentEventArguments), + 'beforeDisplayContent' => new Content\BeforeDisplayEvent('onContentBeforeDisplay', $contentEventArguments), + 'afterDisplayContent' => new Content\AfterDisplayEvent('onContentAfterDisplay', $contentEventArguments), + ]; + + foreach ($contentEvents as $resultKey => $event) { + $this->dispatchEvent($event); + $results = $event['result']; + + $item->event->{$resultKey} = $results ? trim(implode("\n", $results)) : ''; + } // Escape strings for HTML output $this->pageclass_sfx = htmlspecialchars($this->item->params->get('pageclass_sfx', '')); diff --git a/components/com_users/src/Controller/ProfileController.php b/components/com_users/src/Controller/ProfileController.php index 1d830f8f8bda9..4ee53fa500af9 100644 --- a/components/com_users/src/Controller/ProfileController.php +++ b/components/com_users/src/Controller/ProfileController.php @@ -10,6 +10,7 @@ namespace Joomla\Component\Users\Site\Controller; +use Joomla\CMS\Event\Model; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Router\Route; @@ -105,9 +106,13 @@ public function save() // Send an object which can be modified through the plugin event $objData = (object) $requestData; - $app->triggerEvent( + $this->getDispatcher()->dispatch( 'onContentNormaliseRequestData', - ['com_users.user', $objData, $form] + new Model\NormaliseRequestDataEvent('onContentNormaliseRequestData', [ + 'context' => 'com_users.user', + 'data' => $objData, + 'subject' => $form, + ]) ); $requestData = (array) $objData; diff --git a/libraries/src/Event/AbstractEvent.php b/libraries/src/Event/AbstractEvent.php index ae1637ad26d36..bd0ce4b9d3ea8 100644 --- a/libraries/src/Event/AbstractEvent.php +++ b/libraries/src/Event/AbstractEvent.php @@ -111,8 +111,6 @@ public function __construct(string $name, array $arguments = []) { parent::__construct($name, $arguments); - $this->arguments = []; - foreach ($arguments as $argumentName => $value) { $this->setArgument($argumentName, $value); } diff --git a/libraries/src/Event/Content/AfterDisplayEvent.php b/libraries/src/Event/Content/AfterDisplayEvent.php new file mode 100644 index 0000000000000..a394be3647462 --- /dev/null +++ b/libraries/src/Event/Content/AfterDisplayEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Content; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeStringAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Content event. + * Example: + * new AfterDisplayEvent('onEventName', ['context' => 'com_example.example', 'subject' => $contentObject, 'params' => $params, 'page' => $pageNum]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterDisplayEvent extends ContentPrepareEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeStringAware; +} diff --git a/libraries/src/Event/Content/AfterTitleEvent.php b/libraries/src/Event/Content/AfterTitleEvent.php new file mode 100644 index 0000000000000..bff8880ceb23a --- /dev/null +++ b/libraries/src/Event/Content/AfterTitleEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Content; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeStringAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Content event. + * Example: + * new AfterTitleEvent('onEventName', ['context' => 'com_example.example', 'subject' => $contentObject, 'params' => $params, 'page' => $pageNum]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterTitleEvent extends ContentPrepareEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeStringAware; +} diff --git a/libraries/src/Event/Content/BeforeDisplayEvent.php b/libraries/src/Event/Content/BeforeDisplayEvent.php new file mode 100644 index 0000000000000..9e64ed5178fb3 --- /dev/null +++ b/libraries/src/Event/Content/BeforeDisplayEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Content; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeStringAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Content event. + * Example: + * new BeforeDisplayEvent('onEventName', ['context' => 'com_example.example', 'subject' => $contentObject, 'params' => $params, 'page' => $pageNum]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeDisplayEvent extends ContentPrepareEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeStringAware; +} diff --git a/libraries/src/Event/Content/ContentEvent.php b/libraries/src/Event/Content/ContentEvent.php new file mode 100644 index 0000000000000..6faa663463571 --- /dev/null +++ b/libraries/src/Event/Content/ContentEvent.php @@ -0,0 +1,91 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Content; + +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 Content events + * + * @since __DEPLOY_VERSION__ + */ +abstract class ContentEvent 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('context', $arguments)) { + throw new \BadMethodCallException("Argument 'context' of event {$name} is required but has not been provided"); + } + + 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); + } + + /** + * Setter for the context argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setContext(string $value): string + { + return $value; + } + + /** + * Getter for the context argument. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getContext(): string + { + return $this->arguments['context']; + } +} diff --git a/libraries/src/Event/Content/ContentPrepareEvent.php b/libraries/src/Event/Content/ContentPrepareEvent.php new file mode 100644 index 0000000000000..665c730b11ffe --- /dev/null +++ b/libraries/src/Event/Content/ContentPrepareEvent.php @@ -0,0 +1,125 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Content; + +use Joomla\Registry\Registry; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Content event. + * Example: + * new ContentPrepareEvent('onEventName', ['context' => 'com_example.example', 'subject' => $contentObject, 'params' => $params, 'page' => $pageNum]); + * + * @since __DEPLOY_VERSION__ + */ +class ContentPrepareEvent extends ContentEvent +{ + /** + * 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 = ['context', 'subject', 'params', 'page']; + + /** + * 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 params argument. + * + * @param Registry $value The value to set + * + * @return Registry + * + * @since __DEPLOY_VERSION__ + */ + protected function setParams($value): Registry + { + // This is for b/c compatibility, because some extensions pass a mixed types + if (!$value instanceof Registry) { + $value = new Registry($value); + + // @TODO: In 6.0 throw an exception + @trigger_error( + sprintf('The "params" attribute for the event "%s" must be type of Registry. In 6.0 it will throw an exception', $this->getName()), + E_USER_DEPRECATED + ); + } + + return $value; + } + + /** + * Setter for the page argument. + * + * @param ?int $value The value to set + * + * @return ?int + * + * @since __DEPLOY_VERSION__ + */ + protected function setPage(?int $value): ?int + { + return $value; + } + + /** + * Getter for the item argument. + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + public function getItem(): object + { + return $this->arguments['subject']; + } + + /** + * Getter for the item argument. + * + * @return Registry + * + * @since __DEPLOY_VERSION__ + */ + public function getParams(): Registry + { + return $this->arguments['params']; + } + + /** + * Getter for the page argument. + * + * @return ?int + * + * @since __DEPLOY_VERSION__ + */ + public function getPage(): ?int + { + return $this->arguments['page']; + } +} diff --git a/libraries/src/Event/CoreEventAware.php b/libraries/src/Event/CoreEventAware.php index ebf237803991d..e16396f18674f 100644 --- a/libraries/src/Event/CoreEventAware.php +++ b/libraries/src/Event/CoreEventAware.php @@ -54,8 +54,6 @@ trait CoreEventAware 'onBeforeRespond' => Application\BeforeRespondEvent::class, 'onAfterRespond' => Application\AfterRespondEvent::class, 'onError' => ErrorEvent::class, - // Model - 'onBeforeBatch' => Model\BeforeBatchEvent::class, // Quickicon 'onGetIcon' => QuickIcon\GetIconEvent::class, // Table @@ -102,6 +100,34 @@ trait CoreEventAware // Extensions 'onBeforeExtensionBoot' => BeforeExtensionBootEvent::class, 'onAfterExtensionBoot' => AfterExtensionBootEvent::class, + // Content + 'onContentPrepare' => Content\ContentPrepareEvent::class, + 'onContentAfterTitle' => Content\AfterTitleEvent::class, + 'onContentBeforeDisplay' => Content\BeforeDisplayEvent::class, + 'onContentAfterDisplay' => Content\AfterDisplayEvent::class, + // Model + 'onContentNormaliseRequestData' => Model\NormaliseRequestDataEvent::class, + 'onContentBeforeValidateData' => Model\BeforeValidateDataEvent::class, + 'onContentPrepareForm' => Model\PrepareFormEvent::class, + 'onContentPrepareData' => Model\PrepareDataEvent::class, + 'onContentBeforeSave' => Model\BeforeSaveEvent::class, + 'onContentAfterSave' => Model\AfterSaveEvent::class, + 'onContentBeforeDelete' => Model\BeforeDeleteEvent::class, + 'onContentAfterDelete' => Model\AfterDeleteEvent::class, + 'onContentBeforeChangeState' => Model\BeforeChangeStateEvent::class, + 'onContentChangeState' => Model\AfterChangeStateEvent::class, + 'onCategoryChangeState' => Model\AfterCategoryChangeStateEvent::class, + 'onBeforeBatch' => Model\BeforeBatchEvent::class, + // Extension and Installer + 'onExtensionBeforeInstall' => Extension\BeforeInstallEvent::class, + 'onExtensionAfterInstall' => Extension\AfterInstallEvent::class, + 'onExtensionBeforeUninstall' => Extension\BeforeUninstallEvent::class, + 'onExtensionAfterUninstall' => Extension\AfterUninstallEvent::class, + 'onExtensionBeforeUpdate' => Extension\BeforeUpdateEvent::class, + 'onExtensionAfterUpdate' => Extension\AfterUpdateEvent::class, + 'onExtensionBeforeSave' => Model\BeforeSaveEvent::class, + 'onExtensionAfterSave' => Model\AfterSaveEvent::class, + 'onExtensionAfterDelete' => Model\AfterDeleteEvent::class, ]; /** diff --git a/libraries/src/Event/Extension/AbstractExtensionEvent.php b/libraries/src/Event/Extension/AbstractExtensionEvent.php new file mode 100644 index 0000000000000..e0f1cea92d195 --- /dev/null +++ b/libraries/src/Event/Extension/AbstractExtensionEvent.php @@ -0,0 +1,57 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Extension; + +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 Extension events + * + * @since __DEPLOY_VERSION__ + */ +abstract class AbstractExtensionEvent 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); + } + + parent::__construct($name, $arguments); + } +} diff --git a/libraries/src/Event/Extension/AfterInstallEvent.php b/libraries/src/Event/Extension/AfterInstallEvent.php new file mode 100644 index 0000000000000..5c41297696b95 --- /dev/null +++ b/libraries/src/Event/Extension/AfterInstallEvent.php @@ -0,0 +1,109 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Extension; + +use Joomla\CMS\Installer\Installer; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Extension events + * + * @since __DEPLOY_VERSION__ + */ +class AfterInstallEvent extends AbstractExtensionEvent +{ + /** + * 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 = ['installer', 'eid']; + + /** + * 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('installer', $this->arguments)) { + throw new \BadMethodCallException("Argument 'method' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('eid', $this->arguments)) { + throw new \BadMethodCallException("Argument 'eid' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the installer argument. + * + * @param Installer $value The value to set + * + * @return Installer + * + * @since __DEPLOY_VERSION__ + */ + protected function setInstaller(Installer $value): Installer + { + return $value; + } + + /** + * Setter for the eid argument. + * + * @param int|bool $value The value to set + * + * @return int|bool + * + * @since __DEPLOY_VERSION__ + */ + protected function setEid(int|bool $value): int|bool + { + return $value; + } + + /** + * Getter for the installer. + * + * @return Installer + * + * @since __DEPLOY_VERSION__ + */ + public function getInstaller(): Installer + { + return $this->arguments['installer']; + } + + /** + * Getter for the eid. + * + * @return int|bool + * + * @since __DEPLOY_VERSION__ + */ + public function getEid(): int|bool + { + return $this->arguments['eid']; + } +} diff --git a/libraries/src/Event/Extension/AfterUninstallEvent.php b/libraries/src/Event/Extension/AfterUninstallEvent.php new file mode 100644 index 0000000000000..3b99d4373e3a6 --- /dev/null +++ b/libraries/src/Event/Extension/AfterUninstallEvent.php @@ -0,0 +1,139 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Extension; + +use Joomla\CMS\Installer\Installer; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Extension events + * + * @since __DEPLOY_VERSION__ + */ +class AfterUninstallEvent extends AbstractExtensionEvent +{ + /** + * 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 = ['installer', 'eid', 'removed']; + + /** + * 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('installer', $this->arguments)) { + throw new \BadMethodCallException("Argument 'method' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('eid', $this->arguments)) { + throw new \BadMethodCallException("Argument 'eid' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('removed', $this->arguments)) { + throw new \BadMethodCallException("Argument 'removed' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the installer argument. + * + * @param Installer $value The value to set + * + * @return Installer + * + * @since __DEPLOY_VERSION__ + */ + protected function setInstaller(Installer $value): Installer + { + return $value; + } + + /** + * Setter for the eid argument. + * + * @param integer $value The value to set + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + protected function setEid(int $value): int + { + return $value; + } + + /** + * Setter for the removed argument. + * + * @param bool $value The value to set + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + protected function setRemoved(bool $value): bool + { + return $value; + } + + /** + * Getter for the installer. + * + * @return Installer + * + * @since __DEPLOY_VERSION__ + */ + public function getInstaller(): Installer + { + return $this->arguments['installer']; + } + + /** + * Getter for the eid. + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + public function getEid(): int + { + return $this->arguments['eid']; + } + + /** + * Getter for the removed. + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + public function getRemoved(): bool + { + return $this->arguments['removed']; + } +} diff --git a/libraries/src/Event/Extension/AfterUpdateEvent.php b/libraries/src/Event/Extension/AfterUpdateEvent.php new file mode 100644 index 0000000000000..6c21f1db33599 --- /dev/null +++ b/libraries/src/Event/Extension/AfterUpdateEvent.php @@ -0,0 +1,109 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Extension; + +use Joomla\CMS\Installer\Installer; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Extension events + * + * @since __DEPLOY_VERSION__ + */ +class AfterUpdateEvent extends AbstractExtensionEvent +{ + /** + * 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 = ['installer', 'eid']; + + /** + * 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('installer', $this->arguments)) { + throw new \BadMethodCallException("Argument 'method' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('eid', $this->arguments)) { + throw new \BadMethodCallException("Argument 'eid' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the installer argument. + * + * @param Installer $value The value to set + * + * @return Installer + * + * @since __DEPLOY_VERSION__ + */ + protected function setInstaller(Installer $value): Installer + { + return $value; + } + + /** + * Setter for the eid argument. + * + * @param int|bool $value The value to set + * + * @return int|bool + * + * @since __DEPLOY_VERSION__ + */ + protected function setEid(int|bool $value): int|bool + { + return $value; + } + + /** + * Getter for the installer. + * + * @return Installer + * + * @since __DEPLOY_VERSION__ + */ + public function getInstaller(): Installer + { + return $this->arguments['installer']; + } + + /** + * Getter for the eid. + * + * @return int|bool + * + * @since __DEPLOY_VERSION__ + */ + public function getEid(): int|bool + { + return $this->arguments['eid']; + } +} diff --git a/libraries/src/Event/Extension/BeforeInstallEvent.php b/libraries/src/Event/Extension/BeforeInstallEvent.php new file mode 100644 index 0000000000000..cb58ce0770ad0 --- /dev/null +++ b/libraries/src/Event/Extension/BeforeInstallEvent.php @@ -0,0 +1,159 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Extension; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Extension events + * + * @since __DEPLOY_VERSION__ + */ +class BeforeInstallEvent extends AbstractExtensionEvent +{ + /** + * 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 = ['method', 'type', 'manifest', 'extension']; + + /** + * 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('method', $this->arguments)) { + throw new \BadMethodCallException("Argument 'method' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('type', $this->arguments)) { + throw new \BadMethodCallException("Argument 'type' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the method argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setMethod(string $value): string + { + return $value; + } + + /** + * Setter for the type argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setType(string $value): string + { + return $value; + } + + /** + * Setter for the manifest argument. + * + * @param ?\SimpleXMLElement $value The value to set + * + * @return ?\SimpleXMLElement + * + * @since __DEPLOY_VERSION__ + */ + protected function setManifest(?\SimpleXMLElement $value): ?\SimpleXMLElement + { + return $value; + } + + /** + * Setter for the extension argument. + * + * @param integer $value The value to set + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + protected function setExtension(int $value): int + { + return $value; + } + + /** + * Getter for the method. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getMethod(): string + { + return $this->arguments['method']; + } + + /** + * Getter for the type. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getType(): string + { + return $this->arguments['type']; + } + + /** + * Getter for the manifest. + * + * @return ?\SimpleXMLElement + * + * @since __DEPLOY_VERSION__ + */ + public function getManifest(): ?\SimpleXMLElement + { + return $this->arguments['manifest'] ?? null; + } + + /** + * Getter for the extension. + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + public function getExtension(): int + { + return $this->arguments['extension'] ?? 0; + } +} diff --git a/libraries/src/Event/Extension/BeforeUninstallEvent.php b/libraries/src/Event/Extension/BeforeUninstallEvent.php new file mode 100644 index 0000000000000..b9424ef7de572 --- /dev/null +++ b/libraries/src/Event/Extension/BeforeUninstallEvent.php @@ -0,0 +1,77 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Extension; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Extension events + * + * @since __DEPLOY_VERSION__ + */ +class BeforeUninstallEvent extends AbstractExtensionEvent +{ + /** + * 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 = ['eid']; + + /** + * 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('eid', $this->arguments)) { + throw new \BadMethodCallException("Argument 'eid' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the eid argument. + * + * @param integer $value The value to set + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + protected function setEid(int $value): int + { + return $value; + } + + /** + * Getter for the eid. + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + public function getEid(): int + { + return $this->arguments['eid']; + } +} diff --git a/libraries/src/Event/Extension/BeforeUpdateEvent.php b/libraries/src/Event/Extension/BeforeUpdateEvent.php new file mode 100644 index 0000000000000..9dc557d5b8fca --- /dev/null +++ b/libraries/src/Event/Extension/BeforeUpdateEvent.php @@ -0,0 +1,107 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Extension; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Extension events + * + * @since __DEPLOY_VERSION__ + */ +class BeforeUpdateEvent extends AbstractExtensionEvent +{ + /** + * 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 = ['type', 'manifest']; + + /** + * 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('type', $this->arguments)) { + throw new \BadMethodCallException("Argument 'type' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('manifest', $this->arguments)) { + throw new \BadMethodCallException("Argument 'manifest' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the type argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setType(string $value): string + { + return $value; + } + + /** + * Setter for the manifest argument. + * + * @param ?\SimpleXMLElement $value The value to set + * + * @return ?\SimpleXMLElement + * + * @since __DEPLOY_VERSION__ + */ + protected function setManifest(?\SimpleXMLElement $value): ?\SimpleXMLElement + { + return $value; + } + + /** + * Getter for the type. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getType(): string + { + return $this->arguments['type']; + } + + /** + * Getter for the manifest. + * + * @return ?\SimpleXMLElement + * + * @since __DEPLOY_VERSION__ + */ + public function getManifest(): ?\SimpleXMLElement + { + return $this->arguments['manifest']; + } +} diff --git a/libraries/src/Event/Model/AfterCategoryChangeStateEvent.php b/libraries/src/Event/Model/AfterCategoryChangeStateEvent.php new file mode 100644 index 0000000000000..f8727a0ebf4f5 --- /dev/null +++ b/libraries/src/Event/Model/AfterCategoryChangeStateEvent.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new AfterCategoryChangeStateEvent('onEventName', ['context' => $extension, 'subject' => $primaryKeys, 'value' => $newState]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterCategoryChangeStateEvent extends ChangeStateEvent +{ + /** + * Getter for the extension. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getExtension(): string + { + return $this->getContext(); + } +} diff --git a/libraries/src/Event/Model/AfterChangeStateEvent.php b/libraries/src/Event/Model/AfterChangeStateEvent.php new file mode 100644 index 0000000000000..ff7eaee493fa6 --- /dev/null +++ b/libraries/src/Event/Model/AfterChangeStateEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeBooleanAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new AfterChangeStateEvent('onEventName', ['context' => 'com_example.example', 'subject' => $primaryKeys, 'value' => $newState]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterChangeStateEvent extends ChangeStateEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeBooleanAware; +} diff --git a/libraries/src/Event/Model/AfterDeleteEvent.php b/libraries/src/Event/Model/AfterDeleteEvent.php new file mode 100644 index 0000000000000..5aec467cf8c40 --- /dev/null +++ b/libraries/src/Event/Model/AfterDeleteEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new AfterDeleteEvent('onEventName', ['context' => 'com_example.example', 'subject' => $itemObjectToDelete]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterDeleteEvent extends DeleteEvent +{ +} diff --git a/libraries/src/Event/Model/AfterSaveEvent.php b/libraries/src/Event/Model/AfterSaveEvent.php new file mode 100644 index 0000000000000..faecacbf0bc7a --- /dev/null +++ b/libraries/src/Event/Model/AfterSaveEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new AfterSaveEvent('onEventName', ['context' => 'com_example.example', 'subject' => $itemObjectToSave, 'isNew' => $isNew, 'data' => $submittedData]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterSaveEvent extends SaveEvent +{ +} diff --git a/libraries/src/Event/Model/BeforeChangeStateEvent.php b/libraries/src/Event/Model/BeforeChangeStateEvent.php new file mode 100644 index 0000000000000..3e6786339dae7 --- /dev/null +++ b/libraries/src/Event/Model/BeforeChangeStateEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeBooleanAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new BeforeChangeStateEvent('onEventName', ['context' => 'com_example.example', 'subject' => $primaryKeys, 'value' => $newState]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeChangeStateEvent extends ChangeStateEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeBooleanAware; +} diff --git a/libraries/src/Event/Model/BeforeDeleteEvent.php b/libraries/src/Event/Model/BeforeDeleteEvent.php new file mode 100644 index 0000000000000..f703a0d09f9d5 --- /dev/null +++ b/libraries/src/Event/Model/BeforeDeleteEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeBooleanAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new BeforeDeleteEvent('onEventName', ['context' => 'com_example.example', 'subject' => $itemObjectToDelete]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeDeleteEvent extends DeleteEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeBooleanAware; +} diff --git a/libraries/src/Event/Model/BeforeSaveEvent.php b/libraries/src/Event/Model/BeforeSaveEvent.php new file mode 100644 index 0000000000000..6ff988ed1ae1b --- /dev/null +++ b/libraries/src/Event/Model/BeforeSaveEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeBooleanAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new BeforeSaveEvent('onEventName', ['context' => 'com_example.example', 'subject' => $itemObjectToSave, 'isNew' => $isNew, 'data' => $submittedData]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeSaveEvent extends SaveEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeBooleanAware; +} diff --git a/libraries/src/Event/Model/BeforeValidateDataEvent.php b/libraries/src/Event/Model/BeforeValidateDataEvent.php new file mode 100644 index 0000000000000..e75d9f75d8883 --- /dev/null +++ b/libraries/src/Event/Model/BeforeValidateDataEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model Form event. + * Example: + * new BeforeValidateDataEvent('onEventName', ['subject' => $form, 'data' => $data]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeValidateDataEvent extends FormEvent +{ +} diff --git a/libraries/src/Event/Model/ChangeStateEvent.php b/libraries/src/Event/Model/ChangeStateEvent.php new file mode 100644 index 0000000000000..88dd1e037b4b3 --- /dev/null +++ b/libraries/src/Event/Model/ChangeStateEvent.php @@ -0,0 +1,84 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event + * + * @since __DEPLOY_VERSION__ + */ +abstract class ChangeStateEvent extends ModelEvent +{ + /** + * 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 = ['context', 'subject', 'value']; + + /** + * Setter for the subject argument. + * + * @param array $value The value to set + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(array $value): array + { + return $value; + } + + /** + * Setter for the value argument. + * + * @param int $value The value to set + * + * @return int + * + * @since __DEPLOY_VERSION__ + */ + protected function setValue($value): int + { + return (int) $value; + } + + /** + * Getter for the list of primary keys. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getPks(): array + { + return $this->arguments['subject']; + } + + /** + * Getter for the value state. + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + public function getValue(): int + { + return $this->arguments['value']; + } +} diff --git a/libraries/src/Event/Model/DeleteEvent.php b/libraries/src/Event/Model/DeleteEvent.php new file mode 100644 index 0000000000000..03a345355ef5e --- /dev/null +++ b/libraries/src/Event/Model/DeleteEvent.php @@ -0,0 +1,58 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event + * + * @since __DEPLOY_VERSION__ + */ +abstract class DeleteEvent extends ModelEvent +{ + /** + * 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 = ['context', 'subject']; + + /** + * 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; + } + + /** + * Getter for the item. + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + public function getItem(): object + { + return $this->arguments['subject']; + } +} diff --git a/libraries/src/Event/Model/FormEvent.php b/libraries/src/Event/Model/FormEvent.php new file mode 100644 index 0000000000000..69c7c1ea9c8f1 --- /dev/null +++ b/libraries/src/Event/Model/FormEvent.php @@ -0,0 +1,130 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +use Joomla\CMS\Event\AbstractImmutableEvent; +use Joomla\CMS\Event\ReshapeArgumentsAware; +use Joomla\CMS\Form\Form; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model Form event + * + * @since __DEPLOY_VERSION__ + */ +abstract class FormEvent 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 = ['subject', 'data']; + + /** + * 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); + } + + parent::__construct($name, $arguments); + + if (!\array_key_exists('subject', $this->arguments)) { + throw new \BadMethodCallException("Argument 'subject' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('data', $this->arguments)) { + throw new \BadMethodCallException("Argument 'data' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the context argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setContext(string $value): string + { + return $value; + } + + /** + * Getter for the context argument. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getContext(): string + { + return $this->arguments['context']; + } + + /** + * Setter for the subject argument. + * + * @param Form $value The value to set + * + * @return Form + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(Form $value): Form + { + return $value; + } + + /** + * Getter for the form. + * + * @return Form + * + * @since __DEPLOY_VERSION__ + */ + public function getForm(): Form + { + return $this->arguments['subject']; + } + + /** + * Getter for the data. + * + * @return object|array + * + * @since __DEPLOY_VERSION__ + */ + public function getData() + { + return $this->arguments['data']; + } +} diff --git a/libraries/src/Event/Model/ModelEvent.php b/libraries/src/Event/Model/ModelEvent.php new file mode 100644 index 0000000000000..8d189a37bcb53 --- /dev/null +++ b/libraries/src/Event/Model/ModelEvent.php @@ -0,0 +1,91 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +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 Model events + * + * @since __DEPLOY_VERSION__ + */ +abstract class ModelEvent 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); + } + + parent::__construct($name, $arguments); + + if (!\array_key_exists('context', $this->arguments)) { + throw new \BadMethodCallException("Argument 'context' of event {$name} is required but has not been provided"); + } + + if (!\array_key_exists('subject', $this->arguments)) { + throw new \BadMethodCallException("Argument 'subject' of event {$name} is required but has not been provided"); + } + } + + /** + * Setter for the context argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setContext(string $value): string + { + return $value; + } + + /** + * Getter for the context argument. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getContext(): string + { + return $this->arguments['context']; + } +} diff --git a/libraries/src/Event/Model/NormaliseRequestDataEvent.php b/libraries/src/Event/Model/NormaliseRequestDataEvent.php new file mode 100644 index 0000000000000..9ebf81a78fffa --- /dev/null +++ b/libraries/src/Event/Model/NormaliseRequestDataEvent.php @@ -0,0 +1,34 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model Form event. + * Example: + * new NormaliseRequestDataEvent('onEventName', ['context' => 'com_example.example', 'data' => $data, 'subject' => $form]); + * + * @since __DEPLOY_VERSION__ + */ +class NormaliseRequestDataEvent extends BeforeValidateDataEvent +{ + /** + * 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 = ['context', 'data', 'subject']; +} diff --git a/libraries/src/Event/Model/PrepareDataEvent.php b/libraries/src/Event/Model/PrepareDataEvent.php new file mode 100644 index 0000000000000..cbd8fd63c0a88 --- /dev/null +++ b/libraries/src/Event/Model/PrepareDataEvent.php @@ -0,0 +1,46 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event. + * Example: + * new PrepareDataEvent('onEventName', ['context' => 'com_example.example', 'subject' => $data]); + * + * @since __DEPLOY_VERSION__ + */ +class PrepareDataEvent extends ModelEvent +{ + /** + * 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 = ['context', 'subject']; + + /** + * Getter for the data. + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + public function getData() + { + return $this->arguments['subject']; + } +} diff --git a/libraries/src/Event/Model/PrepareFormEvent.php b/libraries/src/Event/Model/PrepareFormEvent.php new file mode 100644 index 0000000000000..45ec1528fb4e4 --- /dev/null +++ b/libraries/src/Event/Model/PrepareFormEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model Form event. + * Example: + * new PrepareFormEvent('onEventName', ['subject' => $form, 'data' => $data]); + * + * @since __DEPLOY_VERSION__ + */ +class PrepareFormEvent extends FormEvent +{ +} diff --git a/libraries/src/Event/Model/SaveEvent.php b/libraries/src/Event/Model/SaveEvent.php new file mode 100644 index 0000000000000..44750e675667d --- /dev/null +++ b/libraries/src/Event/Model/SaveEvent.php @@ -0,0 +1,96 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for Model event + * + * @since __DEPLOY_VERSION__ + */ +abstract class SaveEvent extends ModelEvent +{ + /** + * 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 = ['context', 'subject', 'isNew', 'data']; + + /** + * 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 isNew argument. + * + * @param bool $value The value to set + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + protected function setIsNew($value): bool + { + return (bool) $value; + } + + /** + * Getter for the item. + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + public function getItem(): object + { + return $this->arguments['subject']; + } + + /** + * Getter for the isNew state. + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function getIsNew(): bool + { + return $this->arguments['isNew']; + } + + /** + * Getter for the data. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getData() + { + return $this->arguments['data']; + } +} diff --git a/libraries/src/Event/ReshapeArgumentsAware.php b/libraries/src/Event/ReshapeArgumentsAware.php index aafb15ffc8cda..de3b42c1b0cc9 100644 --- a/libraries/src/Event/ReshapeArgumentsAware.php +++ b/libraries/src/Event/ReshapeArgumentsAware.php @@ -46,7 +46,7 @@ * All this is achieved by the reshapeArguments() method in this trait which has to be called in the * constructor of the concrete event class. * - * This trait is marked as deprecated with a removal target of 5.0 because in Joomla 5 we will only + * This trait is marked as deprecated with a removal target of 6.0 because in Joomla 6 we will only * be using concrete event classes with named arguments, removing legacy listeners and their * positional arguments headaches. * @@ -70,26 +70,21 @@ trait ReshapeArgumentsAware */ protected function reshapeArguments(array $arguments, array $argumentNames, array $defaults = []) { - $mandatoryKeys = array_diff($argumentNames, array_keys($defaults)); - $currentKeys = array_keys($arguments); - $missingKeys = array_diff($mandatoryKeys, $currentKeys); - $extraKeys = array_diff($currentKeys, $argumentNames); + $reconstructed = []; - // Am I missing any mandatory arguments? - if ($missingKeys) { - throw new \DomainException(sprintf('Missing arguments for ‘%s’ event: %s', $this->getName(), implode(', ', $missingKeys))); - } + // Check when the source is non-associative, example [$context, $item, $isNew, $data] + if (key($arguments) === 0) { + foreach ($argumentNames as $i => $name) { + $reconstructed[$name] = $arguments[$i] ?? $defaults[$name] ?? null; + } - // Do I have unknown arguments? - if ($extraKeys) { - throw new \DomainException(sprintf('Unknown arguments for ‘%s’ event: %s', $this->getName(), implode(', ', $missingKeys))); + // Return the reconstructed arguments array + return $reconstructed; } - // Reconstruct the arguments in the order specified in $argumentTypes - $reconstructed = []; - + // Reconstruct the arguments in the order specified in $argumentNames foreach ($argumentNames as $key) { - $reconstructed[$key] = $arguments[$key] ?? $defaults[$key]; + $reconstructed[$key] = $arguments[$key] ?? $defaults[$key] ?? null; } // Return the reconstructed arguments array diff --git a/libraries/src/HTML/Helpers/Content.php b/libraries/src/HTML/Helpers/Content.php index 2ffa7c2e01057..373255597d45b 100644 --- a/libraries/src/HTML/Helpers/Content.php +++ b/libraries/src/HTML/Helpers/Content.php @@ -10,9 +10,9 @@ namespace Joomla\CMS\HTML\Helpers; use Joomla\CMS\Date\Date; +use Joomla\CMS\Event\Content\ContentPrepareEvent; use Joomla\CMS\Factory; use Joomla\CMS\HTML\HTMLHelper; -use Joomla\CMS\Object\CMSObject; use Joomla\CMS\Plugin\PluginHelper; use Joomla\Registry\Registry; @@ -40,14 +40,21 @@ abstract class Content */ public static function prepare($text, $params = null, $context = 'text') { - if ($params === null) { - $params = new CMSObject(); + if (!$params instanceof Registry) { + $params = new Registry($params); } $article = new \stdClass(); $article->text = $text; - PluginHelper::importPlugin('content'); - Factory::getApplication()->triggerEvent('onContentPrepare', [$context, &$article, &$params, 0]); + + $dispatcher = Factory::getApplication()->getDispatcher(); + PluginHelper::importPlugin('content', null, true, $dispatcher); + $dispatcher->dispatch('onContentPrepare', new ContentPrepareEvent('onContentPrepare', [ + 'context' => $context, + 'subject' => $article, + 'params' => $params, + 'page' => 0, + ])); return $article->text; } diff --git a/libraries/src/Installer/Installer.php b/libraries/src/Installer/Installer.php index 3fd879cbfb9a8..84316ceb2de28 100644 --- a/libraries/src/Installer/Installer.php +++ b/libraries/src/Installer/Installer.php @@ -11,6 +11,12 @@ use Joomla\CMS\Adapter\Adapter; use Joomla\CMS\Application\ApplicationHelper; +use Joomla\CMS\Event\Extension\AfterInstallEvent; +use Joomla\CMS\Event\Extension\AfterUninstallEvent; +use Joomla\CMS\Event\Extension\AfterUpdateEvent; +use Joomla\CMS\Event\Extension\BeforeInstallEvent; +use Joomla\CMS\Event\Extension\BeforeUninstallEvent; +use Joomla\CMS\Event\Extension\BeforeUpdateEvent; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Language\Text; @@ -627,17 +633,16 @@ public function install($path = null) $adapter->loadLanguage($path); } + $dispatcher = Factory::getApplication()->getDispatcher(); + // Fire the onExtensionBeforeInstall event. - PluginHelper::importPlugin('extension'); - Factory::getApplication()->triggerEvent( - 'onExtensionBeforeInstall', - [ - 'method' => 'install', - 'type' => $this->manifest->attributes()->type, - 'manifest' => $this->manifest, - 'extension' => 0, - ] - ); + PluginHelper::importPlugin('extension', null, true, $dispatcher); + $dispatcher->dispatch('onExtensionBeforeInstall', new BeforeInstallEvent('onExtensionBeforeInstall', [ + 'method' => 'install', + 'type' => $this->manifest->attributes()->type, + 'manifest' => $this->manifest, + 'extension' => 0, + ])); // Run the install $result = $adapter->install(); @@ -646,10 +651,10 @@ public function install($path = null) clearstatcache(); // Fire the onExtensionAfterInstall - Factory::getApplication()->triggerEvent( - 'onExtensionAfterInstall', - ['installer' => clone $this, 'eid' => $result] - ); + $dispatcher->dispatch('onExtensionAfterInstall', new AfterInstallEvent('onExtensionAfterInstall', [ + 'installer' => clone $this, + 'eid' => $result, + ])); if ($result !== false) { // Refresh versionable assets cache @@ -722,26 +727,25 @@ public function discover_install($eid = null) $adapter->loadLanguage(); } + $dispatcher = Factory::getApplication()->getDispatcher(); + // Fire the onExtensionBeforeInstall event. - PluginHelper::importPlugin('extension'); - Factory::getApplication()->triggerEvent( - 'onExtensionBeforeInstall', - [ - 'method' => 'discover_install', - 'type' => $this->extension->get('type'), - 'manifest' => null, - 'extension' => $this->extension->get('extension_id'), - ] - ); + PluginHelper::importPlugin('extension', null, true, $dispatcher); + $dispatcher->dispatch('onExtensionBeforeInstall', new BeforeInstallEvent('onExtensionBeforeInstall', [ + 'method' => 'discover_install', + 'type' => $this->extension->get('type'), + 'manifest' => null, + 'extension' => (int) $this->extension->get('extension_id'), + ])); // Run the install $result = $adapter->discover_install(); // Fire the onExtensionAfterInstall - Factory::getApplication()->triggerEvent( - 'onExtensionAfterInstall', - ['installer' => clone $this, 'eid' => $result] - ); + $dispatcher->dispatch('onExtensionAfterInstall', new AfterInstallEvent('onExtensionAfterInstall', [ + 'installer' => clone $this, + 'eid' => $result, + ])); if ($result !== false) { // Refresh versionable assets cache @@ -818,21 +822,23 @@ public function update($path = null) $adapter->loadLanguage($path); } + $dispatcher = Factory::getApplication()->getDispatcher(); + // Fire the onExtensionBeforeUpdate event. - PluginHelper::importPlugin('extension'); - Factory::getApplication()->triggerEvent( - 'onExtensionBeforeUpdate', - ['type' => $this->manifest->attributes()->type, 'manifest' => $this->manifest] - ); + PluginHelper::importPlugin('extension', null, true, $dispatcher); + $dispatcher->dispatch('onExtensionBeforeUpdate', new BeforeUpdateEvent('onExtensionBeforeUpdate', [ + 'type' => $this->manifest->attributes()->type, + 'manifest' => $this->manifest, + ])); // Run the update $result = $adapter->update(); // Fire the onExtensionAfterUpdate - Factory::getApplication()->triggerEvent( - 'onExtensionAfterUpdate', - ['installer' => clone $this, 'eid' => $result] - ); + $dispatcher->dispatch('onExtensionAfterUpdate', new AfterUpdateEvent('onExtensionAfterUpdate', [ + 'installer' => clone $this, + 'eid' => $result, + ])); if ($result !== false) { return true; @@ -861,22 +867,24 @@ public function uninstall($type, $identifier) return false; } + $dispatcher = Factory::getApplication()->getDispatcher(); + // We don't load languages here, we get the extension adapter to work it out // Fire the onExtensionBeforeUninstall event. - PluginHelper::importPlugin('extension'); - Factory::getApplication()->triggerEvent( - 'onExtensionBeforeUninstall', - ['eid' => $identifier] - ); + PluginHelper::importPlugin('extension', null, true, $dispatcher); + $dispatcher->dispatch('onExtensionBeforeUninstall', new BeforeUninstallEvent('onExtensionBeforeUninstall', [ + 'eid' => (int) $identifier, + ])); // Run the uninstall $result = $adapter->uninstall($identifier); // Fire the onExtensionAfterInstall - Factory::getApplication()->triggerEvent( - 'onExtensionAfterUninstall', - ['installer' => clone $this, 'eid' => $identifier, 'removed' => $result] - ); + $dispatcher->dispatch('onExtensionAfterUninstall', new AfterUninstallEvent('onExtensionAfterUninstall', [ + 'installer' => clone $this, + 'eid' => (int) $identifier, + 'removed' => $result, + ])); // Refresh versionable assets cache Factory::getApplication()->flushAssets(); diff --git a/libraries/src/MVC/Controller/FormController.php b/libraries/src/MVC/Controller/FormController.php index a1a723809497c..3d06947d0b33b 100644 --- a/libraries/src/MVC/Controller/FormController.php +++ b/libraries/src/MVC/Controller/FormController.php @@ -12,6 +12,7 @@ use Doctrine\Inflector\InflectorFactory; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\Model; use Joomla\CMS\Form\FormFactoryAwareInterface; use Joomla\CMS\Form\FormFactoryAwareTrait; use Joomla\CMS\Form\FormFactoryInterface; @@ -612,10 +613,15 @@ public function save($key = null, $urlVar = null) } // Send an object which can be modified through the plugin event - $objData = (object) $data; - $app->triggerEvent( + $objData = (object) $data; + $dispatcher = $this->getDispatcher() ?: $app->getDispatcher(); + $dispatcher->dispatch( 'onContentNormaliseRequestData', - [$this->option . '.' . $this->context, $objData, $form] + new Model\NormaliseRequestDataEvent('onContentNormaliseRequestData', [ + 'context' => $this->option . '.' . $this->context, + 'data' => $objData, + 'subject' => $form, + ]) ); $data = (array) $objData; diff --git a/libraries/src/MVC/Model/AdminModel.php b/libraries/src/MVC/Model/AdminModel.php index ae0ce5d35585f..0f5fbfb657b64 100644 --- a/libraries/src/MVC/Model/AdminModel.php +++ b/libraries/src/MVC/Model/AdminModel.php @@ -10,7 +10,7 @@ namespace Joomla\CMS\MVC\Model; use Joomla\CMS\Component\ComponentHelper; -use Joomla\CMS\Event\Model\BeforeBatchEvent; +use Joomla\CMS\Event\Model; use Joomla\CMS\Factory; use Joomla\CMS\Form\FormFactoryInterface; use Joomla\CMS\Language\Associations; @@ -365,7 +365,7 @@ protected function batchAccess($value, $pks, $contexts) $this->table->load($pk); $this->table->access = (int) $value; - $event = new BeforeBatchEvent( + $event = new Model\BeforeBatchEvent( $this->event_before_batch, ['src' => $this->table, 'type' => 'access'] ); @@ -468,7 +468,7 @@ protected function batchCopy($value, $pks, $contexts) // New category ID $this->table->catid = $categoryId; - $event = new BeforeBatchEvent( + $event = new Model\BeforeBatchEvent( $this->event_before_batch, ['src' => $this->table, 'type' => 'copy'] ); @@ -569,7 +569,7 @@ protected function batchLanguage($value, $pks, $contexts) $this->table->load($pk); $this->table->language = $value; - $event = new BeforeBatchEvent( + $event = new Model\BeforeBatchEvent( $this->event_before_batch, ['src' => $this->table, 'type' => 'language'] ); @@ -647,7 +647,7 @@ protected function batchMove($value, $pks, $contexts) // Set the new category ID $this->table->catid = $categoryId; - $event = new BeforeBatchEvent( + $event = new Model\BeforeBatchEvent( $this->event_before_batch, ['src' => $this->table, 'type' => 'move'] ); @@ -821,11 +821,12 @@ public function checkout($pk = null) */ public function delete(&$pks) { - $pks = ArrayHelper::toInteger((array) $pks); - $table = $this->getTable(); + $pks = ArrayHelper::toInteger((array) $pks); + $table = $this->getTable(); + $dispatcher = $this->getDispatcher() ?: Factory::getApplication()->getDispatcher(); // Include the plugins for the delete events. - PluginHelper::importPlugin($this->events_map['delete']); + PluginHelper::importPlugin($this->events_map['delete'], null, true, $dispatcher); // Iterate the items to delete each one. foreach ($pks as $i => $pk) { @@ -834,7 +835,12 @@ public function delete(&$pks) $context = $this->option . '.' . $this->name; // Trigger the before delete event. - $result = Factory::getApplication()->triggerEvent($this->event_before_delete, [$context, $table]); + $beforeDeleteEvent = new Model\BeforeDeleteEvent($this->event_before_delete, [ + 'context' => $context, + 'subject' => $table, + ]); + $dispatcher->dispatch($this->event_before_delete, $beforeDeleteEvent); + $result = $beforeDeleteEvent['result'] ?? []; if (\in_array(false, $result, true)) { $this->setError($table->getError()); @@ -896,7 +902,10 @@ public function delete(&$pks) } // Trigger the after event. - Factory::getApplication()->triggerEvent($this->event_after_delete, [$context, $table]); + $dispatcher->dispatch($this->event_after_delete, new Model\AfterDeleteEvent($this->event_after_delete, [ + 'context' => $context, + 'subject' => $table, + ])); } else { // Prune items that you can't change. unset($pks[$i]); @@ -1063,10 +1072,11 @@ public function publish(&$pks, $value = 1) $table = $this->getTable(); $pks = (array) $pks; - $context = $this->option . '.' . $this->name; + $context = $this->option . '.' . $this->name; + $dispatcher = $this->getDispatcher() ?: Factory::getApplication()->getDispatcher(); // Include the plugins for the change of state event. - PluginHelper::importPlugin($this->events_map['change_state']); + PluginHelper::importPlugin($this->events_map['change_state'], null, true, $dispatcher); // Access checks. foreach ($pks as $i => $pk) { @@ -1110,7 +1120,13 @@ public function publish(&$pks, $value = 1) } // Trigger the before change state event. - $result = Factory::getApplication()->triggerEvent($this->event_before_change_state, [$context, $pks, $value]); + $beforeChngEvent = new Model\BeforeChangeStateEvent($this->event_before_change_state, [ + 'context' => $context, + 'subject' => $pks, + 'value' => $value, + ]); + $dispatcher->dispatch($this->event_before_change_state, $beforeChngEvent); + $result = $beforeChngEvent['result'] ?? []; if (\in_array(false, $result, true)) { $this->setError($table->getError()); @@ -1126,7 +1142,13 @@ public function publish(&$pks, $value = 1) } // Trigger the change state event. - $result = Factory::getApplication()->triggerEvent($this->event_change_state, [$context, $pks, $value]); + $afterChngEvent = new Model\AfterChangeStateEvent($this->event_change_state, [ + 'context' => $context, + 'subject' => $pks, + 'value' => $value, + ]); + $dispatcher->dispatch($this->event_change_state, $afterChngEvent); + $result = $afterChngEvent['result'] ?? []; if (\in_array(false, $result, true)) { $this->setError($table->getError()); @@ -1217,6 +1239,7 @@ public function save($data) $table = $this->getTable(); $context = $this->option . '.' . $this->name; $app = Factory::getApplication(); + $dispatcher = $this->getDispatcher() ?: $app->getDispatcher(); if (\array_key_exists('tags', $data) && \is_array($data['tags'])) { $table->newTags = $data['tags']; @@ -1227,7 +1250,7 @@ public function save($data) $isNew = true; // Include the plugins for the save events. - PluginHelper::importPlugin($this->events_map['save']); + PluginHelper::importPlugin($this->events_map['save'], null, true, $dispatcher); // Allow an exception to be thrown. try { @@ -1255,7 +1278,14 @@ public function save($data) } // Trigger the before save event. - $result = $app->triggerEvent($this->event_before_save, [$context, $table, $isNew, $data]); + $beforeSaveEvent = new Model\BeforeSaveEvent($this->event_before_save, [ + 'context' => $context, + 'subject' => $table, + 'isNew' => $isNew, + 'data' => $data, + ]); + $dispatcher->dispatch($this->event_before_save, $beforeSaveEvent); + $result = $beforeSaveEvent['result'] ?? []; if (\in_array(false, $result, true)) { $this->setError($table->getError()); @@ -1274,7 +1304,12 @@ public function save($data) $this->cleanCache(); // Trigger the after save event. - $app->triggerEvent($this->event_after_save, [$context, $table, $isNew, $data]); + $dispatcher->dispatch($this->event_after_save, new Model\AfterSaveEvent($this->event_after_save, [ + 'context' => $context, + 'subject' => $table, + 'isNew' => $isNew, + 'data' => $data, + ])); } catch (\Exception $e) { $this->setError($e->getMessage()); diff --git a/libraries/src/MVC/Model/FormBehaviorTrait.php b/libraries/src/MVC/Model/FormBehaviorTrait.php index 8f68bd7a1cc62..1d9eced899d9d 100644 --- a/libraries/src/MVC/Model/FormBehaviorTrait.php +++ b/libraries/src/MVC/Model/FormBehaviorTrait.php @@ -9,12 +9,15 @@ namespace Joomla\CMS\MVC\Model; +use Joomla\CMS\Event\Model; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Form\FormFactoryInterface; use Joomla\CMS\Form\FormField; use Joomla\CMS\Plugin\PluginHelper; +use Joomla\CMS\Proxy\ArrayProxy; use Joomla\CMS\User\CurrentUserInterface; +use Joomla\Event\DispatcherAwareInterface; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects @@ -145,11 +148,26 @@ protected function loadFormData() */ protected function preprocessData($context, &$data, $group = 'content') { + if ($this instanceof DispatcherAwareInterface) { + $dispatcher = $this->getDispatcher(); + } else { + $dispatcher = Factory::getApplication()->getDispatcher(); + } + // Get the dispatcher and load the users plugins. - PluginHelper::importPlugin($group); + PluginHelper::importPlugin($group, null, true, $dispatcher); + + // When the data is an array wrap it in to an array-access object + $eventData = $data; + if (is_array($data)) { + $eventData = new ArrayProxy($data); + } // Trigger the data preparation event. - Factory::getApplication()->triggerEvent('onContentPrepareData', [$context, &$data]); + $dispatcher->dispatch( + 'onContentPrepareData', + new Model\PrepareDataEvent('onContentPrepareData', ['context' => $context, 'subject' => $eventData]) + ); } /** @@ -167,11 +185,20 @@ protected function preprocessData($context, &$data, $group = 'content') */ protected function preprocessForm(Form $form, $data, $group = 'content') { + if ($this instanceof DispatcherAwareInterface) { + $dispatcher = $this->getDispatcher(); + } else { + $dispatcher = Factory::getApplication()->getDispatcher(); + } + // Import the appropriate plugin group. - PluginHelper::importPlugin($group); + PluginHelper::importPlugin($group, null, true, $dispatcher); // Trigger the form preparation event. - Factory::getApplication()->triggerEvent('onContentPrepareForm', [$form, $data]); + $dispatcher->dispatch( + 'onContentPrepareForm', + new Model\PrepareFormEvent('onContentPrepareForm', ['subject' => $form, 'data' => $data]) + ); } /** diff --git a/libraries/src/MVC/Model/FormModel.php b/libraries/src/MVC/Model/FormModel.php index 2f484c53e42ec..0fe93e9ce7c2f 100644 --- a/libraries/src/MVC/Model/FormModel.php +++ b/libraries/src/MVC/Model/FormModel.php @@ -9,6 +9,7 @@ namespace Joomla\CMS\MVC\Model; +use Joomla\CMS\Event\Model; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Form\Form; @@ -19,6 +20,7 @@ use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\Plugin\PluginHelper; +use Joomla\CMS\Proxy\ArrayProxy; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -192,10 +194,16 @@ public function checkout($pk = null) */ public function validate($form, $data, $group = null) { + $dispatcher = $this->getDispatcher() ?: Factory::getApplication()->getDispatcher(); + // Include the plugins for the delete events. - PluginHelper::importPlugin($this->events_map['validate']); + PluginHelper::importPlugin($this->events_map['validate'], null, true, $dispatcher); - $dispatcher = Factory::getContainer()->get('dispatcher'); + // When the data is an array wrap it in to an array-access object + $eventData = $data; + if (is_array($data)) { + $eventData = new ArrayProxy($data); + } if (!empty($dispatcher->getListeners('onUserBeforeDataValidation'))) { @trigger_error( @@ -204,10 +212,16 @@ public function validate($form, $data, $group = null) E_USER_DEPRECATED ); - Factory::getApplication()->triggerEvent('onUserBeforeDataValidation', [$form, &$data]); + $dispatcher->dispatch('onUserBeforeDataValidation', new Model\BeforeValidateDataEvent('onUserBeforeDataValidation', [ + 'subject' => $form, + 'data' => $eventData, + ])); } - Factory::getApplication()->triggerEvent('onContentBeforeValidateData', [$form, &$data]); + $dispatcher->dispatch('onContentBeforeValidateData', new Model\BeforeValidateDataEvent('onContentBeforeValidateData', [ + 'subject' => $form, + 'data' => $eventData, + ])); // Filter and validate the form data. $return = $form->process($data, $group); diff --git a/libraries/src/MVC/View/CategoryView.php b/libraries/src/MVC/View/CategoryView.php index dc875d3e4bc41..e1ed667723968 100644 --- a/libraries/src/MVC/View/CategoryView.php +++ b/libraries/src/MVC/View/CategoryView.php @@ -184,23 +184,23 @@ public function commonCategoryDisplay() // For some plugins. !empty($itemElement->description) ? $itemElement->text = $itemElement->description : $itemElement->text = ''; - Factory::getApplication()->triggerEvent('onContentPrepare', [$this->extension . '.category', &$itemElement, &$itemElement->params, 0]); + Factory::getApplication()->triggerEvent('onContentPrepare', [$this->extension . '.category', $itemElement, $itemElement->params, 0]); $results = Factory::getApplication()->triggerEvent( 'onContentAfterTitle', - [$this->extension . '.category', &$itemElement, &$itemElement->core_params, 0] + [$this->extension . '.category', $itemElement, $itemElement->core_params ?? $itemElement->params, 0] ); $itemElement->event->afterDisplayTitle = trim(implode("\n", $results)); $results = Factory::getApplication()->triggerEvent( 'onContentBeforeDisplay', - [$this->extension . '.category', &$itemElement, &$itemElement->core_params, 0] + [$this->extension . '.category', $itemElement, $itemElement->core_params ?? $itemElement->params, 0] ); $itemElement->event->beforeDisplayContent = trim(implode("\n", $results)); $results = Factory::getApplication()->triggerEvent( 'onContentAfterDisplay', - [$this->extension . '.category', &$itemElement, &$itemElement->core_params, 0] + [$this->extension . '.category', $itemElement, $itemElement->core_params ?? $itemElement->params, 0] ); $itemElement->event->afterDisplayContent = trim(implode("\n", $results)); diff --git a/libraries/src/Proxy/ArrayProxy.php b/libraries/src/Proxy/ArrayProxy.php new file mode 100644 index 0000000000000..b1a18a0998e7a --- /dev/null +++ b/libraries/src/Proxy/ArrayProxy.php @@ -0,0 +1,174 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Proxy; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Array proxy class + * + * @since __DEPLOY_VERSION__ + */ +class ArrayProxy implements ProxyInterface, \Countable, \ArrayAccess, \Iterator +{ + /** + * Data source + * + * @var array + * + * @since __DEPLOY_VERSION__ + */ + protected $data = []; + + /** + * Class constructor + * + * @param array $data The array for Proxy access + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(array &$data) + { + $this->data = &$data; + } + + /** + * Implementation of ArrayAccess interface + * + * @param mixed $offset The key to check + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function offsetExists(mixed $offset): bool + { + return \array_key_exists($offset, $this->data); + } + + /** + * Implementation of ArrayAccess interface + * + * @param mixed $offset The key to get + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function offsetGet(mixed $offset): mixed + { + return $this->data[$offset] ?? null; + } + + /** + * Implementation of ArrayAccess interface + * + * @param mixed $offset The key to set + * @param mixed $value The value to set + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function offsetSet(mixed $offset, mixed $value): void + { + $this->data[$offset] = $value; + } + + /** + * Implementation of ArrayAccess interface + * + * @param mixed $offset The key to remove + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function offsetUnset(mixed $offset): void + { + unset($this->data[$offset]); + } + + /** + * Implementation of Countable interface + * + * @return int + * + * @since __DEPLOY_VERSION__ + */ + public function count(): int + { + return \count($this->data); + } + + /** + * Implementation of Iterator interface + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function current(): mixed + { + $key = key($this->data); + + return $this->offsetGet($key); + } + + /** + * Implementation of Iterator interface + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function key(): mixed + { + return key($this->data); + } + + /** + * Implementation of Iterator interface + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function next(): void + { + next($this->data); + } + + /** + * Implementation of Iterator interface + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function rewind(): void + { + reset($this->data); + } + + /** + * Implementation of Iterator interface + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function valid(): bool + { + return key($this->data) !== null; + } +} diff --git a/libraries/src/Proxy/ArrayReadOnlyProxy.php b/libraries/src/Proxy/ArrayReadOnlyProxy.php new file mode 100644 index 0000000000000..1cd1704ff9ebb --- /dev/null +++ b/libraries/src/Proxy/ArrayReadOnlyProxy.php @@ -0,0 +1,65 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Proxy; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Array read-only proxy class. + * The class provides read-only feature for Array, including its children. + * + * @since __DEPLOY_VERSION__ + */ +class ArrayReadOnlyProxy extends ArrayProxy implements ReadOnlyProxyInterface +{ + /** + * Implementation of ArrayAccess interface + * + * @param mixed $offset The key to get + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function offsetGet(mixed $offset): mixed + { + $value = $this->data[$offset] ?? null; + + // Ensure that the child also is a read-only + if (\is_scalar($value) || $value === null) { + return $value; + } elseif (\is_array($value)) { + $value = new static($value); + } elseif (\is_object($value)) { + $value = new ObjectReadOnlyProxy($value); + } + + return $value; + } + + /** + * Implementation of ArrayAccess interface + * + * @param mixed $offset The key to set + * @param mixed $value The value to set + * + * @return void + * + * @throws \RuntimeException + * + * @since __DEPLOY_VERSION__ + */ + public function offsetSet(mixed $offset, mixed $value): void + { + throw new \RuntimeException(sprintf('ArrayReadOnlyProxy: trying to modify read-only element, by key "%s"', $offset)); + } +} diff --git a/libraries/src/Proxy/ObjectProxy.php b/libraries/src/Proxy/ObjectProxy.php new file mode 100644 index 0000000000000..4c491224c10e4 --- /dev/null +++ b/libraries/src/Proxy/ObjectProxy.php @@ -0,0 +1,141 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Proxy; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Object proxy class + * + * @since __DEPLOY_VERSION__ + */ +class ObjectProxy implements ProxyInterface, \Iterator +{ + /** + * Data source + * + * @var object + * + * @since __DEPLOY_VERSION__ + */ + protected $data; + + /** + * An iterator instance + * + * @var \ArrayIterator + * + * @since __DEPLOY_VERSION__ + */ + protected $iterator; + + /** + * Class constructor + * + * @param object $data The object for Proxy access + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(object $data) + { + $this->data = $data; + } + + /** + * Implementing reading from object + * + * @param mixed $key The key name to read + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function __get($key): mixed + { + return $this->data->$key ?? null; + } + + /** + * Implementing writing to object + * + * @param mixed $key The key name to write + * @param mixed $value The value to write + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function __set($key, $value): void + { + $this->data->$key = $value; + } + + /** + * Implementation of Iterator interface + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function current(): mixed + { + return $this->iterator->current(); + } + + /** + * Implementation of Iterator interface + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function key(): mixed + { + return $this->iterator->key(); + } + + /** + * Implementation of Iterator interface + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function next(): void + { + $this->iterator->next(); + } + + /** + * Implementation of Iterator interface + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function rewind(): void + { + $this->iterator = new \ArrayIterator($this->data); + } + + /** + * Implementation of Iterator interface + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function valid(): bool + { + return $this->iterator->valid(); + } +} diff --git a/libraries/src/Proxy/ObjectReadOnlyProxy.php b/libraries/src/Proxy/ObjectReadOnlyProxy.php new file mode 100644 index 0000000000000..78c2caeb861ca --- /dev/null +++ b/libraries/src/Proxy/ObjectReadOnlyProxy.php @@ -0,0 +1,88 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Proxy; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Object read-only proxy class. + * The class provides read-only feature for Object, including its children. + * + * @since __DEPLOY_VERSION__ + */ +class ObjectReadOnlyProxy extends ObjectProxy implements ReadOnlyProxyInterface +{ + /** + * Implementing reading from object + * + * @param mixed $key The key name to read + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function __get($key): mixed + { + $value = $this->data->$key ?? null; + + // Ensure that the child also is a read-only + if (\is_scalar($value) || $value === null) { + return $value; + } elseif (\is_object($value)) { + $value = new static($value); + } elseif (\is_array($value)) { + $value = new ArrayReadOnlyProxy($value); + } + + return $value; + } + + /** + * Implementing writing to object + * + * @param mixed $key The key name to write + * @param mixed $value The value to write + * + * @return void + * + * @throws \RuntimeException + * + * @since __DEPLOY_VERSION__ + */ + public function __set($key, $value): void + { + throw new \RuntimeException(sprintf('ObjectReadOnlyProxy: trying to modify read-only element, by key "%s"', $key)); + } + + /** + * Implementation of Iterator interface + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function current(): mixed + { + $value = $this->iterator->current(); + + // Ensure that the child also is a read-only + if (\is_scalar($value) || $value === null) { + return $value; + } elseif (\is_object($value)) { + $value = new static($value); + } elseif (\is_array($value)) { + $value = new ArrayReadOnlyProxy($value); + } + + return $value; + } +} diff --git a/libraries/src/Proxy/ProxyInterface.php b/libraries/src/Proxy/ProxyInterface.php new file mode 100644 index 0000000000000..773da12499a69 --- /dev/null +++ b/libraries/src/Proxy/ProxyInterface.php @@ -0,0 +1,23 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Proxy; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Interface for Proxy classes + * + * @since __DEPLOY_VERSION__ + */ +interface ProxyInterface +{ +} diff --git a/libraries/src/Proxy/ReadOnlyProxyInterface.php b/libraries/src/Proxy/ReadOnlyProxyInterface.php new file mode 100644 index 0000000000000..854be451f7b1e --- /dev/null +++ b/libraries/src/Proxy/ReadOnlyProxyInterface.php @@ -0,0 +1,23 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Proxy; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Interface for read-only Proxy classes + * + * @since __DEPLOY_VERSION__ + */ +interface ReadOnlyProxyInterface extends ProxyInterface +{ +} diff --git a/plugins/content/emailcloak/src/Extension/EmailCloak.php b/plugins/content/emailcloak/src/Extension/EmailCloak.php index a03406ce04d1f..0e9fdec2f3086 100644 --- a/plugins/content/emailcloak/src/Extension/EmailCloak.php +++ b/plugins/content/emailcloak/src/Extension/EmailCloak.php @@ -10,8 +10,10 @@ namespace Joomla\Plugin\Content\EmailCloak\Extension; +use Joomla\CMS\Event\Content\ContentPrepareEvent; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Plugin\CMSPlugin; +use Joomla\Event\SubscriberInterface; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects @@ -23,32 +25,48 @@ * * @since 1.5 */ -final class EmailCloak extends CMSPlugin +final class EmailCloak extends CMSPlugin implements SubscriberInterface { + /** + * Returns an array of events this subscriber will listen to. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function getSubscribedEvents(): array + { + return ['onContentPrepare' => 'onContentPrepare']; + } + /** * Plugin that cloaks all emails in content from spambots via Javascript. * - * @param string $context The context of the content being passed to the plugin. - * @param mixed &$row An object with a "text" property or the string to be cloaked. - * @param mixed &$params Additional parameters. - * @param integer $page Optional page number. Unused. Defaults to zero. + * @param ContentPrepareEvent $event Event instance * * @return void */ - public function onContentPrepare($context, &$row, &$params, $page = 0) + public function onContentPrepare(ContentPrepareEvent $event) { // Don't run if in the API Application // Don't run this plugin when the content is being indexed - if ($this->getApplication()->isClient('api') || $context === 'com_finder.indexer') { + if ($this->getApplication()->isClient('api') || $event->getContext() === 'com_finder.indexer') { return; } - // If the row is not an object or does not have a text property there is nothing to do - if (!is_object($row) || !property_exists($row, 'text')) { + // Get content item + $item = $event->getItem(); + + // If the item does not have a text property there is nothing to do + if (!property_exists($item, 'text')) { return; } - $this->cloak($row->text, $params); + $text = $this->cloak($item->text); + + if ($text) { + $item->text = $text; + } } /** @@ -69,27 +87,23 @@ private function getPattern($link, $text) /** * Cloak all emails in text from spambots via Javascript. * - * @param string &$text The string to be cloaked. - * @param mixed &$params Additional parameters. Parameter "mode" (integer, default 1) - * replaces addresses with "mailto:" links if nonzero. + * @param string $text The string to be cloaked. * - * @return void + * @return string */ - private function cloak(&$text, &$params) + private function cloak($text) { /* * Check for presence of {emailcloak=off} which is explicits disables this * bot for the item. */ if (StringHelper::strpos($text, '{emailcloak=off}') !== false) { - $text = StringHelper::str_ireplace('{emailcloak=off}', '', $text); - - return; + return StringHelper::str_ireplace('{emailcloak=off}', '', $text); } // Simple performance check to determine whether bot should process further. if (StringHelper::strpos($text, '@') === false) { - return; + return ''; } $mode = (int) $this->params->def('mode', 1); @@ -474,5 +488,7 @@ private function cloak(&$text, &$params) // Replace the found address with the js cloaked email $text = substr_replace($text, $replacement, $regs[1][1], strlen($mail)); } + + return $text; } } diff --git a/plugins/system/fields/src/Extension/Fields.php b/plugins/system/fields/src/Extension/Fields.php index d8523cc23ff18..957674b431f73 100644 --- a/plugins/system/fields/src/Extension/Fields.php +++ b/plugins/system/fields/src/Extension/Fields.php @@ -10,7 +10,8 @@ namespace Joomla\Plugin\System\Fields\Extension; -use Joomla\CMS\Form\Form; +use Joomla\CMS\Event\Content; +use Joomla\CMS\Event\Model; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\User\UserFactoryAwareTrait; @@ -41,16 +42,18 @@ final class Fields extends CMSPlugin /** * Normalizes the request data. * - * @param string $context The context - * @param object $data The object - * @param Form $form The form + * @param Model\NormaliseRequestDataEvent $event The event object * * @return void * * @since 3.8.7 */ - public function onContentNormaliseRequestData($context, $data, Form $form) + public function onContentNormaliseRequestData(Model\NormaliseRequestDataEvent $event) { + $context = $event->getContext(); + $data = $event->getData(); + $form = $event->getForm(); + if (!FieldsHelper::extract($context, $data)) { return; } @@ -243,15 +246,16 @@ public function onUserAfterDelete($user, $success, $msg): void /** * The form event. * - * @param Form $form The form - * @param \stdClass $data The data + * @param Model\PrepareFormEvent $event The event object * - * @return boolean + * @return void * * @since 3.7.0 */ - public function onContentPrepareForm(Form $form, $data) + public function onContentPrepareForm(Model\PrepareFormEvent $event) { + $form = $event->getForm(); + $data = $event->getData(); $context = $form->getName(); // When a category is edited, the context is com_categories.categorycom_content @@ -272,7 +276,7 @@ public function onContentPrepareForm(Form $form, $data) $parts = FieldsHelper::extract($context, $form); if (!$parts) { - return true; + return; } $input = $this->getApplication()->getInput(); @@ -289,59 +293,48 @@ public function onContentPrepareForm(Form $form, $data) } FieldsHelper::prepareForm($parts[0] . '.' . $parts[1], $form, $data); - - return true; } /** * The display event. * - * @param string $context The context - * @param \stdClass $item The item - * @param Registry $params The params - * @param integer $limitstart The start + * @param Content\AfterTitleEvent $event The event object * - * @return string + * @return void * * @since 3.7.0 */ - public function onContentAfterTitle($context, $item, $params, $limitstart = 0) + public function onContentAfterTitle(Content\AfterTitleEvent $event) { - return $this->display($context, $item, $params, 1); + $event->addResult($this->display($event->getContext(), $event->getItem(), $event->getParams(), 1)); } /** * The display event. * - * @param string $context The context - * @param \stdClass $item The item - * @param Registry $params The params - * @param integer $limitstart The start + * @param Content\BeforeDisplayEvent $event The event object * - * @return string + * @return void * * @since 3.7.0 */ - public function onContentBeforeDisplay($context, $item, $params, $limitstart = 0) + public function onContentBeforeDisplay(Content\BeforeDisplayEvent $event) { - return $this->display($context, $item, $params, 2); + $event->addResult($this->display($event->getContext(), $event->getItem(), $event->getParams(), 2)); } /** * The display event. * - * @param string $context The context - * @param \stdClass $item The item - * @param Registry $params The params - * @param integer $limitstart The start + * @param Content\AfterDisplayEvent $event The event object * - * @return string + * @return void * * @since 3.7.0 */ - public function onContentAfterDisplay($context, $item, $params, $limitstart = 0) + public function onContentAfterDisplay(Content\AfterDisplayEvent $event) { - return $this->display($context, $item, $params, 3); + $event->addResult($this->display($event->getContext(), $event->getItem(), $event->getParams(), 3)); } /** @@ -429,15 +422,17 @@ private function display($context, $item, $params, $displayType) /** * Performs the display event. * - * @param string $context The context - * @param \stdClass $item The item + * @param Content\ContentPrepareEvent $event The event object * * @return void * * @since 3.7.0 */ - public function onContentPrepare($context, $item) + public function onContentPrepare(Content\ContentPrepareEvent $event) { + $context = $event->getContext(); + $item = $event->getItem(); + // Check property exists (avoid costly & useless recreation), if need to recreate them, just unset the property! if (isset($item->jcfields)) { return; diff --git a/plugins/system/schedulerunner/src/Extension/ScheduleRunner.php b/plugins/system/schedulerunner/src/Extension/ScheduleRunner.php index 477dd6a97bd48..5ad6b42a896e2 100644 --- a/plugins/system/schedulerunner/src/Extension/ScheduleRunner.php +++ b/plugins/system/schedulerunner/src/Extension/ScheduleRunner.php @@ -11,8 +11,8 @@ namespace Joomla\Plugin\System\ScheduleRunner\Extension; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\Model; use Joomla\CMS\Factory; -use Joomla\CMS\Form\Form; use Joomla\CMS\Log\Log; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Router\Route; @@ -296,20 +296,19 @@ private function runScheduler(int $id = 0): ?Task /** * Enhance the scheduler config form by dynamically populating or removing display fields. * - * @param EventInterface $event The onContentPrepareForm event. + * @param Model\PrepareFormEvent $event The onContentPrepareForm event. * * @return void * * @since 4.1.0 - * @throws UnexpectedValueException|RuntimeException + * @throws \UnexpectedValueException|\RuntimeException * * @todo Move to another plugin? */ - public function enhanceSchedulerConfig(EventInterface $event): void + public function enhanceSchedulerConfig(Model\PrepareFormEvent $event): void { - /** @var Form $form */ - $form = $event->getArgument('0'); - $data = $event->getArgument('1'); + $form = $event->getForm(); + $data = $event->getData(); if ( $form->getName() !== 'com_config.component' @@ -344,7 +343,7 @@ public function enhanceSchedulerConfig(EventInterface $event): void public function generateWebcronKey(EventInterface $event): void { /** @var Extension $table */ - [$context, $table] = $event->getArguments(); + [$context, $table] = array_values($event->getArguments()); if ($context !== 'com_config.component' || $table->name !== 'com_scheduler') { return; diff --git a/plugins/system/schemaorg/src/Extension/Schemaorg.php b/plugins/system/schemaorg/src/Extension/Schemaorg.php index 78aae9709d9af..c054b203acacf 100644 --- a/plugins/system/schemaorg/src/Extension/Schemaorg.php +++ b/plugins/system/schemaorg/src/Extension/Schemaorg.php @@ -11,8 +11,8 @@ namespace Joomla\Plugin\System\Schemaorg\Extension; use Joomla\CMS\Event\AbstractEvent; +use Joomla\CMS\Event\Model; use Joomla\CMS\Factory; -use Joomla\CMS\Form\Form; use Joomla\CMS\Helper\ModuleHelper; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; @@ -26,7 +26,6 @@ use Joomla\CMS\User\UserFactoryAwareTrait; use Joomla\Database\DatabaseAwareTrait; use Joomla\Database\ParameterType; -use Joomla\Event\EventInterface; use Joomla\Event\SubscriberInterface; use Joomla\Registry\Registry; @@ -74,20 +73,20 @@ public static function getSubscribedEvents(): array /** * Runs on content preparation * - * @param EventInterface $event The event + * @param Model\PrepareDataEvent $event The event * * @since 5.0.0 * */ - public function onContentPrepareData(EventInterface $event) + public function onContentPrepareData(Model\PrepareDataEvent $event) { - $context = $event->getArgument('0'); - $data = $event->getArgument('1'); + $context = $event->getContext(); + $data = $event->getData(); $app = $this->getApplication(); if ($app->isClient('site') || !$this->isSupported($context)) { - return true; + return; } $data = (object) $data; @@ -109,7 +108,7 @@ public function onContentPrepareData(EventInterface $event) $results = $db->setQuery($query)->loadAssoc(); if (empty($results)) { - return false; + return; } $schemaType = $results['schemaType']; @@ -120,46 +119,37 @@ public function onContentPrepareData(EventInterface $event) $data->schema[$schemaType] = $schema->toArray(); } - $dispatcher = Factory::getApplication()->getDispatcher(); - - $event = AbstractEvent::create( - 'onSchemaPrepareData', - [ - 'subject' => $data, - 'context' => $context, - ] - ); - - PluginHelper::importPlugin('schemaorg'); - $eventResult = $dispatcher->dispatch('onSchemaPrepareData', $event); + $dispatcher = $this->getDispatcher(); + $event = AbstractEvent::create('onSchemaPrepareData', [ + 'context' => $context, + 'subject' => $data, + ]); - return true; + PluginHelper::importPlugin('schemaorg', null, true, $dispatcher); + $dispatcher->dispatch('onSchemaPrepareData', $event); } /** * The form event. * - * @param EventInterface $event The event + * @param Model\PrepareFormEvent $event The event * * @since 5.0.0 */ - public function onContentPrepareForm(EventInterface $event) + public function onContentPrepareForm(Model\PrepareFormEvent $event) { - /** - * @var Form - */ - $form = $event->getArgument('0'); + $form = $event->getForm(); $context = $form->getName(); - - $app = $this->getApplication(); + $app = $this->getApplication(); if (!$app->isClient('administrator') || !$this->isSupported($context)) { - return true; + return; } // Load the form fields $form->loadFile(JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name . '/forms/schemaorg.xml'); + // The user should configure the plugin first if (!$this->params->get('baseType')) { $form->removeField('schemaType', 'schema'); @@ -177,46 +167,39 @@ public function onContentPrepareForm(EventInterface $event) $form->setFieldAttribute('schemainfo', 'description', $infoText, 'schema'); - return true; + return; } - $dispatcher = Factory::getApplication()->getDispatcher(); - - $event = AbstractEvent::create( - 'onSchemaPrepareForm', - [ - 'subject' => $form, - ] - ); - - PluginHelper::importPlugin('schemaorg'); - $eventResult = $dispatcher->dispatch('onSchemaPrepareForm', $event); + $dispatcher = $this->getDispatcher(); + $event = AbstractEvent::create('onSchemaPrepareForm', [ + 'subject' => $form, + ]); - return true; + PluginHelper::importPlugin('schemaorg', null, true, $dispatcher); + $dispatcher->dispatch('onSchemaPrepareForm', $event); } /** * Saves form field data in the database * - * @param EventInterface $event + * @param Model\AfterSaveEvent $event * - * @return boolean + * @return void * * @since 5.0.0 * */ - public function onContentAfterSave(EventInterface $event) + public function onContentAfterSave(Model\AfterSaveEvent $event) { - $context = $event->getArgument('0'); - $table = $event->getArgument('1'); - $isNew = $event->getArgument('2'); - $data = $event->getArgument('3'); - - $app = $this->getApplication(); - $db = $this->getDatabase(); + $context = $event->getContext(); + $table = $event->getItem(); + $isNew = $event->getIsNew(); + $data = $event->getData(); + $app = $this->getApplication(); + $db = $this->getDatabase(); if (!$app->isClient('administrator') || !$this->isSupported($context)) { - return true; + return; } $itemId = (int) $table->id; @@ -232,7 +215,7 @@ public function onContentAfterSave(EventInterface $event) $db->setQuery($query)->execute(); - return true; + return; } $query = $db->getQuery(true); @@ -261,25 +244,20 @@ public function onContentAfterSave(EventInterface $event) } } - PluginHelper::importPlugin('schemaorg'); - - $dispatcher = $app->getDispatcher(); + $dispatcher = $this->getDispatcher(); + $event = AbstractEvent::create('onSchemaPrepareSave', [ + 'context' => $context, + 'subject' => $entry, + 'item' => $table, + 'isNew' => $isNew, + 'schema' => $data['schema'], + ]); - $event = AbstractEvent::create( - 'onSchemaPrepareSave', - [ - 'subject' => $entry, - 'context' => $context, - 'table' => $table, - 'isNew' => $isNew, - 'schema' => $data['schema'], - ] - ); - - $eventResult = $dispatcher->dispatch('onSchemaPrepareSave', $event); + PluginHelper::importPlugin('schemaorg', null, true, $dispatcher); + $dispatcher->dispatch('onSchemaPrepareSave', $event); if (!isset($entry->schemaType)) { - return true; + return; } if (!empty($entry->id)) { @@ -287,8 +265,6 @@ public function onContentAfterSave(EventInterface $event) } else { $db->insertObject('#__schemaorg', $entry, 'id'); } - - return true; } /** @@ -447,16 +423,14 @@ public function onBeforeCompileHead() $schema->loadArray($baseSchema); - $event = AbstractEvent::create( - 'onSchemaBeforeCompileHead', - [ - 'subject' => $schema, - 'context' => $context . '.' . $itemId, - ] - ); + $dispatcher = $this->getDispatcher(); + $event = AbstractEvent::create('onSchemaBeforeCompileHead', [ + 'context' => $context . '.' . $itemId, + 'subject' => $schema, + ]); - PluginHelper::importPlugin('schemaorg'); - $eventResult = $app->getDispatcher()->dispatch('onSchemaBeforeCompileHead', $event); + PluginHelper::importPlugin('schemaorg', null, true, $dispatcher); + $dispatcher->dispatch('onSchemaBeforeCompileHead', $event); $data = $schema->get('@graph'); @@ -471,7 +445,7 @@ public function onBeforeCompileHead() if ($schemaString !== '{}') { $wa = $this->getApplication()->getDocument()->getWebAssetManager(); - $wa->addInlineScript($schemaString, ['position' => 'after'], ['type' => 'application/ld+json']); + $wa->addInlineScript($schemaString, ['name' => 'inline.schemaorg'], ['type' => 'application/ld+json']); } } @@ -548,13 +522,12 @@ private function cleanupSchema($schema) */ protected function isSupported($context) { - $parts = explode('.', $context); - // We need at least the extension + view for loading the table fields - if (count($parts) < 2) { + if (!str_contains($context, '.')) { return false; } + $parts = explode('.', $context, 2); $component = $this->getApplication()->bootComponent($parts[0]); return $component instanceof SchemaorgServiceInterface; diff --git a/plugins/system/tasknotification/src/Extension/TaskNotification.php b/plugins/system/tasknotification/src/Extension/TaskNotification.php index e7d3b7f7d4c6b..149b203c9d9e2 100644 --- a/plugins/system/tasknotification/src/Extension/TaskNotification.php +++ b/plugins/system/tasknotification/src/Extension/TaskNotification.php @@ -10,8 +10,8 @@ namespace Joomla\Plugin\System\TaskNotification\Extension; +use Joomla\CMS\Event\Model; use Joomla\CMS\Factory; -use Joomla\CMS\Form\Form; use Joomla\CMS\Log\Log; use Joomla\CMS\Mail\MailTemplate; use Joomla\CMS\Plugin\CMSPlugin; @@ -20,7 +20,6 @@ use Joomla\Component\Scheduler\Administrator\Task\Task; use Joomla\Database\DatabaseAwareTrait; use Joomla\Event\Event; -use Joomla\Event\EventInterface; use Joomla\Event\SubscriberInterface; use Joomla\Filesystem\Path; use PHPMailer\PHPMailer\Exception as MailerException; @@ -81,16 +80,15 @@ public static function getSubscribedEvents(): array /** * Inject fields to support configuration of post-execution notifications into the task item form. * - * @param EventInterface $event The onContentPrepareForm event. + * @param Model\PrepareFormEvent $event The onContentPrepareForm event. * * @return boolean True if successful. * * @since 4.1.0 */ - public function injectTaskNotificationFieldset(EventInterface $event): bool + public function injectTaskNotificationFieldset(Model\PrepareFormEvent $event): bool { - /** @var Form $form */ - $form = $event->getArgument('0'); + $form = $event->getForm(); if ($form->getName() !== 'com_scheduler.task') { return true; diff --git a/plugins/system/webauthn/src/PluginTraits/AdditionalLoginButtons.php b/plugins/system/webauthn/src/PluginTraits/AdditionalLoginButtons.php index 184aa3e76cc54..4e5fb83a2f5a0 100644 --- a/plugins/system/webauthn/src/PluginTraits/AdditionalLoginButtons.php +++ b/plugins/system/webauthn/src/PluginTraits/AdditionalLoginButtons.php @@ -61,7 +61,7 @@ trait AdditionalLoginButtons public function onUserLoginButtons(Event $event): void { /** @var string $form The HTML ID of the form we are enclosed in */ - [$form] = $event->getArguments(); + [$form] = array_values($event->getArguments()); // If we determined we should not inject a button return early if (!$this->mustDisplayButton()) { diff --git a/plugins/system/webauthn/src/PluginTraits/UserDeletion.php b/plugins/system/webauthn/src/PluginTraits/UserDeletion.php index d583e9a498094..5dd83d6016c69 100644 --- a/plugins/system/webauthn/src/PluginTraits/UserDeletion.php +++ b/plugins/system/webauthn/src/PluginTraits/UserDeletion.php @@ -45,7 +45,7 @@ public function onUserAfterDelete(Event $event): void * @var bool $success True if user was successfully stored in the database * @var string|null $msg Message */ - [$user, $success, $msg] = $event->getArguments(); + [$user, $success, $msg] = array_values($event->getArguments()); if (!$success) { $this->returnFromEvent($event, true); diff --git a/plugins/system/webauthn/src/PluginTraits/UserProfileFields.php b/plugins/system/webauthn/src/PluginTraits/UserProfileFields.php index bca452dbbb819..df816fdee3839 100644 --- a/plugins/system/webauthn/src/PluginTraits/UserProfileFields.php +++ b/plugins/system/webauthn/src/PluginTraits/UserProfileFields.php @@ -99,7 +99,7 @@ public function onContentPrepareForm(Event $event) * @var Form $form The form to be altered. * @var mixed $data The associated data for the form. */ - [$form, $data] = $event->getArguments(); + [$form, $data] = array_values($event->getArguments()); $name = $form->getName(); @@ -160,7 +160,7 @@ public function onContentPrepareData(Event $event): void * @var string|null $context The context for the data * @var array|object|null $data An object or array containing the data for the form. */ - [$context, $data] = $event->getArguments(); + [$context, $data] = array_values($event->getArguments()); if (!\in_array($context, ['com_users.profile', 'com_users.user'])) { return; diff --git a/plugins/workflow/featuring/src/Extension/Featuring.php b/plugins/workflow/featuring/src/Extension/Featuring.php index b51aa5942ab22..6e58f496454bb 100644 --- a/plugins/workflow/featuring/src/Extension/Featuring.php +++ b/plugins/workflow/featuring/src/Extension/Featuring.php @@ -11,6 +11,7 @@ namespace Joomla\Plugin\Workflow\Featuring\Extension; use Joomla\CMS\Event\AbstractEvent; +use Joomla\CMS\Event\Model; use Joomla\CMS\Event\Table\BeforeStoreEvent; use Joomla\CMS\Event\View\DisplayEvent; use Joomla\CMS\Event\Workflow\WorkflowFunctionalityUsedEvent; @@ -83,14 +84,14 @@ public static function getSubscribedEvents(): array /** * The form event. * - * @param EventInterface $event The event + * @param Model\PrepareFormEvent $event The event * * @since 4.0.0 */ - public function onContentPrepareForm(EventInterface $event) + public function onContentPrepareForm(Model\PrepareFormEvent $event) { - $form = $event->getArgument('0'); - $data = $event->getArgument('1'); + $form = $event->getForm(); + $data = $event->getData(); $context = $form->getName(); @@ -109,7 +110,7 @@ public function onContentPrepareForm(EventInterface $event) * Check also for the workflow implementation and if the field exists * * @param Form $form The form - * @param stdClass $data The data + * @param object $data The data * * @return boolean * @@ -363,25 +364,23 @@ public function onContentBeforeChangeFeatured(FeatureEvent $event) /** * The save event. * - * @param EventInterface $event + * @param Model\BeforeSaveEvent $event * * @return boolean * * @since 4.0.0 */ - public function onContentBeforeSave(EventInterface $event) + public function onContentBeforeSave(Model\BeforeSaveEvent $event) { - $context = $event->getArgument('0'); - - /** @var TableInterface $table */ - $table = $event->getArgument('1'); - $isNew = $event->getArgument('2'); - $data = $event->getArgument('3'); + $context = $event->getContext(); if (!$this->isSupported($context)) { return true; } + /** @var TableInterface $table */ + $table = $event->getItem(); + $data = $event->getData(); $keyName = $table->getColumnAlias('featured'); // Check for the old value diff --git a/plugins/workflow/notification/src/Extension/Notification.php b/plugins/workflow/notification/src/Extension/Notification.php index ed172971c345a..1c10d1adea177 100644 --- a/plugins/workflow/notification/src/Extension/Notification.php +++ b/plugins/workflow/notification/src/Extension/Notification.php @@ -11,6 +11,7 @@ namespace Joomla\Plugin\Workflow\Notification\Extension; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\Model; use Joomla\CMS\Event\Workflow\WorkflowTransitionEvent; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\LanguageFactoryInterface; @@ -20,7 +21,6 @@ use Joomla\CMS\Workflow\WorkflowServiceInterface; use Joomla\Database\DatabaseAwareTrait; use Joomla\Event\DispatcherInterface; -use Joomla\Event\EventInterface; use Joomla\Event\SubscriberInterface; use Joomla\Utilities\ArrayHelper; @@ -90,18 +90,16 @@ public function __construct(DispatcherInterface $dispatcher, array $config, Lang /** * The form event. * - * @param Form $form The form - * @param stdClass $data The data + * @param Model\PrepareFormEvent $event The event * * @return boolean * * @since 4.0.0 */ - public function onContentPrepareForm(EventInterface $event) + public function onContentPrepareForm(Model\PrepareFormEvent $event) { - $form = $event->getArgument('0'); - $data = $event->getArgument('1'); - + $form = $event->getForm(); + $data = $event->getData(); $context = $form->getName(); // Extend the transition form diff --git a/plugins/workflow/publishing/src/Extension/Publishing.php b/plugins/workflow/publishing/src/Extension/Publishing.php index dda0d07fb105d..61dbc7ab04e48 100644 --- a/plugins/workflow/publishing/src/Extension/Publishing.php +++ b/plugins/workflow/publishing/src/Extension/Publishing.php @@ -10,6 +10,7 @@ namespace Joomla\Plugin\Workflow\Publishing\Extension; +use Joomla\CMS\Event\Model; use Joomla\CMS\Event\Table\BeforeStoreEvent; use Joomla\CMS\Event\View\DisplayEvent; use Joomla\CMS\Event\Workflow\WorkflowFunctionalityUsedEvent; @@ -81,15 +82,14 @@ public static function getSubscribedEvents(): array /** * The form event. * - * @param EventInterface $event The event + * @param Model\PrepareFormEvent $event The event * * @since 4.0.0 */ - public function onContentPrepareForm(EventInterface $event) + public function onContentPrepareForm(Model\PrepareFormEvent $event) { - $form = $event->getArgument('0'); - $data = $event->getArgument('1'); - + $form = $event->getForm(); + $data = $event->getData(); $context = $form->getName(); // Extend the transition form @@ -344,17 +344,17 @@ public function onWorkflowAfterTransition(WorkflowTransitionEvent $event) /** * Change State of an item. Used to disable state change * - * @param EventInterface $event + * @param Model\BeforeChangeStateEvent $event * * @return boolean * * @throws \Exception * @since 4.0.0 */ - public function onContentBeforeChangeState(EventInterface $event) + public function onContentBeforeChangeState(Model\BeforeChangeStateEvent $event) { - $context = $event->getArgument('0'); - $pks = $event->getArgument('1'); + $context = $event->getContext(); + $pks = $event->getPks(); if (!$this->isSupported($context)) { return true; @@ -372,25 +372,23 @@ public function onContentBeforeChangeState(EventInterface $event) /** * The save event. * - * @param EventInterface $event + * @param Model\BeforeSaveEvent $event * * @return boolean * * @since 4.0.0 */ - public function onContentBeforeSave(EventInterface $event) + public function onContentBeforeSave(Model\BeforeSaveEvent $event) { - $context = $event->getArgument('0'); - - /** @var TableInterface $table */ - $table = $event->getArgument('1'); - $isNew = $event->getArgument('2'); - $data = $event->getArgument('3'); + $context = $event->getContext(); if (!$this->isSupported($context)) { return true; } + /** @var TableInterface $table */ + $table = $event->getItem(); + $data = $event->getData(); $keyName = $table->getColumnAlias('published'); // Check for the old value diff --git a/tests/Unit/Libraries/Cms/Proxy/ProxyArrayTest.php b/tests/Unit/Libraries/Cms/Proxy/ProxyArrayTest.php new file mode 100644 index 0000000000000..e65384e67f1d3 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Proxy/ProxyArrayTest.php @@ -0,0 +1,165 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Proxy; + +use Joomla\CMS\Proxy\ArrayProxy; +use Joomla\CMS\Proxy\ArrayReadOnlyProxy; +use Joomla\CMS\Proxy\ObjectReadOnlyProxy; +use Joomla\Tests\Unit\UnitTestCase; + +/** + * Test class for \Joomla\CMS\Proxy\ArrayProxy classes + * + * @package Joomla.UnitTest + * @subpackage Plugin + * + * @since __DEPLOY_VERSION__ + */ +class ProxyArrayTest extends UnitTestCase +{ + /** + * @testdox Array referencing keep the changes + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testArrayAccessAndModification() + { + $data = [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ArrayProxy($data); + + $proxy['bar2'] = 'foo2'; + + $this->assertEquals($data['bar2'], 'foo2', 'A referenced Array should get a Proxy value'); + $this->assertEquals($proxy['foo'], 'bar', 'Proxy object should return value from Array'); + } + + /** + * @testdox Array Countable implementations + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testArrayCountable() + { + $data = [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ArrayProxy($data); + + $this->assertEquals(count($proxy), 2, 'Countable implementation should count correctly'); + } + + /** + * @testdox Array Iterator implementations + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testArrayIterator() + { + $data = [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ArrayProxy($data); + + $this->assertEquals($data, iterator_to_array($proxy)); + } + + /** + * @testdox Array read-only Iterator implementations + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testArrayReadOnlyIterator() + { + $data = [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + 'child2' => [ + 'text' => 'child', + ], + ]; + + $proxy = new ArrayReadOnlyProxy($data); + $result = iterator_to_array($proxy); + + $this->assertInstanceOf(ObjectReadOnlyProxy::class, $result['child'], 'Read-only iterator should return ObjectReadOnlyProxy'); + $this->assertInstanceOf(ArrayReadOnlyProxy::class, $result['child2'], 'Read-only iterator should return ArrayReadOnlyProxy'); + } + + /** + * @testdox Array read-only access + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testArrayReadOnlyAccessAndModification() + { + $data = [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ArrayReadOnlyProxy($data); + + $this->expectException(\RuntimeException::class); + + $proxy['foo'] = 'foobar'; + } + + /** + * @testdox Array read-only access to child + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testArrayReadOnlyChildAccessAndModification() + { + $data = [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ArrayReadOnlyProxy($data); + + $this->expectException(\RuntimeException::class); + + $proxy['child']->text = 'foobar'; + } +} diff --git a/tests/Unit/Libraries/Cms/Proxy/ProxyObjectTest.php b/tests/Unit/Libraries/Cms/Proxy/ProxyObjectTest.php new file mode 100644 index 0000000000000..53d9daaf0ba61 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Proxy/ProxyObjectTest.php @@ -0,0 +1,144 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Proxy; + +use Joomla\CMS\Proxy\ArrayReadOnlyProxy; +use Joomla\CMS\Proxy\ObjectProxy; +use Joomla\CMS\Proxy\ObjectReadOnlyProxy; +use Joomla\Tests\Unit\UnitTestCase; + +/** + * Test class for \Joomla\CMS\Proxy\ObjectProxy classes + * + * @package Joomla.UnitTest + * @subpackage Plugin + * + * @since __DEPLOY_VERSION__ + */ +class ProxyObjectTest extends UnitTestCase +{ + /** + * @testdox Object referencing keep the changes + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testObjectAccessAndModification() + { + $data = (object) [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ObjectProxy($data); + + $proxy->bar2 = 'foo2'; + + $this->assertEquals($data->bar2, 'foo2', 'A referenced Object should get a Proxy value'); + $this->assertEquals($proxy->foo, 'bar', 'Proxy object should return value from Object'); + } + + /** + * @testdox Object read-only access + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testObjectReadOnlyAccessAndModification() + { + $data = (object) [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ObjectReadOnlyProxy($data); + + $this->expectException(\RuntimeException::class); + + $proxy->foo = 'foobar'; + } + + /** + * @testdox Object read-only access to child + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testObjectReadOnlyChildAccessAndModification() + { + $data = (object) [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ObjectReadOnlyProxy($data); + + $this->expectException(\RuntimeException::class); + + $proxy->child->text = 'foobar'; + } + + /** + * @testdox Object Iterator implementations + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testObjectIterator() + { + $data = (object) [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + ]; + + $proxy = new ObjectProxy($data); + + $this->assertEquals((array) $data, iterator_to_array($proxy)); + } + + /** + * @testdox Object read-only Iterator implementations + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testObjectReadOnlyIterator() + { + $data = (object) [ + 'foo' => 'bar', + 'child' => (object) [ + 'text' => 'child', + ], + 'child2' => [ + 'text' => 'child', + ], + ]; + + $proxy = new ObjectReadOnlyProxy($data); + $result = iterator_to_array($proxy); + + $this->assertInstanceOf(ObjectReadOnlyProxy::class, $result['child'], 'Read-only iterator should return ObjectReadOnlyProxy'); + $this->assertInstanceOf(ArrayReadOnlyProxy::class, $result['child2'], 'Read-only iterator should return ArrayReadOnlyProxy'); + } +}