<?php
/**
 * Login
 *
 * Copyright 2010 by Jason Coward <jason@modxcms.com> and Shaun McCormick
 * <shaun@modxcms.com>
 *
 * Login is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any
 * later version.
 *
 * Login is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * Login; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @package login
 */
/**
 * Handle register form
 *
 * @package login
 * @subpackage processors
 */
class LoginRegisterProcessor extends LoginProcessor {
    /** @var modUser $user */
    public $user;
    /** @var modUserProfile $profile */
    public $profile;
    /** @var array $userGroups */
    public $userGroups = array();

    public $persistParams = array();
    public $live = true;

    /**
     * @return mixed
     */
    public function process() {
        $this->cleanseFields();

        /* create user and profile */
        $this->user = $this->modx->newObject('modUser');
        $this->profile = $this->modx->newObject('modUserProfile');

        if ($this->controller->getProperty('useExtended',true,'isset')) {
            $this->setExtended();
        }

        $this->setUserFields();

        /* save user */
        if ($this->live) {
            if (!$this->user->save()) {
                $this->modx->log(modX::LOG_LEVEL_ERROR,'[Login] Could not save newly registered user: '.$this->user->get('id').' with username: '.$this->user->get('username'));
                return $this->modx->lexicon('register.user_err_save');
            }
        }

        $this->preparePersistentParameters();

        /* send activation email (if chosen) */
        $email = $this->profile->get('email');
        $activation = $this->controller->getProperty('activation',true,'isset');
        $activationResourceId = $this->controller->getProperty('activationResourceId','','isset');
        $moderated = $this->checkForModeration();
        if ($activation && !empty($email) && !empty($activationResourceId) && !$moderated) {
            $this->sendActivationEmail();

        } else if (!$moderated) {
            $this->user->set('active',true);
            if ($this->live) {
                $this->user->save();
            }
        }

        $this->runPostHooks();

        if ($this->controller->getProperty('autoLogin') == true && $this->controller->getProperty('activation') == false) {
            $this->autoLogin();
        }

        $this->checkForModerationRedirect();

        $this->checkForRegisteredRedirect();

        $successMsg = $this->controller->getProperty('successMsg','');
        $this->modx->toPlaceholder('error.message',$successMsg);
        return true;
    }

    /**
     * Remove any fields used for anti-spam, submission or moderation from the submission returns
     * @return void
     */
    public function cleanseFields() {
        $this->dictionary->remove('nospam');
        $this->dictionary->remove('blank');
        $submitVar = $this->controller->getProperty('submitVar');
        if (!empty($submitVar)) {
            $this->dictionary->remove($submitVar);
        }
    }

    /**
     * If wanted, set extra values in the form to profile extended field
     * @return void
     */
    public function setExtended() {
        /* first cut out regular and unwanted fields */
        $excludeExtended = $this->controller->getProperty('excludeExtended','');
        $excludeExtended = explode(',',$excludeExtended);
        $profileFields = $this->profile->toArray();
        $userFields = $this->user->toArray();
        $extended = array();
        $fields = $this->dictionary->toArray();
        $fields = $this->filterAllowedFields($fields);
        $userGroupField = $this->controller->getProperty('usergroupsField','');
        foreach ($fields as $field => $value) {
            if (!isset($profileFields[$field])
                && !isset($userFields[$field])
                && $field != 'password_confirm'
                && $field != 'passwordconfirm'
                && $field != $userGroupField
                && !in_array($field,$excludeExtended)
               ) {
                $extended[$field] = $value;
            }
        }
        /* now set extended data */
        $this->profile->set('extended',$extended);
    }

    /**
     * Setup the user data and create the user/profile objects
     *
     * @return void
     */
    public function setUserFields() {
        $fields = $this->dictionary->toArray();
        /* allow overriding of class key */
        if (empty($fields['class_key'])) $fields['class_key'] = 'modUser';

        $fields = $this->filterAllowedFields($fields);

        /* set user and profile */
        $this->user->fromArray($fields);
        $this->user->set('username',$fields[$this->controller->getProperty('usernameField','username')]);
        $this->user->set('active',0);
        $version = $this->modx->getVersionData();
        /* 2.1.x+ */
        if (version_compare($version['full_version'],'2.1.0-rc1') >= 0) {
            $this->user->set('password',$fields[$this->controller->getProperty('passwordField','password')]);
        } else { /* 2.0.x */
            $this->user->set('password',md5($fields[$this->controller->getProperty('passwordField','password')]));
        }
        $this->profile->fromArray($fields);
        $this->profile->set('email',$this->dictionary->get($this->controller->getProperty('emailField','email')));
        $this->user->addOne($this->profile,'Profile');

        /* add user groups, if set */
        $userGroupsField = $this->controller->getProperty('usergroupsField','');
        $userGroups = !empty($userGroupsField) && array_key_exists($userGroupsField,$fields) ? $fields[$userGroupsField] : array();
        $this->setUserGroups($userGroups);
    }

    /**
     * Return an array of filtered fields if the allowedFields property is set and prevents setting of certain fields
     * @param array $fields
     * @return array
     */
    public function filterAllowedFields(array $fields = array()) {
        $allowedFields = $this->controller->getProperty('allowedFields','');
        if (!empty($allowedFields)) {
            $allowedFields = is_array($allowedFields) ? $allowedFields : explode(',',$allowedFields);
            $userGroupField = $this->controller->getProperty('usergroupsField','');
            $usernameField = $this->controller->getProperty('usernameField','username');
            $passwordField = $this->controller->getProperty('passwordField','password');
            $emailField = $this->controller->getProperty('emailField','email');
            array_push($allowedFields,$usernameField,$passwordField,'password_confirm',$emailField,'class_key');
            if (!empty($userGroupField)) array_push($allowedFields,$userGroupField);
            $allowedFields = array_unique($allowedFields);
            foreach ($fields as $k => $v) {
                if (!in_array($k,$allowedFields)) unset($fields[$k]);
            }
        }
        return $fields;
    }

    /**
     * If user groups were passed, set them here
     * @param string $userGroups
     * @return array
     */
    public function setUserGroups($userGroups) {
        $added = array();
        /* if $userGroups set in form, override here; otherwise use snippet property */
        $this->userGroups = !empty($userGroups) ? $userGroups : $this->controller->getProperty('usergroups', '');
        if (!empty($this->userGroups)) {
            $this->userGroups = is_array($this->userGroups) ? $this->userGroups : explode(',',$this->userGroups);

            foreach ($this->userGroups as $userGroupMeta) {
                $userGroupMeta = explode(':',$userGroupMeta);
                if (empty($userGroupMeta[0])) continue;

                /* get usergroup */
                $pk = array();
                $pk[intval($userGroupMeta[0]) > 0 ? 'id' : 'name'] = trim($userGroupMeta[0]);
                /** @var modUserGroup $userGroup */
                $userGroup = $this->modx->getObject('modUserGroup',$pk);
                if (!$userGroup) continue;

                /* get role */
                $rolePk = !empty($userGroupMeta[1]) ? $userGroupMeta[1] : 'Member';
                /** @var modUserGroupRole $role */
                $role = $this->modx->getObject('modUserGroupRole',array('name' => $rolePk));

                /* create membership */
                /** @var modUserGroupMember $member */
                $member = $this->modx->newObject('modUserGroupMember');
                $member->set('member',0);
                $member->set('user_group',$userGroup->get('id'));
                if (!empty($role)) {
                    $member->set('role',$role->get('id'));
                } else {
                    $member->set('role',1);
                }
                $rank = (isset($userGroupMeta[2])) ? $userGroupMeta[2] : 0;
                $member->set('rank', $rank);
                $this->user->addMany($member,'UserGroupMembers');
                $added[] = $userGroup->get('name');
            }
        }
        return $added;
    }

    /**
     * Setup persistent parameters to go through the request cycle
     * @return array
     */
    public function preparePersistentParameters() {
        $this->persistParams = $this->controller->getProperty('persistParams','');
        if (!empty($this->persistParams)) $this->persistParams = $this->modx->fromJSON($this->persistParams);
        if (empty($this->persistParams) || !is_array($this->persistParams)) $this->persistParams = array();
        return $this->persistParams;
    }

    /**
     * Send an activation email to the user with an encrypted username and password hash, to allow for secure
     * activation processes that are not vulnerable to middle-man attacks.
     *
     * @return boolean
     */
    public function sendActivationEmail() {
        $emailProperties = $this->gatherActivationEmailProperties();

        /* send either to user's email or a specified activation email */
        $activationEmail = $this->controller->getProperty('activationEmail',$this->profile->get('email'));
        $subject = $this->controller->getProperty('activationEmailSubject',$this->modx->lexicon('register.activation_email_subject'));
        return $this->login->sendEmail($activationEmail,$this->user->get('username'),$subject,$emailProperties);
    }

    /**
     * Get all the properties for the activation email
     * @return array
     */
    public function gatherActivationEmailProperties() {
        /* generate a password and encode it and the username into the url */
        $pword = $this->modx->user->generatePassword();
        $confirmParams['lp'] = $this->login->base64url_encode($pword);
        $confirmParams['lu'] = $this->login->base64url_encode($this->user->get('username'));
        $confirmParams = array_merge($this->persistParams,$confirmParams);

        /* if using redirectBack param, set here to allow dynamic redirection
         * handling from other forms.
         */
        $redirectBack = $this->modx->getOption('redirectBack',$_REQUEST,$this->controller->getProperty('redirectBack',''));
        if (!empty($redirectBack)) {
            $confirmParams['redirectBack'] = $redirectBack;
        }
        $redirectBackParams = $this->modx->getOption('redirectBackParams',$_REQUEST,$this->controller->getProperty('redirectBackParams',''));
        if (!empty($redirectBackParams)) {
            $confirmParams['redirectBackParams'] = $redirectBackParams;
        }

        /* generate confirmation url */
        if ($this->login->inTestMode) {
            $confirmUrl = $this->modx->makeUrl(1,'',$confirmParams,'full');
        } else {
            $confirmUrl = $this->modx->makeUrl($this->controller->getProperty('activationResourceId',1),'',$confirmParams,'full');
        }

        /* set confirmation email properties */
        $emailTpl = $this->controller->getProperty('activationEmailTpl','lgnActivateEmailTpl');
        $emailTplAlt = $this->controller->getProperty('activationEmailTplAlt','');
        $emailTplType = $this->controller->getProperty('activationEmailTplType','modChunk');
        $emailProperties = $this->user->toArray();
        $emailProperties['confirmUrl'] = $confirmUrl;
        $emailProperties['tpl'] = $emailTpl;
        $emailProperties['tplAlt'] = $emailTplAlt;
        $emailProperties['tplType'] = $emailTplType;
        $emailProperties['password'] = $this->dictionary->get($this->controller->getProperty('passwordField','password'));

        $this->setCachePassword($pword);
        return $emailProperties;
    }

    public function setCachePassword($password) {
        /* now set new password to registry to prevent middleman attacks.
         * Will read from the registry on the confirmation page. */
        $this->modx->getService('registry', 'registry.modRegistry');
        $this->modx->registry->addRegister('login','registry.modFileRegister');
        $this->modx->registry->login->connect();
        $this->modx->registry->login->subscribe('/useractivation/');
        $this->modx->registry->login->send('/useractivation/',array($this->user->get('username') => $password),array(
            'ttl' => ($this->controller->getProperty('activationttl',180)*60),
        ));
        /* set cachepwd here to prevent re-registration of inactive users */
        $this->user->set('cachepwd',md5($password));
        if ($this->live) {
            $success = $this->user->save();
        } else {
            $success = true;
        }
        if (!$success) {
            $this->modx->log(modX::LOG_LEVEL_ERROR,'[Login] Could not update cachepwd for activation for User: '.$this->user->get('username'));
        }
        return $success;
    }

    /**
     * Check to see if a pre/post hook told Login to set the user to moderated (inactive) status
     * @return boolean
     */
    public function checkForModeration() {
        $moderate = $this->dictionary->get('register.moderate');
        return !empty($moderate);
    }

    /**
     * Run any post-registration hooks
     *
     * @return void
     */
    public function runPostHooks() {
        $postHooks = $this->controller->getProperty('postHooks','');
        $this->controller->loadHooks('postHooks');
        $fields = $this->dictionary->toArray();
        $fields['register.user'] =& $this->user;
        $fields['register.profile'] =& $this->profile;
        $fields['register.usergroups'] = $this->userGroups;
        $this->controller->postHooks->loadMultiple($postHooks,$fields);

        /* process hooks */
        if ($this->controller->postHooks->hasErrors()) {
            $errors = array();
            $hookErrors = $this->controller->postHooks->getErrors();
            foreach ($hookErrors as $key => $error) {
                $errors[$key] = str_replace('[[+error]]',$error,$this->controller->getProperty('errTpl'));
            }
            $this->modx->toPlaceholders($errors,'error');

            $errorMsg = $this->controller->postHooks->getErrorMessage();
            $this->modx->toPlaceholder('message',$errorMsg,'error');
        }
    }

    /**
     * if a hook set the user as moderated, if set, send to an optional other moderation resource id
     * @return boolean
     */
    public function checkForModerationRedirect() {
        $moderated = $this->checkForModeration();
        if (!empty($moderated)) {
            $moderatedResourceId = $this->controller->getProperty('moderatedResourceId','');
            if (!empty($moderatedResourceId)) {
                if ($this->controller->getProperty('redirectUnsetDefaultParams') == false) {
                    $persistParams = array_merge($this->persistParams,array(
                        'username' => $this->user->get('username'),
                        'email' => $this->profile->get('email'),
                    ));
                }
                $url = $this->modx->makeUrl($moderatedResourceId,'',$persistParams,'full');
                if (!$this->login->inTestMode) {
                    $this->modx->sendRedirect($url);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Check for a redirect if the user was successfully registered. If one found, redirect.
     *
     * @return boolean
     */
    public function checkForRegisteredRedirect() {
        /* if provided a redirect id, will redirect to that resource, with the
         * GET params `username` and `email` for you to use */
        $submittedResourceId = $this->controller->getProperty('submittedResourceId','');
        if (!empty($submittedResourceId)) {
            if ($this->controller->getProperty('redirectUnsetDefaultParams') == false) {
                $persistParams = array_merge($this->persistParams,array(
                    'username' => $this->user->get('username'),
                    'email' => $this->profile->get('email'),
                ));
            }
            $url = $this->modx->makeUrl($submittedResourceId,'',$persistParams,'full');
            if (!$this->login->inTestMode) {
                $this->modx->sendRedirect($url);
            }
            return true;
        }
        return false;
    }

    public function autoLogin() {
        if ($this->user == null) { return false; }

        $contexts = $this->controller->getProperty('authenticateContexts', $this->modx->context->get('key'));
        $fields = $this->dictionary->toArray();

        $c = array(
            'login_context' => $this->modx->context->key,
            'add_contexts' => $contexts,
            'username' => $this->user->username,
            'password' => $fields[$this->controller->getProperty('passwordField','password')],
            'returnUrl' => '',
        );
        $a = $this->modx->runProcessor('security/login',$c);

        return true;
    }
}
return 'LoginRegisterProcessor';
