. */ namespace Fisharebest\Webtrees; use Symfony\Component\HttpFoundation\Request; /** * Session handling */ class Session { /** * Start a session * * @param array $config */ public static function start(array $config = []) { $default_config = [ 'use_cookies' => '1', 'name' => 'WT_SESSION', 'cookie_lifetime' => '0', 'gc_maxlifetime' => '7200', 'gc_probability' => '1', 'gc_divisor' => '100', 'cookie_path' => '', 'cookie_httponly' => '1', ]; session_register_shutdown(); foreach ($config + $default_config as $key => $value) { ini_set('session.' . $key, $value); } session_start(); } /** * Read a value from the session * * @param string $name * @param mixed $default * * @return mixed */ public static function get($name, $default = null) { if (isset($_SESSION[$name])) { return $_SESSION[$name]; } else { return $default; } } /** * Write a value to the session * * @param string $name * @param mixed $value */ public static function put($name, $value) { $_SESSION[$name] = $value; } /** * Remove a value from the session * * @param string $name */ public static function forget($name) { unset($_SESSION[$name]); } /** * Does a session variable exist? * * @param string $name * * @return bool */ public static function has($name) { return isset($_SESSION[$name]); } /** * Remove all stored data from the session. */ public static function clear() { $_SESSION = []; } /** * After any change in authentication level, we should use a new session ID. * * @param bool $destroy */ public static function regenerate($destroy = false) { if ($destroy) { self::clear(); } session_regenerate_id($destroy); } /** * Set an explicit session ID. Typically used for search robots. * * @param string $id */ public static function setId($id) { session_id($id); } /** * Initialise our session save handler */ public static function setSaveHandler() { session_set_save_handler( function (): bool { return Session::open(); }, function ():bool { return Session::close(); }, function (string $id): string { return Session::read($id); }, function (string $id, string $data): bool { return Session::write($id, $data); }, function (string $id): bool { return Session::destroy($id); }, function (int $maxlifetime):bool { return Session::gc($maxlifetime); } ); } /** * For session_set_save_handler() * * @return bool */ private static function close() { return true; } /** * For session_set_save_handler() * * @param string $id * * @return bool */ private static function destroy(string $id) { Database::prepare( "DELETE FROM `##session` WHERE session_id = :session_id" )->execute([ 'session_id' => $id ]); return true; } /** * For session_set_save_handler() * * @param int $maxlifetime * * @return bool */ private static function gc(int $maxlifetime) { Database::prepare( "DELETE FROM `##session` WHERE session_time < DATE_SUB(NOW(), INTERVAL :maxlifetime SECOND)" )->execute([ 'maxlifetime' => $maxlifetime ]); return true; } /** * For session_set_save_handler() * * @return bool */ private static function open() { return true; } /** * For session_set_save_handler() * * @param string $id * * @return string */ private static function read(string $id): string { return (string) Database::prepare( "SELECT session_data FROM `##session` WHERE session_id = :session_id" )->execute([ 'session_id' => $id ])->fetchOne(); } /** * For session_set_save_handler() * * @param string $id * @param string $data * * @return bool */ private static function write(string $id, string $data): bool { $request = Request::createFromGlobals(); // Only update the session table once per minute, unless the session data has actually changed. Database::prepare( "INSERT INTO `##session` (session_id, user_id, ip_address, session_data, session_time)" . " VALUES (:session_id, :user_id, :ip_address, :data, CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP))" . " ON DUPLICATE KEY UPDATE" . " user_id = VALUES(user_id)," . " ip_address = VALUES(ip_address)," . " session_data = VALUES(session_data)," . " session_time = CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP)" )->execute([ 'session_id' => $id, 'user_id' => (int) Auth::id(), 'ip_address' => $request->getClientIp(), 'data' => $data, ]); return true; } }