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;
+?>
+
+
+
+
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."