1<?php 2 3/** 4 * Creates a new user object. 5 * 6 * A user are recognized by the session-id using getUserBySessionId(), by his 7 * using getUserById() or by his nickname (login) using getUserByLogin(). New 8 * are created using createNewUser(). 9 * 10 * This Source Code Form is subject to the terms of the Mozilla Public License, 11 * v. 2.0. If a copy of the MPL was not distributed with this file, You can 12 * obtain one at http://mozilla.org/MPL/2.0/. 13 * 14 * @package phpMyFAQ 15 * @author Lars Tiedemann <php@larstiedemann.de> 16 * @author Thorsten Rinne <thorsten@phpmyfaq.de> 17 * @author Sarah Hermann <sayh@gmx.de> 18 * @copyright 2005-2020 phpMyFAQ Team 19 * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 20 * @link https://www.phpmyfaq.de 21 * @since 2005-09-17 22 */ 23 24namespace phpMyFAQ; 25 26use phpMyFAQ\Auth\AuthDatabase; 27use phpMyFAQ\Auth\AuthDriverInterface; 28use phpMyFAQ\Auth\AuthHttp; 29use phpMyFAQ\Auth\AuthLdap; 30use phpMyFAQ\Auth\AuthSso; 31use phpMyFAQ\Permission\BasicPermission; 32use phpMyFAQ\Permission\MediumPermission; 33use phpMyFAQ\User\UserData; 34 35if (!defined('PMF_ENCRYPTION_TYPE')) { 36 define('PMF_ENCRYPTION_TYPE', 'md5'); // Fallback to md5() 37} 38 39/** 40 * Class User 41 * 42 * @package phpMyFAQ 43 */ 44class User 45{ 46 public const ERROR_USER_ADD = 'Account could not be created. '; 47 public const ERROR_USER_CANNOT_CREATE_USER = 'User account could not be created. '; 48 public const ERROR_USER_CANNOT_CREATE_USERDATA = 'Entry for user data could not be created. '; 49 public const ERROR_USER_CANNOT_DELETE_USER = 'User account could not be deleted. '; 50 public const ERROR_USER_CANNOT_DELETE_USERDATA = 'Entry for user data could not be deleted. '; 51 public const ERROR_USER_CHANGE = 'Account could not be updated. '; 52 public const ERROR_USER_DELETE = 'Account could not be deleted. '; 53 public const ERROR_USER_INCORRECT_LOGIN = 'Specified login could not be found. '; 54 public const ERROR_USER_INCORRECT_PASSWORD = 'Specified password is not correct.'; 55 public const ERROR_USER_INVALID_STATUS = 'Undefined user status.'; 56 public const ERROR_USER_LOGINNAME_TOO_SHORT = 'The chosen loginname is too short.'; 57 public const ERROR_USER_LOGIN_NOT_UNIQUE = 'Specified login name already exists. '; 58 public const ERROR_USER_LOGIN_INVALID = 'The chosen login is invalid. A valid login has at least four ' . 59 'characters. Only letters, numbers and underscore _ are allowed. The first letter must be a letter. '; 60 public const ERROR_USER_NO_PERM = 'No permission container specified.'; 61 public const ERROR_USER_NO_USERID = 'No user-ID found. '; 62 public const ERROR_USER_NO_USERLOGINDATA = 'No user login data found. '; 63 public const ERROR_USER_NOT_FOUND = 'User account could not be found. '; 64 public const ERROR_USER_NO_AUTH_WRITABLE = 'No authentication object is writable.'; 65 public const ERROR_USER_TOO_MANY_FAILED_LOGINS = 'You exceeded the maximum amounts of login attempts and are ' . 66 'temporarily blocked. Please try again later.'; 67 68 public const STATUS_USER_PROTECTED = 'User account is protected. '; 69 public const STATUS_USER_BLOCKED = 'User account is blocked. '; 70 public const STATUS_USER_ACTIVE = 'User account is active. '; 71 72 /** 73 * Permission container. 74 * 75 * @var BasicPermission|MediumPermission 76 */ 77 public $perm = null; 78 79 /** 80 * User-data storage container. 81 * 82 * @var UserData 83 */ 84 public $userdata = null; 85 /** 86 * Public array that contains error messages. 87 * 88 * @var array 89 */ 90 public $errors = []; 91 /** 92 * authentication container. 93 * 94 * @var AuthDriverInterface[] 95 */ 96 protected $authContainer = []; 97 /** 98 * Configuration. 99 * 100 * @var Configuration 101 */ 102 protected $config = null; 103 /** 104 * Default Authentication properties. 105 * 106 * @var array 107 */ 108 private $authData = [ 109 'authSource' => [ 110 'name' => 'database', 111 'type' => 'local', 112 ], 113 'encType' => PMF_ENCRYPTION_TYPE, 114 'readOnly' => false, 115 ]; 116 /** 117 * login string. 118 * 119 * @var string 120 */ 121 private $login = ''; 122 /** 123 * minimum length of login string (default: 2). 124 * 125 * @var int 126 */ 127 private $loginMinLength = 2; 128 /** 129 * regular expression to find invalid login strings 130 * (default: /^[a-z0-9][\w\.\-@]+/i ). 131 * 132 * @var string 133 */ 134 private $validUsername = '/^[a-z0-9][\w\.\-@]+/i'; 135 /** 136 * user ID. 137 * 138 * @var int 139 */ 140 private $userId = -1; 141 /** 142 * Status of user. 143 * 144 * @var string 145 */ 146 private $status = ''; 147 /** 148 * IS the user a super admin? 149 * 150 * @var bool 151 */ 152 private $isSuperAdmin = false; 153 /** 154 * array of allowed values for status. 155 * 156 * @var array 157 */ 158 private $allowedStatus = [ 159 'active' => self::STATUS_USER_ACTIVE, 160 'blocked' => self::STATUS_USER_BLOCKED, 161 'protected' => self::STATUS_USER_PROTECTED, 162 ]; 163 164 /** 165 * Constructor. 166 * 167 * @param Configuration $config 168 */ 169 public function __construct(Configuration $config) 170 { 171 $this->config = $config; 172 173 $perm = Permission::selectPerm($this->config->get('security.permLevel'), $this->config); 174 if (!$this->addPerm($perm)) { 175 return; 176 } 177 178 // authentication objects 179 // always make a 'local' $auth object (see: $authData) 180 $this->authContainer = []; 181 $auth = new Auth($this->config); 182 /** 183 * @var AuthDatabase|AuthHttp|AuthLdap|AuthSso 184*/ 185 $authLocal = $auth->selectAuth($this->getAuthSource('name')); 186 $authLocal->selectEncType($this->getAuthData('encType')); 187 $authLocal->setReadOnly($this->getAuthData('readOnly')); 188 189 if (!$this->addAuth($authLocal, $this->getAuthSource('type'))) { 190 return; 191 } 192 193 // additionally, set given $auth objects 194 if (count($this->authContainer) > 0) { 195 foreach ($auth as $name => $authObject) { 196 if (!$authObject instanceof Auth || !$this->addAuth($authObject, $name)) { 197 break; 198 } 199 } 200 } 201 202 // user data object 203 $this->userdata = new UserData($this->config); 204 } 205 206 /** 207 * adds a permission object to the user. 208 * 209 * @param Permission $perm Permission object 210 * 211 * @return bool 212 */ 213 public function addPerm(Permission $perm) 214 { 215 if ($this->checkPerm($perm)) { 216 $this->perm = $perm; 217 return true; 218 } 219 220 $this->perm = null; 221 return false; 222 } 223 224 /** 225 * returns true if perm is a valid permission object. 226 * 227 * @param Permission $perm Permission object 228 * 229 * @return bool 230 */ 231 private function checkPerm($perm) 232 { 233 if ($perm instanceof Permission) { 234 return true; 235 } 236 $this->errors[] = self::ERROR_USER_NO_PERM; 237 238 return false; 239 } 240 241 /** 242 * Returns a specific entry from the auth data source array. 243 * 244 * @param string $key 245 * 246 * @return string|null 247 */ 248 public function getAuthSource($key) 249 { 250 if (isset($this->authData['authSource'][$key])) { 251 return $this->authData['authSource'][$key]; 252 } 253 return null; 254 } 255 256 /** 257 * Returns a specific entry from the auth data array. 258 * 259 * @param string $key 260 * 261 * @return string|null 262 */ 263 public function getAuthData($key) 264 { 265 if (isset($this->authData[$key])) { 266 return $this->authData[$key]; 267 } 268 return null; 269 } 270 271 /** 272 * adds a new authentication object to the user object. 273 * 274 * @param Auth $auth Driver object 275 * @param string $name Auth name 276 * 277 * @return bool 278 */ 279 public function addAuth(Auth $auth, $name) 280 { 281 if ($this->checkAuth($auth)) { 282 $this->authContainer[$name] = $auth; 283 284 return true; 285 } 286 287 return false; 288 } 289 290 /** 291 * Returns true if auth is a valid authentication object. 292 * 293 * @param Auth $auth Auth object 294 * 295 * @return bool 296 */ 297 protected function checkAuth(Auth $auth) 298 { 299 $methods = ['checkPassword']; 300 foreach ($methods as $method) { 301 if (!method_exists($auth, $method)) { 302 return false; 303 break; 304 } 305 } 306 307 return true; 308 } 309 310 /** 311 * loads basic user information from the database selecting the user with 312 * specified cookie information. 313 * 314 * @param string $cookie 315 * 316 * @return bool 317 */ 318 public function getUserByCookie($cookie) 319 { 320 $select = sprintf( 321 " 322 SELECT 323 user_id, 324 login, 325 account_status 326 FROM 327 %sfaquser 328 WHERE 329 remember_me = '%s' AND account_status != 'blocked'", 330 Database::getTablePrefix(), 331 $this->config->getDb()->escape($cookie) 332 ); 333 334 $res = $this->config->getDb()->query($select); 335 if ($this->config->getDb()->numRows($res) !== 1) { 336 $this->errors[] = self::ERROR_USER_INCORRECT_LOGIN; 337 338 return false; 339 } 340 $user = $this->config->getDb()->fetchArray($res); 341 342 // Don't ever login via anonymous user 343 if (-1 === $user['user_id']) { 344 return false; 345 } 346 347 $this->userId = (int)$user['user_id']; 348 $this->login = (string)$user['login']; 349 $this->status = (string)$user['account_status']; 350 351 // get user-data 352 if (!$this->userdata instanceof UserData) { 353 $this->userdata = new UserData($this->config); 354 } 355 $this->userdata->load($this->getUserId()); 356 357 return true; 358 } 359 360 /** 361 * Returns the User ID of the user. 362 * 363 * @return int 364 */ 365 public function getUserId() 366 { 367 if (isset($this->userId) && is_int($this->userId)) { 368 return (int)$this->userId; 369 } 370 $this->userId = -1; 371 $this->errors[] = self::ERROR_USER_NO_USERID; 372 373 return -1; 374 } 375 376 /** 377 * Checks if display name is already used. Returns true, if already in use. 378 * 379 * @param string $name 380 * 381 * @return bool 382 */ 383 public function checkDisplayName($name) 384 { 385 if (!$this->userdata instanceof UserData) { 386 $this->userdata = new UserData($this->config); 387 } 388 389 if ($name === $this->userdata->fetch('display_name', $name)) { 390 return true; 391 } else { 392 return false; 393 } 394 } 395 396 /** 397 * Checks if email address is already used. Returns true, if already in use. 398 * 399 * @param string $name 400 * 401 * @return bool 402 */ 403 public function checkMailAddress($name) 404 { 405 if (!$this->userdata instanceof UserData) { 406 $this->userdata = new UserData($this->config); 407 } 408 409 if ($name === $this->userdata->fetch('email', $name)) { 410 return true; 411 } else { 412 return false; 413 } 414 } 415 416 /** 417 * search users by login. 418 * 419 * @param string $search Login name 420 * 421 * @return array 422 */ 423 public function searchUsers($search) 424 { 425 $select = sprintf( 426 " 427 SELECT 428 login, 429 user_id, 430 account_status 431 FROM 432 %sfaquser 433 WHERE 434 login LIKE '%s'", 435 Database::getTablePrefix(), 436 $this->config->getDb()->escape($search . '%') 437 ); 438 439 $res = $this->config->getDb()->query($select); 440 if (!$res) { 441 return []; 442 } 443 444 $result = []; 445 while ($row = $this->config->getDb()->fetchArray($res)) { 446 $result[] = $row; 447 } 448 449 return $result; 450 } 451 452 /** 453 * Creates a new user and stores basic data in the database. 454 * 455 * @param string $login 456 * @param string $pass 457 * @param string $domain 458 * @param int $userId 459 * 460 * @return boolean 461 */ 462 public function createUser($login, $pass = '', $domain = '', $userId = 0) 463 { 464 foreach ($this->authContainer as $auth) { 465 if (!$this->checkAuth($auth)) { 466 return false; 467 } 468 } 469 470 // is $login valid? 471 $login = (string)$login; 472 if (!$this->isValidLogin($login)) { 473 $this->errors[] = self::ERROR_USER_LOGINNAME_TOO_SHORT; 474 475 return false; 476 } 477 478 // does $login already exist? 479 if ($this->getUserByLogin($login, false)) { 480 $this->errors[] = self::ERROR_USER_LOGIN_NOT_UNIQUE; 481 482 return false; 483 } 484 485 // set user-ID 486 if (0 == $userId) { 487 $this->userId = (int)$this->config->getDb()->nextId(Database::getTablePrefix() . 'faquser', 'user_id'); 488 } else { 489 $this->userId = $userId; 490 } 491 492 // create user entry 493 $insert = sprintf( 494 " 495 INSERT INTO 496 %sfaquser 497 (user_id, login, session_timestamp, member_since) 498 VALUES 499 (%d, '%s', %d, '%s')", 500 Database::getTablePrefix(), 501 $this->getUserId(), 502 $this->config->getDb()->escape($login), 503 $_SERVER['REQUEST_TIME'], 504 date('YmdHis', $_SERVER['REQUEST_TIME']) 505 ); 506 507 $this->config->getDb()->query($insert); 508 if (!$this->userdata instanceof UserData) { 509 $this->userdata = new UserData($this->config); 510 } 511 $data = $this->userdata->add($this->getUserId()); 512 if (!$data) { 513 $this->errors[] = self::ERROR_USER_CANNOT_CREATE_USERDATA; 514 515 return false; 516 } 517 518 // create authentication entry 519 if ($pass == '') { 520 $pass = $this->createPassword(); 521 } 522 $success = false; 523 524 foreach ($this->authContainer as $name => $auth) { 525 if ($auth->setReadOnly()) { 526 continue; 527 } 528 if (!$auth->add($login, $pass, $domain)) { 529 $this->errors[] = self::ERROR_USER_CANNOT_CREATE_USER . 'in Auth ' . $name; 530 } else { 531 $success = true; 532 } 533 } 534 if (!$success) { 535 return false; 536 } 537 538 if ($this->perm instanceof MediumPermission) { 539 $this->perm->autoJoin($this->userId); 540 } 541 542 return $this->getUserByLogin($login, false); 543 } 544 545 /** 546 * returns true if login is a valid login string. 547 * 548 * $this->loginMinLength defines the minimum length the 549 * login string. If login has more characters than allowed, 550 * false is returned. 551 * $this->login_invalidRegExp is a regular expression. 552 * If login matches this false is returned. 553 * 554 * @param string $login Login name 555 * 556 * @return bool 557 */ 558 public function isValidLogin($login) 559 { 560 $login = (string)$login; 561 562 if (strlen($login) < $this->loginMinLength || !preg_match($this->validUsername, $login)) { 563 $this->errors[] = self::ERROR_USER_LOGIN_INVALID; 564 565 return false; 566 } 567 568 return true; 569 } 570 571 /** 572 * loads basic user information from the database selecting the user with 573 * specified login. 574 * 575 * @param string $login Login name 576 * @param bool $raiseError Raise error? 577 * 578 * @return bool 579 */ 580 public function getUserByLogin($login, $raiseError = true) 581 { 582 $select = sprintf( 583 " 584 SELECT 585 user_id, 586 login, 587 account_status 588 FROM 589 %sfaquser 590 WHERE 591 login = '%s'", 592 Database::getTablePrefix(), 593 $this->config->getDb()->escape($login) 594 ); 595 596 $res = $this->config->getDb()->query($select); 597 if ($this->config->getDb()->numRows($res) !== 1) { 598 if ($raiseError) { 599 $this->errors[] = self::ERROR_USER_INCORRECT_LOGIN; 600 } 601 602 return false; 603 } 604 $user = $this->config->getDb()->fetchArray($res); 605 $this->userId = (int)$user['user_id']; 606 $this->login = (string)$user['login']; 607 $this->status = (string)$user['account_status']; 608 609 // get user-data 610 if (!$this->userdata instanceof UserData) { 611 $this->userdata = new UserData($this->config); 612 } 613 $this->userdata->load($this->getUserId()); 614 615 return true; 616 } 617 618 /** 619 * Returns a new password. 620 * 621 * @param int $minimumLength 622 * @param bool $allowUnderscore 623 * 624 * @return string 625 */ 626 public function createPassword($minimumLength = 8, $allowUnderscore = true) 627 { 628 // To make passwords harder to get wrong, a few letters & numbers have been omitted. 629 // This will ensure safety with browsers using fonts with confusable letters. 630 // Removed: o,O,0,1,l,L 631 $consonants = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z']; 632 $vowels = ['a', 'e', 'i', 'u']; 633 $newPassword = ''; 634 $nextChar = ''; 635 $skipped = false; 636 637 while (strlen($newPassword) < $minimumLength) { 638 if (Utils::createRandomNumber(0, 1)) { 639 $caseFunc = 'strtoupper'; 640 } else { 641 $caseFunc = 'strtolower'; 642 } 643 644 switch (Utils::createRandomNumber(0, $skipped ? 3 : ($allowUnderscore ? 5 : 4))) { 645 case 0: 646 case 1: 647 $nextChar = $caseFunc($consonants[rand(0, 18)]); 648 break; 649 case 2: 650 case 3: 651 $nextChar = $caseFunc($vowels[rand(0, 3)]); 652 break; 653 case 4: 654 $nextChar = (string)rand(2, 9); 655 break; 656 case 5: 657 $newPassword .= '_'; 658 continue 2; 659 break; 660 } 661 662 $skipped = false; 663 664 // Ensure letters and numbers only occur once. 665 if (strpos($newPassword, $nextChar) === false) { 666 $newPassword .= $nextChar; 667 } else { 668 $skipped = true; 669 } 670 } 671 672 return $newPassword; 673 } 674 675 /** 676 * deletes the user from the database. 677 * 678 * @return bool 679 */ 680 public function deleteUser() 681 { 682 if (!isset($this->userId) || $this->userId == 0) { 683 $this->errors[] = self::ERROR_USER_NO_USERID; 684 685 return false; 686 } 687 688 if (!isset($this->login) || strlen($this->login) == 0) { 689 $this->errors[] = self::ERROR_USER_LOGIN_INVALID; 690 691 return false; 692 } 693 694 if ( 695 isset($this->allowedStatus[$this->status]) && 696 $this->allowedStatus[$this->status] == self::STATUS_USER_PROTECTED 697 ) { 698 $this->errors[] = self::ERROR_USER_CANNOT_DELETE_USER . self::STATUS_USER_PROTECTED; 699 700 return false; 701 } 702 703 $this->perm->refuseAllUserRights($this->userId); 704 705 $delete = sprintf( 706 ' 707 DELETE FROM 708 %sfaquser 709 WHERE 710 user_id = %d', 711 Database::getTablePrefix(), 712 $this->userId 713 ); 714 715 $res = $this->config->getDb()->query($delete); 716 if (!$res) { 717 $this->errors[] = self::ERROR_USER_CANNOT_DELETE_USER . 'error(): ' . $this->config->getDb()->error(); 718 719 return false; 720 } 721 722 if (!$this->userdata instanceof UserData) { 723 $this->userdata = new UserData($this->config); 724 } 725 $data = $this->userdata->delete($this->getUserId()); 726 if (!$data) { 727 $this->errors[] = self::ERROR_USER_CANNOT_DELETE_USERDATA; 728 729 return false; 730 } 731 732 $readOnly = 0; 733 $authCount = 0; 734 $delete = []; 735 foreach ($this->authContainer as $auth) { 736 ++$authCount; 737 if ($auth->setReadOnly()) { 738 ++$readOnly; 739 continue; 740 } 741 $delete[] = $auth->delete($this->login); 742 } 743 744 if ($readOnly == $authCount) { 745 $this->errors[] = self::ERROR_USER_NO_AUTH_WRITABLE; 746 } 747 if (!in_array(true, $delete)) { 748 return false; 749 } 750 751 return true; 752 } 753 754 /** 755 * Returns a string with error messages. 756 * 757 * The string returned by error() contains messages for all errors that 758 * during object processing. Messages are separated by new lines. 759 * 760 * Error messages are stored in the public array errors. 761 * 762 * @return string 763 */ 764 public function error() 765 { 766 $message = ''; 767 foreach ($this->errors as $error) { 768 $message .= $error . "<br>\n"; 769 } 770 $this->errors = []; 771 772 return $message; 773 } 774 775 /** 776 * Returns the data aof the auth container. 777 * 778 * @return AuthDriverInterface[] 779 */ 780 public function getAuthContainer() 781 { 782 return $this->authContainer; 783 } 784 785 /** 786 * Get all users in <option> tags. 787 * 788 * @param int $id Selected user ID 789 * @param bool $allowBlockedUsers Allow blocked users as well, e.g. in admin 790 * 791 * @return string 792 */ 793 public function getAllUserOptions($id = 1, $allowBlockedUsers = false) 794 { 795 $options = ''; 796 $allUsers = $this->getAllUsers(true, $allowBlockedUsers); 797 798 foreach ($allUsers as $userId) { 799 if (-1 !== $userId) { 800 $this->getUserById($userId); 801 $options .= sprintf( 802 '<option value="%d" %s>%s (%s)</option>', 803 $userId, 804 (($userId === $id) ? 'selected' : ''), 805 $this->getUserData('display_name'), 806 $this->getLogin() 807 ); 808 } 809 } 810 811 return $options; 812 } 813 814 /** 815 * Returns an array with the user-IDs of all users found in 816 * the database. By default, the Anonymous User will not be returned. 817 * 818 * @param bool $withoutAnonymous Without anonymous? 819 * @param bool $allowBlockedUsers Allow blocked users as well, e.g. in admin 820 * 821 * @return array 822 */ 823 public function getAllUsers($withoutAnonymous = true, $allowBlockedUsers = true) 824 { 825 $select = sprintf( 826 ' 827 SELECT 828 user_id 829 FROM 830 %sfaquser 831 WHERE 832 1 = 1 833 %s 834 %s 835 ORDER BY 836 user_id ASC', 837 Database::getTablePrefix(), 838 ($withoutAnonymous ? 'AND user_id <> -1' : ''), 839 ($allowBlockedUsers ? '' : "AND account_status != 'blocked'") 840 ); 841 842 $res = $this->config->getDb()->query($select); 843 if (!$res) { 844 return []; 845 } 846 847 $result = []; 848 while ($row = $this->config->getDb()->fetchArray($res)) { 849 $result[] = $row['user_id']; 850 } 851 852 return $result; 853 } 854 855 /** 856 * Loads basic user information from the database selecting the user with 857 * specified user-ID. 858 * 859 * @param int $userId User ID 860 * @param bool $allowBlockedUsers Allow blocked users as well, e.g. in admin 861 * 862 * @return bool 863 */ 864 public function getUserById($userId, $allowBlockedUsers = false) 865 { 866 $select = sprintf( 867 ' 868 SELECT 869 user_id, 870 login, 871 account_status, 872 is_superadmin 873 FROM 874 %sfaquser 875 WHERE 876 user_id = %d ' . ($allowBlockedUsers ? '' : "AND account_status != 'blocked'"), 877 Database::getTablePrefix(), 878 (int)$userId 879 ); 880 881 $res = $this->config->getDb()->query($select); 882 if ($this->config->getDb()->numRows($res) != 1) { 883 $this->errors[] = self::ERROR_USER_NO_USERID . 'error(): ' . $this->config->getDb()->error(); 884 885 return false; 886 } 887 $user = $this->config->getDb()->fetchArray($res); 888 $this->userId = (int)$user['user_id']; 889 $this->login = (string)$user['login']; 890 $this->status = (string)$user['account_status']; 891 $this->isSuperAdmin = (bool)$user['is_superadmin']; 892 893 // get encrypted password 894 // @todo: Add a getEncPassword method to the Auth* classes for the (local and remote) Auth Sources. 895 if ('db' === $this->getAuthSource('name')) { 896 $select = sprintf( 897 " 898 SELECT 899 pass 900 FROM 901 %sfaquserlogin 902 WHERE 903 login = '%s'", 904 Database::getTablePrefix(), 905 $this->login 906 ); 907 908 $res = $this->config->getDb()->query($select); 909 if ($this->config->getDb()->numRows($res) != 1) { 910 $this->errors[] = self::ERROR_USER_NO_USERLOGINDATA . 'error(): ' . $this->config->getDb()->error(); 911 912 return false; 913 } 914 } 915 // get user-data 916 if (!$this->userdata instanceof UserData) { 917 $this->userdata = new UserData($this->config); 918 } 919 $this->userdata->load($this->getUserId()); 920 921 return true; 922 } 923 924 /** 925 * Returns the data of the current user. 926 * 927 * @param string $field Field 928 * 929 * @return array|string|int 930 */ 931 public function getUserData($field = '*') 932 { 933 if (!($this->userdata instanceof UserData)) { 934 $this->userdata = new UserData($this->config); 935 } 936 937 return $this->userdata->get($field); 938 } 939 940 /** 941 * Adds user data. 942 * 943 * @param array $data Array with user data 944 * 945 * @return bool 946 */ 947 public function setUserData(array $data) 948 { 949 if (!($this->userdata instanceof UserData)) { 950 $this->userdata = new UserData($this->config); 951 } 952 $this->userdata->load($this->getUserId()); 953 954 return $this->userdata->set(array_keys($data), array_values($data)); 955 } 956 957 /** 958 * returns the user's login. 959 * 960 * @return string 961 */ 962 public function getLogin() 963 { 964 return $this->login; 965 } 966 967 /** 968 * sets the minimum login string length. 969 * 970 * @param int $loginMinLength Minimum length of login name 971 */ 972 public function setLoginMinLength($loginMinLength) 973 { 974 if (is_int($loginMinLength)) { 975 $this->loginMinLength = $loginMinLength; 976 } 977 } 978 979 /** 980 * Returns true on success. 981 * 982 * This will change a users status to active, and send an email with a new password. 983 * 984 * @return bool 985 */ 986 public function activateUser() 987 { 988 if ($this->getStatus() == 'blocked') { 989 // Generate and change user password. 990 $newPassword = $this->createPassword(); 991 $this->changePassword($newPassword); 992 // Send activation email. 993 $subject = '[%sitename%] Login name / activation'; 994 $message = sprintf( 995 "\nName: %s\nLogin name: %s\nNew password: %s\n\n", 996 $this->getUserData('display_name'), 997 $this->getLogin(), 998 $newPassword 999 ); 1000 // Only set to active if the activation mail sent correctly. 1001 if ($this->mailUser($subject, $message)) { 1002 return $this->setStatus('active'); 1003 } 1004 return true; 1005 } 1006 1007 return false; 1008 } 1009 1010 /** 1011 * returns the user's status. 1012 * 1013 * @return string 1014 */ 1015 public function getStatus() 1016 { 1017 if (isset($this->status) && strlen($this->status) > 0) { 1018 return $this->status; 1019 } 1020 1021 return false; 1022 } 1023 1024 /** 1025 * Sets the user's status and updates the database entry. 1026 * 1027 * @param string $status Status 1028 * @return bool 1029 */ 1030 public function setStatus($status) 1031 { 1032 // is status allowed? 1033 $status = strtolower($status); 1034 if (!in_array($status, array_keys($this->allowedStatus))) { 1035 $this->errors[] = self::ERROR_USER_INVALID_STATUS; 1036 1037 return false; 1038 } 1039 1040 // update status 1041 $this->status = $status; 1042 $update = sprintf( 1043 " 1044 UPDATE 1045 %sfaquser 1046 SET 1047 account_status = '%s' 1048 WHERE 1049 user_id = %d", 1050 Database::getTablePrefix(), 1051 $this->config->getDb()->escape($status), 1052 $this->userId 1053 ); 1054 1055 $res = $this->config->getDb()->query($update); 1056 1057 if ($res) { 1058 return true; 1059 } 1060 1061 return false; 1062 } 1063 1064 /** 1065 * changes the user's password. If $pass is omitted, a new 1066 * password is generated using the createPassword() method. 1067 * 1068 * @param string $pass Password 1069 * 1070 * @return bool 1071 */ 1072 public function changePassword($pass = '') 1073 { 1074 foreach ($this->authContainer as $auth) { 1075 if (!$this->checkAuth($auth)) { 1076 return false; 1077 } 1078 } 1079 1080 $login = $this->getLogin(); 1081 if ($pass == '') { 1082 $pass = $this->createPassword(); 1083 } 1084 1085 $success = false; 1086 foreach ($this->authContainer as $auth) { 1087 if ($auth->setReadOnly()) { 1088 continue; 1089 } 1090 if (!$auth->changePassword($login, $pass)) { 1091 continue; 1092 } else { 1093 $success = true; 1094 } 1095 } 1096 1097 return $success; 1098 } 1099 1100 /** 1101 * Sends mail to the current user. 1102 * 1103 * @param string $subject 1104 * @param string $message 1105 * @return bool 1106 */ 1107 public function mailUser($subject, $message) 1108 { 1109 $mail = new Mail($this->config); 1110 $mail->addTo($this->getUserData('email')); 1111 $mail->subject = $subject; 1112 $mail->message = $message; 1113 $result = $mail->send(); 1114 unset($mail); 1115 1116 return $result; 1117 } 1118 1119 /** 1120 * Returns true, if a user is a super admin. 1121 * 1122 * @return bool 1123 */ 1124 public function isSuperAdmin() 1125 { 1126 return $this->isSuperAdmin; 1127 } 1128 1129 /** 1130 * Sets the users "is_superadmin" flag and updates the database entry. 1131 * 1132 * @param $isSuperAdmin 1133 * @return bool 1134 */ 1135 public function setSuperAdmin($isSuperAdmin) 1136 { 1137 $this->isSuperAdmin = $isSuperAdmin; 1138 $update = sprintf( 1139 " 1140 UPDATE 1141 %sfaquser 1142 SET 1143 is_superadmin = %d 1144 WHERE 1145 user_id = %d", 1146 Database::getTablePrefix(), 1147 (int)$this->isSuperAdmin, 1148 $this->userId 1149 ); 1150 1151 $res = $this->config->getDb()->query($update); 1152 1153 if ($res) { 1154 return true; 1155 } 1156 1157 return false; 1158 } 1159} 1160