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