<?php
/**
 * @defgroup user_form User Forms
 */

/**
 * @file classes/user/form/RegistrationForm.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2003-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class RegistrationForm
 *
 * @ingroup user_form
 *
 * @brief Form for user registration.
 */

namespace PKP\user\form;

use APP\core\Application;
use APP\facades\Repo;
use APP\notification\form\NotificationSettingsForm;
use APP\template\TemplateManager;
use PKP\config\Config;
use PKP\core\Core;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\form\Form;
use PKP\security\Role;
use PKP\security\Validation;
use PKP\session\SessionManager;
use PKP\site\Site;
use PKP\user\InterestManager;
use PKP\user\User;

class RegistrationForm extends Form
{
    /** @var User The user object being created (available to hooks during registrationform::execute hook) */
    public $user;

    /** @var bool user is already registered with another context */
    public $existingUser;

    /** @var bool whether or not captcha is enabled for this form */
    public $captchaEnabled;

    /**
     * Constructor.
     *
     * @param Site $site
     */
    public function __construct($site)
    {
        parent::__construct('frontend/pages/userRegister.tpl');

        // Validation checks for this form
        $form = $this;
        $this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'username', 'required', 'user.register.form.usernameExists', [Repo::user(), 'getByUsername'], [true], true));
        $this->addCheck(new \PKP\form\validation\FormValidator($this, 'username', 'required', 'user.profile.form.usernameRequired'));
        $this->addCheck(new \PKP\form\validation\FormValidator($this, 'password', 'required', 'user.profile.form.passwordRequired'));
        $this->addCheck(new \PKP\form\validation\FormValidatorUsername($this, 'username', 'required', 'user.register.form.usernameAlphaNumeric'));
        $this->addCheck(new \PKP\form\validation\FormValidatorLength($this, 'password', 'required', 'user.register.form.passwordLengthRestriction', '>=', $site->getMinPasswordLength()));
        $this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'password', 'required', 'user.register.form.passwordsDoNotMatch', fn ($password) => $password == $form->getData('password2')));

        $this->addCheck(new \PKP\form\validation\FormValidator($this, 'givenName', 'required', 'user.profile.form.givenNameRequired'));

        $this->addCheck(new \PKP\form\validation\FormValidator($this, 'country', 'required', 'user.profile.form.countryRequired'));

        // Email checks
        $this->addCheck(new \PKP\form\validation\FormValidatorEmail($this, 'email', 'required', 'user.profile.form.emailRequired'));
        $this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'email', 'required', 'user.register.form.emailExists', [Repo::user(), 'getByEmail'], [true], true));

        $this->captchaEnabled = Config::getVar('captcha', 'captcha_on_register') && Config::getVar('captcha', 'recaptcha');
        if ($this->captchaEnabled) {
            $request = Application::get()->getRequest();
            $this->addCheck(new \PKP\form\validation\FormValidatorReCaptcha($this, $request->getRemoteAddr(), 'common.captcha.error.invalid-input-response', $request->getServerHost()));
        }

        $context = Application::get()->getRequest()->getContext();
        if ($context && $context->getData('privacyStatement')) {
            $this->addCheck(new \PKP\form\validation\FormValidator($this, 'privacyConsent', 'required', 'user.profile.form.privacyConsentRequired'));
        }

        $this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
        $this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
    }

    /**
     * @copydoc Form::fetch()
     *
     * @param null|mixed $template
     */
    public function fetch($request, $template = null, $display = false)
    {
        $templateMgr = TemplateManager::getManager($request);
        $site = $request->getSite();

        if ($this->captchaEnabled) {
            $templateMgr->assign('recaptchaPublicKey', Config::getVar('captcha', 'recaptcha_public_key'));
        }

        $countries = [];
        foreach (Locale::getCountries() as $country) {
            $countries[$country->getAlpha2()] = $country->getLocalName();
        }
        asort($countries);
        $templateMgr->assign('countries', $countries);

        $userFormHelper = new UserFormHelper();
        $userFormHelper->assignRoleContent($templateMgr, $request);

        $templateMgr->assign([
            'source' => $request->getUserVar('source'),
            'minPasswordLength' => $site->getMinPasswordLength(),
            'enableSiteWidePrivacyStatement' => Config::getVar('general', 'sitewide_privacy_statement'),
            'siteWidePrivacyStatement' => $site->getData('privacyStatement'),
        ]);

        return parent::fetch($request, $template, $display);
    }

    /**
     * @copydoc Form::initData()
     */
    public function initData()
    {
        $this->_data = [
            'locales' => [],
            'userGroupIds' => [],
        ];
    }

    /**
     * Assign form data to user-submitted data.
     */
    public function readInputData()
    {
        parent::readInputData();

        $this->readUserVars([
            'username',
            'password',
            'password2',
            'givenName',
            'familyName',
            'affiliation',
            'email',
            'country',
            'interests',
            'emailConsent',
            'privacyConsent',
            'readerGroup',
            'reviewerGroup',
        ]);

        if ($this->captchaEnabled) {
            $this->readUserVars([
                'g-recaptcha-response',
            ]);
        }

        // Collect the specified user group IDs into a single piece of data
        $this->setData('userGroupIds', array_merge(
            array_keys((array) $this->getData('readerGroup')),
            array_keys((array) $this->getData('reviewerGroup'))
        ));
    }

    /**
     * @copydoc Form::validate()
     */
    public function validate($callHooks = true)
    {
        $request = Application::get()->getRequest();

        // Ensure the consent checkbox has been completed for the site and any user
        // group sign-ups if we're in the site-wide registration form
        if (!$request->getContext()) {
            if ($request->getSite()->getData('privacyStatement')) {
                $privacyConsent = (array) $this->getData('privacyConsent');
                if (!is_array($privacyConsent) || !array_key_exists(Application::CONTEXT_ID_NONE, $privacyConsent)) {
                    $this->addError('privacyConsent[' . Application::CONTEXT_ID_NONE . ']', __('user.register.form.missingSiteConsent'));
                }
            }

            if (!Config::getVar('general', 'sitewide_privacy_statement')) {
                $contextIds = [];
                foreach ($this->getData('userGroupIds') as $userGroupId) {
                    $userGroup = Repo::userGroup()->get($userGroupId);
                    $contextIds[] = $userGroup->getContextId();
                }

                $contextIds = array_unique($contextIds);
                if (!empty($contextIds)) {
                    $contextDao = Application::getContextDao();
                    $privacyConsent = (array) $this->getData('privacyConsent');
                    foreach ($contextIds as $contextId) {
                        $context = $contextDao->getById($contextId);
                        if ($context->getData('privacyStatement') && !array_key_exists($contextId, $privacyConsent)) {
                            $this->addError('privacyConsent[' . $contextId . ']', __('user.register.form.missingContextConsent'));
                            break;
                        }
                    }
                }
            }
        }

        return parent::validate($callHooks);
    }

    /**
     * Register a new user.
     *
     * @return int|null User ID, or false on failure
     */
    public function execute(...$functionArgs)
    {
        $requireValidation = Config::getVar('email', 'require_validation');

        // New user
        $this->user = $user = Repo::user()->newDataObject();

        $user->setUsername($this->getData('username'));

        // The multilingual user data (givenName, familyName and affiliation) will be saved
        // in the current UI locale and copied in the site's primary locale too
        $request = Application::get()->getRequest();
        $site = $request->getSite();
        $sitePrimaryLocale = $site->getPrimaryLocale();
        $currentLocale = Locale::getLocale();

        // Set the base user fields (name, etc.)
        $user->setGivenName($this->getData('givenName'), $currentLocale);
        $user->setFamilyName($this->getData('familyName'), $currentLocale);
        $user->setEmail($this->getData('email'));
        $user->setCountry($this->getData('country'));
        $user->setAffiliation($this->getData('affiliation'), $currentLocale);

        if ($sitePrimaryLocale != $currentLocale) {
            $user->setGivenName($this->getData('givenName'), $sitePrimaryLocale);
            $user->setFamilyName($this->getData('familyName'), $sitePrimaryLocale);
            $user->setAffiliation($this->getData('affiliation'), $sitePrimaryLocale);
        }

        $user->setDateRegistered(Core::getCurrentDate());
        $user->setInlineHelp(1); // default new users to having inline help visible.
        $user->setPassword(Validation::encryptCredentials($this->getData('username'), $this->getData('password')));

        if ($requireValidation) {
            // The account should be created in a disabled
            // state.
            $user->setDisabled(true);
            $user->setDisabledReason(__('user.login.accountNotValidated', ['email' => $this->getData('email')]));
        }

        parent::execute(...$functionArgs);

        Repo::user()->add($user);
        $userId = $user->getId();
        if (!$userId) {
            return false;
        }

        // Associate the new user with the existing session
        $sessionManager = SessionManager::getManager();
        $session = $sessionManager->getUserSession();
        $session->setSessionVar('username', $user->getUsername());

        // Save the selected roles or assign the Reader role if none selected
        if ($request->getContext() && !$this->getData('reviewerGroup')) {
            $defaultReaderGroup = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_READER], $request->getContext()->getId(), true)->first();
            if ($defaultReaderGroup) {
                Repo::userGroup()->assignUserToGroup($user->getId(), $defaultReaderGroup->getId(), $request->getContext()->getId());
            }
        } else {
            $userFormHelper = new UserFormHelper();
            $userFormHelper->saveRoleContent($this, $user);
        }

        // Save the email notification preference
        if ($request->getContext() && !$this->getData('emailConsent')) {
            // Get the public notification types
            $notificationSettingsForm = new NotificationSettingsForm();
            $notificationCategories = $notificationSettingsForm->getNotificationSettingCategories($request->getContext());
            foreach ($notificationCategories as $notificationCategory) {
                if ($notificationCategory['categoryKey'] === 'notification.type.public') {
                    $publicNotifications = $notificationCategory['settings'];
                }
            }
            if (isset($publicNotifications)) {
                $notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO'); /** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */
                $notificationSubscriptionSettingsDao->updateNotificationSubscriptionSettings(
                    'blocked_emailed_notification',
                    $publicNotifications,
                    $user->getId(),
                    $request->getContext()->getId()
                );
            }
        }

        // Insert the user interests
        $interestManager = new InterestManager();
        $interestManager->setInterestsForUser($user, $this->getData('interests'));

        return $userId;
    }
}

if (!PKP_STRICT_MODE) {
    class_alias('\PKP\user\form\RegistrationForm', '\RegistrationForm');
}
