<?php

/**
 * LoginModel
 *
 * The login part of the model: Handles the login / logout stuff
 */
class LoginModel
{
    /**
     * Login process (for DEFAULT user accounts).
     *
     * @param $user_name string The user's name
     * @param $user_password string The user's password
     * @param $set_remember_me_cookie mixed Marker for usage of remember-me cookie feature
     *
     * @return bool success state
     */
    public static function login($user_name,
                                 $user_password,
                                 $set_remember_me_cookie = null)
    {
        // we do negative-first checks here,
        // for simplicity empty username and empty password in one line
        if (empty($user_name) OR empty($user_password))
        {
            Session::add('feedback_negative',
                         Text::get('FEEDBACK_USERNAME_OR_PASSWORD_FIELD_EMPTY'));
            return false;
        }

        // checks if user exists, if login is not blocked (due to failed logins)
        // and if password fits the hash
        $result = self::validateAndGetUser($user_name, $user_password);

        if (!$result) {
            return false;
        }

        // reset the failed login counter for that user (if necessary)
        if ($result->user_last_failed_login > 0) {
            self::resetFailedLoginCounterOfUser($result->user_name);
        }

        // save timestamp of this login in the database line of that user
        self::saveTimestampOfLoginOfUser($result->user_name);

        // if user has checked the "remember me" checkbox,
        // then write token into database and into cookie
        if ($set_remember_me_cookie) {
            self::setRememberMeInDatabaseAndCookie($result->user_id);
        }

        // successfully logged in, so we write all necessary data into the session
        // and set "user_logged_in" to true
        self::setSuccessfulLoginIntoSession($result->user_id,
                                            $result->user_name,
                                            $result->user_email,
                                            $result->user_is_admin);

        // return true to make clear the login was successful
        // maybe do this in dependence of setSuccessfulLoginIntoSession ?
        return true;
    }

    /**
     * Validates the inputs of the users, checks if password is correct etc.
     * If successful, user is returned
     *
     * @param $user_name
     * @param $user_password
     *
     * @return bool|mixed
     */
    private static function validateAndGetUser($user_name, $user_password)
    {
        // get all data of that user (to later check if password and password_hash fit)
        $result = UserModel::getUserDataByUsername($user_name);

        // Check if that user exists. We don't give back a cause in the feedback
        // to avoid giving an attacker details.
        if (!$result) {
            Session::add('feedback_negative', Text::get('FEEDBACK_LOGIN_FAILED'));
            return false;
        }

        // block login attempt if somebody has already failed 3 times and
        // the last login attempt is less than 30sec ago
        if (($result->user_failed_logins >= 3) AND
            ($result->user_last_failed_login > (time() - 30))) {
            Session::add('feedback_negative',
                         Text::get('FEEDBACK_PASSWORD_WRONG_3_TIMES'));
            return false;
        }

        // if hash of provided password does NOT match the hash in the database:
        // +1 failed-login counter
        if (!password_verify($user_password, $result->user_password_hash)) {
            self::incrementFailedLoginCounterOfUser($result->user_name);
            // We don't give back a cause in the feedback
            // to avoid giving an attacker details.
            Session::add('feedback_negative', Text::get('FEEDBACK_LOGIN_FAILED'));
            return false;
        }

        // if user is not active (= has not verified account by verification mail)
        if ($result->user_active != 1) {
            Session::add('feedback_negative',
                         Text::get('FEEDBACK_ACCOUNT_NOT_ACTIVATED_YET'));
            return false;
        }

        // if user is not approved (users must be approved is turned on in settings)
        if ($result->user_approved != 1) {
            Session::add('feedback_negative',
                         Text::get('FEEDBACK_ACCOUNT_NOT_APPROVED_YET'));
            return false;
        }

        return $result;
    }

    /**
     * Log out process: delete session and start a new one
     */
    public static function logout()
    {
        Session::destroy();
        Session::init();
        session_regenerate_id(true);
        $_SESSION = array();
    }

    /**
     * The real login process: The user's data is written into the session.
     * Cheesy name, maybe rename. Also maybe refactoring this, using an array.
     *
     * @param $user_id
     * @param $user_name
     * @param $user_email
     * @param $user_account_type
     */
    public static function setSuccessfulLoginIntoSession($user_id,
                                                         $user_name,
                                                         $user_email,
                                                         $user_is_admin)
    {
        Session::init();

        // remove old and regenerate session ID.
        // It's important to regenerate session on sensitive actions,
        // and to avoid fixated session.
        // e.g. when a user logs in
        session_regenerate_id(true);
        $_SESSION = array();

        Session::set('user_id', $user_id);
        Session::set('user_name', $user_name);
        Session::set('user_email', $user_email);
        Session::set('user_account_type', $user_is_admin);

        // finally, set user as logged-in
        Session::set('user_logged_in', true);

        // set session cookie setting manually,
        // Why? because you need to explicitly set session expiry, path, domain, secure, and HTTP.
        // @see https://www.owasp.org/index.php/PHP_Security_Cheat_Sheet#Cookies
        setcookie(session_name(),
                  session_id(),
                  time() + Config::get('SESSION_RUNTIME'),
                  Config::get('COOKIE_PATH'),
                  Config::get('COOKIE_DOMAIN'),
                  Config::get('COOKIE_SECURE'),
                  Config::get('COOKIE_HTTP'));
    }

    /**
     * Increments the failed-login counter of a user
     *
     * @param $user_name
     */
    public static function incrementFailedLoginCounterOfUser($user_name)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();

        $sql = "UPDATE ".$prefix."users
                SET user_failed_logins = user_failed_logins+1,
                    user_last_failed_login = :user_last_failed_login
                 WHERE user_name = :user_name
                    OR user_email = :user_name
                 LIMIT 1";
        $sth = $database->prepare($sql);
        $sth->execute(array(':user_name' => $user_name,
                            ':user_last_failed_login' => time() ));
    }

    /**
     * Resets the failed-login counter of a user back to 0
     *
     * @param $user_name
     */
    public static function resetFailedLoginCounterOfUser($user_name)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();

        $sql = "UPDATE ".$prefix."users
                SET user_failed_logins = 0,
                    user_last_failed_login = NULL
                WHERE user_name = :user_name
                  AND user_failed_logins != 0
                LIMIT 1";
        $sth = $database->prepare($sql);
        $sth->execute(array(':user_name' => $user_name));
    }

    /**
     * Write timestamp of this login into database
     * (we only write a "real" login via login form into the database,
     * not the session-login on every page request
     *
     * @param $user_name
     */
    public static function saveTimestampOfLoginOfUser($user_name)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();

        $sql = "UPDATE ".$prefix."users
                SET user_last_login_timestamp = :user_last_login_timestamp
                WHERE user_name = :user_name
                LIMIT 1";
        $sth = $database->prepare($sql);
        $sth->execute(array(':user_name' => $user_name,
                            ':user_last_login_timestamp' => time()));
    }

    /**
     * Deletes the cookie
     * It's necessary to split deleteCookie() and logout()
     * as cookies are deleted without logging out too!
     * Sets the remember-me-cookie to ten years ago (3600sec * 24 hours * 365 days * 10).
     * that's obviously the best practice to kill a cookie
     * @see http://stackoverflow.com/a/686166/1114320
     */
    public static function deleteCookie()
    {
        setcookie('remember_me',
                  false,
                  time() - (3600 * 24 * 3650),
                  Config::get('COOKIE_PATH'));
    }

    /**
     * Returns the current state of the user's login
     *
     * @return bool user's login status
     */
    public static function isUserLoggedIn()
    {
        return Session::userIsLoggedIn();
    }
}
