diff --git a/components/com_users/src/Model/RemindModel.php b/components/com_users/src/Model/RemindModel.php index 5a0c0920221bf..937147d5402cd 100644 --- a/components/com_users/src/Model/RemindModel.php +++ b/components/com_users/src/Model/RemindModel.php @@ -10,6 +10,7 @@ namespace Joomla\Component\Users\Site\Model; +use Joomla\CMS\Event\User\AfterRemindEvent; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; @@ -201,7 +202,9 @@ public function processRemindRequest($data) return false; } - Factory::getApplication()->triggerEvent('onUserAfterRemind', [$user]); + $this->getDispatcher()->dispatch('onUserAfterRemind', new AfterRemindEvent('onUserAfterRemind', [ + 'subject' => $user, + ])); return true; } diff --git a/libraries/src/Application/CMSApplication.php b/libraries/src/Application/CMSApplication.php index d276b284b9a44..49b30637ff78e 100644 --- a/libraries/src/Application/CMSApplication.php +++ b/libraries/src/Application/CMSApplication.php @@ -20,6 +20,13 @@ use Joomla\CMS\Event\Application\BeforeRenderEvent; use Joomla\CMS\Event\Application\BeforeRespondEvent; use Joomla\CMS\Event\ErrorEvent; +use Joomla\CMS\Event\User\AfterLoginEvent; +use Joomla\CMS\Event\User\AfterLogoutEvent; +use Joomla\CMS\Event\User\AuthorisationFailureEvent; +use Joomla\CMS\Event\User\LoginEvent; +use Joomla\CMS\Event\User\LoginFailureEvent; +use Joomla\CMS\Event\User\LogoutEvent; +use Joomla\CMS\Event\User\LogoutFailureEvent; use Joomla\CMS\Exception\ExceptionHandler; use Joomla\CMS\Extension\ExtensionManagerTrait; use Joomla\CMS\Factory; @@ -837,9 +844,10 @@ public function login($credentials, $options = []) // Get the global Authentication object. $authenticate = Authentication::getInstance($this->authenticationPluginType); $response = $authenticate->authenticate($credentials, $options); + $dispatcher = $this->getDispatcher(); // Import the user plugin group. - PluginHelper::importPlugin('user', null, true, $this->getDispatcher()); + PluginHelper::importPlugin('user', null, true, $dispatcher); if ($response->status === Authentication::STATUS_SUCCESS) { /* @@ -852,7 +860,10 @@ public function login($credentials, $options = []) foreach ($authorisations as $authorisation) { if ((int) $authorisation->status & $denied_states) { // Trigger onUserAuthorisationFailure Event. - $this->triggerEvent('onUserAuthorisationFailure', [(array) $authorisation]); + $dispatcher->dispatch('onUserAuthorisationFailure', new AuthorisationFailureEvent('onUserAuthorisationFailure', [ + 'subject' => (array) $authorisation, + 'options' => $options, + ])); // If silent is set, just return false. if (isset($options['silent']) && $options['silent']) { @@ -862,17 +873,17 @@ public function login($credentials, $options = []) // Return the error. switch ($authorisation->status) { case Authentication::STATUS_EXPIRED: - Factory::getApplication()->enqueueMessage(Text::_('JLIB_LOGIN_EXPIRED'), 'error'); + $this->enqueueMessage(Text::_('JLIB_LOGIN_EXPIRED'), 'error'); return false; case Authentication::STATUS_DENIED: - Factory::getApplication()->enqueueMessage(Text::_('JLIB_LOGIN_DENIED'), 'error'); + $this->enqueueMessage(Text::_('JLIB_LOGIN_DENIED'), 'error'); return false; default: - Factory::getApplication()->enqueueMessage(Text::_('JLIB_LOGIN_AUTHORISATION'), 'error'); + $this->enqueueMessage(Text::_('JLIB_LOGIN_AUTHORISATION'), 'error'); return false; } @@ -880,7 +891,9 @@ public function login($credentials, $options = []) } // OK, the credentials are authenticated and user is authorised. Let's fire the onLogin event. - $results = $this->triggerEvent('onUserLogin', [(array) $response, $options]); + $loginEvent = new LoginEvent('onUserLogin', ['subject' => (array) $response, 'options' => $options]); + $dispatcher->dispatch('onUserLogin', $loginEvent); + $results = $loginEvent['result'] ?? []; /* * If any of the user plugins did not successfully complete the login routine @@ -900,14 +913,20 @@ public function login($credentials, $options = []) $options['responseType'] = $response->type; // The user is successfully logged in. Run the after login events - $this->triggerEvent('onUserAfterLogin', [$options]); + $dispatcher->dispatch('onUserAfterLogin', new AfterLoginEvent('onUserAfterLogin', [ + 'options' => $options, + 'subject' => (array) $response, + ])); return true; } } // Trigger onUserLoginFailure Event. - $this->triggerEvent('onUserLoginFailure', [(array) $response]); + $dispatcher->dispatch('onUserLoginFailure', new LoginFailureEvent('onUserLoginFailure', [ + 'subject' => (array) $response, + 'options' => $options, + ])); // If silent is set, just return false. if (isset($options['silent']) && $options['silent']) { @@ -942,7 +961,8 @@ public function login($credentials, $options = []) public function logout($userid = null, $options = []) { // Get a user object from the Application. - $user = Factory::getUser($userid); + $user = Factory::getUser($userid); + $dispatcher = $this->getDispatcher(); // Build the credentials array. $parameters = [ @@ -956,21 +976,29 @@ public function logout($userid = null, $options = []) } // Import the user plugin group. - PluginHelper::importPlugin('user', null, true, $this->getDispatcher()); + PluginHelper::importPlugin('user', null, true, $dispatcher); // OK, the credentials are built. Lets fire the onLogout event. - $results = $this->triggerEvent('onUserLogout', [$parameters, $options]); + $logoutEvent = new LogoutEvent('onUserLogout', ['subject' => $parameters, 'options' => $options]); + $dispatcher->dispatch('onUserLogout', $logoutEvent); + $results = $logoutEvent['result'] ?? []; // Check if any of the plugins failed. If none did, success. if (!\in_array(false, $results, true)) { $options['username'] = $user->get('username'); - $this->triggerEvent('onUserAfterLogout', [$options]); + $dispatcher->dispatch('onUserAfterLogout', new AfterLogoutEvent('onUserAfterLogout', [ + 'options' => $options, + 'subject' => $parameters, + ])); return true; } // Trigger onUserLogoutFailure Event. - $this->triggerEvent('onUserLogoutFailure', [$parameters]); + $dispatcher->dispatch('onUserLogoutFailure', new LogoutFailureEvent('onUserLogoutFailure', [ + 'subject' => $parameters, + 'options' => $options, + ])); return false; } diff --git a/libraries/src/Authentication/Authentication.php b/libraries/src/Authentication/Authentication.php index 3c4eef7223bb4..7aaa29b05cd8a 100644 --- a/libraries/src/Authentication/Authentication.php +++ b/libraries/src/Authentication/Authentication.php @@ -9,6 +9,7 @@ namespace Joomla\CMS\Authentication; +use Joomla\CMS\Event\User\AuthorisationEvent; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; @@ -212,10 +213,14 @@ public function authenticate($credentials, $options = []) */ public function authorise($response, $options = []) { + $dispatcher = $this->getDispatcher(); + // Get plugins in case they haven't been imported already - PluginHelper::importPlugin('user'); - $results = Factory::getApplication()->triggerEvent('onUserAuthorisation', [$response, $options]); + PluginHelper::importPlugin('user', null, true, $dispatcher); + + $event = new AuthorisationEvent('onUserAuthorisation', ['subject' => $response, 'options' => $options]); + $dispatcher->dispatch('onUserAuthorisation', $event); - return $results; + return $event['result'] ?? []; } } diff --git a/libraries/src/Event/CoreEventAware.php b/libraries/src/Event/CoreEventAware.php index b76ba0978c8bf..1cf47a31481db 100644 --- a/libraries/src/Event/CoreEventAware.php +++ b/libraries/src/Event/CoreEventAware.php @@ -121,6 +121,26 @@ trait CoreEventAware 'onContentChangeState' => Model\AfterChangeStateEvent::class, 'onCategoryChangeState' => Model\AfterCategoryChangeStateEvent::class, 'onBeforeBatch' => Model\BeforeBatchEvent::class, + // User + 'onUserAuthorisation' => User\AuthorisationEvent::class, + 'onUserAuthorisationFailure' => User\AuthorisationFailureEvent::class, + 'onUserLogin' => User\LoginEvent::class, + 'onUserAfterLogin' => User\AfterLoginEvent::class, + 'onUserLoginFailure' => User\LoginFailureEvent::class, + 'onUserLogout' => User\LogoutEvent::class, + 'onUserAfterLogout' => User\AfterLogoutEvent::class, + 'onUserLogoutFailure' => User\LogoutFailureEvent::class, + 'onUserLoginButtons' => User\LoginButtonsEvent::class, + 'onUserBeforeSave' => User\BeforeSaveEvent::class, + 'onUserAfterSave' => User\AfterSaveEvent::class, + 'onUserBeforeDelete' => User\BeforeDeleteEvent::class, + 'onUserAfterDelete' => User\AfterDeleteEvent::class, + 'onUserAfterRemind' => User\AfterRemindEvent::class, + // User Group + 'onUserBeforeSaveGroup' => Model\BeforeSaveEvent::class, + 'onUserAfterSaveGroup' => Model\AfterSaveEvent::class, + 'onUserBeforeDeleteGroup' => Model\BeforeDeleteEvent::class, + 'onUserAfterDeleteGroup' => Model\AfterDeleteEvent::class, // Modules 'onRenderModule' => Module\BeforeRenderModuleEvent::class, 'onAfterRenderModule' => Module\AfterRenderModuleEvent::class, diff --git a/libraries/src/Event/User/AbstractDeleteEvent.php b/libraries/src/Event/User/AbstractDeleteEvent.php new file mode 100644 index 0000000000000..93438d5c39ceb --- /dev/null +++ b/libraries/src/Event/User/AbstractDeleteEvent.php @@ -0,0 +1,58 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Base class for User delete event + * + * @since __DEPLOY_VERSION__ + */ +abstract class AbstractDeleteEvent extends UserEvent +{ + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = ['subject']; + + /** + * Setter for the subject argument. + * + * @param array $value The value to set + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(array $value): array + { + return $value; + } + + /** + * Getter for the user. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getUser(): array + { + return $this->arguments['subject']; + } +} diff --git a/libraries/src/Event/User/AbstractLoginEvent.php b/libraries/src/Event/User/AbstractLoginEvent.php new file mode 100644 index 0000000000000..794c1de8f10eb --- /dev/null +++ b/libraries/src/Event/User/AbstractLoginEvent.php @@ -0,0 +1,84 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Base class for User login event + * + * @since __DEPLOY_VERSION__ + */ +abstract class AbstractLoginEvent extends UserEvent +{ + /** + * 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', 'options']; + + /** + * 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 options argument. + * + * @param array $value The value to set + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + protected function setOptions(array $value): array + { + return $value; + } + + /** + * Getter for the response. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getAuthenticationResponse(): array + { + return $this->arguments['subject']; + } + + /** + * Getter for the options. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getOptions(): array + { + return $this->arguments['options'] ?? []; + } +} diff --git a/libraries/src/Event/User/AbstractLogoutEvent.php b/libraries/src/Event/User/AbstractLogoutEvent.php new file mode 100644 index 0000000000000..c99941f3580cf --- /dev/null +++ b/libraries/src/Event/User/AbstractLogoutEvent.php @@ -0,0 +1,84 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Base class for User logout event + * + * @since __DEPLOY_VERSION__ + */ +abstract class AbstractLogoutEvent extends UserEvent +{ + /** + * 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', 'options']; + + /** + * 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 options argument. + * + * @param array $value The value to set + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + protected function setOptions(array $value): array + { + return $value; + } + + /** + * Getter for the parameters. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getParameters(): array + { + return $this->arguments['subject']; + } + + /** + * Getter for the options. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getOptions(): array + { + return $this->arguments['options'] ?? []; + } +} diff --git a/libraries/src/Event/User/AbstractSaveEvent.php b/libraries/src/Event/User/AbstractSaveEvent.php new file mode 100644 index 0000000000000..747a556994291 --- /dev/null +++ b/libraries/src/Event/User/AbstractSaveEvent.php @@ -0,0 +1,84 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Base class for User save event + * + * @since __DEPLOY_VERSION__ + */ +abstract class AbstractSaveEvent extends UserEvent +{ + /** + * 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', 'isNew']; + + /** + * 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 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 user. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getUser(): array + { + return $this->arguments['subject']; + } + + /** + * Getter for the isNew state. + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function getIsNew(): bool + { + return $this->arguments['isNew']; + } +} diff --git a/libraries/src/Event/User/AfterDeleteEvent.php b/libraries/src/Event/User/AfterDeleteEvent.php new file mode 100644 index 0000000000000..98d62dba75114 --- /dev/null +++ b/libraries/src/Event/User/AfterDeleteEvent.php @@ -0,0 +1,86 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User delete event. + * Example: + * new AfterDeleteEvent('onEventName', ['subject' => $userArray, 'deletingResult' => $result, 'errorMessage' => $errorStr]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterDeleteEvent extends AbstractDeleteEvent +{ + /** + * 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', 'deletingResult', 'errorMessage']; + + /** + * Setter for the deletingResult argument. + * + * @param bool $value The value to set + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + protected function setDeletingResult(bool $value): bool + { + return $value; + } + + /** + * Setter for the errorMessage argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setErrorMessage(string $value): string + { + return $value; + } + + /** + * Getter for the deleting result. + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + public function getDeletingResult(): bool + { + return $this->arguments['deletingResult']; + } + + /** + * Getter for the error message. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getErrorMessage(): string + { + return $this->arguments['errorMessage']; + } +} diff --git a/libraries/src/Event/User/AfterLoginEvent.php b/libraries/src/Event/User/AfterLoginEvent.php new file mode 100644 index 0000000000000..e4702961d79a4 --- /dev/null +++ b/libraries/src/Event/User/AfterLoginEvent.php @@ -0,0 +1,34 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new AfterLoginEvent('onEventName', ['subject' => $authenticationResponseArray, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterLoginEvent extends AbstractLoginEvent +{ + /** + * 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 = ['options', 'subject']; +} diff --git a/libraries/src/Event/User/AfterLogoutEvent.php b/libraries/src/Event/User/AfterLogoutEvent.php new file mode 100644 index 0000000000000..9b0363f287c54 --- /dev/null +++ b/libraries/src/Event/User/AfterLogoutEvent.php @@ -0,0 +1,34 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new AfterLogoutEvent('onEventName', ['subject' => $parameters, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterLogoutEvent extends AbstractLogoutEvent +{ + /** + * 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 = ['options', 'subject']; +} diff --git a/libraries/src/Event/User/AfterRemindEvent.php b/libraries/src/Event/User/AfterRemindEvent.php new file mode 100644 index 0000000000000..dc1689f9dc401 --- /dev/null +++ b/libraries/src/Event/User/AfterRemindEvent.php @@ -0,0 +1,60 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new AfterRemindEvent('onEventName', ['subject' => $user]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterRemindEvent extends UserEvent +{ + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = ['subject']; + + /** + * Setter for the subject argument. + * + * @param object $value The value to set + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(object $value): object + { + return $value; + } + + /** + * Getter for the user. + * + * @return object + * + * @since __DEPLOY_VERSION__ + */ + public function getUser(): object + { + return $this->arguments['subject']; + } +} diff --git a/libraries/src/Event/User/AfterSaveEvent.php b/libraries/src/Event/User/AfterSaveEvent.php new file mode 100644 index 0000000000000..539eb62836418 --- /dev/null +++ b/libraries/src/Event/User/AfterSaveEvent.php @@ -0,0 +1,86 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User save event. + * Example: + * new AfterSaveEvent('onEventName', ['subject' => $userArray, 'isNew' => $isNew, 'savingResult' => $result, 'errorMessage' => $errorStr]); + * + * @since __DEPLOY_VERSION__ + */ +class AfterSaveEvent extends AbstractSaveEvent +{ + /** + * 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', 'isNew', 'savingResult', 'errorMessage']; + + /** + * Setter for the savingResult argument. + * + * @param bool $value The value to set + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + protected function setSavingResult(bool $value): bool + { + return $value; + } + + /** + * Setter for the errorMessage argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setErrorMessage(string $value): string + { + return $value; + } + + /** + * Getter for the saving result. + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + public function getSavingResult(): bool + { + return $this->arguments['savingResult']; + } + + /** + * Getter for the error message. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getErrorMessage(): string + { + return $this->arguments['errorMessage']; + } +} diff --git a/libraries/src/Event/User/AuthorisationEvent.php b/libraries/src/Event/User/AuthorisationEvent.php new file mode 100644 index 0000000000000..5120a50ede745 --- /dev/null +++ b/libraries/src/Event/User/AuthorisationEvent.php @@ -0,0 +1,110 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +use Joomla\CMS\Authentication\AuthenticationResponse; +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new AuthorisationEvent('onEventName', ['subject' => $authenticationResponse, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class AuthorisationEvent extends UserEvent implements ResultAwareInterface +{ + use ResultAware; + + /** + * 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', 'options']; + + /** + * Setter for the subject argument. + * + * @param AuthenticationResponse $value The value to set + * + * @return AuthenticationResponse + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(AuthenticationResponse $value): AuthenticationResponse + { + return $value; + } + + /** + * Setter for the options argument. + * + * @param array $value The value to set + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + protected function setOptions(array $value): array + { + return $value; + } + + /** + * Checks the type of the data being appended to the result argument. + * + * @param mixed $data The data to type check + * + * @return void + * @throws \InvalidArgumentException + * + * @internal + * @since __DEPLOY_VERSION__ + */ + public function typeCheckResult($data): void + { + if (!$data instanceof AuthenticationResponse) { + throw new \InvalidArgumentException(sprintf('Event %s only accepts AuthenticationResponse results.', $this->getName())); + } + } + + /** + * Getter for the response. + * + * @return AuthenticationResponse + * + * @since __DEPLOY_VERSION__ + */ + public function getAuthenticationResponse(): AuthenticationResponse + { + return $this->arguments['subject']; + } + + /** + * Getter for the options. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getOptions(): array + { + return $this->arguments['options'] ?? []; + } +} diff --git a/libraries/src/Event/User/AuthorisationFailureEvent.php b/libraries/src/Event/User/AuthorisationFailureEvent.php new file mode 100644 index 0000000000000..2a42f40f07d19 --- /dev/null +++ b/libraries/src/Event/User/AuthorisationFailureEvent.php @@ -0,0 +1,86 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new AuthorisationFailureEvent('onEventName', ['subject' => $authenticationResponseArray, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class AuthorisationFailureEvent extends UserEvent +{ + /** + * 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', 'options']; + + /** + * 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 options argument. + * + * @param array $value The value to set + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + protected function setOptions(array $value): array + { + return $value; + } + + /** + * Getter for the response. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getAuthenticationResponse(): array + { + return $this->arguments['subject']; + } + + /** + * Getter for the options. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getOptions(): array + { + return $this->arguments['options'] ?? []; + } +} diff --git a/libraries/src/Event/User/BeforeDeleteEvent.php b/libraries/src/Event/User/BeforeDeleteEvent.php new file mode 100644 index 0000000000000..90ca0924c557b --- /dev/null +++ b/libraries/src/Event/User/BeforeDeleteEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User delete event. + * Example: + * new BeforeDeleteEvent('onEventName', ['subject' => $userArray]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeDeleteEvent extends AbstractDeleteEvent +{ +} diff --git a/libraries/src/Event/User/BeforeSaveEvent.php b/libraries/src/Event/User/BeforeSaveEvent.php new file mode 100644 index 0000000000000..5030bb83747c5 --- /dev/null +++ b/libraries/src/Event/User/BeforeSaveEvent.php @@ -0,0 +1,67 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +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 User save event. + * Example: + * new BeforeSaveEvent('onEventName', ['subject' => $oldUserArray, 'isNew' => $isNew, 'data' => $data]); + * + * @since __DEPLOY_VERSION__ + */ +class BeforeSaveEvent extends AbstractSaveEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeBooleanAware; + + /** + * 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', 'isNew', 'data']; + + /** + * Setter for the data argument. + * + * @param array $value The value to set + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + protected function setData(array $value): array + { + return $value; + } + + /** + * Getter for the data. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getData(): array + { + return $this->arguments['data'] ?? []; + } +} diff --git a/libraries/src/Event/User/LoginButtonsEvent.php b/libraries/src/Event/User/LoginButtonsEvent.php new file mode 100644 index 0000000000000..0c334cb8c3d1d --- /dev/null +++ b/libraries/src/Event/User/LoginButtonsEvent.php @@ -0,0 +1,67 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +use Joomla\CMS\Event\Result\ResultAware; +use Joomla\CMS\Event\Result\ResultAwareInterface; +use Joomla\CMS\Event\Result\ResultTypeArrayAware; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new LoginButtonsEvent('onEventName', ['subject' => $formId]); + * + * @since __DEPLOY_VERSION__ + */ +class LoginButtonsEvent extends UserEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeArrayAware; + + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = ['subject']; + + /** + * Setter for the subject argument. + * + * @param string $value The value to set + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function setSubject(string $value): string + { + return $value; + } + + /** + * Getter for the formId. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getFormId(): string + { + return $this->arguments['subject']; + } +} diff --git a/libraries/src/Event/User/LoginEvent.php b/libraries/src/Event/User/LoginEvent.php new file mode 100644 index 0000000000000..74d9bd792245a --- /dev/null +++ b/libraries/src/Event/User/LoginEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +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 User event. + * Example: + * new LoginEvent('onEventName', ['subject' => $authenticationResponseArray, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class LoginEvent extends AbstractLoginEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeBooleanAware; +} diff --git a/libraries/src/Event/User/LoginFailureEvent.php b/libraries/src/Event/User/LoginFailureEvent.php new file mode 100644 index 0000000000000..c1a6519ee09ca --- /dev/null +++ b/libraries/src/Event/User/LoginFailureEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new LoginFailureEvent('onEventName', ['subject' => $authenticationResponseArray, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class LoginFailureEvent extends AbstractLoginEvent +{ +} diff --git a/libraries/src/Event/User/LogoutEvent.php b/libraries/src/Event/User/LogoutEvent.php new file mode 100644 index 0000000000000..7d9029629ae49 --- /dev/null +++ b/libraries/src/Event/User/LogoutEvent.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +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 User event. + * Example: + * new LogoutEvent('onEventName', ['subject' => $parameters, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class LogoutEvent extends AbstractLogoutEvent implements ResultAwareInterface +{ + use ResultAware; + use ResultTypeBooleanAware; +} diff --git a/libraries/src/Event/User/LogoutFailureEvent.php b/libraries/src/Event/User/LogoutFailureEvent.php new file mode 100644 index 0000000000000..edb360add3d2e --- /dev/null +++ b/libraries/src/Event/User/LogoutFailureEvent.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class for User event. + * Example: + * new LogoutFailureEvent('onEventName', ['subject' => $parameters, 'options' => $options]); + * + * @since __DEPLOY_VERSION__ + */ +class LogoutFailureEvent extends AbstractLogoutEvent +{ +} diff --git a/libraries/src/Event/User/UserEvent.php b/libraries/src/Event/User/UserEvent.php new file mode 100644 index 0000000000000..b0b4aabccad48 --- /dev/null +++ b/libraries/src/Event/User/UserEvent.php @@ -0,0 +1,61 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event\User; + +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 User events + * + * @since __DEPLOY_VERSION__ + */ +abstract class UserEvent extends AbstractImmutableEvent +{ + use ReshapeArgumentsAware; + + /** + * The argument names, in order expected by legacy plugins. + * + * @var array + * + * @since __DEPLOY_VERSION__ + * @deprecated 5.0 will be removed in 6.0 + */ + protected $legacyArgumentsOrder = []; + + /** + * Constructor. + * + * @param string $name The event name. + * @param array $arguments The event arguments. + * + * @throws \BadMethodCallException + * + * @since __DEPLOY_VERSION__ + */ + public function __construct($name, array $arguments = []) + { + // Reshape the arguments array to preserve b/c with legacy listeners + if ($this->legacyArgumentsOrder) { + $arguments = $this->reshapeArguments($arguments, $this->legacyArgumentsOrder); + } + + if (!\array_key_exists('subject', $arguments)) { + throw new \BadMethodCallException("Argument 'subject' of event {$name} is required but has not been provided"); + } + + parent::__construct($name, $arguments); + } +} diff --git a/libraries/src/Helper/AuthenticationHelper.php b/libraries/src/Helper/AuthenticationHelper.php index f783e67eeedb6..49ecc19498957 100644 --- a/libraries/src/Helper/AuthenticationHelper.php +++ b/libraries/src/Helper/AuthenticationHelper.php @@ -9,7 +9,7 @@ namespace Joomla\CMS\Helper; -use Joomla\CMS\Application\CMSApplication; +use Joomla\CMS\Event\User\LoginButtonsEvent; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; @@ -89,19 +89,19 @@ public static function getTwoFactorMethods() */ public static function getLoginButtons(string $formId): array { - // Get all the User plugins. - PluginHelper::importPlugin('user'); - - // Trigger the onUserLoginButtons event and return the button definitions. try { - /** @var CMSApplication $app */ - $app = Factory::getApplication(); + // Get all the User plugins. + $dispatcher = Factory::getApplication()->getDispatcher(); + PluginHelper::importPlugin('user', null, true, $dispatcher); } catch (\Exception $e) { return []; } - $results = $app->triggerEvent('onUserLoginButtons', [$formId]); - $buttons = []; + // Trigger the onUserLoginButtons event and return the button definitions. + $btnEvent = new LoginButtonsEvent('onUserLoginButtons', ['subject' => $formId]); + $dispatcher->dispatch('onUserLoginButtons', $btnEvent); + $results = $btnEvent['result'] ?? []; + $buttons = []; foreach ($results as $result) { // Did we get garbage back from the plugin? diff --git a/libraries/src/User/User.php b/libraries/src/User/User.php index 72cd19eae925c..f6b2e06b26f85 100644 --- a/libraries/src/User/User.php +++ b/libraries/src/User/User.php @@ -10,6 +10,10 @@ namespace Joomla\CMS\User; use Joomla\CMS\Access\Access; +use Joomla\CMS\Event\User\AfterDeleteEvent; +use Joomla\CMS\Event\User\AfterSaveEvent; +use Joomla\CMS\Event\User\BeforeDeleteEvent; +use Joomla\CMS\Event\User\BeforeSaveEvent; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Object\LegacyErrorHandlingTrait; @@ -765,9 +769,16 @@ public function save($updateOnly = false) } // Fire the onUserBeforeSave event. - PluginHelper::importPlugin('user'); + $dispatcher = Factory::getApplication()->getDispatcher(); + PluginHelper::importPlugin('user', null, true, $dispatcher); - $result = Factory::getApplication()->triggerEvent('onUserBeforeSave', [$oldUser->getProperties(), $isNew, $this->getProperties()]); + $saveEvent = new BeforeSaveEvent('onUserBeforeSave', [ + 'subject' => $oldUser->getProperties(), + 'isNew' => $isNew, + 'data' => $this->getProperties(), + ]); + $dispatcher->dispatch('onUserBeforeSave', $saveEvent); + $result = $saveEvent['result'] ?? []; if (\in_array(false, $result, true)) { // Plugin will have to raise its own error or throw an exception. @@ -788,7 +799,12 @@ public function save($updateOnly = false) } // Fire the onUserAfterSave event - Factory::getApplication()->triggerEvent('onUserAfterSave', [$this->getProperties(), $isNew, $result, $this->getError()]); + $dispatcher->dispatch('onUserAfterSave', new AfterSaveEvent('onUserAfterSave', [ + 'subject' => $this->getProperties(), + 'isNew' => $isNew, + 'savingResult' => $result, + 'errorMessage' => $this->getError() ?? '', + ])); } catch (\Exception $e) { $this->setError($e->getMessage()); @@ -807,10 +823,13 @@ public function save($updateOnly = false) */ public function delete() { - PluginHelper::importPlugin('user'); + $dispatcher = Factory::getApplication()->getDispatcher(); + PluginHelper::importPlugin('user', null, true, $dispatcher); // Trigger the onUserBeforeDelete event - Factory::getApplication()->triggerEvent('onUserBeforeDelete', [$this->getProperties()]); + $dispatcher->dispatch('onUserBeforeDelete', new BeforeDeleteEvent('onUserBeforeDelete', [ + 'subject' => $this->getProperties(), + ])); // Create the user table object $table = $this->getTable(); @@ -820,7 +839,11 @@ public function delete() } // Trigger the onUserAfterDelete event - Factory::getApplication()->triggerEvent('onUserAfterDelete', [$this->getProperties(), $result, $this->getError()]); + $dispatcher->dispatch('onUserAfterDelete', new AfterDeleteEvent('onUserAfterDelete', [ + 'subject' => $this->getProperties(), + 'deletingResult' => $result, + 'errorMessage' => $this->getError() ?? '', + ])); return $result; } diff --git a/plugins/system/actionlogs/src/Extension/ActionLogs.php b/plugins/system/actionlogs/src/Extension/ActionLogs.php index a8cec4635b16c..4d8f3d75b6f5d 100644 --- a/plugins/system/actionlogs/src/Extension/ActionLogs.php +++ b/plugins/system/actionlogs/src/Extension/ActionLogs.php @@ -154,7 +154,7 @@ public function onContentPrepareData($context, $data) $data = (object) $data; } - if (!$this->getUserFactory()->loadUserById($data->id)->authorise('core.admin')) { + if (empty($data->id) || !$this->getUserFactory()->loadUserById($data->id)->authorise('core.admin')) { return true; } diff --git a/plugins/system/webauthn/src/PluginTraits/EventReturnAware.php b/plugins/system/webauthn/src/PluginTraits/EventReturnAware.php index c8ff8e7d7cf2e..6b6417c03e5fe 100644 --- a/plugins/system/webauthn/src/PluginTraits/EventReturnAware.php +++ b/plugins/system/webauthn/src/PluginTraits/EventReturnAware.php @@ -10,6 +10,7 @@ namespace Joomla\Plugin\System\Webauthn\PluginTraits; +use Joomla\CMS\Event\Result\ResultAwareInterface; use Joomla\Event\Event; // phpcs:disable PSR1.Files.SideEffects @@ -34,6 +35,11 @@ trait EventReturnAware */ private function returnFromEvent(Event $event, $value = null): void { + if ($event instanceof ResultAwareInterface) { + $event->addResult($value); + return; + } + $result = $event->getArgument('result') ?: []; if (!is_array($result)) {