/symphony/lib/core/class.session.php
PHP | 257 lines | 108 code | 27 blank | 122 comment | 17 complexity | 445e4d979dd48184e30c6747290cd884 MD5 | raw file
1<?php 2 3 /** 4 * @package core 5 */ 6 7 /** 8 * The Session class is a handler for all Session related logic in PHP. The functions 9 * map directly to all handler functions as defined by session_set_save_handler in 10 * PHP. In Symphony, this function is used in conjunction with the `Cookie` class. 11 * Based on: http://php.net/manual/en/function.session-set-save-handler.php#81761 12 * by klose at openriverbed dot de which was based on 13 * http://php.net/manual/en/function.session-set-save-handler.php#79706 by 14 * maria at junkies dot jp 15 * 16 * @link http://php.net/manual/en/function.session-set-save-handler.php 17 */ 18 require_once(CORE . '/class.cacheable.php'); 19 20 Class Session{ 21 22 /** 23 * If a Session has been created, this will be true, otherwise false 24 * 25 * @var boolean 26 */ 27 private static $_initialized = false; 28 29 /** 30 * Starts a Session object, only if one doesn't already exist. This function maps 31 * the Session Handler functions to this classes methods by reading the default 32 * information from the PHP ini file. 33 * 34 * @link http://php.net/manual/en/function.session-set-save-handler.php 35 * @link http://php.net/manual/en/function.session-set-cookie-params.php 36 * @param integer $lifetime 37 * How long a Session is valid for, by default this is 0, which means it 38 * never expires 39 * @param string $path 40 * The path the cookie is valid for on the domain 41 * @param string $domain 42 * The domain this cookie is valid for 43 * @param boolean $httpOnly 44 * Whether this cookie can be read by Javascript. By default the cookie 45 * cannot be read by Javascript 46 * @param boolean $secure 47 * Whether this cookie should only be sent on secure servers. By default this is 48 * false, which means the cookie can be sent over HTTP and HTTPS 49 * @throws Exception 50 * @return string|boolean 51 * Returns the Session ID on success, or false on error. 52 */ 53 public static function start($lifetime = 0, $path = '/', $domain = NULL, $httpOnly = true, $secure = false) { 54 55 if (!self::$_initialized) { 56 if(!is_object(Symphony::Database()) || !Symphony::Database()->isConnected()) return false; 57 58 if (session_id() == '') { 59 ini_set('session.save_handler', 'user'); 60 ini_set('session.gc_maxlifetime', $lifetime); 61 ini_set('session.gc_probability', '1'); 62 ini_set('session.gc_divisor', Symphony::Configuration()->get('session_gc_divisor', 'symphony')); 63 } 64 65 session_set_save_handler( 66 array('Session', 'open'), 67 array('Session', 'close'), 68 array('Session', 'read'), 69 array('Session', 'write'), 70 array('Session', 'destroy'), 71 array('Session', 'gc') 72 ); 73 74 session_set_cookie_params($lifetime, $path, ($domain ? $domain : self::getDomain()), $secure, $httpOnly); 75 76 if(session_id() == ''){ 77 if(headers_sent()){ 78 throw new Exception('Headers already sent. Cannot start session.'); 79 } 80 register_shutdown_function('session_write_close'); 81 session_start(); 82 } 83 84 self::$_initialized = true; 85 } 86 87 return session_id(); 88 } 89 90 /** 91 * Returns the current domain for the Session to be saved to, if the installation 92 * is on localhost, this returns null and just allows PHP to take care of setting 93 * the valid domain for the Session, otherwise it will return the non-www version 94 * of the domain host. 95 * 96 * @return string|null 97 * Null if on localhost, or HTTP_HOST is not set, a string of the domain name sans 98 * www otherwise 99 */ 100 public static function getDomain() { 101 if(isset($_SERVER['HTTP_HOST'])){ 102 103 if(preg_match('/(localhost|127\.0\.0\.1)/', $_SERVER['HTTP_HOST']) || $_SERVER['SERVER_ADDR'] == '127.0.0.1'){ 104 return null; // prevent problems on local setups 105 } 106 107 return preg_replace('/(^www\.|:\d+$)/i', NULL, $_SERVER['HTTP_HOST']); 108 109 } 110 111 return null; 112 } 113 114 /** 115 * Allows the Session to open without any further logic. 116 * 117 * @return boolean 118 * Always returns true 119 */ 120 public static function open() { 121 return true; 122 } 123 124 /** 125 * Allows the Session to close without any further logic. Acts as a 126 * destructor function for the Session. 127 * 128 * @return boolean 129 * Always returns true 130 */ 131 public static function close() { 132 return true; 133 } 134 135 /** 136 * Given an ID, and some data, save it into `tbl_sessions`. This uses 137 * the ID as a unique key, and will override any existing data. If the 138 * `$data` is deemed to be empty, no row will be saved in the database 139 * unless there is an existing row. 140 * 141 * @param string $id 142 * The ID of the Session, usually a hash 143 * @param string $data 144 * The Session information, usually a serialized object of 145 * `$_SESSION[Cookie->_index]` 146 * @throws DatabaseException 147 * @return boolean 148 * True if the Session information was saved successfully, false otherwise 149 */ 150 public static function write($id, $data) { 151 // Only prevent this record from saving if there isn't already a record 152 // in the database. This prevents empty Sessions from being created, but 153 // allows them to be nulled. 154 $session_data = Session::read($id); 155 if(is_null($session_data)) { 156 $empty = true; 157 $unserialized_data = Session::unserialize_session($session_data); 158 foreach ($unserialized_data as $d) { 159 if (!empty($d)) { 160 $empty = false; 161 } 162 } 163 164 if ($empty) { 165 return false; 166 } 167 } 168 169 $fields = array( 170 'session' => $id, 171 'session_expires' => time(), 172 'session_data' => $data 173 ); 174 return Symphony::Database()->insert($fields, 'tbl_sessions', true); 175 } 176 177 /** 178 * Given raw session data return the unserialized array. 179 * Used to check if the session is really empty before writing. 180 * 181 * @since Symphony 2.3.3 182 * @param string $data 183 * The serialized session data 184 * @return string 185 * The unserialised session data 186 */ 187 private static function unserialize_session($data) { 188 $hasBuffer = isset($_SESSION); 189 $buffer = $_SESSION; 190 session_decode($data); 191 $session = $_SESSION; 192 if($hasBuffer) { 193 $_SESSION = $buffer; 194 } 195 else { 196 unset($_SESSION); 197 } 198 199 return $session; 200 } 201 202 /** 203 * Given a session's ID, return it's row from `tbl_sessions` 204 * 205 * @param string $id 206 * The identifier for the Session to fetch 207 * @return string 208 * The serialised session data 209 */ 210 public static function read($id) { 211 return Symphony::Database()->fetchVar( 212 'session_data', 0, 213 sprintf( 214 "SELECT `session_data` FROM `tbl_sessions` WHERE `session` = '%s' LIMIT 1", 215 Symphony::Database()->cleanValue($id) 216 ) 217 ); 218 } 219 220 /** 221 * Given a session's ID, remove it's row from `tbl_sessions` 222 * 223 * @param string $id 224 * The identifier for the Session to destroy 225 * @throws DatabaseException 226 * @return boolean 227 * True if the Session was deleted successfully, false otherwise 228 */ 229 public static function destroy($id) { 230 return Symphony::Database()->query( 231 sprintf( 232 "DELETE FROM `tbl_sessions` WHERE `session` = '%s'", 233 Symphony::Database()->cleanValue($id) 234 ) 235 ); 236 } 237 238 /** 239 * The garbage collector, which removes all empty Sessions, or any 240 * Sessions that have expired. This has a 10% chance of firing based 241 * off the `gc_probability`/`gc_divisor`. 242 * 243 * @param integer $max 244 * The max session lifetime. 245 * @throws DatabaseException 246 * @return boolean 247 * True on Session deletion, false if an error occurs 248 */ 249 public static function gc($max) { 250 return Symphony::Database()->query( 251 sprintf( 252 "DELETE FROM `tbl_sessions` WHERE `session_expires` <= '%s'", 253 Symphony::Database()->cleanValue(time() - $max) 254 ) 255 ); 256 } 257 }