Skip to content
Snippets Groups Projects
class.user.php 38.7 KiB
Newer Older
    const LANG_MAILOUTS = 1;            // Language preference for mailouts

    var $_status;
    var $_extra;
    function getStatus() {
        if (!isset($this->_status))
            $this->_status = new UserAccountStatus($this->get('status'));
        return $this->_status;
    protected function hasStatus($flag) {
        return $this->getStatus()->check($flag);
    protected function clearStatus($flag) {
Peter Rotich's avatar
Peter Rotich committed
        return $this->set('status', $this->get('status') & ~$flag);
    }

    protected function setStatus($flag) {
Peter Rotich's avatar
Peter Rotich committed
        return $this->set('status', $this->get('status') | $flag);
    }

    function confirm() {
        $this->setStatus(UserAccountStatus::CONFIRMED);
        return $this->save();
    }

    function isConfirmed() {
        return $this->getStatus()->isConfirmed();
        $this->setStatus(UserAccountStatus::LOCKED);
        return $this->save();
    function unlock() {
        $this->clearStatus(UserAccountStatus::LOCKED);
        return $this->save();
        return $this->getStatus()->isLocked();
    }

    function forcePasswdReset() {
        $this->setStatus(UserAccountStatus::REQUIRE_PASSWD_RESET);
        return $this->save();
    }

    function isPasswdResetForced() {
        return $this->hasStatus(UserAccountStatus::REQUIRE_PASSWD_RESET);
    }

    function isPasswdResetEnabled() {
        return !$this->hasStatus(UserAccountStatus::FORBID_PASSWD_RESET);
Peter Rotich's avatar
Peter Rotich committed
    function getInfo() {
        return $this->ht;
    }

    function getId() {
        return $this->get('id');
    }

    function getUserId() {
        return $this->get('user_id');
    }

    function getUser() {
    function getExtraAttr($attr=false, $default=null) {
        if (!isset($this->_extra))
            $this->_extra = JsonDataParser::decode($this->get('extra', ''));

        return $attr ? (@$this->_extra[$attr] ?: $default) : $this->_extra;
    }

    function setExtraAttr($attr, $value) {
        $this->getExtraAttr();
        $this->_extra[$attr] = $value;
    }

    /**
     * Function: getLanguage
     *
     * Returns the language preference for the user or false if no
     * preference is defined. False indicates the browser indicated
     * preference should be used. For requests apart from browser requests,
     * the last language preference of the browser is set in the
     * 'browser_lang' extra attribute upon logins. Send the LANG_MAILOUTS
     * flag to also consider this saved value. Such is useful when sending
     * the user a message (such as an email), and the user's browser
     * preference is not available in the HTTP request.
     *
     * Parameters:
     * $flags - (int) Send UserAccount::LANG_MAILOUTS if the user's
     *      last-known browser preference should be considered. Normally
     *      only the user's saved language preference is considered.
     *
     * Returns:
     * Current or last-known language preference or false if no language
     * preference is currently set or known.
     */
    function getLanguage($flags=false) {
        $lang = $this->get('lang', false);
        if (!$lang && ($flags & UserAccount::LANG_MAILOUTS))
            $lang = $this->getExtraAttr('browser_lang', false);

        return $lang;
    }

    function getTimezone() {
        return $this->timezone;
    }

    function save($refetch=false) {
        // Serialize the extra column on demand
        if (isset($this->_extra)) {
            $this->extra = JsonDataEncoder::encode($this->_extra);
        }
        return parent::save($refetch);
    function hasPassword() {
        return (bool) $this->get('passwd');
    }
    function sendResetEmail() {
Peter Rotich's avatar
Peter Rotich committed
        return static::sendUnlockEmail('pwreset-client') === true;
    }

    function sendConfirmEmail() {
Peter Rotich's avatar
Peter Rotich committed
        return static::sendUnlockEmail('registration-client') === true;
    function setPassword($new) {
        $this->set('passwd', Passwd::hash($new));
        // Clean sessions
        Signal::send('auth.clean', $this->getUser());
    protected function sendUnlockEmail($template) {
        global $ost, $cfg;

        $token = Misc::randCode(48); // 290-bits

        $email = $cfg->getDefaultEmail();
        $content = Page::lookupByType($template);

        if (!$email ||  !$content)
            return new BaseError(sprintf(_S('%s: Unable to retrieve template'),

        $vars = array(
            'url' => $ost->getConfig()->getBaseUrl(),
            'token' => $token,
            'user' => $this->getUser(),
            'recipient' => $this->getUser(),
            'link' => sprintf(
                "%s/pwreset.php?token=%s",
                $ost->getConfig()->getBaseUrl(),
                $token),
        );
        $vars['reset_link'] = &$vars['link'];

        $info = array('email' => $email, 'vars' => &$vars, 'log'=>true);
        Signal::send('auth.pwreset.email', $this->getUser(), $info);
        $lang = $this->getLanguage(UserAccount::LANG_MAILOUTS);
        $msg = $ost->replaceTemplateVariables(array(
            'subj' => $content->getLocalName($lang),
            'body' => $content->getLocalBody($lang),
        ), $vars);

        $_config = new Config('pwreset');
        $_config->set($vars['token'], 'c'.$this->getUser()->getId());
Peter Rotich's avatar
Peter Rotich committed
        $email->send($this->getUser()->getEmail(),
            Format::striptags($msg['subj']), $msg['body']);
Peter Rotich's avatar
Peter Rotich committed

        return true;
Peter Rotich's avatar
Peter Rotich committed
    function __toString() {
        return (string) $this->getStatus();
    }

    /*
aydreeihn's avatar
aydreeihn committed
     * Updates may be done by Staff or by the User if registration
     * options are set to Public
Peter Rotich's avatar
Peter Rotich committed
     */
    function update($vars, &$errors) {

        // TODO: Make sure the username is unique

        // Timezone selection is not required. System default is a valid
        // fallback
Peter Rotich's avatar
Peter Rotich committed

        // Changing password?
        if ($vars['passwd1'] || $vars['passwd2']) {
            if (!$vars['passwd1'])
                $errors['passwd1'] = __('New password is required');
Peter Rotich's avatar
Peter Rotich committed
            elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6)
                $errors['passwd1'] = __('Must be at least 6 characters');
Peter Rotich's avatar
Peter Rotich committed
            elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
                $errors['passwd2'] = __('Passwords do not match');
        // Make sure the username is not an email.
        if ($vars['username'] && Validator::is_email($vars['username']))
            $errors['username'] =
                __('Users can always sign in with their email address');

Peter Rotich's avatar
Peter Rotich committed
        if ($errors) return false;

        $this->set('timezone', $vars['timezone']);
Peter Rotich's avatar
Peter Rotich committed
        $this->set('username', $vars['username']);

        if ($vars['passwd1']) {
            $this->setPassword($vars['passwd1']);
            $this->setStatus(UserAccountStatus::CONFIRMED);
Peter Rotich's avatar
Peter Rotich committed
        // Set flags
        foreach (array(
                'pwreset-flag' => UserAccountStatus::REQUIRE_PASSWD_RESET,
                'locked-flag' => UserAccountStatus::LOCKED,
                'forbid-pwchange-flag' => UserAccountStatus::FORBID_PASSWD_RESET
        ) as $ck=>$flag) {
            if ($vars[$ck])
                $this->setStatus($flag);
            else
                $this->clearStatus($flag);
        }
Peter Rotich's avatar
Peter Rotich committed
        return $this->save(true);
    }

    static function createForUser($user, $defaults=false) {
        $acct = new static(array('user_id'=>$user->getId()));
        if ($defaults && is_array($defaults)) {
            foreach ($defaults as $k => $v)
                $acct->set($k, $v);
        }
        return $acct;
    }

    static function lookupByUsername($username) {
        if (strpos($username, '@') !== false)
            $user = static::lookup(array('user__emails__address'=>$username));
        else
            $user = static::lookup(array('username'=>$username));

        return $user;
    }
    static function register($user, $vars, &$errors) {
Peter Rotich's avatar
Peter Rotich committed

        if (!$user || !$vars)
            return false;

        //Require temp password.
        if ((!$vars['backend'] || $vars['backend'] != 'client')
                && !isset($vars['sendemail'])) {
Peter Rotich's avatar
Peter Rotich committed
            if (!$vars['passwd1'])
                $errors['passwd1'] = 'Temporary password required';
Peter Rotich's avatar
Peter Rotich committed
            elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6)
                $errors['passwd1'] = 'Must be at least 6 characters';
            elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
                $errors['passwd2'] = 'Passwords do not match';
Peter Rotich's avatar
Peter Rotich committed
        }

        if ($errors) return false;

        $account = new UserAccount(array(
            'user_id' => $user->getId(),
            'timezone' => $vars['timezone'],
            'backend' => $vars['backend'],
        ));
Peter Rotich's avatar
Peter Rotich committed

        if ($vars['username'] && strcasecmp($vars['username'], $user->getEmail()))
            $account->set('username', $vars['username']);

        if ($vars['passwd1'] && !$vars['sendemail']) {
            $account->set('passwd', Passwd::hash($vars['passwd1']));
            $account->setStatus(UserAccountStatus::CONFIRMED);
                $account->setStatus(UserAccountStatus::REQUIRE_PASSWD_RESET);
            if ($vars['forbid-pwreset-flag'])
                $account->setStatus(UserAccountStatus::FORBID_PASSWD_RESET);
Peter Rotich's avatar
Peter Rotich committed
        }
        elseif ($vars['backend'] && $vars['backend'] != 'client') {
            // Auto confirm remote accounts
            $account->setStatus(UserAccountStatus::CONFIRMED);
Peter Rotich's avatar
Peter Rotich committed

        $account->save(true);

        if (!$account->isConfirmed() && $vars['sendemail'])
Peter Rotich's avatar
Peter Rotich committed
            $account->sendConfirmEmail();

        return $account;
    }

class UserAccountStatus {

    var $flag;

    const CONFIRMED             = 0x0001;
    const LOCKED                = 0x0002;
    const REQUIRE_PASSWD_RESET  = 0x0004;
    const FORBID_PASSWD_RESET   = 0x0008;

    function __construct($flag) {
        $this->flag = $flag;
    }

    function check($flag) {
        return 0 !== ($this->flag & $flag);
    }

    function isLocked() {
        return $this->check(self::LOCKED);
    }

    function isConfirmed() {
        return $this->check(self::CONFIRMED);
    }

    function __toString() {

        if ($this->isLocked())
            return __('Locked (Administrative)');

        if (!$this->isConfirmed())
            return __('Locked (Pending Activation)');
        // ... Other flags here (password reset, etc).

        return __('Active (Registered)');
/*
 *  Generic user list.
 */
class UserList extends MailingList {
   function add($user) {
        if (!$user instanceof ITicketUser)
            throw new InvalidArgumentException('User expected');
        return parent::add($user);