<?php
/**
 * @package    Grav\Plugin\Login
 *
 * @copyright  Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
 * @license    MIT License; see LICENSE file for details.
 */
namespace Grav\Plugin;

use Grav\Common\Data\Data;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Common\Plugin;
use Grav\Common\Twig\Twig;
use Grav\Common\User\User;
use Grav\Common\Utils;
use Grav\Common\Uri;
use Grav\Plugin\Login\Events\UserLoginEvent;
use Grav\Plugin\Login\Login;
use Grav\Plugin\Login\Controller;
use Grav\Plugin\Login\RememberMe\RememberMe;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Session\Message;

/**
 * Class LoginPlugin
 * @package Grav\Plugin\Login
 */
class LoginPlugin extends Plugin
{
    const TMP_COOKIE_NAME = 'tmp-message';

    /** @var string */
    protected $route;

    /** @var string */
    protected $route_register;

    /** @var string */
    protected $route_forgot;

    /** @var bool */
    protected $authenticated = true;

    /** @var Login */
    protected $login;

    protected $redirect_to_login;

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            'onPluginsInitialized'      => [['initializeSession', 10000], ['initializeLogin', 1000]],
            'onTask.login.login'        => ['loginController', 0],
            'onTask.login.twofa'        => ['loginController', 0],
            'onTask.login.forgot'       => ['loginController', 0],
            'onTask.login.logout'       => ['loginController', 0],
            'onTask.login.reset'        => ['loginController', 0],
            'onPagesInitialized'        => [['storeReferrerPage', 0], ['pageVisibility', 0]],
            'onPageInitialized'         => ['authorizePage', 0],
            'onPageFallBackUrl'         => ['authorizeFallBackUrl', 0],
            'onTwigTemplatePaths'       => ['onTwigTemplatePaths', 0],
            'onTwigSiteVariables'       => ['onTwigSiteVariables', -100000],
            'onFormProcessed'           => ['onFormProcessed', 0],
            'onUserLoginAuthenticate'   => [['userLoginAuthenticateByRegistration', 10002], ['userLoginAuthenticateByRememberMe', 10001], ['userLoginAuthenticateByEmail', 10000], ['userLoginAuthenticate', 0]],
            'onUserLoginAuthorize'      => ['userLoginAuthorize', 0],
            'onUserLoginFailure'        => ['userLoginFailure', 0],
            'onUserLogin'               => ['userLogin', 0],
            'onUserLogout'              => ['userLogout', 0],
        ];
    }

    /**
     * [onPluginsInitialized] Initialize login plugin if path matches.
     * @throws \RuntimeException
     */
    public function initializeSession()
    {
        // Check to ensure sessions are enabled.
        if (!$this->config->get('system.session.enabled')) {
            throw new \RuntimeException('The Login plugin requires "system.session" to be enabled');
        }

        // Autoload classes
        $autoload = __DIR__ . '/vendor/autoload.php';
        if (!is_file($autoload)) {
            throw new \RuntimeException('Login Plugin failed to load. Composer dependencies not met.');
        }
        require_once $autoload;

        // Define login service.
        $this->grav['login'] = function (Grav $c) {
            return new Login($c);
        };

        // Define current user service.
        $this->grav['user'] = function (Grav $c) {
            $session = $c['session'];

            if (empty($session->user)) {
                $session->user = $c['login']->login(['username' => ''], ['remember_me' => true, 'remember_me_login' => true]);
            }

            return $session->user;
        };
    }

    /**
     * [onPluginsInitialized] Initialize login plugin if path matches.
     * @throws \RuntimeException
     */
    public function initializeLogin()
    {
        $this->login = $this->grav['login'];

        /** @var Uri $uri */
        $uri = $this->grav['uri'];

        // Admin has its own login; make sure we're not in admin.
        if (!isset($this->grav['admin'])) {
            $this->route = $this->config->get('plugins.login.route');
        }

        $path = $uri->path();
        $this->redirect_to_login = $this->config->get('plugins.login.redirect_to_login');

        // Register route to login page if it has been set.
        if ($this->route && $this->route === $path) {
            $this->enable([
                'onPagesInitialized' => ['addLoginPage', 0],
            ]);
            return;
        }

        if ($path === $this->config->get('plugins.login.route_forgot')) {
            $this->enable([
                'onPagesInitialized' => ['addForgotPage', 0],
            ]);
            return;
        }

        if ($path === $this->config->get('plugins.login.route_reset')) {
            $this->enable([
                'onPagesInitialized' => ['addResetPage', 0],
            ]);
            return;
        }

        if ($path === $this->config->get('plugins.login.route_register')) {
            if ($this->config->get('plugins.login.user_registration.enabled')) {
                $this->enable([
                    'onPagesInitialized' => ['addRegisterPage', 0],
                ]);
            } else {
                throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REGISTRATION_DISABLED'), 404);
            }
            return;
        }

        if ($path === $this->config->get('plugins.login.route_activate')) {
            $this->enable([
                'onPagesInitialized' => ['handleUserActivation', 0],
            ]);
            return;
        }

        if ($path === $this->config->get('plugins.login.route_profile')) {
            $this->enable([
                'onPagesInitialized' => ['addProfilePage', 0],
            ]);
            return;
        }
    }

    /**
     * Optional ability to dynamically set visibility based on page access and page header
     * that states `login.visibility_requires_access: true`
     *
     * Note that this setting may be slow on large sites as it loads all pages into memory for each page load!
     *
     * @param Event $e
     */
    public function pageVisibility(Event $e)
    {
        if ($this->config->get('plugins.login.dynamic_page_visibility')) {
            /** @var Pages $pages */
            $pages = $e['pages'];
            $user = $this->grav['user'];

            foreach ($pages->instances() as $page) {
                $header = $page->header();
                if (isset($header->login['visibility_requires_access'])) {
                    $config = $this->mergeConfig($page);
                    $access = $this->login->isUserAuthorizedForPage($user, $page, $config);
                    if ($access === false) {
                        $page->visible(false);
                    }
                }
            }
        }
    }

    /**
     * [onPagesInitialized]
     */
    public function storeReferrerPage()
    {
        $invalid_redirect_routes = [
            $this->config->get('plugins.login.route') ?: '/login',
            $this->config->get('plugins.login.route_register') ?: '/register',
            $this->config->get('plugins.login.route_activate') ?: '/activate_user',
            $this->config->get('plugins.login.route_forgot') ?: '/forgot_password',
            $this->config->get('plugins.login.route_reset') ?: '/reset_password',
        ];

        /** @var Uri $uri */
        $uri = $this->grav['uri'];
        $current_route = $uri->route();
        $redirect = $this->grav['config']->get('plugins.login.redirect_after_login');

        if (!$redirect && !in_array($current_route, $invalid_redirect_routes, true)) {
            // No login redirect set in the configuration; can we redirect to the current page?
            $allowed = true;

            /** @var Page $page */
            $page = $this->grav['pages']->dispatch($current_route);

            if ($page) {
                $header = $page->header();
                if (isset($header->login_redirect_here) && $header->login_redirect_here === false) {
                    $allowed = false;
                }

                if ($allowed && $page->routable()) {
                    $redirect = $page->route() . ($uri->params() ?: '');
                }
            }
        }

        $this->grav['session']->redirect_after_login = $redirect;
    }

    /**
     * Add Login page
     * @throws \Exception
     */
    public function addLoginPage()
    {
        /** @var Pages $pages */
        $pages = $this->grav['pages'];
        $page = $pages->dispatch($this->route);

        if (!$page) {
            // Only add login page if it hasn't already been defined.
            $page = new Page;
            $page->init(new \SplFileInfo(__DIR__ . '/pages/login.md'));
            $page->slug(basename($this->route));

            $pages->addPage($page, $this->route);
        }
    }

    /**
     * Add Login page
     * @throws \Exception
     */
    public function addForgotPage()
    {
        $route = $this->config->get('plugins.login.route_forgot');
        /** @var Pages $pages */
        $pages = $this->grav['pages'];
        $page = $pages->dispatch($route);

        if (!$page) {
            // Only add forgot page if it hasn't already been defined.
            $page = new Page;
            $page->init(new \SplFileInfo(__DIR__ . '/pages/forgot.md'));
            $page->slug(basename($route));

            $pages->addPage($page, $route);
        }
    }

    /**
     * Add Reset page
     * @throws \Exception
     */
    public function addResetPage()
    {
        $route = $this->config->get('plugins.login.route_reset');

        $uri = $this->grav['uri'];
        $token = $uri->param('token');
        $user = $uri->param('user');

        if (!$user || !$token) {
            return;
        }

        /** @var Pages $pages */
        $pages = $this->grav['pages'];
        $page = $pages->dispatch($route);

        if (!$page) {
            // Only add login page if it hasn't already been defined.
            $page = new Page;
            $page->init(new \SplFileInfo(__DIR__ . '/pages/reset.md'));
            $page->slug(basename($route));

            $pages->addPage($page, $route);
        }
    }

    /**
     * Add Register page
     * @throws \Exception
     */
    public function addRegisterPage()
    {
        $route = $this->config->get('plugins.login.route_register');

        /** @var Pages $pages */
        $pages = $this->grav['pages'];
        $page = $pages->dispatch($route);

        if (!$page) {
            $page = new Page;
            $page->init(new \SplFileInfo(__DIR__ . '/pages/register.md'));
            $page->slug(basename($route));

            $pages->addPage($page, $route);
        }
    }

    /**
     * Handle user activation
     * @throws \RuntimeException
     */
    public function handleUserActivation()
    {
        /** @var Uri $uri */
        $uri = $this->grav['uri'];

        /** @var Message $messages */
        $messages = $this->grav['messages'];

        $username = $uri->param('username');

        $token = $uri->param('token');
        $user = User::load($username);

        $redirect_route = $this->config->get('plugins.login.user_registration.redirect_after_activation');
        $redirect_code = null;

        if (empty($user->activation_token)) {
            $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST');
            $messages->add($message, 'error');
        } else {
            list($good_token, $expire) = explode('::', $user->activation_token, 2);

            if ($good_token === $token) {
                if (time() > $expire) {
                    $message = $this->grav['language']->translate('PLUGIN_LOGIN.ACTIVATION_LINK_EXPIRED');
                    $messages->add($message, 'error');
                } else {
                    if ($this->config->get('plugins.login.user_registration.options.manually_enable', false)) {
                        $message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY_NOT_ENABLED');
                    } else {
                        $user['state'] = 'enabled';
                        $message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY');
                    }

                    $messages->add($message, 'info');
                    unset($user['activation_token']);
                    $user->save();

                    if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) {
                        $this->login->sendWelcomeEmail($user);
                    }
                    if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) {
                        $this->login->sendNotificationEmail($user);
                    }

                    if ($this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
                        $loginEvent = $this->login->login(['username' => $username], ['after_registration' => true, 'return_event' => true]);

                        // If there's no activation redirect, get one from login.
                        if (!$redirect_route) {
                            $message = $loginEvent->getMessage();
                            if ($message) {
                                $messages->add($message, $loginEvent->getMessageType());
                            }

                            $redirect_route = $loginEvent->getRedirect();
                            $redirect_code = $loginEvent->getRedirectCode();
                        }
                    }
                }
            } else {
                $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST');
                $messages->add($message, 'error');
            }
        }

        $this->grav->redirectLangSafe($redirect_route ?: '/', $redirect_code);
    }

    /**
     * Add Profile page
     */
    public function addProfilePage()
    {
        $route = $this->config->get('plugins.login.route_profile');
        /** @var Pages $pages */
        $pages = $this->grav['pages'];
        $page = $pages->dispatch($route);

        if (!$page) {
            // Only add forgot page if it hasn't already been defined.
            $page = new Page;
            $page->init(new \SplFileInfo(__DIR__ . "/pages/profile.md"));
            $page->slug(basename($route));

            $pages->addPage($page, $route);
        }

        $this->storeReferrerPage();
    }

    /**
     * Set Unauthorized page
     * @throws \Exception
     */
    public function setUnauthorizedPage()
    {
        $route = $this->config->get('plugins.login.route_unauthorized');

        /** @var Pages $pages */
        $pages = $this->grav['pages'];
        $page = $pages->dispatch($route);

        if (!$page) {
            $page = new Page;
            $page->init(new \SplFileInfo(__DIR__ . '/pages/unauthorized.md'));
            $page->slug(basename($route));

            $pages->addPage($page, $route);
        }

        unset($this->grav['page']);
        $this->grav['page'] = $page;
    }

    /**
     * Initialize login controller
     */
    public function loginController()
    {
        /** @var Uri $uri */
        $uri = $this->grav['uri'];
        $task = !empty($_POST['task']) ? $_POST['task'] : $uri->param('task');
        $task = substr($task, strlen('login.'));
        $post = !empty($_POST) ? $_POST : [];

        switch ($task) {
            case 'login':
                if (!isset($post['login-form-nonce']) || !Utils::verifyNonce($post['login-form-nonce'], 'login-form')) {
                    $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'),
                        'info');
                    $twig = $this->grav['twig'];
                    $twig->twig_vars['notAuthorized'] = true;

                    return;
                }
                break;

            case 'forgot':
                if (!isset($post['forgot-form-nonce']) || !Utils::verifyNonce($post['forgot-form-nonce'], 'forgot-form')) {
                    $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'),'info');
                    return;
                }
                break;
        }

        $controller = new Controller($this->grav, $task, $post);
        $controller->execute();
        $controller->redirect();
    }

    /**
     * Authorize the Page fallback url (page media accessed through the page route)
     */
    public function authorizeFallBackUrl()
    {
        if ($this->config->get('plugins.login.protect_protected_page_media', false)) {
            $page_url = dirname($this->grav['uri']->path());
            $page = $this->grav['pages']->find($page_url);
            unset($this->grav['page']);
            $this->grav['page'] = $page;
            $this->authorizePage();
        }
    }

    /**
     * [onPageInitialized] Authorize Page
     */
    public function authorizePage()
    {
        if (!$this->authenticated) {
            return;
        }

        /** @var User $user */
        $user = $this->grav['user'];

        /** @var Page $page */
        $page = $this->grav['page'];

        if (!$page || $this->grav['login']->isUserAuthorizedForPage($user, $page, $this->mergeConfig($page))) {
            return;
        }

        // If this is not an HTML page request, simply throw a 403 error
        $uri_extension = $this->grav['uri']->extension('html');
        $supported_types = $this->config->get('media.types');
        if ($uri_extension !== 'html' && array_key_exists($uri_extension, $supported_types)) {
            header('HTTP/1.0 403 Forbidden');
            exit;
        }

        $authorized = $user->authenticated && $user->authorized;

        // User is not logged in; redirect to login page.
        if ($this->redirect_to_login && $this->route && !$authorized) {
            $this->grav->redirectLangSafe($this->route, 302);
        }

        /** @var Twig $twig */
        $twig = $this->grav['twig'];

        // Reset page with login page.
        if (!$authorized) {
            if ($this->route) {
                $page = $this->grav['pages']->dispatch($this->route);
            } else {

                $page = new Page;
                // $this->grav['session']->redirect_after_login = $this->grav['uri']->path() . ($this->grav['uri']->params() ?: '');

                // Get the admin Login page is needed, else teh default
                if ($this->isAdmin()) {
                    $login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md');
                    $page->init(new \SplFileInfo($login_file));
                } else {
                    $page->init(new \SplFileInfo(__DIR__ . '/pages/login.md'));
                }

                $page->slug(basename($this->route));

                /** @var Pages $pages */
                $pages = $this->grav['pages'];
                $pages->addPage($page, $this->route);
            }

            $this->authenticated = false;
            unset($this->grav['page']);
            $this->grav['page'] = $page;

            $twig->twig_vars['form'] = new Form($page);
        } else {
            /** @var Language $l */
            $l = $this->grav['language'];
            $this->grav['messages']->add($l->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error');
            $twig->twig_vars['notAuthorized'] = true;

            $this->setUnauthorizedPage();
        }
    }

    /**
     * [onTwigTemplatePaths] Add twig paths to plugin templates.
     */
    public function onTwigTemplatePaths()
    {
        $twig = $this->grav['twig'];
        $twig->twig_paths[] = __DIR__ . '/templates';
    }

    /**
     * [onTwigSiteVariables] Set all twig variables for generating output.
     */
    public function onTwigSiteVariables()
    {
        /** @var Twig $twig */
        $twig = $this->grav['twig'];

        $this->grav->fireEvent('onLoginPage');

        $extension = $this->grav['uri']->extension();
        $extension = $extension ?: 'html';

        if (!$this->authenticated) {
            $twig->template = "login.{$extension}.twig";
        }

        // add CSS for frontend if required
        if (!$this->isAdmin() && $this->config->get('plugins.login.built_in_css')) {
            $this->grav['assets']->add('plugin://login/css/login.css');
        }

        $task = $this->grav['uri']->param('task') ?: (isset($_POST['task']) ? $_POST['task'] : '');
        $task = substr($task, strlen('login.'));
        if ($task === 'reset') {
            $username = $this->grav['uri']->param('user');
            $token = $this->grav['uri']->param('token');

            if (!empty($username) && !empty($token)) {
                $twig->twig_vars['username'] = $username;
                $twig->twig_vars['token'] = $token;
            }
        } elseif ($task === 'login') {
            $twig->twig_vars['username'] = isset($_POST['username']) ? $_POST['username'] : '';
        }

        $flashData = $this->grav['session']->getFlashCookieObject(self::TMP_COOKIE_NAME);

        if (isset($flashData->message)) {
            $this->grav['messages']->add($flashData->message, $flashData->status);
        }
    }

    /**
     * Process the user registration, triggered by a registration form
     *
     * @param Form $form
     * @throws \RuntimeException
     */
    private function processUserRegistration($form, Event $event)
    {
        $language = $this->grav['language'];
        $messages = $this->grav['messages'];

        if (!$this->config->get('plugins.login.enabled')) {
            throw new \RuntimeException($language->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED'));
        }

        if (!$this->config->get('plugins.login.user_registration.enabled')) {
            throw new \RuntimeException($language->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED'));
        }

        $form->validate();
        $form->filter();

        /** @var Data $form_data */
        $form_data = $form->getData();

        // Check for existing username
        $username = $form_data->get('username');
        $existing_username = User::find($username,['username']);
        if ($existing_username->exists()) {
            $this->grav->fireEvent('onFormValidationError', new Event([
                'form'    => $form,
                'message' => $language->translate([
                    'PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE',
                    $username
                ])
            ]));
            $event->stopPropagation();
            return;
        }

        // Check for existing email
        $email    = $form_data->get('email');
        $existing_email = User::find($email,['email']);
        if ($existing_email->exists()) {
            $this->grav->fireEvent('onFormValidationError', new Event([
                'form'    => $form,
                'message' => $language->translate([
                    'PLUGIN_LOGIN.EMAIL_NOT_AVAILABLE',
                    $email
                ])
            ]));
            $event->stopPropagation();
            return;
        }

        $data = [];
        $data['username'] = $username;


        // if multiple password fields, check they match and set password field from it
        if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2',
            false)
        ) {
            if ($form_data->get('password1') !== $form_data->get('password2')) {
                $this->grav->fireEvent('onFormValidationError', new Event([
                    'form'    => $form,
                    'message' => $language->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH')
                ]));
                $event->stopPropagation();

                return;
            }
            $data['password'] = $form_data->get('password1');
        }

        $fields = (array)$this->config->get('plugins.login.user_registration.fields', []);

        foreach ($fields as $field) {
            // Process value of field if set in the page process.register_user
            $default_values = (array)$this->config->get('plugins.login.user_registration.default_values');
            if ($default_values) {
                foreach ($default_values as $key => $param) {

                    if ($key === $field) {
                        if (is_array($param)) {
                            $values = explode(',', $param);
                        } else {
                            $values = $param;
                        }
                        $data[$field] = $values;
                    }
                }
            }

            if (!isset($data[$field]) && $form_data->get($field)) {
                $data[$field] = $form_data->get($field);
            }
        }

        if ($this->config->get('plugins.login.user_registration.options.set_user_disabled', false)) {
            $data['state'] = 'disabled';
        } else {
            $data['state'] = 'enabled';
        }
        $data_object = (object) $data;
        $this->grav->fireEvent('onUserLoginRegisterData', new Event(['data' => &$data_object]));
        $user = $this->login->register((array)$data_object);
        $this->grav->fireEvent('onUserLoginRegisteredUser', new Event(['user' => &$user]));

        $fullname = $user->fullname ?: $user->username;

        if ($this->config->get('plugins.login.user_registration.options.send_activation_email', false)) {
            $this->login->sendActivationEmail($user);
            $message = $language->translate(['PLUGIN_LOGIN.ACTIVATION_NOTICE_MSG', $fullname]);
            $messages->add($message, 'info');
        } else {
            if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) {
                $this->login->sendWelcomeEmail($user);
            }
            if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) {
                $this->login->sendNotificationEmail($user);
            }
            $message = $language->translate(['PLUGIN_LOGIN.WELCOME_NOTICE_MSG', $fullname]);
            $messages->add($message, 'info');
        }

        $this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user]));

        $redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration');
        $redirect_code = null;

        if (isset($data['state']) && $data['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
            $loginEvent = $this->login->login(['username' => $username], ['after_registration' => true], ['user' => $user, 'return_event' => true]);

            // If there's no registration redirect, get one from login.
            if (!$redirect) {
                $message = $loginEvent->getMessage();
                if ($message) {
                    $messages->add($message, $loginEvent->getMessageType());
                }

                $redirect = $loginEvent->getRedirect();
                $redirect_code = $loginEvent->getRedirectCode();
            }
        }

        if ($redirect) {
            $this->grav->redirectLangSafe($redirect, $redirect_code);
        }
    }

    /**
     * Save user profile information
     *
     * @param Form $form
     * @param Event $event
     * @return bool
     */
    private function processUserProfile($form, Event $event)
    {
        /** @var User $user */
        $user     = $this->grav['user'];
        $language = $this->grav['language'];

        $form->validate();
        $form->filter();

        /** @var Data $form_data */
        $form_data = $form->getData();

        // Don't save if user doesn't exist
        if (!$user->exists()) {
            $this->grav->fireEvent('onFormValidationError', new Event([
                'form'    => $form,
                'message' => $language->translate('PLUGIN_LOGIN.USER_IS_REMOTE_ONLY')
            ]));
            $event->stopPropagation();
            return;
        }

        // Stop overloading of username
        $username = $form->data('username');
        if (isset($username)) {
            $this->grav->fireEvent('onFormValidationError', new Event([
                'form'    => $form,
                'message' => $language->translate([
                    'PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE',
                    $username
                ])
            ]));
            $event->stopPropagation();
            return;
        }

        // Check for existing email
        $email    = $form->data('email');
        $existing_email = User::find($email,['email']);
        if ($user->username != $existing_email->username && $existing_email->exists()) {
            $this->grav->fireEvent('onFormValidationError', new Event([
                'form'    => $form,
                'message' => $language->translate([
                    'PLUGIN_LOGIN.EMAIL_NOT_AVAILABLE',
                    $email
                ])
            ]));
            $event->stopPropagation();
            return;
        }

        $fields = (array)$this->config->get('plugins.login.user_registration.fields', []);

        $data = [];
        foreach ($fields as $field) {
            if (!isset($data[$field]) && $form_data->get($field)) {
                $data[$field] = $form_data->get($field);
            }
        }
        $user->merge($data);

        try {
            $user->save();
        } catch (\Exception $e) {
            $form->setMessage($e->getMessage(), 'error');
            return false;
        }

        return true;
    }

    /**
     * [onFormProcessed] Process a registration form. Handles the following actions:
     *
     * - register_user: registers a user
     * - update_user: updates user profile
     *
     * @param Event $event
     * @throws \RuntimeException
     */
    public function onFormProcessed(Event $event)
    {
        $form = $event['form'];
        $action = $event['action'];

        switch ($action) {
            case 'register_user':
                $this->processUserRegistration($form, $event);
                break;
            case 'update_user':
                $this->processUserProfile($form, $event);
                break;
        }
    }

    /**
     * @param UserLoginEvent $event
     * @throws \RuntimeException
     */
    public function userLoginAuthenticateByRegistration(UserLoginEvent $event)
    {
        // Check that we're logging in after registration.
        if (!$event->getOption('after_registration') || $this->isAdmin()) {
            return;
        }

        $event->setStatus($event::AUTHENTICATION_SUCCESS);
        $event->stopPropagation();
    }

    /**
     * @param UserLoginEvent $event
     * @throws \RuntimeException
     */
    public function userLoginAuthenticateByRememberMe(UserLoginEvent $event)
    {
        // Check that we're logging in with remember me.
        if (!$event->getOption('remember_me_login') || !$event->getOption('remember_me') || $this->isAdmin()) {
            return;
        }

        // Only use remember me if user isn't set and feature is enabled.
        if ($this->grav['config']->get('plugins.login.rememberme.enabled') && !$event->getUser()->exists()) {
            /** @var Debugger $debugger */
            $debugger = $this->grav['debugger'];

            /** @var RememberMe $rememberMe */
            $rememberMe = $this->grav['login']->rememberMe();
            $username = $rememberMe->login();

            if ($rememberMe->loginTokenWasInvalid()) {
                // Token was invalid. We will display error page as this was likely an attack.
                $debugger->addMessage('Remember Me: Stolen token!');

                throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REMEMBER_ME_STOLEN_COOKIE'), 403);
            }

            if ($username === false) {
                // User has not been remembered, there is no point of continuing.
                $debugger->addMessage('Remember Me: No token matched.');

                $event->setStatus($event::AUTHENTICATION_FAILURE);
                $event->stopPropagation();

                return;
            }

            // Allow remember me to work with different login methods.
            $user = User::load($username, false);
            $event->setCredential('username', $username);
            $event->setUser($user);

            if (!$user->exists()) {
                $debugger->addMessage('Remember Me: User does not exist');

                $event->setStatus($event::AUTHENTICATION_FAILURE);
                $event->stopPropagation();

                return;
            }

            $debugger->addMessage('Remember Me: Authenticated!');

            $event->setStatus($event::AUTHENTICATION_SUCCESS);
            $event->stopPropagation();

            return;
        }
    }

    public function userLoginAuthenticateByEmail(UserLoginEvent $event)
    {
        if (($username = $event->getCredential('username')) && !$event->getUser()->exists()) {
            $event->setUser(User::find($username));
        }
    }

    public function userLoginAuthenticate(UserLoginEvent $event)
    {
        $user = $event->getUser();
        $credentials = $event->getCredentials();

        if (!$user->exists()) {
            // Never let non-existing users to pass the authentication.
            // Higher level plugins may override this behavior by stopping propagation.
            $event->setStatus($event::AUTHENTICATION_FAILURE);
            $event->stopPropagation();

            return;
        }

        // Never let empty password to pass the authentication.
        // Higher level plugins may override this behavior by stopping propagation.
        if (empty($credentials['password'])) {
            $event->setStatus($event::AUTHENTICATION_FAILURE);
            $event->stopPropagation();

            return;
        }

        // Try default user authentication. Stop propagation if authentication succeeds.
        if ($user->authenticate($credentials['password'])) {
            $event->setStatus($event::AUTHENTICATION_SUCCESS);
            $event->stopPropagation();

            return;
        }

        // If authentication status is undefined, lower level event handlers may still be able to authenticate user.
    }

    public function userLoginAuthorize(UserLoginEvent $event)
    {
        // Always block access if authorize defaulting to site.login fails.
        $user = $event->getUser();
        foreach ($event->getAuthorize() as $authorize) {
            if (!$user->authorize($authorize)) {
                $event->setStatus($event::AUTHORIZATION_DENIED);
                $event->stopPropagation();

                return;
            }
        }

        if ($event->getOption('twofa') && $user->twofa_enabled && $user->twofa_secret) {
            $event->setStatus($event::AUTHORIZATION_DELAYED);
        }
    }

    public function userLoginFailure(UserLoginEvent $event)
    {
        $this->grav['session']->user = User::load('', false);
    }

    public function userLogin(UserLoginEvent $event)
    {
        $session = $this->grav['session'];
        $session->user = $event->getUser();

        if ($event->getOption('remember_me')) {
            /** @var Login $login */
            $login = $this->grav['login'];

            $session->remember_me = (bool)$event->getOption('remember_me_login');

            // If the user wants to be remembered, create Rememberme cookie.
            $username = $event->getUser()->get('username');
            if ($event->getCredential('rememberme')) {
                $login->rememberMe()->createCookie($username);
            }
        }
    }

    public function userLogout(UserLoginEvent $event)
    {
        if ($event->getOption('remember_me')) {
            /** @var Login $login */
            $login = $this->grav['login'];

            if (!$login->rememberMe()->login()) {
                $login->rememberMe()->getStorage()->cleanAllTriplets($event->getUser()->get('username'));
            }
            $login->rememberMe()->clearCookie();
        }

        $this->grav['session']->invalidate()->start();
    }
}
