diff --git a/index.php b/index.php index 2ecfd545..17d8ffeb 100644 --- a/index.php +++ b/index.php @@ -33,19 +33,6 @@ spl_autoload_register( } ); -/** - * Session - * - * Has to be setup first, before anything else, so translations can be loaded. - */ -session_start( - array( - 'name' => 'wishthis', - ) -); - -$user = User::getCurrent(); - /** * Config */ @@ -55,6 +42,22 @@ if (file_exists($configPath)) { require $configPath; } +/** + * Session + * + * Has to be setup first, before anything else, so translations can be loaded. + * The configuration is the only exception, since `loadFromSession` needs the + * database. + */ +session_start( + array( + 'name' => 'wishthis', + 'cookie_lifetime' => \ini_get('session.gc_maxlifetime') ?: 1440, + ) +); + +$user = User::getCurrent(); +$user->loadFromSession(); /** * Database diff --git a/src/classes/wishthis/Page.php b/src/classes/wishthis/Page.php index ace954e3..f6526ef1 100644 --- a/src/classes/wishthis/Page.php +++ b/src/classes/wishthis/Page.php @@ -160,6 +160,10 @@ class Page */ $user = User::getCurrent(); + if ($user->isLoggedIn()) { + $user->refreshSession(); + } + /** * Login */ diff --git a/src/classes/wishthis/User.php b/src/classes/wishthis/User.php index a25bc823..298b4b15 100644 --- a/src/classes/wishthis/User.php +++ b/src/classes/wishthis/User.php @@ -45,9 +45,7 @@ class User $_SESSION['user'] = new self(); } - $user = $_SESSION['user']; - - return $user; + return $_SESSION['user']; } /** @@ -159,6 +157,13 @@ class User */ private bool $advertisements = false; + /** + * Whether the user wants to stay logged in. + * + * @var bool + */ + private bool $stayLoggedIn = false; + /** * Non-Static */ @@ -244,7 +249,38 @@ class User */ public function isLoggedIn(): bool { - return isset($this->id) && $this->id >= 1; + if (!isset($_COOKIE['wishthis'])) { + return false; + } + + $database = new Database( + DATABASE_HOST, + DATABASE_NAME, + DATABASE_USER, + DATABASE_PASSWORD + ); + $database->connect(); + + $session = $database + ->query( + 'SELECT * + FROM `sessions` + WHERE `session` = :session', + array( + 'session' => $_COOKIE['wishthis'], + ) + ) + ->fetch(); + + if (false === $session) { + return false; + } + + if (\strtotime($session['expires']) <= \time()) { + return false; + } + + return true; } /** @@ -313,7 +349,7 @@ class User * * @return bool Whether the log in was successful. */ - public function logIn(string $email = '', string $password = '', bool $user_login_is_persistent = false): bool + public function logIn(string $email = '', string $password = '', bool $userLoginIsPersistent = false): bool { $database = new Database( DATABASE_HOST, @@ -344,7 +380,7 @@ class User 'user_password' => $password, ) ) - ->fetch(); + ->fetch(\PDO::FETCH_ASSOC); /** * Fetching the user fields has failed and we are now assuming that the @@ -368,30 +404,19 @@ class User 'user_password' => $password, ) ); - $user_database_fields['last_login'] = time(); + $user_database_fields['last_login'] = date('Y-m-d H:i'); /** * Set session duration */ - $database - ->query( - 'REPLACE INTO `sessions` (`user`, `session`, `expires`) VALUES ( - :user_id, - :session_id, - :session_expires - )', - array( - 'user_id' => $user_database_fields['id'], - 'session_id' => \session_id(), - 'session_expires' => date('Y-m-d H:i', time() + 1800), - ) - ); + $this->refreshSession($user_database_fields['id']); /** * Create a `User` object instance and assign it for later use. */ if (\is_array($user_database_fields)) { $this->__construct($user_database_fields); + $this->stayLoggedIn = $userLoginIsPersistent; $_SESSION['user'] = $this; @@ -404,6 +429,22 @@ class User public function logOut(): void { /** Destroy session */ + $database = new Database( + DATABASE_HOST, + DATABASE_NAME, + DATABASE_USER, + DATABASE_PASSWORD + ); + $database->connect(); + $database + ->query( + 'DELETE FROM `sessions` + WHERE `session` = :session', + array( + 'session' => $_COOKIE['wishthis'], + ) + ); + session_destroy(); unset($_SESSION); } @@ -510,4 +551,133 @@ class User { return $this->password_reset_valid_until; } + + public function refreshSession(int $forUser = 0): void + { + $sessionId = $_COOKIE['wishthis']; + $sessionDurationSeconds = \ini_get('session.gc_maxlifetime') ?: 1440; + + if ($this->stayLoggedIn) { + $sessionDurationSeconds = 31104000; // One year + } + + if (0 === $forUser) { + $forUser = $this->id; + } + + $database = new Database( + DATABASE_HOST, + DATABASE_NAME, + DATABASE_USER, + DATABASE_PASSWORD + ); + $database->connect(); + + /** Delete outdated sessions */ + $database + ->query( + 'DELETE FROM `sessions` + WHERE `expires` <= NOW()', + ); + + /** Find existing session */ + $sessionsExisting = $database + ->query( + 'SELECT * + FROM `sessions` + WHERE `session` = :session', + array( + 'session' => $sessionId, + ) + ) + ->fetchAll(); + + /** The session exists and can be updated now */ + foreach ($sessionsExisting as $session) { + if ($session['session'] === $sessionId) { + $database + ->query( + 'UPDATE `sessions` + SET `expires` = :expires + WHERE `session` = :session + AND `user` = :user', + array( + 'expires' => date('Y-m-d H:i', time() + $sessionDurationSeconds), + 'session' => $sessionId, + 'user' => $forUser, + ) + ); + + /** There's no need to do anything further. */ + return; + } + } + + /** + * Since there has been no return until now, we are assuming the session + * does not exist and will create it now. + */ + $database + ->query( + 'INSERT INTO `sessions` (`user`, `session`, `expires`) VALUES ( + :user_id, + :session_id, + :session_expires + )', + array( + 'user_id' => $forUser, + 'session_id' => $sessionId, + 'session_expires' => date('Y-m-d H:i', time() + $sessionDurationSeconds), + ) + ); + } + + public function loadFromSession(): void + { + if (!$this->isLoggedIn()) { + return; + } + + $database = new Database( + DATABASE_HOST, + DATABASE_NAME, + DATABASE_USER, + DATABASE_PASSWORD + ); + $database->connect(); + + $session = $database + ->query( + 'SELECT * + FROM `sessions` + WHERE `session` = :session + AND `user` = :user', + array( + 'session' => $_COOKIE['wishthis'], + 'user' => $this->id, + ) + ) + ->fetch(\PDO::FETCH_ASSOC); + + if (false === $session) { + return; + } + + $user = $database + ->query( + 'SELECT * + FROM `users` + WHERE `id` = :user', + array( + 'user' => $this->id, + ) + ) + ->fetch(\PDO::FETCH_ASSOC); + + if (false === $user) { + return; + } + + $this->__construct($user); + } } diff --git a/src/pages/login.php b/src/pages/login.php index 7d996761..3736a175 100644 --- a/src/pages/login.php +++ b/src/pages/login.php @@ -14,11 +14,12 @@ $page = new Page(__FILE__, __('Login')); * Login */ if (isset($_POST['login'], $_POST['email'], $_POST['password'])) { - $user_email = \filter_input(\INPUT_POST, 'email', FILTER_SANITIZE_EMAIL); - $user_password = User::passwordToHash($_POST['password']); - $user_login_is_persistent = isset($_POST['persistent']); + $user_email = \filter_input(\INPUT_POST, 'email', FILTER_SANITIZE_EMAIL); + $user_password = User::passwordToHash($_POST['password']); + $userLoginIsPersistent = isset($_POST['persistent']); - $user->login($user_email, $user_password, $user_login_is_persistent); + $user->login($user_email, $user_password, $userLoginIsPersistent); + $user = User::getCurrent(); if (!$user->isLoggedIn()) { $page->messages[] = Page::error(