* @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Site\Model; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Date\Date; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Form\FormFactoryInterface; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\Mail\MailTemplate; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\FormModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Router\Route; use Joomla\CMS\String\PunycodeHelper; use Joomla\CMS\Uri\Uri; use Joomla\CMS\User\User; use Joomla\CMS\User\UserHelper; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Registration model class for Users. * * @since 1.6 */ class RegistrationModel extends FormModel { /** * @var object The user registration data. * @since 1.6 */ protected $data; /** * Constructor. * * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request). * @param MVCFactoryInterface $factory The factory. * @param FormFactoryInterface $formFactory The form factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = array(), MVCFactoryInterface $factory = null, FormFactoryInterface $formFactory = null) { $config = array_merge( array( 'events_map' => array('validate' => 'user') ), $config ); parent::__construct($config, $factory, $formFactory); } /** * Method to get the user ID from the given token * * @param string $token The activation token. * * @return mixed False on failure, id of the user on success * * @since 3.8.13 */ public function getUserIdFromToken($token) { $db = $this->getDatabase(); // Get the user id based on the token. $query = $db->getQuery(true); $query->select($db->quoteName('id')) ->from($db->quoteName('#__users')) ->where($db->quoteName('activation') . ' = :activation') ->where($db->quoteName('block') . ' = 1') ->where($db->quoteName('lastvisitDate') . ' IS NULL') ->bind(':activation', $token); $db->setQuery($query); try { return (int) $db->loadResult(); } catch (\RuntimeException $e) { $this->setError(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage())); return false; } } /** * Method to activate a user account. * * @param string $token The activation token. * * @return mixed False on failure, user object on success. * * @since 1.6 */ public function activate($token) { $app = Factory::getApplication(); $userParams = ComponentHelper::getParams('com_users'); $userId = $this->getUserIdFromToken($token); // Check for a valid user id. if (!$userId) { $this->setError(Text::_('COM_USERS_ACTIVATION_TOKEN_NOT_FOUND')); return false; } // Load the users plugin group. PluginHelper::importPlugin('user'); // Activate the user. $user = Factory::getUser($userId); // Admin activation is on and user is verifying their email if (($userParams->get('useractivation') == 2) && !$user->getParam('activate', 0)) { $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE; // Compile the admin notification mail values. $data = $user->getProperties(); $data['activation'] = ApplicationHelper::getHash(UserHelper::genRandomPassword()); $user->set('activation', $data['activation']); $data['siteurl'] = Uri::base(); $data['activate'] = Route::link( 'site', 'index.php?option=com_users&task=registration.activate&token=' . $data['activation'], false, $linkMode, true ); $data['fromname'] = $app->get('fromname'); $data['mailfrom'] = $app->get('mailfrom'); $data['sitename'] = $app->get('sitename'); $user->setParam('activate', 1); // Get all admin users $db = $this->getDatabase(); $query = $db->getQuery(true) ->select($db->quoteName(array('name', 'email', 'sendEmail', 'id'))) ->from($db->quoteName('#__users')) ->where($db->quoteName('sendEmail') . ' = 1') ->where($db->quoteName('block') . ' = 0'); $db->setQuery($query); try { $rows = $db->loadObjectList(); } catch (\RuntimeException $e) { $this->setError(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage())); return false; } // Send mail to all users with users creating permissions and receiving system emails foreach ($rows as $row) { $usercreator = Factory::getUser($row->id); if ($usercreator->authorise('core.create', 'com_users') && $usercreator->authorise('core.manage', 'com_users')) { try { $mailer = new MailTemplate('com_users.registration.admin.verification_request', $app->getLanguage()->getTag()); $mailer->addTemplateData($data); $mailer->addRecipient($row->email); $return = $mailer->send(); } catch (\Exception $exception) { try { Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); $return = false; } catch (\RuntimeException $exception) { Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); $return = false; } } // Check for an error. if ($return !== true) { $this->setError(Text::_('COM_USERS_REGISTRATION_ACTIVATION_NOTIFY_SEND_MAIL_FAILED')); return false; } } } } elseif (($userParams->get('useractivation') == 2) && $user->getParam('activate', 0)) { // Admin activation is on and admin is activating the account $user->set('activation', ''); $user->set('block', '0'); // Compile the user activated notification mail values. $data = $user->getProperties(); $user->setParam('activate', 0); $data['fromname'] = $app->get('fromname'); $data['mailfrom'] = $app->get('mailfrom'); $data['sitename'] = $app->get('sitename'); $data['siteurl'] = Uri::base(); $mailer = new MailTemplate('com_users.registration.user.admin_activated', $app->getLanguage()->getTag()); $mailer->addTemplateData($data); $mailer->addRecipient($data['email']); try { $return = $mailer->send(); } catch (\Exception $exception) { try { Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); $return = false; } catch (\RuntimeException $exception) { Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); $return = false; } } // Check for an error. if ($return !== true) { $this->setError(Text::_('COM_USERS_REGISTRATION_ACTIVATION_NOTIFY_SEND_MAIL_FAILED')); return false; } } else { $user->set('activation', ''); $user->set('block', '0'); } // Store the user object. if (!$user->save()) { $this->setError(Text::sprintf('COM_USERS_REGISTRATION_ACTIVATION_SAVE_FAILED', $user->getError())); return false; } return $user; } /** * Method to get the registration form data. * * The base form data is loaded and then an event is fired * for users plugins to extend the data. * * @return mixed Data object on success, false on failure. * * @since 1.6 * @throws \Exception */ public function getData() { if ($this->data === null) { $this->data = new \stdClass(); $app = Factory::getApplication(); $params = ComponentHelper::getParams('com_users'); // Override the base user data with any data in the session. $temp = (array) $app->getUserState('com_users.registration.data', array()); // Don't load the data in this getForm call, or we'll call ourself $form = $this->getForm(array(), false); foreach ($temp as $k => $v) { // Here we could have a grouped field, let's check it if (is_array($v)) { $this->data->$k = new \stdClass(); foreach ($v as $key => $val) { if ($form->getField($key, $k) !== false) { $this->data->$k->$key = $val; } } } elseif ($form->getField($k) !== false) { // Only merge the field if it exists in the form. $this->data->$k = $v; } } // Get the groups the user should be added to after registration. $this->data->groups = array(); // Get the default new user group, guest or public group if not specified. $system = $params->get('new_usertype', $params->get('guest_usergroup', 1)); $this->data->groups[] = $system; // Unset the passwords. unset($this->data->password1, $this->data->password2); // Get the dispatcher and load the users plugins. PluginHelper::importPlugin('user'); // Trigger the data preparation event. Factory::getApplication()->triggerEvent('onContentPrepareData', array('com_users.registration', $this->data)); } return $this->data; } /** * Method to get the registration form. * * The base form is loaded from XML and then an event is fired * for users plugins to extend the form with extra fields. * * @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 Form A Form object on success, false on failure * * @since 1.6 */ public function getForm($data = array(), $loadData = true) { // Get the form. $form = $this->loadForm('com_users.registration', 'registration', array('control' => 'jform', 'load_data' => $loadData)); if (empty($form)) { return false; } // When multilanguage is set, a user's default site language should also be a Content Language if (Multilanguage::isEnabled()) { $form->setFieldAttribute('language', 'type', 'frontend_language', 'params'); } return $form; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 1.6 */ protected function loadFormData() { $data = $this->getData(); if (Multilanguage::isEnabled() && empty($data->language)) { $data->language = Factory::getLanguage()->getTag(); } $this->preprocessData('com_users.registration', $data); return $data; } /** * Override preprocessForm to load the user plugin group instead of content. * * @param Form $form A Form 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 * * @since 1.6 * @throws \Exception if there is an error in the form event. */ protected function preprocessForm(Form $form, $data, $group = 'user') { $userParams = ComponentHelper::getParams('com_users'); // Add the choice for site language at registration time if ($userParams->get('site_language') == 1 && $userParams->get('frontend_userparams') == 1) { $form->loadFile('sitelang', false); } parent::preprocessForm($form, $data, $group); } /** * Method to auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 1.6 * @throws \Exception */ protected function populateState() { // Get the application object. $app = Factory::getApplication(); $params = $app->getParams('com_users'); // Load the parameters. $this->setState('params', $params); } /** * Method to save the form data. * * @param array $temp The form data. * * @return mixed The user id on success, false on failure. * * @since 1.6 * @throws \Exception */ public function register($temp) { $params = ComponentHelper::getParams('com_users'); // Initialise the table with Joomla\CMS\User\User. $user = new User(); $data = (array) $this->getData(); // Merge in the registration data. foreach ($temp as $k => $v) { $data[$k] = $v; } // Prepare the data for the user object. $data['email'] = PunycodeHelper::emailToPunycode($data['email1']); $data['password'] = $data['password1']; $useractivation = $params->get('useractivation'); $sendpassword = $params->get('sendpassword', 1); // Check if the user needs to activate their account. if (($useractivation == 1) || ($useractivation == 2)) { $data['activation'] = ApplicationHelper::getHash(UserHelper::genRandomPassword()); $data['block'] = 1; } // Bind the data. if (!$user->bind($data)) { $this->setError($user->getError()); return false; } // Load the users plugin group. PluginHelper::importPlugin('user'); // Store the data. if (!$user->save()) { $this->setError(Text::sprintf('COM_USERS_REGISTRATION_SAVE_FAILED', $user->getError())); return false; } $app = Factory::getApplication(); $db = $this->getDatabase(); $query = $db->getQuery(true); // Compile the notification mail values. $data = $user->getProperties(); $data['fromname'] = $app->get('fromname'); $data['mailfrom'] = $app->get('mailfrom'); $data['sitename'] = $app->get('sitename'); $data['siteurl'] = Uri::root(); // Handle account activation/confirmation emails. if ($useractivation == 2) { // Set the link to confirm the user email. $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE; $data['activate'] = Route::link( 'site', 'index.php?option=com_users&task=registration.activate&token=' . $data['activation'], false, $linkMode, true ); $mailtemplate = 'com_users.registration.user.admin_activation'; } elseif ($useractivation == 1) { // Set the link to activate the user account. $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE; $data['activate'] = Route::link( 'site', 'index.php?option=com_users&task=registration.activate&token=' . $data['activation'], false, $linkMode, true ); $mailtemplate = 'com_users.registration.user.self_activation'; } else { $mailtemplate = 'com_users.registration.user.registration_mail'; } if ($sendpassword) { $mailtemplate .= '_w_pw'; } // Try to send the registration email. try { $mailer = new MailTemplate($mailtemplate, $app->getLanguage()->getTag()); $mailer->addTemplateData($data); $mailer->addRecipient($data['email']); $return = $mailer->send(); } catch (\Exception $exception) { try { Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); $return = false; } catch (\RuntimeException $exception) { Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); $this->setError(Text::_('COM_MESSAGES_ERROR_MAIL_FAILED')); $return = false; } } // Send mail to all users with user creating permissions and receiving system emails if (($params->get('useractivation') < 2) && ($params->get('mail_to_admin') == 1)) { // Get all admin users $query->clear() ->select($db->quoteName(array('name', 'email', 'sendEmail', 'id'))) ->from($db->quoteName('#__users')) ->where($db->quoteName('sendEmail') . ' = 1') ->where($db->quoteName('block') . ' = 0'); $db->setQuery($query); try { $rows = $db->loadObjectList(); } catch (\RuntimeException $e) { $this->setError(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage())); return false; } // Send mail to all superadministrators id foreach ($rows as $row) { $usercreator = Factory::getUser($row->id); if (!$usercreator->authorise('core.create', 'com_users') || !$usercreator->authorise('core.manage', 'com_users')) { continue; } try { $mailer = new MailTemplate('com_users.registration.admin.new_notification', $app->getLanguage()->getTag()); $mailer->addTemplateData($data); $mailer->addRecipient($row->email); $return = $mailer->send(); } catch (\Exception $exception) { try { Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); $return = false; } catch (\RuntimeException $exception) { Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); $return = false; } } // Check for an error. if ($return !== true) { $this->setError(Text::_('COM_USERS_REGISTRATION_ACTIVATION_NOTIFY_SEND_MAIL_FAILED')); return false; } } } // Check for an error. if ($return !== true) { $this->setError(Text::_('COM_USERS_REGISTRATION_SEND_MAIL_FAILED')); // Send a system message to administrators receiving system mails $db = $this->getDatabase(); $query->clear() ->select($db->quoteName('id')) ->from($db->quoteName('#__users')) ->where($db->quoteName('block') . ' = 0') ->where($db->quoteName('sendEmail') . ' = 1'); $db->setQuery($query); try { $userids = $db->loadColumn(); } catch (\RuntimeException $e) { $this->setError(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage())); return false; } if (count($userids) > 0) { $jdate = new Date(); $dateToSql = $jdate->toSql(); $subject = Text::_('COM_USERS_MAIL_SEND_FAILURE_SUBJECT'); $message = Text::sprintf('COM_USERS_MAIL_SEND_FAILURE_BODY', $data['username']); // Build the query to add the messages foreach ($userids as $userid) { $values = [ ':user_id_from', ':user_id_to', ':date_time', ':subject', ':message', ]; $query->clear() ->insert($db->quoteName('#__messages')) ->columns($db->quoteName(['user_id_from', 'user_id_to', 'date_time', 'subject', 'message'])) ->values(implode(',', $values)); $query->bind(':user_id_from', $userid, ParameterType::INTEGER) ->bind(':user_id_to', $userid, ParameterType::INTEGER) ->bind(':date_time', $dateToSql) ->bind(':subject', $subject) ->bind(':message', $message); $db->setQuery($query); try { $db->execute(); } catch (\RuntimeException $e) { $this->setError(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage())); return false; } } } return false; } if ($useractivation == 1) { return 'useractivate'; } elseif ($useractivation == 2) { return 'adminactivate'; } else { return $user->id; } } }