diff --git a/administrator/components/com_users/access.xml b/administrator/components/com_users/access.xml index 80d23eca72f60..3801122555c4a 100644 --- a/administrator/components/com_users/access.xml +++ b/administrator/components/com_users/access.xml @@ -6,6 +6,7 @@ <action name="core.manage" title="JACTION_MANAGE" /> <action name="core.create" title="JACTION_CREATE" /> <action name="core.delete" title="JACTION_DELETE" /> + <action name="core.trash" title="JACTION_TRASH" /> <action name="core.edit" title="JACTION_EDIT" /> <action name="core.edit.state" title="JACTION_EDITSTATE" /> <action name="core.edit.value" title="JACTION_EDITVALUE" /> @@ -13,6 +14,7 @@ <section name="category"> <action name="core.create" title="JACTION_CREATE" /> <action name="core.delete" title="JACTION_DELETE" /> + <action name="core.trash" title="JACTION_TRASH" /> <action name="core.edit" title="JACTION_EDIT" /> <action name="core.edit.state" title="JACTION_EDITSTATE" /> <action name="core.edit.own" title="JACTION_EDITOWN" /> @@ -20,6 +22,7 @@ <section name="fieldgroup"> <action name="core.create" title="JACTION_CREATE" /> <action name="core.delete" title="JACTION_DELETE" /> + <action name="core.trash" title="JACTION_TRASH" /> <action name="core.edit" title="JACTION_EDIT" /> <action name="core.edit.state" title="JACTION_EDITSTATE" /> <action name="core.edit.own" title="JACTION_EDITOWN" /> @@ -27,6 +30,7 @@ </section> <section name="field"> <action name="core.delete" title="JACTION_DELETE" /> + <action name="core.trash" title="JACTION_TRASH" /> <action name="core.edit" title="JACTION_EDIT" /> <action name="core.edit.state" title="JACTION_EDITSTATE" /> <action name="core.edit.value" title="JACTION_EDITVALUE" /> diff --git a/administrator/components/com_users/src/Controller/UserController.php b/administrator/components/com_users/src/Controller/UserController.php index 390753331d231..1a33c4d0f05a8 100644 --- a/administrator/components/com_users/src/Controller/UserController.php +++ b/administrator/components/com_users/src/Controller/UserController.php @@ -11,6 +11,8 @@ namespace Joomla\Component\Users\Administrator\Controller; use Joomla\CMS\Access\Access; +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\FormController; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\Router\Route; @@ -147,6 +149,31 @@ public function batch($model = null) return parent::batch($model); } + /** + * Trashes a user. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function trash() + { + $app = Factory::getApplication(); + $input = $app->input; + $userId = $input->get('cid', [], 'array'); + + if (!$userId) { + $app->enqueueMessage(Text::_('COM_USERS_ERROR_USER_NOT_FOUND'), 'error'); + return; + } + + $model = $this->getModel('User', 'Administrator', []); + $model->trash($userId); + + $app->enqueueMessage(Text::sprintf('COM_USERS_USER_TRASHED_SUCCESS', 2), 'message'); + $this->setRedirect(Route::_('index.php?option=com_users&view=users', false)); + } + /** * Function that allows child controller access to model data after the data has been saved. * diff --git a/administrator/components/com_users/src/Model/UserModel.php b/administrator/components/com_users/src/Model/UserModel.php index 889add8c3d370..e3ab46e0b6ae1 100644 --- a/administrator/components/com_users/src/Model/UserModel.php +++ b/administrator/components/com_users/src/Model/UserModel.php @@ -301,6 +301,57 @@ public function save($data) return true; } + /** + * Method to trash user records. + * + * @param array $pks The ids of the items to trash. + * + * @return boolean True on success. + * + * @since __DEPLOY_VERSION__ + * @throws \Exception + */ + public function trash(&$pks) + { + $user = $this->getCurrentUser(); + $table = $this->getTable(); + $pks = (array) $pks; + + $iAmSuperAdmin = $user->authorise('core.admin'); + + if (\in_array($user->id, $pks)) { + $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_TRASH_SELF')); + return false; + } + + foreach ($pks as $i => $pk) { + if ($table->load($pk)) { + $allow = $user->authorise('core.trash', 'com_users'); + $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow; + + if ($allow) { + $user_to_trash = $this->getUserFactory()->loadUserById($pk); + $user_to_trash->block = 2; + + if (!$user_to_trash->save()) { + $this->setError($user_to_trash->getError()); + return false; + } + } else { + unset($pks[$i]); + Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_TRASH_NOT_PERMITTED'), 'error'); + } + } + + Factory::getApplication()->enqueueMessage( + Text::_('User moved to Trash. Delete again to remove permanently.'), + 'notice' + ); + } + + return true; + } + /** * Method to delete rows. * diff --git a/administrator/components/com_users/src/Model/UsersModel.php b/administrator/components/com_users/src/Model/UsersModel.php index 4c7ba76416b18..7893bde87cd50 100644 --- a/administrator/components/com_users/src/Model/UsersModel.php +++ b/administrator/components/com_users/src/Model/UsersModel.php @@ -173,6 +173,10 @@ public function getItems() $items = parent::getItems(); } + $items = array_filter($items, function ($item) { + return $item->block != 2; // Exclude trashed users + }); + // Bail out on an error or empty list. if (empty($items)) { $this->cache[$store] = $items; diff --git a/administrator/components/com_users/src/View/Groups/HtmlView.php b/administrator/components/com_users/src/View/Groups/HtmlView.php index 6f41696ed6389..199f79c31bac3 100644 --- a/administrator/components/com_users/src/View/Groups/HtmlView.php +++ b/administrator/components/com_users/src/View/Groups/HtmlView.php @@ -116,6 +116,11 @@ protected function addToolbar() $toolbar->divider(); } + if ($canDo->get('core.trash')) { + $toolbar->archive('groups.trash') + ->message('JTRASHED'); + } + if ($canDo->get('core.admin') || $canDo->get('core.options')) { $toolbar->preferences('com_users'); $toolbar->divider(); diff --git a/administrator/components/com_users/src/View/Levels/HtmlView.php b/administrator/components/com_users/src/View/Levels/HtmlView.php index 7f06fac278c38..f0bd424207a55 100644 --- a/administrator/components/com_users/src/View/Levels/HtmlView.php +++ b/administrator/components/com_users/src/View/Levels/HtmlView.php @@ -116,6 +116,11 @@ protected function addToolbar() $toolbar->divider(); } + if ($canDo->get('core.trash')) { + $toolbar->trash('level.trash') + ->message('JTRASHED'); + } + if ($canDo->get('core.admin') || $canDo->get('core.options')) { $toolbar->preferences('com_users'); $toolbar->divider(); diff --git a/administrator/components/com_users/src/View/Notes/HtmlView.php b/administrator/components/com_users/src/View/Notes/HtmlView.php index ca945fbd99bbe..38a3c713dbb92 100644 --- a/administrator/components/com_users/src/View/Notes/HtmlView.php +++ b/administrator/components/com_users/src/View/Notes/HtmlView.php @@ -157,6 +157,7 @@ protected function addToolbar() $childBar->unpublish('notes.unpublish')->listCheck(true); $childBar->archive('notes.archive')->listCheck(true); $childBar->checkin('notes.checkin')->listCheck(true); + $childBar->trash('notes.trash'); } if ($this->state->get('filter.published') != -2 && $canDo->get('core.edit.state')) { diff --git a/administrator/components/com_users/src/View/Users/HtmlView.php b/administrator/components/com_users/src/View/Users/HtmlView.php index f3cd707f52a4a..e8c0bef4c5265 100644 --- a/administrator/components/com_users/src/View/Users/HtmlView.php +++ b/administrator/components/com_users/src/View/Users/HtmlView.php @@ -173,6 +173,10 @@ protected function addToolbar() ->message('JGLOBAL_CONFIRM_DELETE') ->listCheck(true); } + + if ($canDo->get('core.trash')) { + $childBar->trash('user.trash', 'JTOOLBAR_TRASH'); + } } if ($canDo->get('core.admin') || $canDo->get('core.options')) { diff --git a/administrator/components/com_users/tmpl/users/default.php b/administrator/components/com_users/tmpl/users/default.php index b02e1b66fe5ee..393f8fa430238 100644 --- a/administrator/components/com_users/tmpl/users/default.php +++ b/administrator/components/com_users/tmpl/users/default.php @@ -139,6 +139,9 @@ </td> <td class="text-center d-md-table-cell"> <?php $self = $loggeduser->id == $item->id; ?> + <?php if ($item->block == 2) { + echo '<span class="badge bg-danger">' . Text::_('COM_USERS_TRASHED') . '</span>'; + } ?> <?php if ($canChange) : ?> <?php echo HTMLHelper::_('jgrid.state', HTMLHelper::_('users.blockStates', $self), $item->block, $i, 'users.', !$self); ?> <?php else : ?> @@ -227,3 +230,4 @@ </div> </div> </form> + diff --git a/administrator/language/en-GB/com_users.ini b/administrator/language/en-GB/com_users.ini index 4120dbfd6deaf..d7ee3c7d6c9dd 100644 --- a/administrator/language/en-GB/com_users.ini +++ b/administrator/language/en-GB/com_users.ini @@ -353,6 +353,8 @@ COM_USERS_OPTION_SELECT_COMPONENT="- Select Component -" COM_USERS_OPTION_SELECT_LEVEL_END="- Select End Level -" COM_USERS_OPTION_SELECT_LEVEL_START="- Select Start Level -" COM_USERS_PASSWORD_RESET_REQUIRED="Password Reset Required" +COM_USERS_ERROR_USER_NOT_FOUND="User not found" +COM_USERS_USER_TRASHED_SUCCESS="User %s is trashed successfully" COM_USERS_POSTINSTALL_MULTIFACTORAUTH_ACTION="Enable the new Multi-factor Authentication plugins" COM_USERS_POSTINSTALL_MULTIFACTORAUTH_BODY="<p>Joomla! comes with a drastically improved <a href=\"https://en.wikipedia.org/wiki/Multi-factor_authentication\" target=\"_blank\" rel=\"noopener noreferrer\">Multi-factor Authentication</a> experience to help you secure the logins of your users.</p><p>Unlike the Two Factor Authentication feature in previous versions of Joomla, users <em>no longer have to enter a Security Code with their username and password</em>. The Multi-factor Authentication happens in a separate step after logging into the site. Until they complete their Multi-factor Authentication validation users cannot navigate to other pages or use the site. This makes Multi-factor Authentication <a href=\"https://en.wikipedia.org/wiki/Phishing\" target=\"_blank\" rel=\"noopener noreferrer\">phishing</a>–resistant. It also allows for interactive validation methods like passkeys (including integration with Windows Hello, Apple TouchID / FaceID and Android Biometric Screen Lock), or sending 6-digit authentication codes by email. Both of these interactive, convenient methods are now available as plugins shipped with Joomla! itself.</p>" COM_USERS_POSTINSTALL_MULTIFACTORAUTH_TITLE="Improved Multi-factor Authentication" @@ -384,6 +386,7 @@ COM_USERS_UNACTIVATED="Unactivated" COM_USERS_USERGROUP_DETAILS="User Group Details" COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF="You can't block yourself." COM_USERS_USERS_ERROR_CANNOT_DELETE_SELF="You can't delete yourself." +COM_USERS_USERS_ERROR_CANNOT_TRASH_SELF="You can't trash yourself." COM_USERS_USERS_ERROR_CANNOT_DEMOTE_SELF="You can't remove your own Super User permissions." COM_USERS_USERS_ERROR_CANNOT_EDIT_OWN_GROUP="You can't edit your own user groups. User groups saving was skipped." COM_USERS_USERS_ERROR_CANNOT_REQUIRERESET_SELF="You can't require a Password reset for yourself." diff --git a/administrator/language/en-GB/joomla.ini b/administrator/language/en-GB/joomla.ini index 3433db03d7275..0e3566e2dfc2c 100644 --- a/administrator/language/en-GB/joomla.ini +++ b/administrator/language/en-GB/joomla.ini @@ -183,6 +183,7 @@ JERROR_ALERTNOTEMPLATE="The template for this display is not available." JERROR_AN_ERROR_HAS_OCCURRED="An error has occurred." JERROR_CORE_CREATE_NOT_PERMITTED="Create not permitted." JERROR_CORE_DELETE_NOT_PERMITTED="Delete not permitted." +JERROR_CORE_TRASH_NOT_PERMITTED="Trash not permitted." JERROR_COULD_NOT_FIND_TEMPLATE="Could not find template \"%s\"." JERROR_INVALID_CONTROLLER="Invalid controller" JERROR_INVALID_CONTROLLER_CLASS="Invalid controller class" diff --git a/api/language/en-GB/joomla.ini b/api/language/en-GB/joomla.ini index 7e5cf62b974c4..f09a2925358c2 100644 --- a/api/language/en-GB/joomla.ini +++ b/api/language/en-GB/joomla.ini @@ -181,6 +181,7 @@ JERROR_ALERTNOTEMPLATE="The template for this display is not available." JERROR_AN_ERROR_HAS_OCCURRED="An error has occurred." JERROR_CORE_CREATE_NOT_PERMITTED="Create not permitted." JERROR_CORE_DELETE_NOT_PERMITTED="Delete not permitted." +JERROR_CORE_TRASH_NOT_PERMITTED="Trash not permitted." JERROR_COULD_NOT_FIND_TEMPLATE="Could not find template \"%s\"." JERROR_INVALID_CONTROLLER="Invalid controller" JERROR_INVALID_CONTROLLER_CLASS="Invalid controller class" diff --git a/libraries/src/User/User.php b/libraries/src/User/User.php index ef031c15ddfdb..1a18da2cd573c 100644 --- a/libraries/src/User/User.php +++ b/libraries/src/User/User.php @@ -385,6 +385,11 @@ public function defParam($key, $value) */ public function authorise($action, $assetname = null) { + // Deny access if the user is trashed + if ($this->block == 2) { + return false; + } + // Make sure we only check for core.admin once during the run. if ($this->isRoot === null) { $this->isRoot = false;