diff --git a/administrator/components/com_users/models/user.php b/administrator/components/com_users/models/user.php index aa7cc87572d42..901825bcfade6 100644 --- a/administrator/components/com_users/models/user.php +++ b/administrator/components/com_users/models/user.php @@ -375,8 +375,23 @@ public function delete(&$pks) // Get users data for the users to delete. $user_to_delete = JFactory::getUser($pk); - // Fire the before delete event. - $dispatcher->trigger($this->event_before_delete, array($table->getProperties())); + // Fire the before delete events. + $content = $dispatcher->trigger($this->event_before_delete, array($user_to_delete->getProperties())); + $proceed = true; + + foreach ($content[0] as $result) + { + if ($result->success) + { + JFactory::getApplication()->enqueueMessage($result->message, 'error'); + $proceed = false; + } + } + + if (!$proceed) + { + return false; + } if (!$table->delete($pk)) { diff --git a/administrator/language/en-GB/en-GB.com_users.ini b/administrator/language/en-GB/en-GB.com_users.ini index 892e5d615f4ed..fc0add644db82 100644 --- a/administrator/language/en-GB/en-GB.com_users.ini +++ b/administrator/language/en-GB/en-GB.com_users.ini @@ -82,6 +82,7 @@ COM_USERS_EDIT_USER="Edit User %s" COM_USERS_EMPTY_REVIEW="-" COM_USERS_EMPTY_SUBJECT="- No subject -" COM_USERS_ERROR_CANNOT_BATCH_SUPERUSER="A non-Super User can't perform batch operations on Super Users." +COM_USERS_ERROR_CANNOT_DELETE_CONTENT="The selected user(s) have content. Delete user(s) articles before" COM_USERS_ERROR_INVALID_GROUP="Invalid Group" COM_USERS_ERROR_LEVELS_NOLEVELS_SELECTED="No View Permission Level(s) selected." COM_USERS_ERROR_NO_ADDITIONS="The selected user(s) are already assigned to the selected group." @@ -380,3 +381,7 @@ JLIB_RULES_SETTING_NOTES="Changes apply to this component only.
getModel($vName); break; + case 'delete': + // If the user is not logged in, redirect to the login page. + $user = JFactory::getUser(); + + if (($user->get('guest') == 1) && ($lName == 'default')) + { + // Redirect to login page. + $this->setRedirect(JRoute::_('index.php?option=com_users&view=login', false)); + + return; + } + + $model = $this->getModel($vName); + break; + default: $model = $this->getModel('Login'); break; diff --git a/components/com_users/controllers/delete.php b/components/com_users/controllers/delete.php new file mode 100644 index 0000000000000..8b4fcb2107e77 --- /dev/null +++ b/components/com_users/controllers/delete.php @@ -0,0 +1,61 @@ +checkToken('post'); + + $model = $this->getModel('Delete', 'UsersModel'); + $data = $this->input->post->get('jform', array(), 'array'); + + // Submit the user delete request. + $return = $model->processDeleteRequest($data); + + // Check for errors. + if ($return == false) + { + $message = JText::sprintf('COM_USERS_DELETE_REQUEST_FAILED', $model->getError()); + // The request failed. + // Go back to the request form. + if (!$model->getError()) + { + $message = $this->setError(JText::_('COM_USERS_ERROR_CANNOT_DELETE_CONTENT')); + } + $this->setRedirect(JRoute::_('index.php?option=com_users&view=delete', false), $message, 'warning'); + + return false; + } + else + { + // The request succeeded. + $this->setRedirect(JRoute::_('index.php?option=com_users&view=delete&layout=complete', false)); + + return true; + } + } +} diff --git a/components/com_users/helpers/legacyrouter.php b/components/com_users/helpers/legacyrouter.php index a1e421ca60a34..9e7ab392b1761 100644 --- a/components/com_users/helpers/legacyrouter.php +++ b/components/com_users/helpers/legacyrouter.php @@ -66,6 +66,7 @@ public function build(&$query, &$segments) static $remind; static $resend; static $reset; + static $delete; // Get the relevant menu items if not loaded. if (empty($items)) @@ -111,6 +112,12 @@ public function build(&$query, &$segments) { $profile = $items[$i]->id; } + + // Check to see if we have found the delete menu item. + if (empty($delete) && !empty($items[$i]->query['view']) && $items[$i]->query['view'] === 'delete') + { + $delete = $items[$i]->id; + } } // Set the default menu item to use for com_users if possible. @@ -187,6 +194,17 @@ public function build(&$query, &$segments) } break; + case 'delete': + if ($query['Itemid'] = $delete) + { + unset ($query['view']); + } + else + { + $query['Itemid'] = $default; + } + break; + default: case 'profile': if (!empty($query['view'])) diff --git a/components/com_users/helpers/route.php b/components/com_users/helpers/route.php index 646370a3c182d..0caaace49ba61 100644 --- a/components/com_users/helpers/route.php +++ b/components/com_users/helpers/route.php @@ -210,4 +210,31 @@ public static function getResetRoute() return $itemid; } + + /** + * Method to get a route configuration for the delete view. + * + * @return mixed Integer menu id on success, null on failure. + * + * @since __DEPLOY_VERSION__ + * @deprecated 4.0 + */ + public static function getDeleteRoute() + { + // Get the items. + $items = self::getItems(); + $itemid = null; + + // Search for a suitable menu id. + foreach ($items as $item) + { + if (isset($item->query['view']) && $item->query['view'] === 'delete') + { + $itemid = $item->id; + break; + } + } + + return $itemid; + } } diff --git a/components/com_users/models/delete.php b/components/com_users/models/delete.php new file mode 100644 index 0000000000000..46c4063b5b4e0 --- /dev/null +++ b/components/com_users/models/delete.php @@ -0,0 +1,178 @@ +loadForm('com_users.delete', 'delete', array('control' => 'jform', 'load_data' => $loadData)); + + if (empty($form)) + { + return false; + } + + return $form; + } + + /** + * Override preprocessForm to load the user plugin group instead of content. + * + * @param JForm $form A JForm object. + * @param mixed $data The data expected for the form. + * @param string $group The name of the plugin group to import (defaults to "content"). + * + * @return void + * + * @throws Exception if there is an error in the form event. + * + * @since __DEPLOY_VERSION__ + */ + protected function preprocessForm(JForm $form, $data, $group = 'user') + { + parent::preprocessForm($form, $data, 'user'); + } + + /** + * Method to auto-populate the model state. + * + * Note. Calling getState in this method will result in recursion. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function populateState() + { + // Get the application object. + $app = JFactory::getApplication(); + $params = $app->getParams('com_users'); + + // Load the parameters. + $this->setState('params', $params); + } + + /** + * Process the delete username account + * + * @param array $data Array with the data received from the form + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function processDeleteRequest($data) + { + // Get the form. + $form = $this->getForm(); + $data['email'] = JStringPunycode::emailToPunycode($data['email']); + + // Check for an error. + if (empty($form)) + { + return false; + } + + // Validate the data. + $data = $this->validate($form, $data); + + // Check for an error. + if ($data instanceof Exception) + { + return false; + } + + // Check the validation results. + if ($data === false) + { + // Get the validation messages from the form. + foreach ($form->getErrors() as $formError) + { + $this->setError($formError->getMessage()); + } + + return false; + } + + // Check the user id for the given email address. + if (JFactory::getUser()->email !== $data['email']) + { + $this->setError(JText::_('COM_USERS_USER_NOT_FOUND')); + + return false; + } + + // Check if I am a Super Admin + if (JFactory::getUser()->authorise('core.admin')) + { + $this->setError(JText::_('COM_USERS_ERROR_CANNOT_DELETE_SUPERUSER')); + + return false; + } + + JPluginHelper::importPlugin('user'); + $dispatcher = JEventDispatcher::getInstance(); + $table = JTable::getInstance('User'); + + // Get user data for the user to delete. + $user_to_delete = JFactory::getUser(JFactory::getUser()->id); + + // Fire the before delete events. + $content = $dispatcher->trigger('onUserBeforeDelete', array($user_to_delete->getProperties())); + $proceed = true; + + foreach ($content[0] as $result) + { + if ($result->success) + { + JFactory::getApplication()->enqueueMessage($result->message, 'error'); + $proceed = false; + } + } + + if (!$proceed) + { + return false; + } + + if (!$table->delete(JFactory::getUser()->id)) + { + $this->setError($table->getError()); + + return false; + } + else + { + // Trigger the after delete event. + $dispatcher->trigger('onUserAfterDelete', array($user_to_delete->getProperties(), true, $this->getError())); + } + + return true; + } +} diff --git a/components/com_users/models/forms/delete.xml b/components/com_users/models/forms/delete.xml new file mode 100644 index 0000000000000..7735aa11426b0 --- /dev/null +++ b/components/com_users/models/forms/delete.xml @@ -0,0 +1,22 @@ + +
+
+ + +
+
diff --git a/components/com_users/router.php b/components/com_users/router.php index 5ed16fe5f3631..3241d4300efcd 100644 --- a/components/com_users/router.php +++ b/components/com_users/router.php @@ -31,6 +31,7 @@ public function __construct($app = null, $menu = null) $this->registerView(new JComponentRouterViewconfiguration('registration')); $this->registerView(new JComponentRouterViewconfiguration('remind')); $this->registerView(new JComponentRouterViewconfiguration('reset')); + $this->registerView(new JComponentRouterViewconfiguration('delete')); parent::__construct($app, $menu); diff --git a/components/com_users/views/delete/tmpl/complete.php b/components/com_users/views/delete/tmpl/complete.php new file mode 100644 index 0000000000000..b7acc5dd8773f --- /dev/null +++ b/components/com_users/views/delete/tmpl/complete.php @@ -0,0 +1,20 @@ + +
+ params->get('show_page_heading')) : ?> +

+ escape($this->params->get('page_heading')); ?> +

+ +
+

diff --git a/components/com_users/views/delete/tmpl/default.php b/components/com_users/views/delete/tmpl/default.php new file mode 100644 index 0000000000000..377473cebb538 --- /dev/null +++ b/components/com_users/views/delete/tmpl/default.php @@ -0,0 +1,51 @@ + +
+ params->get('show_page_heading')) : ?> + + +
+ form->getFieldsets() as $fieldset) : ?> +
+

label); ?>

+ form->getFieldset($fieldset->name) as $name => $field) : ?> + hidden === false) : ?> +
+
+ label; ?> +
+
+ input; ?> +
+
+ + +
+ +
+
+ +
+
+ +
+
diff --git a/components/com_users/views/delete/tmpl/default.xml b/components/com_users/views/delete/tmpl/default.xml new file mode 100644 index 0000000000000..dfbdbab09213f --- /dev/null +++ b/components/com_users/views/delete/tmpl/default.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/components/com_users/views/delete/view.html.php b/components/com_users/views/delete/view.html.php new file mode 100644 index 0000000000000..dc53dfcfea3b2 --- /dev/null +++ b/components/com_users/views/delete/view.html.php @@ -0,0 +1,122 @@ +form = $this->get('Form'); + $this->state = $this->get('State'); + $this->params = $this->state->params; + + // Check for errors. + if (count($errors = $this->get('Errors'))) + { + JError::raiseError(500, implode('
', $errors)); + + return false; + } + + // Check for layout override + $active = JFactory::getApplication()->getMenu()->getActive(); + + if (isset($active->query['layout'])) + { + $this->setLayout($active->query['layout']); + } + + // Escape strings for HTML output + $this->pageclass_sfx = htmlspecialchars($this->params->get('pageclass_sfx'), ENT_COMPAT, 'UTF-8'); + + $this->prepareDocument(); + + parent::display($tpl); + } + + /** + * Prepares the document. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function prepareDocument() + { + $app = JFactory::getApplication(); + $menus = $app->getMenu(); + + // Because the application sets a default page title, + // we need to get it from the menu item itself + $menu = $menus->getActive(); + + if ($menu) + { + $this->params->def('page_heading', $this->params->get('page_title', $menu->title)); + } + else + { + $this->params->def('page_heading', JText::_('COM_USERS_DELETE')); + } + + $title = $this->params->get('page_title', ''); + + if (empty($title)) + { + $title = $app->get('sitename'); + } + elseif ($app->get('sitename_pagetitles', 0) == 1) + { + $title = JText::sprintf('JPAGETITLE', $app->get('sitename'), $title); + } + elseif ($app->get('sitename_pagetitles', 0) == 2) + { + $title = JText::sprintf('JPAGETITLE', $title, $app->get('sitename')); + } + + $this->document->setTitle($title); + + if ($this->params->get('menu-meta_description')) + { + $this->document->setDescription($this->params->get('menu-meta_description')); + } + + if ($this->params->get('menu-meta_keywords')) + { + $this->document->setMetadata('keywords', $this->params->get('menu-meta_keywords')); + } + + if ($this->params->get('robots')) + { + $this->document->setMetadata('robots', $this->params->get('robots')); + } + } +} diff --git a/language/en-GB/en-GB.com_users.ini b/language/en-GB/en-GB.com_users.ini index d567219ba8930..029aefc65cbee 100644 --- a/language/en-GB/en-GB.com_users.ini +++ b/language/en-GB/en-GB.com_users.ini @@ -7,6 +7,11 @@ COM_USERS_ACTIVATION_TOKEN_NOT_FOUND="Verification code not found." COM_USERS_CAPTCHA_LABEL="Captcha" COM_USERS_CAPTCHA_DESC="Please complete the security check." COM_USERS_DATABASE_ERROR="Error getting the user from the database: %s" +COM_USERS_DELETE="Delete" +COM_USERS_DELETE_CONFIRM="Are you sure you want to delete? Confirming will permanently delete your account." +COM_USERS_DELETE_DEFAULT_LABEL="Please enter the email address associated with your User account." +COM_USERS_DELETE_REQUEST_FAILED="Delete failed: %s" +COM_USERS_DELETE_REQUEST_SUCCESS="Deleted" COM_USERS_DESIRED_PASSWORD="Enter your desired password." COM_USERS_DESIRED_USERNAME="Enter your desired username." COM_USERS_EDIT_PROFILE="Edit Profile" @@ -26,7 +31,11 @@ COM_USERS_EMAIL_REGISTERED_WITH_ADMIN_ACTIVATION_BODY="Hello %s,\n\nThank you fo COM_USERS_EMAIL_REGISTERED_WITH_ADMIN_ACTIVATION_BODY_NOPW="Hello %s,\n\nThank you for registering at %s. Your account is created and must be verified before you can use it.\nTo verify the account select the following link or copy-paste it in your browser:\n %s \n\nAfter verification an administrator will be notified to activate your account. You'll receive a confirmation when it's done.\nOnce that account has been activated you may login to %s using the following username and the password you entered during registration:\n\nUsername: %s" COM_USERS_EMAIL_USERNAME_REMINDER_BODY="Hello,\n\nA username reminder has been requested for your %s account.\n\nYour username is %s.\n\nTo login to your account, select the link below.\n\n%s \n\nThank you." COM_USERS_EMAIL_USERNAME_REMINDER_SUBJECT="Your %s username" +COM_USERS_ERROR_CANNOT_DELETE_CONTENT="The selected user(s) have content. Delete user(s) articles before" +COM_USERS_ERROR_CANNOT_DELETE_SUPERUSER="Cannot delete SuperUser" COM_USERS_ERROR_SECRET_CODE_WITHOUT_TFA="You have entered a Secret Code but two factor authentication is not enabled in your user account. If you want to use a secret code to secure your login please edit your user profile and enable two factor authentication." +COM_USERS_FIELD_DELETE_EMAIL_DESC="Please enter the email address associated with your User account to proceed with deletion." +COM_USERS_FIELD_DELETE_EMAIL_LABEL="Email Address" COM_USERS_FIELD_PASSWORD_RESET_DESC="Please enter the email address associated with your User account.
A verification code will be sent to you. Once you have received the verification code, you will be able to choose a new password for your account." COM_USERS_FIELD_PASSWORD_RESET_LABEL="Email Address" COM_USERS_FIELD_REMIND_EMAIL_DESC="Please enter the email address associated with your User account.
Your username will be emailed to the email address on file." diff --git a/plugins/user/joomla/joomla.php b/plugins/user/joomla/joomla.php index 59f8ebbfbf496..9072e9c9c8afe 100644 --- a/plugins/user/joomla/joomla.php +++ b/plugins/user/joomla/joomla.php @@ -392,4 +392,44 @@ protected function _getUser($user, $options = array()) return $instance; } + + /** + * Check existence of content items for the user name + * + * Method is called before user data is deleted from the database + * + * @param array $user Holds the user data + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function onUserBeforeDelete($user) + { + JPluginHelper::importPlugin('userdelete'); + $i = 0; + $response = array(); + $response[0] = new stdClass; + $response[0]->success = false; + $response[0]->message = 'delete'; + + // Trigger the userdelete events + $responses = (array) $this->app->triggerEvent('onSystemUserBeforeDelete', array($user)); + + if (($responses !== false) && (count($responses) > 0)) + { + foreach ($responses as $result) + { + if ($result['success']) + { + $i++; + $response[$i] = new stdClass; + $response[$i]->message = $result['message']; + $response[$i]->success = true; + } + } + } + + return $response; + } } diff --git a/plugins/userdelete/contact/contact.php b/plugins/userdelete/contact/contact.php new file mode 100644 index 0000000000000..006b4e1fa028d --- /dev/null +++ b/plugins/userdelete/contact/contact.php @@ -0,0 +1,74 @@ +loadLanguage(); + + $response = array(); + $response['success'] = true; + $response['message'] = JText::_('PLG_USERDELETE_CONTACT_MESSAGE'); + + $query = $this->db->getQuery(true) + ->select($this->db->quoteName('id')) + ->from($this->db->quoteName('#__contact_details')) + ->where($this->db->quoteName('created_by') . ' = ' . (int) $user['id']) + ->orWhere($this->db->quoteName('modified_by') . ' = ' . (int) $user['id']) + ->orWhere($this->db->quoteName('user_id') . ' = ' . (int) $user['id']); + $this->db->setQuery($query); + + try + { + $items = $this->db->loadRowList(); + } + catch (JDatabaseExceptionExecuting $e) + { + return $response; + } + + if (($items !== false) && (count($items) > 0)) + { + // The user have contacts + return $response; + } + + $response['success'] = false; + return $response; + } +} diff --git a/plugins/userdelete/contact/contact.xml b/plugins/userdelete/contact/contact.xml new file mode 100644 index 0000000000000..bee2ec6ee71a9 --- /dev/null +++ b/plugins/userdelete/contact/contact.xml @@ -0,0 +1,23 @@ + + + plg_userdelete_contact + Joomla! Project + December 2017 + Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + 3.8.0 + PLG_USERDELETE_CONTACT_XML_DESCRIPTION + + contact.php + + + en-GB.plg_userdelete_contact.ini + en-GB.plg_userdelete_contact.sys.ini + + + + + + \ No newline at end of file diff --git a/plugins/userdelete/content/content.php b/plugins/userdelete/content/content.php new file mode 100644 index 0000000000000..0239bc797e182 --- /dev/null +++ b/plugins/userdelete/content/content.php @@ -0,0 +1,74 @@ +loadLanguage(); + + $response = array(); + $response['success'] = true; + $response['message'] = JText::_('PLG_USERDELETE_CONTENT_MESSAGE'); + + $query = $this->db->getQuery(true) + ->select($this->db->quoteName('id')) + ->from($this->db->quoteName('#__content')) + ->where($this->db->quoteName('created_by') . ' = ' . (int) $user['id']) + ->orWhere($this->db->quoteName('modified_by') . ' = ' . (int) $user['id']); + $this->db->setQuery($query); + + try + { + $items = $this->db->loadRowList(); + } + catch (JDatabaseExceptionExecuting $e) + { + return $response; + } + + if (($items !== false) && (count($items) > 0)) + { + // The user have contents + return $response; + } + + $response['success'] = false; + return $response; + } +} diff --git a/plugins/userdelete/content/content.xml b/plugins/userdelete/content/content.xml new file mode 100644 index 0000000000000..3b4793d3a2989 --- /dev/null +++ b/plugins/userdelete/content/content.xml @@ -0,0 +1,23 @@ + + + plg_userdelete_content + Joomla! Project + December 2017 + Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + 3.8.0 + PLG_USERDELETE_CONTENT_XML_DESCRIPTION + + content.php + + + en-GB.plg_userdelete_content.ini + en-GB.plg_userdelete_content.sys.ini + + + + + + \ No newline at end of file