<?php
require_once 'lib/gettext/gettext.inc.php';

/**
 * @todo look into making this behave closer to what SessionHandlerInterface intends
 */
class Sessions implements \SessionHandlerInterface {
	private int $session_expire;
	private string $session_name;

	public function __construct() {
		$this->session_expire = min(2147483647 - time() - 1, max(Config::get(Config::SESSION_COOKIE_LIFETIME), 86400));
		$this->session_name = Config::get(Config::SESSION_NAME);
	}

	/**
	 * Adjusts session-related PHP configuration options
	 */
	public function configure(): void {
		if (Config::is_server_https()) {
			ini_set('session.cookie_secure', 'true');
		}

		ini_set('session.gc_probability', '75');
		ini_set('session.name', $this->session_name);
		ini_set('session.use_only_cookies', 'true');
		ini_set('session.gc_maxlifetime', $this->session_expire);
		ini_set('session.cookie_lifetime', '0');
	}

	/**
	 * Extend the validity of the PHP session cookie (if it exists)
	 * @return bool Whether the new cookie was set successfully
	 */
	public function extend_session(): bool {
		if (isset($_COOKIE[$this->session_name])) {
			return setcookie($this->session_name,
				$_COOKIE[$this->session_name],
				time() + $this->session_expire,
				ini_get('session.cookie_path'),
				ini_get('session.cookie_domain'),
				ini_get('session.cookie_secure'),
				ini_get('session.cookie_httponly'));
		}
		return false;
	}

	public function open(string $path, string $name): bool {
		return true;
	}

	public function close(): bool {
		return true;
	}

	/**
	 * @todo set return type to string|false, and remove ReturnTypeWillChange, when min supported is PHP 8
	 * @return string|false
	 */
	#[\ReturnTypeWillChange]
	public function read(string $id) {
		$sth = Db::pdo()->prepare('SELECT data FROM ttrss_sessions WHERE id=?');
		$sth->execute([$id]);

		if ($row = $sth->fetch()) {
			return base64_decode($row['data']);
		}

		$expire = time() + $this->session_expire;

		$sth = Db::pdo()->prepare("INSERT INTO ttrss_sessions (id, data, expire)
			VALUES (?, '', ?)");
		return $sth->execute([$id, $expire]) ? '' : false;
	}

	public function write(string $id, string $data): bool {
		$data = base64_encode($data);
		$expire = time() + $this->session_expire;

		$sth = Db::pdo()->prepare('SELECT id FROM ttrss_sessions WHERE id=?');
		$sth->execute([$id]);

		if ($sth->fetch()) {
			$sth = Db::pdo()->prepare('UPDATE ttrss_sessions SET data=?, expire=? WHERE id=?');
			return $sth->execute([$data, $expire, $id]);
		}

		$sth = Db::pdo()->prepare('INSERT INTO ttrss_sessions (id, data, expire) VALUES (?, ?, ?)');
		return $sth->execute([$id, $data, $expire]);
	}

	public function destroy(string $id): bool {
		$sth = Db::pdo()->prepare('DELETE FROM ttrss_sessions WHERE id = ?');
		return $sth->execute([$id]);
	}

	/**
	 * @todo set return type to int|false, and remove ReturnTypeWillChange, when min supported is PHP 8
	 * @return int|false the number of deleted sessions on success, or false on failure
	 */
	#[\ReturnTypeWillChange]
	public function gc(int $max_lifetime) {
		$result = Db::pdo()->query('DELETE FROM ttrss_sessions WHERE expire < ' . time());
		return $result === false ? false : $result->rowCount();
	}

	public static function validate_session(): bool {
		if (Config::get(Config::SINGLE_USER_MODE)) return true;

		$pdo = Db::pdo();

		if (!empty($_SESSION['uid'])) {
			$user = ORM::for_table('ttrss_users')->find_one($_SESSION['uid']);

			if ($user) {
				if ($user->pwd_hash != $_SESSION['pwd_hash']) {
					$_SESSION['login_error_msg'] = __('Session failed to validate (password changed)');
					return false;
				}

				if ($user->access_level == UserHelper::ACCESS_LEVEL_DISABLED) {
					$_SESSION['login_error_msg'] = __('Session failed to validate (account is disabled)');
					return false;
				}

				// default to true because there might not be any hooks and this is our last check
				$hook_result = true;

				PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_VALIDATE_SESSION,
					function ($result) use (&$hook_result) {
						$hook_result = $result;

						if (!$result) {
							return true;
						}
					});

				return $hook_result;

			} else {
				$_SESSION['login_error_msg'] = __('Session failed to validate (user not found)');
				return false;
			}
		}

		return true;
	}
}
