1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2017 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16namespace Fisharebest\Webtrees; 17 18/** 19 * Temporary class to migrate to Symfony-based sessions, which need PHP 5.4. 20 */ 21class Session { 22 /** 23 * Start a session 24 * 25 * @param array $config 26 */ 27 public static function start(array $config = []) { 28 $default_config = [ 29 'use_cookies' => '1', 30 'name' => 'WT_SESSION', 31 'cookie_lifetime' => '0', 32 'gc_maxlifetime' => '7200', 33 'gc_probability' => '1', 34 'gc_divisor' => '100', 35 'cookie_path' => '', 36 'cookie_httponly' => '1', 37 ]; 38 session_register_shutdown(); 39 foreach ($config + $default_config as $key => $value) { 40 ini_set('session.' . $key, $value); 41 } 42 session_start(); 43 } 44 45 /** 46 * Read a value from the session 47 * 48 * @param string $name 49 * @param mixed $default 50 * 51 * @return mixed 52 */ 53 public static function get($name, $default = null) { 54 if (isset($_SESSION[$name])) { 55 return $_SESSION[$name]; 56 } else { 57 return $default; 58 } 59 } 60 61 /** 62 * Write a value to the session 63 * 64 * @param string $name 65 * @param mixed $value 66 */ 67 public static function put($name, $value) { 68 $_SESSION[$name] = $value; 69 } 70 71 /** 72 * Remove a value from the session 73 * 74 * @param string $name 75 */ 76 public static function forget($name) { 77 unset($_SESSION[$name]); 78 } 79 80 /** 81 * Does a session variable exist? 82 * 83 * @param string $name 84 * 85 * @return bool 86 */ 87 public static function has($name) { 88 return isset($_SESSION[$name]); 89 } 90 91 /** 92 * Remove all stored data from the session. 93 */ 94 public static function clear() { 95 $_SESSION = []; 96 } 97 98 /** 99 * After any change in authentication level, we should use a new session ID. 100 * 101 * @param bool $destroy 102 */ 103 public static function regenerate($destroy = false) { 104 if ($destroy) { 105 self::clear(); 106 } 107 session_regenerate_id($destroy); 108 } 109 110 /** 111 * Set an explicit session ID. Typically used for search robots. 112 * 113 * @param string $id 114 */ 115 public static function setId($id) { 116 session_id($id); 117 } 118 119 /** 120 * Initialise our session save handler 121 */ 122 public static function setSaveHandler() { 123 session_set_save_handler( 124 function (): bool { 125 return Session::open(); 126 }, 127 function ():bool { 128 return Session::close(); 129 }, 130 function (string $id): string { 131 return Session::read($id); 132 }, 133 function (string $id, string $data): bool { 134 return Session::write($id, $data); 135 }, 136 function (string $id): bool { 137 return Session::destroy($id); 138 }, 139 function (int $maxlifetime):bool { 140 return Session::gc($maxlifetime); 141 } 142 ); 143 } 144 145 /** 146 * For session_set_save_handler() 147 * 148 * @return bool 149 */ 150 private static function close() { 151 return true; 152 } 153 154 /** 155 * For session_set_save_handler() 156 * 157 * @param string $id 158 * 159 * @return bool 160 */ 161 private static function destroy(string $id) { 162 Database::prepare( 163 "DELETE FROM `##session` WHERE session_id = :session_id" 164 )->execute([ 165 'session_id' => $id 166 ]); 167 168 return true; 169 } 170 171 /** 172 * For session_set_save_handler() 173 * 174 * @param int $maxlifetime 175 * 176 * @return bool 177 */ 178 private static function gc(int $maxlifetime) { 179 Database::prepare( 180 "DELETE FROM `##session` WHERE session_time < DATE_SUB(NOW(), INTERVAL :maxlifetime SECOND)" 181 )->execute([ 182 'maxlifetime' => $maxlifetime 183 ]); 184 185 return true; 186 } 187 188 /** 189 * For session_set_save_handler() 190 * 191 * @return bool 192 */ 193 private static function open() { 194 return true; 195 } 196 197 /** 198 * For session_set_save_handler() 199 * 200 * @param string $id 201 * 202 * @return string 203 */ 204 private static function read(string $id): string { 205 return (string) Database::prepare( 206 "SELECT session_data FROM `##session` WHERE session_id = :session_id" 207 )->execute([ 208 'session_id' => $id 209 ])->fetchOne(); 210 } 211 212 /** 213 * For session_set_save_handler() 214 * 215 * @param string $id 216 * @param string $data 217 * 218 * @return bool 219 */ 220 private static function write(string $id, string $data): bool { 221 // Only update the session table once per minute, unless the session data has actually changed. 222 Database::prepare( 223 "INSERT INTO `##session` (session_id, user_id, ip_address, session_data, session_time)" . 224 " VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP))" . 225 " ON DUPLICATE KEY UPDATE" . 226 " user_id = VALUES(user_id)," . 227 " ip_address = VALUES(ip_address)," . 228 " session_data = VALUES(session_data)," . 229 " session_time = CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP)" 230 )->execute([ 231 $id, 232 (int) Auth::id(), 233 WT_CLIENT_IP, 234 $data] 235 ); 236 237 return true; 238 } 239} 240