[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Plugin 5 * @subpackage User.joomla 6 * 7 * @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org> 8 * @license GNU General Public License version 2 or later; see LICENSE.txt 9 10 * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace 11 */ 12 13 use Joomla\CMS\Component\ComponentHelper; 14 use Joomla\CMS\Factory; 15 use Joomla\CMS\Language\LanguageFactoryInterface; 16 use Joomla\CMS\Language\Text; 17 use Joomla\CMS\Log\Log; 18 use Joomla\CMS\Mail\MailTemplate; 19 use Joomla\CMS\Plugin\CMSPlugin; 20 use Joomla\CMS\Uri\Uri; 21 use Joomla\CMS\User\User; 22 use Joomla\CMS\User\UserHelper; 23 use Joomla\Database\Exception\ExecutionFailureException; 24 use Joomla\Database\ParameterType; 25 use Joomla\Registry\Registry; 26 27 // phpcs:disable PSR1.Files.SideEffects 28 \defined('_JEXEC') or die; 29 // phpcs:enable PSR1.Files.SideEffects 30 31 /** 32 * Joomla User plugin 33 * 34 * @since 1.5 35 */ 36 class PlgUserJoomla extends CMSPlugin 37 { 38 /** 39 * @var \Joomla\CMS\Application\CMSApplication 40 * 41 * @since 3.2 42 */ 43 protected $app; 44 45 /** 46 * @var \Joomla\Database\DatabaseDriver 47 * 48 * @since 3.2 49 */ 50 protected $db; 51 52 /** 53 * Set as required the passwords fields when mail to user is set to No 54 * 55 * @param \Joomla\CMS\Form\Form $form The form to be altered. 56 * @param mixed $data The associated data for the form. 57 * 58 * @return boolean 59 * 60 * @since 4.0.0 61 */ 62 public function onContentPrepareForm($form, $data) 63 { 64 // Check we are manipulating a valid user form before modifying it. 65 $name = $form->getName(); 66 67 if ($name === 'com_users.user') { 68 // In case there is a validation error (like duplicated user), $data is an empty array on save. 69 // After returning from error, $data is an array but populated 70 if (!$data) { 71 $data = Factory::getApplication()->input->get('jform', array(), 'array'); 72 } 73 74 if (is_array($data)) { 75 $data = (object) $data; 76 } 77 78 // Passwords fields are required when mail to user is set to No 79 if (empty($data->id) && !$this->params->get('mail_to_user', 1)) { 80 $form->setFieldAttribute('password', 'required', 'true'); 81 $form->setFieldAttribute('password2', 'required', 'true'); 82 } 83 } 84 85 return true; 86 } 87 88 /** 89 * Remove all sessions for the user name 90 * 91 * Method is called after user data is deleted from the database 92 * 93 * @param array $user Holds the user data 94 * @param boolean $success True if user was successfully stored in the database 95 * @param string $msg Message 96 * 97 * @return void 98 * 99 * @since 1.6 100 */ 101 public function onUserAfterDelete($user, $success, $msg): void 102 { 103 if (!$success) { 104 return; 105 } 106 107 $userId = (int) $user['id']; 108 109 // Only execute this if the session metadata is tracked 110 if ($this->app->get('session_metadata', true)) { 111 UserHelper::destroyUserSessions($userId, true); 112 } 113 114 try { 115 $this->db->setQuery( 116 $this->db->getQuery(true) 117 ->delete($this->db->quoteName('#__messages')) 118 ->where($this->db->quoteName('user_id_from') . ' = :userId') 119 ->bind(':userId', $userId, ParameterType::INTEGER) 120 )->execute(); 121 } catch (ExecutionFailureException $e) { 122 // Do nothing. 123 } 124 125 // Delete Multi-factor Authentication user profile records 126 $profileKey = 'mfa.%'; 127 $query = $this->db->getQuery(true) 128 ->delete($this->db->quoteName('#__user_profiles')) 129 ->where($this->db->quoteName('user_id') . ' = :userId') 130 ->where($this->db->quoteName('profile_key') . ' LIKE :profileKey') 131 ->bind(':userId', $userId, ParameterType::INTEGER) 132 ->bind(':profileKey', $profileKey, ParameterType::STRING); 133 134 try { 135 $this->db->setQuery($query)->execute(); 136 } catch (Exception $e) { 137 // Do nothing 138 } 139 140 // Delete Multi-factor Authentication records 141 $query = $this->db->getQuery(true) 142 ->delete($this->db->qn('#__user_mfa')) 143 ->where($this->db->quoteName('user_id') . ' = :userId') 144 ->bind(':userId', $userId, ParameterType::INTEGER); 145 146 try { 147 $this->db->setQuery($query)->execute(); 148 } catch (Exception $e) { 149 // Do nothing 150 } 151 } 152 153 /** 154 * Utility method to act on a user after it has been saved. 155 * 156 * This method sends a registration email to new users created in the backend. 157 * 158 * @param array $user Holds the new user data. 159 * @param boolean $isnew True if a new user is stored. 160 * @param boolean $success True if user was successfully stored in the database. 161 * @param string $msg Message. 162 * 163 * @return void 164 * 165 * @since 1.6 166 */ 167 public function onUserAfterSave($user, $isnew, $success, $msg): void 168 { 169 $mail_to_user = $this->params->get('mail_to_user', 1); 170 171 if (!$isnew || !$mail_to_user) { 172 return; 173 } 174 175 // @todo: Suck in the frontend registration emails here as well. Job for a rainy day. 176 // The method check here ensures that if running as a CLI Application we don't get any errors 177 if (method_exists($this->app, 'isClient') && ($this->app->isClient('site') || $this->app->isClient('cli'))) { 178 return; 179 } 180 181 // Check if we have a sensible from email address, if not bail out as mail would not be sent anyway 182 if (strpos($this->app->get('mailfrom'), '@') === false) { 183 $this->app->enqueueMessage(Text::_('JERROR_SENDING_EMAIL'), 'warning'); 184 185 return; 186 } 187 188 $defaultLanguage = Factory::getLanguage(); 189 $defaultLocale = $defaultLanguage->getTag(); 190 191 /** 192 * Look for user language. Priority: 193 * 1. User frontend language 194 * 2. User backend language 195 */ 196 $userParams = new Registry($user['params']); 197 $userLocale = $userParams->get('language', $userParams->get('admin_language', $defaultLocale)); 198 199 // Temporarily set application language to user's language. 200 if ($userLocale !== $defaultLocale) { 201 Factory::$language = Factory::getContainer() 202 ->get(LanguageFactoryInterface::class) 203 ->createLanguage($userLocale, $this->app->get('debug_lang', false)); 204 } 205 206 // Load plugin language files. 207 $this->loadLanguage(); 208 209 // Collect data for mail 210 $data = [ 211 'name' => $user['name'], 212 'sitename' => $this->app->get('sitename'), 213 'url' => Uri::root(), 214 'username' => $user['username'], 215 'password' => $user['password_clear'], 216 'email' => $user['email'], 217 ]; 218 219 $mailer = new MailTemplate('plg_user_joomla.mail', $userLocale); 220 $mailer->addTemplateData($data); 221 $mailer->addRecipient($user['email'], $user['name']); 222 223 try { 224 $res = $mailer->send(); 225 } catch (\Exception $exception) { 226 try { 227 Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); 228 229 $res = false; 230 } catch (\RuntimeException $exception) { 231 $this->app->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); 232 233 $res = false; 234 } 235 } 236 237 if ($res === false) { 238 $this->app->enqueueMessage(Text::_('JERROR_SENDING_EMAIL'), 'warning'); 239 } 240 241 // Set application language back to default if we changed it 242 if ($userLocale !== $defaultLocale) { 243 Factory::$language = $defaultLanguage; 244 } 245 } 246 247 /** 248 * This method should handle any login logic and report back to the subject 249 * 250 * @param array $user Holds the user data 251 * @param array $options Array holding options (remember, autoregister, group) 252 * 253 * @return boolean True on success 254 * 255 * @since 1.5 256 */ 257 public function onUserLogin($user, $options = []) 258 { 259 $instance = $this->_getUser($user, $options); 260 261 // If _getUser returned an error, then pass it back. 262 if ($instance instanceof Exception) { 263 return false; 264 } 265 266 // If the user is blocked, redirect with an error 267 if ($instance->block == 1) { 268 $this->app->enqueueMessage(Text::_('JERROR_NOLOGIN_BLOCKED'), 'warning'); 269 270 return false; 271 } 272 273 // Authorise the user based on the group information 274 if (!isset($options['group'])) { 275 $options['group'] = 'USERS'; 276 } 277 278 // Check the user can login. 279 $result = $instance->authorise($options['action']); 280 281 if (!$result) { 282 $this->app->enqueueMessage(Text::_('JERROR_LOGIN_DENIED'), 'warning'); 283 284 return false; 285 } 286 287 // Mark the user as logged in 288 $instance->guest = 0; 289 290 // Load the logged in user to the application 291 $this->app->loadIdentity($instance); 292 293 $session = $this->app->getSession(); 294 295 // Grab the current session ID 296 $oldSessionId = $session->getId(); 297 298 // Fork the session 299 $session->fork(); 300 301 // Register the needed session variables 302 $session->set('user', $instance); 303 304 // Update the user related fields for the Joomla sessions table if tracking session metadata. 305 if ($this->app->get('session_metadata', true)) { 306 $this->app->checkSession(); 307 } 308 309 // Purge the old session 310 $query = $this->db->getQuery(true) 311 ->delete($this->db->quoteName('#__session')) 312 ->where($this->db->quoteName('session_id') . ' = :sessionid') 313 ->bind(':sessionid', $oldSessionId); 314 315 try { 316 $this->db->setQuery($query)->execute(); 317 } catch (RuntimeException $e) { 318 // The old session is already invalidated, don't let this block logging in 319 } 320 321 // Hit the user last visit field 322 $instance->setLastVisit(); 323 324 // Add "user state" cookie used for reverse caching proxies like Varnish, Nginx etc. 325 if ($this->app->isClient('site')) { 326 $this->app->input->cookie->set( 327 'joomla_user_state', 328 'logged_in', 329 0, 330 $this->app->get('cookie_path', '/'), 331 $this->app->get('cookie_domain', ''), 332 $this->app->isHttpsForced(), 333 true 334 ); 335 } 336 337 return true; 338 } 339 340 /** 341 * This method should handle any logout logic and report back to the subject 342 * 343 * @param array $user Holds the user data. 344 * @param array $options Array holding options (client, ...). 345 * 346 * @return boolean True on success 347 * 348 * @since 1.5 349 */ 350 public function onUserLogout($user, $options = []) 351 { 352 $my = Factory::getUser(); 353 $session = Factory::getSession(); 354 355 $userid = (int) $user['id']; 356 357 // Make sure we're a valid user first 358 if ($user['id'] === 0 && !$my->get('tmp_user')) { 359 return true; 360 } 361 362 $sharedSessions = $this->app->get('shared_session', '0'); 363 364 // Check to see if we're deleting the current session 365 if ($my->id == $userid && ($sharedSessions || (!$sharedSessions && $options['clientid'] == $this->app->getClientId()))) { 366 // Hit the user last visit field 367 $my->setLastVisit(); 368 369 // Destroy the php session for this user 370 $session->destroy(); 371 } 372 373 // Enable / Disable Forcing logout all users with same userid, but only if session metadata is tracked 374 $forceLogout = $this->params->get('forceLogout', 1) && $this->app->get('session_metadata', true); 375 376 if ($forceLogout) { 377 $clientId = $sharedSessions ? null : (int) $options['clientid']; 378 UserHelper::destroyUserSessions($user['id'], false, $clientId); 379 } 380 381 // Delete "user state" cookie used for reverse caching proxies like Varnish, Nginx etc. 382 if ($this->app->isClient('site')) { 383 $this->app->input->cookie->set('joomla_user_state', '', 1, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain', '')); 384 } 385 386 return true; 387 } 388 389 /** 390 * Hooks on the Joomla! login event. Detects silent logins and disables the Multi-Factor 391 * Authentication page in this case. 392 * 393 * Moreover, it will save the redirection URL and the Captive URL which is necessary in Joomla 4. You see, in Joomla 394 * 4 having unified sessions turned on makes the backend login redirect you to the frontend of the site AFTER 395 * logging in, something which would cause the Captive page to appear in the frontend and redirect you to the public 396 * frontend homepage after successfully passing the Two Step verification process. 397 * 398 * @param array $options Passed by Joomla. user: a User object; responseType: string, authentication response type. 399 * 400 * @return void 401 * @since 4.2.0 402 */ 403 public function onUserAfterLogin(array $options): void 404 { 405 if (!($this->app->isClient('administrator')) && !($this->app->isClient('site'))) { 406 return; 407 } 408 409 $this->disableMfaOnSilentLogin($options); 410 } 411 412 /** 413 * Detect silent logins and disable MFA if the relevant com_users option is set. 414 * 415 * @param array $options The array of login options and login result 416 * 417 * @return void 418 * @since 4.2.0 419 */ 420 private function disableMfaOnSilentLogin(array $options): void 421 { 422 $userParams = ComponentHelper::getParams('com_users'); 423 $doMfaOnSilentLogin = $userParams->get('mfaonsilent', 0) == 1; 424 425 // Should I show MFA even on silent logins? Default: 1 (yes, show) 426 if ($doMfaOnSilentLogin) { 427 return; 428 } 429 430 // Make sure I have a valid user 431 /** @var User $user */ 432 $user = $options['user']; 433 434 if (!is_object($user) || !($user instanceof User) || $user->guest) { 435 return; 436 } 437 438 $silentResponseTypes = array_map( 439 'trim', 440 explode(',', $userParams->get('silentresponses', '') ?: '') 441 ); 442 $silentResponseTypes = $silentResponseTypes ?: ['cookie', 'passwordless']; 443 444 // Only proceed if this is not a silent login 445 if (!in_array(strtolower($options['responseType'] ?? ''), $silentResponseTypes)) { 446 return; 447 } 448 449 // Set the flag indicating that MFA is already checked. 450 $this->app->getSession()->set('com_users.mfa_checked', 1); 451 } 452 453 /** 454 * This method will return a user object 455 * 456 * If options['autoregister'] is true, if the user doesn't exist yet they will be created 457 * 458 * @param array $user Holds the user data. 459 * @param array $options Array holding options (remember, autoregister, group). 460 * 461 * @return User 462 * 463 * @since 1.5 464 */ 465 protected function _getUser($user, $options = []) 466 { 467 $instance = User::getInstance(); 468 $id = (int) UserHelper::getUserId($user['username']); 469 470 if ($id) { 471 $instance->load($id); 472 473 return $instance; 474 } 475 476 // @todo : move this out of the plugin 477 $params = ComponentHelper::getParams('com_users'); 478 479 // Read the default user group option from com_users 480 $defaultUserGroup = $params->get('new_usertype', $params->get('guest_usergroup', 1)); 481 482 $instance->id = 0; 483 $instance->name = $user['fullname']; 484 $instance->username = $user['username']; 485 $instance->password_clear = $user['password_clear']; 486 487 // Result should contain an email (check). 488 $instance->email = $user['email']; 489 $instance->groups = [$defaultUserGroup]; 490 491 // If autoregister is set let's register the user 492 $autoregister = $options['autoregister'] ?? $this->params->get('autoregister', 1); 493 494 if ($autoregister) { 495 if (!$instance->save()) { 496 Log::add('Failed to automatically create account for user ' . $user['username'] . '.', Log::WARNING, 'error'); 497 } 498 } else { 499 // No existing user and autoregister off, this is a temporary user. 500 $instance->set('tmp_user', true); 501 } 502 503 return $instance; 504 } 505 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |