diff --git a/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-05-16.sql b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-05-16.sql new file mode 100644 index 0000000000000..fc6649ab0978d --- /dev/null +++ b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-05-16.sql @@ -0,0 +1,5 @@ +-- +-- Add access column for access levels #__users +-- + +ALTER TABLE `#__users` ADD COLUMN `access` int(10) unsigned NOT NULL DEFAULT 0; diff --git a/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-05-16.sql b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-05-16.sql new file mode 100644 index 0000000000000..e1d20120be6ab --- /dev/null +++ b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-05-16.sql @@ -0,0 +1 @@ +ALTER TABLE "#__users" ADD COLUMN "access" bigint NOT NULL DEFAULT 0; diff --git a/administrator/components/com_users/Helper/UsersHelper.php b/administrator/components/com_users/Helper/UsersHelper.php index 5101a36be21a1..5706187c593c4 100644 --- a/administrator/components/com_users/Helper/UsersHelper.php +++ b/administrator/components/com_users/Helper/UsersHelper.php @@ -340,6 +340,7 @@ public static function getContexts() $contexts = array( 'com_users.user' => Text::_('COM_USERS'), + 'com_users.contact' => Text::_('COM_USERS_FIELDS_CONTEXT_CONTACT'), ); return $contexts; diff --git a/administrator/components/com_users/Model/UserModel.php b/administrator/components/com_users/Model/UserModel.php index e48385cdabd84..e96971b91053f 100644 --- a/administrator/components/com_users/Model/UserModel.php +++ b/administrator/components/com_users/Model/UserModel.php @@ -690,6 +690,16 @@ public function batch($commands, $pks, $contexts) $done = true; } + if (!empty($commands['assetgroup_id'])) + { + if (!$this->batchAccess($commands['assetgroup_id'], $pks, $contexts)) + { + return false; + } + + $done = true; + } + if (!$done) { $this->setError(Text::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION')); diff --git a/administrator/components/com_users/Model/UsersModel.php b/administrator/components/com_users/Model/UsersModel.php index 10ad9aa9f3cda..6c691c76d14cb 100644 --- a/administrator/components/com_users/Model/UsersModel.php +++ b/administrator/components/com_users/Model/UsersModel.php @@ -54,6 +54,7 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu 'range', 'lastvisitrange', 'state', + 'access', 'a.access', 'access_level', ); } @@ -101,6 +102,9 @@ protected function populateState($ordering = 'a.name', $direction = 'asc') $this->setState('filter.excluded', $excluded); + $access = $app->input->getInt('access'); + $this->setState('filter.access', $access); + // Load the parameters. $params = ComponentHelper::getParams('com_users'); $this->setState('params', $params); @@ -130,6 +134,7 @@ protected function getStoreId($id = '') $id .= ':' . $this->getState('filter.state'); $id .= ':' . $this->getState('filter.group_id'); $id .= ':' . $this->getState('filter.range'); + $id .= ':' . $this->getState('filter.access'); return parent::getStoreId($id); } @@ -300,6 +305,10 @@ protected function getListQuery() } } + // Join over the asset groups. + $query->select($db->quoteName('ag.title') . ' AS access_level') + ->leftJoin('#__viewlevels AS ag ON ag.id = a.access'); + // Filter the items over the group id if set. $groupId = $this->getState('filter.group_id'); $groups = $this->getState('filter.groups'); @@ -325,7 +334,8 @@ protected function getListQuery() 'a.resetCount', 'a.otpKey', 'a.otep', - 'a.requireReset' + 'a.requireReset', + 'a.access' ) ) ); diff --git a/administrator/components/com_users/View/Users/HtmlView.php b/administrator/components/com_users/View/Users/HtmlView.php index 3b17c45cc70c7..682512f595dc8 100644 --- a/administrator/components/com_users/View/Users/HtmlView.php +++ b/administrator/components/com_users/View/Users/HtmlView.php @@ -202,6 +202,7 @@ protected function getSortFields() 'a.username' => Text::_('JGLOBAL_USERNAME'), 'a.block' => Text::_('COM_USERS_HEADING_ENABLED'), 'a.activation' => Text::_('COM_USERS_HEADING_ACTIVATED'), + 'access_level' => Text::_('JGRID_HEADING_ACCESS'), 'a.email' => Text::_('JGLOBAL_EMAIL'), 'a.lastvisitDate' => Text::_('COM_USERS_HEADING_LAST_VISIT_DATE'), 'a.registerDate' => Text::_('COM_USERS_HEADING_REGISTRATION_DATE'), diff --git a/administrator/components/com_users/config.xml b/administrator/components/com_users/config.xml index a5a7b3d8e41dc..a8c76809c0045 100644 --- a/administrator/components/com_users/config.xml +++ b/administrator/components/com_users/config.xml @@ -184,6 +184,112 @@ /> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
@@ -213,7 +319,7 @@
+ description="COM_USERS_MASS_MAIL_DESC" > + > -
- -
- -
-
-
diff --git a/administrator/components/com_users/forms/user.xml b/administrator/components/com_users/forms/user.xml index 11de26f71648d..9bd52d833d4c0 100644 --- a/administrator/components/com_users/forms/user.xml +++ b/administrator/components/com_users/forms/user.xml @@ -139,6 +139,13 @@ readonly="true" /> + +
@@ -203,5 +210,82 @@
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ diff --git a/administrator/components/com_users/tmpl/users/default.php b/administrator/components/com_users/tmpl/users/default.php index 64e822971a5c8..dfce0cd516ef6 100644 --- a/administrator/components/com_users/tmpl/users/default.php +++ b/administrator/components/com_users/tmpl/users/default.php @@ -60,6 +60,9 @@ + + + @@ -148,6 +151,9 @@ group_names); ?> + + escape($item->access_level); ?> + escape($item->email)); ?> diff --git a/administrator/components/com_users/tmpl/users/default_batch_body.php b/administrator/components/com_users/tmpl/users/default_batch_body.php index 442f32240d0aa..0a459f0b2986b 100644 --- a/administrator/components/com_users/tmpl/users/default_batch_body.php +++ b/administrator/components/com_users/tmpl/users/default_batch_body.php @@ -39,6 +39,9 @@ +
+ +
diff --git a/administrator/language/en-GB/en-GB.com_users.ini b/administrator/language/en-GB/en-GB.com_users.ini index 562f512ff71dd..8d00d68f84e8f 100644 --- a/administrator/language/en-GB/en-GB.com_users.ini +++ b/administrator/language/en-GB/en-GB.com_users.ini @@ -15,13 +15,22 @@ COM_USERS_BATCH_SET="Set To Group" COM_USERS_CATEGORIES_TITLE="User Notes: Categories" COM_USERS_CATEGORY_HEADING="Category" COM_USERS_CONFIG_FIELD_ALLOWREGISTRATION_LABEL="Allow User Registration" +COM_USERS_CONFIG_FIELD_BANNED_EMAIL_DESC="Separate multiple email addresses with a semicolon." +COM_USERS_CONFIG_FIELD_BANNED_EMAIL_LABEL="Banned Email Address" +COM_USERS_CONFIG_FIELD_BANNED_SUBJECT_DESC=" Separate multiple subjects with a semicolon." +COM_USERS_CONFIG_FIELD_BANNED_SUBJECT_LABEL="Banned Subject" +COM_USERS_CONFIG_FIELD_BANNED_TEXT_DESC="Separate multiple words with a semicolon." +COM_USERS_CONFIG_FIELD_BANNED_TEXT_LABEL="Banned Text" COM_USERS_CONFIG_FIELD_CAPTCHA_LABEL="Captcha" COM_USERS_CONFIG_FIELD_CHANGEUSERNAME_LABEL="Change Username" +COM_USERS_CONFIG_FIELD_CONTACT_FORM="Form" +COM_USERS_CONFIG_FIELD_CUSTOM_REPLY_LABEL="Custom Reply" COM_USERS_CONFIG_FIELD_FRONTEND_LANG_LABEL="Frontend Language" COM_USERS_CONFIG_FIELD_FRONTEND_RESET_COUNT_LABEL="Maximum Reset Count" COM_USERS_CONFIG_FIELD_FRONTEND_RESET_TIME_LABEL="Reset Time" COM_USERS_CONFIG_FIELD_FRONTEND_USERPARAMS_LABEL="Frontend User Parameters" COM_USERS_CONFIG_FIELD_GUEST_USER_GROUP_LABEL="Guest User Group" +COM_USERS_CONFIG_FIELD_INDIVIDUAL_CONTACT_DESC="These settings apply for all Users unless they are changed for a specific User." COM_USERS_CONFIG_FIELD_MAILBODY_SUFFIX_LABEL="Mailbody Suffix" COM_USERS_CONFIG_FIELD_MAILTOADMIN_LABEL="Send Mail to Administrators" COM_USERS_CONFIG_FIELD_MINIMUM_INTEGERS="Minimum Integers" @@ -68,6 +77,16 @@ COM_USERS_ERROR_NO_ADDITIONS="The selected user(s) are already assigned to the s COM_USERS_ERROR_VIEW_LEVEL_IN_USE="You can't delete the view access level '%d:%s' because it is being used by content." 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_CATEGORY_ID_LABEL="Category" +COM_USERS_FIELD_CONFIG_SESSION_CHECK_LABEL="Session Check" +COM_USERS_FIELD_EMAIL_BANNED_EMAIL_DESC="Email addresses not allowed to submit contact form. Separate multiple email addresses with a semicolon." +COM_USERS_FIELD_EMAIL_BANNED_EMAIL_LABEL="Banned Email" +COM_USERS_FIELD_EMAIL_BANNED_SUBJECT_DESC="Subjects not allowed in contact form. Separate multiple subjects with a semicolon." +COM_USERS_FIELD_EMAIL_BANNED_SUBJECT_LABEL="Banned Subject" +COM_USERS_FIELD_EMAIL_BANNED_TEXT_DESC="Text not allowed in contact form body. Separate multiple words with a semicolon." +COM_USERS_FIELD_EMAIL_BANNED_TEXT_LABEL="Banned Text" +COM_USERS_FIELD_EMAIL_EMAIL_COPY_LABEL="Send Copy to Submitter" +COM_USERS_FIELD_EMAIL_SHOW_FORM_LABEL="Contact Form" +COM_USERS_FIELD_CONFIG_REDIRECT_LABEL="Contact Redirect URL" COM_USERS_FIELD_ID_LABEL="ID" COM_USERS_FIELD_LOGIN_MENUITEM="Menu Item" COM_USERS_FIELD_LOGIN_REDIRECT_PLACEHOLDER="index.php?Itemid=999&lang=en-GB" @@ -83,9 +102,11 @@ COM_USERS_FIELD_NOTEBODY_LABEL="Note" COM_USERS_FIELD_REVIEW_TIME_LABEL="Review Date" COM_USERS_FIELD_SUBJECT_LABEL="Subject" COM_USERS_FIELD_USER_ID_LABEL="User" +COM_USERS_FIELDS_CONTEXT_CONTACT="Contact" COM_USERS_FIELDS_USER_FIELDS_TITLE="Users: Fields" COM_USERS_FIELDS_USER_FIELD_ADD_TITLE="Users: New Field" COM_USERS_FIELDS_USER_FIELD_EDIT_TITLE="Users: Edit Field" +COM_USERS_FIELDSET_CONTACT_LABEL="Form" COM_USERS_FILTER_ACTIVE="- Select Active State -" COM_USERS_FILTER_NOTES="Show notes list" COM_USERS_FILTER_STATE="- Select State -" diff --git a/administrator/language/en-GB/en-GB.com_users.sys.ini b/administrator/language/en-GB/en-GB.com_users.sys.ini index e99cc7c55b9d1..294b428c45033 100644 --- a/administrator/language/en-GB/en-GB.com_users.sys.ini +++ b/administrator/language/en-GB/en-GB.com_users.sys.ini @@ -9,12 +9,16 @@ COM_USERS_CONTENT_TYPE_NOTE="User Notes" COM_USERS_CONTENT_TYPE_USER="User" COM_USERS_GROUPS_VIEW_DEFAULT_DESC="Shows a List of User Groups" COM_USERS_GROUPS_VIEW_DEFAULT_TITLE="User Groups" +COM_USERS_GROUP_LABEL="Group" COM_USERS_GROUP_VIEW_EDIT_DESC="Shows a form to create a new User Group" COM_USERS_GROUP_VIEW_EDIT_TITLE="Create User Group" COM_USERS_LEVELS_VIEW_DEFAULT_DESC="Shows a List of Access Levels" COM_USERS_LEVELS_VIEW_DEFAULT_TITLE="Access Levels" COM_USERS_LEVEL_VIEW_EDIT_DESC="Shows a form to create a new Access Level" COM_USERS_LEVEL_VIEW_EDIT_TITLE="Create Access Level" +COM_USERS_LIST_VIEW_DEFAULT_DESC="Displays a list of available users in a group." +COM_USERS_LIST_VIEW_DEFAULT_OPTION="User List" +COM_USERS_LIST_VIEW_DEFAULT_TITLE="User List" COM_USERS_MAIL_VIEW_DEFAULT_DESC="Shows a form to send mass email to multiple users." COM_USERS_MAIL_VIEW_DEFAULT_TITLE="Mass Mail Users" COM_USERS_NOTES_VIEW_DEFAULT_DESC="Shows a List of User Notes" @@ -24,8 +28,12 @@ COM_USERS_NOTE_VIEW_EDIT_TITLE="Create User Note" COM_USERS_TAGS_CATEGORY="User Note Category" COM_USERS_USERS_VIEW_DEFAULT_DESC="Shows a List of Users" COM_USERS_USERS_VIEW_DEFAULT_TITLE="Users" +COM_USERS_USER_DETAILS_VIEW_DEFAULT_DESC="Displays the details of the chosen user." +COM_USERS_USER_DETAILS_VIEW_DEFAULT_OPTION="User details" +COM_USERS_USER_DETAILS_VIEW_DEFAULT_TITLE="User details" COM_USERS_USER_VIEW_EDIT_DESC="Shows a form to create a new User Account" COM_USERS_USER_VIEW_EDIT_TITLE="Create User" +COM_USERS_XML_DESCRIPTION="Component for managing users." COM_USER_LOGIN_VIEW_DEFAULT_DESC="Displays a login form." COM_USER_LOGIN_VIEW_DEFAULT_OPTION="Login Form" COM_USER_LOGIN_VIEW_DEFAULT_TITLE="Login Form" @@ -47,4 +55,3 @@ COM_USER_REMIND_VIEW_DEFAULT_TITLE="Username Reminder Request" COM_USER_RESET_VIEW_DEFAULT_DESC="Displays a request to reset password." COM_USER_RESET_VIEW_DEFAULT_OPTION="Default" COM_USER_RESET_VIEW_DEFAULT_TITLE="Password Reset" -COM_USERS_XML_DESCRIPTION="Component for managing users." diff --git a/components/com_users/Controller/DisplayController.php b/components/com_users/Controller/DisplayController.php index d8ce47a1a10cc..d6c169ed479ec 100644 --- a/components/com_users/Controller/DisplayController.php +++ b/components/com_users/Controller/DisplayController.php @@ -126,6 +126,14 @@ public function display($cachable = false, $urlparams = false) $model = $this->getModel($vName); break; + case 'users': + $model = $this->getModel($vName); + break; + + case 'user': + $model = $this->getModel($vName); + break; + default: $model = $this->getModel('Login'); break; diff --git a/components/com_users/Controller/UserController.php b/components/com_users/Controller/UserController.php index 9781fd40d0b43..5f8d8a56148c4 100644 --- a/components/com_users/Controller/UserController.php +++ b/components/com_users/Controller/UserController.php @@ -12,20 +12,39 @@ use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Language\Multilanguage; -use Joomla\CMS\MVC\Controller\BaseController; +use Joomla\CMS\MVC\Controller\FormController; use Joomla\CMS\Session\Session; use Joomla\CMS\Router\Route; use Joomla\CMS\Uri\Uri; use Joomla\CMS\Language\Text; use Joomla\CMS\Factory; +use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\String\PunycodeHelper; +use Joomla\CMS\User\User; /** * Registration controller class for Users. * * @since 1.6 */ -class UserController extends BaseController +class UserController extends FormController { + /** + * Method to get a model object, loading it if required. + * + * @param string $name The model name. Optional. + * @param string $prefix The class prefix. Optional. + * @param array $config Configuration array for model. Optional. + * + * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model. + * + * @since 1.6.4 + */ + public function getModel($name = '', $prefix = '', $config = array('ignore_request' => true)) + { + return parent::getModel($name, $prefix, array('ignore_request' => false)); + } + /** * Method to log in a user. * @@ -362,4 +381,195 @@ public function resend() // Check for request forgeries // $this->checkToken('post'); } + + /** + * Method to submit the contact form and send an email. + * + * @return boolean True on success sending the email. False on failure. + * + * @since 1.5.19 + * @throws \Exception + */ + public function submit() + { + // Check for request forgeries. + $this->checkToken(); + + $app = Factory::getApplication(); + $model = $this->getModel('user'); + + $params = ComponentHelper::getParams('com_users'); + + $stub = $this->input->getString('id'); + $id = (int) $stub; + + // Get the data from POST + $data = $this->input->post->get('jform', array(), 'array'); + $contact = $model->getItem($id); + + $params->merge($contact->params); + + // Check for a valid session cookie + if ($params->get('validate_session', 0)) + { + if (Factory::getSession()->getState() !== 'active') + { + $this->app->enqueueMessage(Text::_('JLIB_ENVIRONMENT_SESSION_INVALID'), 'warning'); + + // Save the data in the session. + $this->app->setUserState('com_users.contact.data', $data); + + // Redirect back to the contact form. + $this->setRedirect(Route::_('index.php?option=com_users&view=user&id=' . $stub, false)); + + return false; + } + } + + // Validate the posted data. + $form = $model->getForm(); + + if (!$form) + { + throw new \Exception($model->getError(), 500); + + return false; + } + + if (!$model->validate($form, $data)) + { + $errors = $model->getErrors(); + + foreach ($errors as $error) + { + $errorMessage = $error; + + if ($error instanceof \Exception) + { + $errorMessage = $error->getMessage(); + } + + $app->enqueueMessage($errorMessage, 'error'); + } + + $app->setUserState('com_users.contact.data', $data); + + $this->setRedirect(Route::_('index.php?option=com_users&view=user&id=' . $stub, false)); + + return false; + } + + // Send the email + $sent = false; + + if (!$params->get('custom_reply')) + { + $sent = $this->_sendEmail($data, $contact, $params->get('show_email_copy', 0)); + } + + // Set the success message if it was a success + if (!($sent instanceof \Exception)) + { + $msg = Text::_('COM_USERS_CONTACT_EMAIL_THANKS'); + } + else + { + $msg = ''; + } + + // Flush the data from the session + $this->app->setUserState('com_users.contact.data', null); + + // Redirect if it is set in the parameters, otherwise redirect back to where we came from + if ($contact->params->get('redirect')) + { + $this->setRedirect($contact->params->get('redirect'), $msg); + } + else + { + $this->setRedirect(Route::_('index.php?option=com_users&view=user&id=' . $stub, false), $msg); + } + + return true; + } + + /** + * Method to send an email. + * + * @param array $data The data to send in the email. + * @param \stdClass $contact The user information to send the email to + * @param boolean $copy_email_activated True to send a copy of the email to the user. + * + * @return boolean True on success sending the email, false on failure. + * + * @since 1.6.4 + */ + private function _sendEmail($data, $contact, $copy_email_activated) + { + $app = $this->app; + + if ($contact->email == '' && $contact->id != 0) + { + $contact_user = User::getInstance($contact->id); + $contact->email = $contact_user->get('email'); + } + + $mailfrom = $app->get('mailfrom'); + $fromname = $app->get('fromname'); + $sitename = $app->get('sitename'); + + $name = $data['contact_name']; + $email = PunycodeHelper::emailToPunycode($data['contact_email']); + $subject = $data['contact_subject']; + $body = $data['contact_message']; + + // Prepare email body + $prefix = Text::sprintf('COM_USERS_CONTACT_ENQUIRY_TEXT', Uri::base()); + $body = $prefix . "\n" . $name . ' <' . $email . '>' . "\r\n\r\n" . stripslashes($body); + + // Load the custom fields + if (!empty($data['com_fields']) && $fields = \FieldsHelper::getFields('com_users.contact', $contact, true, $data['com_fields'])) + { + $output = \FieldsHelper::render( + 'com_users.contact', + 'fields.render', + array( + 'context' => 'com_users.contact', + 'item' => $contact, + 'fields' => $fields, + ) + ); + + if ($output) + { + $body .= "\r\n\r\n" . $output; + } + } + + $mail = Factory::getMailer(); + $mail->addRecipient($contact->email); + $mail->addReplyTo($email, $name); + $mail->setSender(array($mailfrom, $fromname)); + $mail->setSubject($sitename . ': ' . $subject); + $mail->setBody($body); + $sent = $mail->Send(); + + // Check whether email copy function activated + if ($copy_email_activated == true && !empty($data['contact_email_copy'])) + { + $copytext = Text::sprintf('COM_USERS_CONTACT_COPYTEXT_OF', $contact->name, $sitename); + $copytext .= "\r\n\r\n" . $body; + $copysubject = Text::sprintf('COM_USERS_CONTACT_COPYSUBJECT_OF', $subject); + + $mail = Factory::getMailer(); + $mail->addRecipient($email); + $mail->addReplyTo($email, $name); + $mail->setSender(array($mailfrom, $fromname)); + $mail->setSubject($copysubject); + $mail->setBody($copytext); + $sent = $mail->Send(); + } + + return $sent; + } } diff --git a/components/com_users/Model/UserModel.php b/components/com_users/Model/UserModel.php new file mode 100644 index 0000000000000..82c9825468fbf --- /dev/null +++ b/components/com_users/Model/UserModel.php @@ -0,0 +1,154 @@ +input->getInt('id'); + $this->setState('user.id', $pk); + + $offset = $app->input->getUInt('limitstart'); + $this->setState('list.offset', $offset); + + // Load the parameters. + $params = $app->getParams(); + $this->setState('params', $params); + } + + /** + * Method to get user data. + * + * @param integer $pk The id of the user. + * + * @return object User instance + * + * @throws \Exception + */ + public function getItem($pk = null) + { + $pk = (!empty($pk)) ? $pk : (int) $this->getState('user.id'); + + if ($this->_item === null) + { + $this->_item = array(); + } + + if (!isset($this->_item[$pk])) + { + $user = User::getInstance($pk); + + if (empty($user)) + { + throw new \Exception(Text::_('COM_USERS_ERROR_USER_NOT_FOUND'), 404); + } + + $loggedUser = Factory::getUser(); + $groups = $loggedUser->getAuthorisedViewLevels(); + + $registry = new Registry($user->params); + $user->params = $this->getState('params'); + $user->params = clone $this->getState('params'); + $user->params->merge($registry); + + // Compute view access permissions. + $user->params->set('access-view', in_array($user->access, $groups)); + + $this->_item[$pk] = $user; + } + + return $this->_item[$pk]; + } + + /** + * Method to get the contact form. + * The base form is loaded from XML and then an event is fired + * + * @param array $data An optional array of data for the form to interrogate. + * @param boolean $loadData True if the form is to load its own data (default case), false if not. + * + * @return \JForm A \JForm object on success, false on failure + * + * @since 1.6 + */ + public function getForm($data = array(), $loadData = true) + { + $form = $this->loadForm('com_users.contact', 'contact', array('control' => 'jform', 'load_data' => true)); + + if (empty($form)) + { + return false; + } + + $user = $this->_item[$this->getState('user.id')]; + + if (!$user->params->get('show_email_copy', 0)) + { + $form->removeField('contact_email_copy'); + } + + return $form; + } + + /** + * Method to get the data that should be injected in the form. + * + * @return array The default data is an empty array. + * + * @since 1.6.2 + * @throws \Exception + */ + protected function loadFormData() + { + $data = (array) Factory::getApplication()->getUserState('com_users.contact.data', array()); + + if (empty($data['language']) && Multilanguage::isEnabled()) + { + $data['language'] = Factory::getLanguage()->getTag(); + } + + $this->preprocessData('com_users.contact', $data); + + return $data; + } +} diff --git a/components/com_users/Model/UsersModel.php b/components/com_users/Model/UsersModel.php new file mode 100644 index 0000000000000..c67fe60674ef5 --- /dev/null +++ b/components/com_users/Model/UsersModel.php @@ -0,0 +1,111 @@ +input->get('id'); + $this->setState('user.group', $groupId); + + parent::populateState($ordering = 'ordering', $direction = 'ASC'); + } + + /** + * Get the list of items. + * + * @return \JDatabaseQuery|\Joomla\Database\DatabaseQuery + * + * @throws \Exception + */ + protected function getListQuery() + { + $app = Factory::getApplication(); + $user = Factory::getUser(); + + // Create a new query object. + $db = $this->getDbo(); + $query = $db->getQuery(true); + + // Select the required fields from the table. + $query->select( + ' a.id, a.name, a.username, a.email, a.access, map.group_id' + ); + + $query->from($db->quoteName('#__users') . ' AS a') + ->leftJoin($db->quoteName('#__user_usergroup_map') . ' AS map ON a.id = map.user_id') + ->where('map.group_id = (' . $this->getState('user.group') . ')'); + + // Filter by access level. + if ($this->getState('filter.access', true)) + { + $groups = implode(',', $user->getAuthorisedViewLevels()); + $query->where('a.access IN (' . $groups . ')'); + } + + $params = $app->getParams(); + $this->setState('params', $params); + + return $query; + } + + /** + * Method to get group name for the current group + * + * @return string + */ + public function getGroup() + { + $db = $this->getDbo(); + $query = $db->getQuery(true); + + $query->select( + 'a.id, a.title' + ); + + $query->from($db->quoteName('#__usergroups') . ' AS a') + ->where('a.id = (' . $this->getState('user.group') . ')'); + + $db->setQuery($query); + + return $db->loadObject(); + } +} diff --git a/components/com_users/Rule/ContactEmailMessageRule.php b/components/com_users/Rule/ContactEmailMessageRule.php new file mode 100644 index 0000000000000..eb7b134310929 --- /dev/null +++ b/components/com_users/Rule/ContactEmailMessageRule.php @@ -0,0 +1,57 @@ + tag for the form field object. + * @param mixed $value The form field value to validate. + * @param string $group The field name group control value. This acts as an array container for the field. + * For example if the field has name="foo" and the group value is set to "bar" then the + * full field name would end up being "bar[foo]". + * @param Registry $input An optional Registry object with the entire data set to validate against the entire form. + * @param Form $form The form object for which the field is being tested. + * + * @return boolean True if the value is valid, false otherwise. + */ + public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null) + { + $params = ComponentHelper::getParams('com_users'); + $banned = $params->get('banned_text'); + + if ($banned) + { + foreach (explode(';', $banned) as $item) + { + if ($item != '' && StringHelper::stristr($value, $item) !== false) + { + return false; + } + } + } + + return true; + } +} diff --git a/components/com_users/Rule/ContactEmailRule.php b/components/com_users/Rule/ContactEmailRule.php new file mode 100644 index 0000000000000..0974b22458ab1 --- /dev/null +++ b/components/com_users/Rule/ContactEmailRule.php @@ -0,0 +1,62 @@ + tag for the form field object. + * @param mixed $value The form field value to validate. + * @param string $group The field name group control value. This acts as an array container for the field. + * For example if the field has name="foo" and the group value is set to "bar" then the + * full field name would end up being "bar[foo]". + * @param Registry $input An optional Registry object with the entire data set to validate against the entire form. + * @param Form $form The form object for which the field is being tested. + * + * @return boolean True if the value is valid, false otherwise. + */ + public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null) + { + if (!parent::test($element, $value, $group, $input, $form)) + { + return false; + } + + $params = ComponentHelper::getParams('com_users'); + $banned = $params->get('banned_email'); + + if ($banned) + { + foreach (explode(';', $banned) as $item) + { + if ($item != '' && StringHelper::stristr($value, $item) !== false) + { + return false; + } + } + } + + return true; + } +} diff --git a/components/com_users/Rule/ContactEmailSubjectRule.php b/components/com_users/Rule/ContactEmailSubjectRule.php new file mode 100644 index 0000000000000..2db8297ac75c5 --- /dev/null +++ b/components/com_users/Rule/ContactEmailSubjectRule.php @@ -0,0 +1,57 @@ + tag for the form field object. + * @param mixed $value The form field value to validate. + * @param string $group The field name group control value. This acts as an array container for the field. + * For example if the field has name="foo" and the group value is set to "bar" then the + * full field name would end up being "bar[foo]". + * @param Registry $input An optional Registry object with the entire data set to validate against the entire form. + * @param Form $form The form object for which the field is being tested. + * + * @return boolean True if the value is valid, false otherwise + */ + public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null) + { + $params = ComponentHelper::getParams('com_users'); + $banned = $params->get('banned_subject'); + + if ($banned) + { + foreach (explode(';', $banned) as $item) + { + if ($item != '' && StringHelper::stristr($value, $item) !== false) + { + return false; + } + } + } + + return true; + } +} diff --git a/components/com_users/View/User/HtmlView.php b/components/com_users/View/User/HtmlView.php new file mode 100644 index 0000000000000..2576bd52b17fe --- /dev/null +++ b/components/com_users/View/User/HtmlView.php @@ -0,0 +1,142 @@ +item = $this->get('Item'); + $this->state = $this->get('State'); + $this->form = $this->get('Form'); + $this->params = $this->state->get('params'); + $user = Factory::getUser(); + + /** + * Check for no 'access-view', + * - Redirect guest users to login + * - Deny access to logged users with 403 code + */ + if ($this->item->params->get('access-view') == false) + { + $app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error'); + $app->setHeader('status', 403, true); + + return; + } + + // Process the content plugins. + PluginHelper::importPlugin('content'); + $offset = $this->state->get('list.offset'); + + $this->item = (object) $this->item; + + // Store the events for later + $this->item->event = new \stdClass; + + $results = $app->triggerEvent('onContentAfterTitle', array('com_users.user', &$this->item, &$this->item->params, $offset)); + $this->item->event->afterDisplayTitle = trim(implode("\n", $results)); + + $results = $app->triggerEvent('onContentBeforeDisplay', array('com_users.user', &$this->item, &$this->item->params, $offset)); + $this->item->event->beforeDisplayContent = trim(implode("\n", $results)); + + $results = $app->triggerEvent('onContentAfterDisplay', array('com_users.user', &$this->item, &$this->item->params, $offset)); + $this->item->event->afterDisplayContent = trim(implode("\n", $results)); + + $captchaSet = $this->item->params->get('captcha', Factory::getApplication()->get('captcha', '0')); + + foreach (PluginHelper::getPlugin('captcha') as $plugin) + { + if ($captchaSet === $plugin->name) + { + $this->captchaEnabled = true; + break; + } + } + + $this->_prepareDocument(); + + return parent::display($tpl); + } + /** + * Prepares the document. + * + * @return void + * + * @throws \Exception + */ + protected function _prepareDocument() + { + $app = Factory::getApplication(); + $pathway = $app->getPathway(); + + $pathway->addItem($this->item->name, ''); + } +} diff --git a/components/com_users/View/Users/HtmlView.php b/components/com_users/View/Users/HtmlView.php new file mode 100644 index 0000000000000..a2a998e450e1f --- /dev/null +++ b/components/com_users/View/Users/HtmlView.php @@ -0,0 +1,139 @@ +items = $this->get('Items'); + $this->state = $this->get('State'); + $this->params = $this->state->get('params'); + $this->group = $this->get('Group'); + + PluginHelper::importPlugin('content'); + + foreach ($this->items as $item) + { + $item->slug = $item->id . ":" . ApplicationHelper::stringURLSafe($item->name); + + // Store the events for later + $item->event = new \stdClass; + + $item->text = ''; + + $app->triggerEvent('onContentPrepare', array('com_users.user', &$item, &$item->params, 0)); + + $results = $app->triggerEvent('onContentAfterTitle', array('com_users.user', &$item, &$item->params, 0)); + $item->event->afterDisplayTitle = trim(implode("\n", $results)); + + $results = $app->triggerEvent('onContentBeforeDisplay', array('com_users.user', &$item, &$item->params, 0)); + $item->event->beforeDisplayContent = trim(implode("\n", $results)); + + $results = $app->triggerEvent('onContentAfterDisplay', array('com_users.user', &$item, &$item->params, 0)); + $item->event->afterDisplayContent = trim(implode("\n", $results)); + } + + $menus = $app->getMenu(); + $menu = $menus->getActive(); + + if ($menu + && $menu->component == 'com_users' + && isset($menu->query['view'], $menu->query['id']) + && $menu->query['view'] == 'users' + && $menu->query['id'] == $this->group->id) + { + $this->params->def('page_heading', $this->params->get('page_title', $menu->title)); + $title = $this->params->get('page_title', $menu->title); + } + else + { + $this->params->def('page_heading', $this->group->title); + $title = $this->group->title; + $this->params->set('page_title', $title); + } + + // Check for empty title and add site name if param is set + if (empty($title)) + { + $title = $app->get('sitename'); + } + elseif ($app->get('sitename_pagetitles', 0) == 1) + { + $title = Text::sprintf('JPAGETITLE', $app->get('sitename'), $title); + } + elseif ($app->get('sitename_pagetitles', 0) == 2) + { + $title = Text::sprintf('JPAGETITLE', $title, $app->get('sitename')); + } + + if (empty($title)) + { + $title = $this->group->title; + } + + $this->document->setTitle($title); + + return parent::display($tpl); + } + +} diff --git a/components/com_users/forms/contact.xml b/components/com_users/forms/contact.xml new file mode 100644 index 0000000000000..f4158fcc39748 --- /dev/null +++ b/components/com_users/forms/contact.xml @@ -0,0 +1,74 @@ + +
+
+ + + + + + + + + + + +
+ +
+ +
+
diff --git a/components/com_users/helpers/route.php b/components/com_users/helpers/route.php index 3fb8b3dcf44ca..fdab5fe3bf6ac 100644 --- a/components/com_users/helpers/route.php +++ b/components/com_users/helpers/route.php @@ -18,7 +18,7 @@ * @since 1.6 * @deprecated 4.0 */ -class UsersHelperRoute +abstract class UsersHelperRoute { /** * Method to get the menu items for the component. @@ -200,4 +200,25 @@ public static function getResetRoute() return null; } + + /** + * Method to get a route configuration for the user view + * + * @param integer $id The route of the user item. + * @param integer $groupId The id of the group. + * @param integer $language The language code. + * + * @return string The user's route + */ + public static function getUserRoute($id, $groupId, $language) + { + $link = 'index.php?option=com_users&view=user&id=' . $id . '&groupId=' . $groupId; + + if ($language && $language !== '*' && JLanguageMultilang::isEnabled()) + { + $link .= '&lang=' . $language; + } + + return $link; + } } diff --git a/components/com_users/layouts/field/render.php b/components/com_users/layouts/field/render.php new file mode 100644 index 0000000000000..9b70c36fa19af --- /dev/null +++ b/components/com_users/layouts/field/render.php @@ -0,0 +1,47 @@ +label); +$value = $field->value; +$class = $field->params->get('render_class'); +$showLabel = $field->params->get('showlabel'); + +if ($field->context == 'com_users.contact') +{ + // Prepare the value for the contact form mail + $value = html_entity_decode($value); + + echo ($showLabel ? $label . ': ' : '') . $value . "\r\n"; + + return; +} + +if (!$value) +{ + return; +} + +?> +
+ + : + +
+
+ +
diff --git a/components/com_users/layouts/fields/render.php b/components/com_users/layouts/fields/render.php new file mode 100644 index 0000000000000..67fea30fecfdb --- /dev/null +++ b/components/com_users/layouts/fields/render.php @@ -0,0 +1,77 @@ +jcfields ?: FieldsHelper::getFields($context, $item, true); +} + +if (!$fields) +{ + return; +} + +// Check if we have contact context in first element +$isMail = (reset($fields)->context == 'com_users.contact'); + +if (!$isMail) +{ + // Print the container tag + echo '
'; +} + +// Loop through the fields and print them +foreach ($fields as $field) +{ + // If the value is empty do nothing + if (empty($field->value) && !$isMail) + { + continue; + } + + echo FieldsHelper::render($context, 'field.render', array('field' => $field)); +} + +if (!$isMail) +{ + // Close the container + echo '
'; +} diff --git a/components/com_users/router.php b/components/com_users/router.php index 5de01dca6495b..c5916d24611bf 100644 --- a/components/com_users/router.php +++ b/components/com_users/router.php @@ -15,6 +15,7 @@ use Joomla\CMS\Component\Router\Rules\MenuRules; use Joomla\CMS\Component\Router\Rules\NomenuRules; use Joomla\CMS\Component\Router\Rules\StandardRules; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Menu\AbstractMenu; /** @@ -24,6 +25,8 @@ */ class UsersRouter extends RouterView { + protected $noIDs = false; + /** * Users Component router constructor * @@ -40,10 +43,72 @@ public function __construct($app = null, $menu = null) $this->registerView(new RouterViewConfiguration('remind')); $this->registerView(new RouterViewConfiguration('reset')); + $users = new RouterViewConfiguration('users'); + $users->setKey('id'); + $this->registerView($users); + + $user = new RouterViewConfiguration('user'); + $user->setKey('id')->setParent($users, 'groupId'); + $this->registerView($user); + parent::__construct($app, $menu); $this->attachRule(new MenuRules($this)); $this->attachRule(new StandardRules($this)); $this->attachRule(new NomenuRules($this)); } + + /** + * Method to get the segment(s) for a category + * + * @param string $id ID of the category to retrieve the segments for + * @param array $query The request that is built right now + * + * @return array|string The segments of this item + */ + public function getUsersSegment($id, $query) + { + return array((int) $id => $id); + } + + /** + * Method to get the segment(s) for an user + * + * @param string $id ID of the user to retrieve the segments for + * @param array $query The request that is built right now + * + * @return array|string The segments of this item + */ + public function getUserSegment($id, $query) + { + if ($this->noIDs) + { + list($void, $segment) = explode(':', $id, 2); + + return array($void => $segment); + } + + return array((int) $id => $id); + } + + /** + * Method to get the segment(s) for an user + * + * @param string $segment Segment of the user to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getUserId($segment, $query) + { + + if ($this->noIDs) + { + list($id, $segmentList) = explode('-', $segment, 2); + + return (int) $id; + } + + return (int) $segment; + } } diff --git a/components/com_users/tmpl/user/default.php b/components/com_users/tmpl/user/default.php new file mode 100644 index 0000000000000..3922e935736ef --- /dev/null +++ b/components/com_users/tmpl/user/default.php @@ -0,0 +1,35 @@ +item->params; +?> +
+ + + item->event->afterDisplayTitle; ?> + + item->event->beforeDisplayContent; ?> + +
escape($this->item->username); ?>
+
escape($this->item->email); ?>
+ + get('show_email_form')) : ?> + loadTemplate('form'); ?> + + + item->event->afterDisplayContent; ?> +
+ + diff --git a/components/com_users/tmpl/user/default.xml b/components/com_users/tmpl/user/default.xml new file mode 100644 index 0000000000000..ee1d06467b945 --- /dev/null +++ b/components/com_users/tmpl/user/default.xml @@ -0,0 +1,16 @@ + + + + + + + + +
+ +
+
+
diff --git a/components/com_users/tmpl/user/default_form.php b/components/com_users/tmpl/user/default_form.php new file mode 100644 index 0000000000000..1907c54faff03 --- /dev/null +++ b/components/com_users/tmpl/user/default_form.php @@ -0,0 +1,47 @@ + +
+
+ form->getFieldsets() as $fieldset) : ?> + name === 'captcha' && !$this->captchaEnabled) : ?> + + + form->getFieldset($fieldset->name); ?> + +
+ label) && ($legend = trim(Text::_($fieldset->label))) !== '') : ?> + + + + renderField(); ?> + +
+ + +
+
+ + + + +
+
+
+
diff --git a/components/com_users/tmpl/users/default.php b/components/com_users/tmpl/users/default.php new file mode 100644 index 0000000000000..9883c300fe7c4 --- /dev/null +++ b/components/com_users/tmpl/users/default.php @@ -0,0 +1,46 @@ + + +params->get('show_page_heading')) : ?> + + + +params->get('show_group_title', 1)) : ?> +

+ group->title; ?> +

+ + +items as $item) : ?> +
+
+
+

+ +

+ event->afterDisplayTitle; ?> + event->beforeDisplayContent; ?> + event->afterDisplayContent; ?> +
+
+
+ diff --git a/components/com_users/tmpl/users/default.xml b/components/com_users/tmpl/users/default.xml new file mode 100644 index 0000000000000..2357771ff8f3f --- /dev/null +++ b/components/com_users/tmpl/users/default.xml @@ -0,0 +1,34 @@ + + + + + + + + +
+ + +
+
+ + + +
+ + + + +
+
+
+ diff --git a/installation/sql/mysql/joomla.sql b/installation/sql/mysql/joomla.sql index 3583b9bcc54ad..cafc6e5e3e3c1 100644 --- a/installation/sql/mysql/joomla.sql +++ b/installation/sql/mysql/joomla.sql @@ -1817,6 +1817,7 @@ CREATE TABLE IF NOT EXISTS `#__users` ( `otpKey` varchar(1000) NOT NULL DEFAULT '' COMMENT 'Two factor authentication encrypted keys', `otep` varchar(1000) NOT NULL DEFAULT '' COMMENT 'One time emergency passwords', `requireReset` tinyint(4) NOT NULL DEFAULT 0 COMMENT 'Require user to reset password on next login', + `access` int(10) unsigned NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_name` (`name`(100)), KEY `idx_block` (`block`), diff --git a/installation/sql/postgresql/joomla.sql b/installation/sql/postgresql/joomla.sql index 932acf9b484e3..f946289b1ad6f 100644 --- a/installation/sql/postgresql/joomla.sql +++ b/installation/sql/postgresql/joomla.sql @@ -1821,6 +1821,7 @@ CREATE TABLE IF NOT EXISTS "#__users" ( "otpKey" varchar(1000) DEFAULT '' NOT NULL, "otep" varchar(1000) DEFAULT '' NOT NULL, "requireReset" smallint DEFAULT 0, + "access" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id") ); CREATE INDEX "#__users_idx_name" ON "#__users" ("name"); diff --git a/language/en-GB/en-GB.com_users.ini b/language/en-GB/en-GB.com_users.ini index ed9c063c98b86..d0f58ffafc30b 100644 --- a/language/en-GB/en-GB.com_users.ini +++ b/language/en-GB/en-GB.com_users.ini @@ -6,7 +6,20 @@ 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_CONTACT_COPYSUBJECT_OF="Copy of: %s" +COM_USERS_CONTACT_COPYTEXT_OF="This is a copy of the following message you sent to %s via %s" +COM_USERS_CONTACT_DEFAULT_LABEL="Send an Email" +COM_USERS_CONTACT_EMAIL_A_COPY_LABEL="Send a copy to yourself" +COM_USERS_CONTACT_EMAIL_NAME_LABEL="Name" +COM_USERS_CONTACT_EMAIL_THANKS="Thank you for your email." +COM_USERS_CONTACT_ENQUIRY_TEXT="This is an enquiry email via %s from:" +COM_USERS_CONTACT_ENTER_MESSAGE_LABEL="Message" +COM_USERS_CONTACT_MESSAGE_SUBJECT_LABEL="Subject" +COM_USERS_CONTACT_REPLY_EMAIL_LABEL="Reply to email" +COM_USERS_CONTACT_REQUIRED="* Required field" +COM_USERS_CONTACT_SEND="Send Email" COM_USERS_DATABASE_ERROR="Error getting the user from the database: %s" +COM_USERS_DEFAULT_PAGE_TITLE="Users" COM_USERS_DESIRED_USERNAME="Enter your desired username." COM_USERS_EDIT_PROFILE="Edit Profile" COM_USERS_EMAIL_ACCOUNT_DETAILS="Account Details for %s at %s" @@ -26,6 +39,7 @@ COM_USERS_EMAIL_REGISTERED_WITH_ADMIN_ACTIVATION_BODY_NOPW="Hello %s,\n\nThank y 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_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_ERROR_USER_NOT_FOUND="User not found" 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."