session.php
1 <?php
2
/**
3  * Wraps the session control functions and the `$_SESSION` superglobal for a more consistent and safer API
4  *
5  * A `Cannot send session cache limiter` warning will be triggered if ::open(),
6  * ::add(), ::clear(), ::delete(), ::get() or ::set() is called after output has
7  * been sent to the browser. To prevent such a warning, explicitly call ::open()
8  * before generating any output.
9  *
10  * @author Zhou Yuan <yuanzhou19@gmail.com>
11  * @link http://www.infopotato.com/
12  * @copyright Copyright &copy; 2009-2011 Zhou Yuan
13  * @license http://www.opensource.org/licenses/mit-license.php MIT Licence
14  * @link   based on    http://flourishlib.com/fSession
15  */
16
class Session {
17
18     
/**
19      * The length for a normal session
20      * 
21      * @var integer 
22      */
23     
private static $_normal_timespan NULL;
24     
25     
/**
26      * If the session is open
27      * 
28      * @var boolean 
29      */
30     
private static $_open FALSE;
31     
32     
/**
33      * The length for a persistent session cookie - one that survives browser restarts
34      * 
35      * @var integer 
36      */
37     
private static $_persistent_timespan NULL;
38     
39     
/**
40      * If the session ID was regenerated during this script
41      * 
42      * @var boolean 
43      */
44     
private static $_regenerated FALSE;
45     
46     
/**
47      * Forces use as a static class
48      * 
49      * @return Session
50      */
51     
private function __construct() { }
52     
53     
/**
54      * Adds a value to an already-existing array value, or to a new array value
55      *
56      * @param  string  $key        The name to access the array under - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
57      * @param  mixed   $value      The value to add to the array
58      * @param  boolean $beginning  If the value should be added to the beginning
59      * @return void
60      */
61     
public static function add($key$value$beginning FALSE) {
62         
self::open();
63         
$tip =& $_SESSION;
64         
65         if (
$bracket_pos strpos($key'[')) {
66             
$original_key $key;
67             
$array_dereference substr($key$bracket_pos);
68             
$key substr($key0$bracket_pos);
69             
70             
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#'$array_dereference$array_keysPREG_SET_ORDER);
71             
// The current() callback function returns the value of the array element that's currently being pointed to by the internal pointer.
72             
$array_keys array_map('current'$array_keys);
73             
array_unshift($array_keys$key);
74             
75             foreach (
array_slice($array_keys0, -1) as $array_key) {
76                 if ( ! isset(
$tip[$array_key])) {
77                     
$tip[$array_key] = array();
78                 } elseif ( ! 
is_array($tip[$array_key])) {
79                     
halt('A System Error Was Encountered'"Session::add() was called for the key, {$original_key}, which is not an array"'sys_error');
80                 }
81                 
$tip =& $tip[$array_key];
82             }
83             
$key end($array_keys);
84         }
85         
86         
87         if ( ! isset(
$tip[$key])) {
88             
$tip[$key] = array();
89         } elseif ( ! 
is_array($tip[$key])) {            
90             
halt('A System Error Was Encountered'"Session::add() was called for the key, {$key}, which is not an array"'sys_error');
91         }
92         
93         if (
$beginning) {
94             
array_unshift($tip[$key], $value);
95         } else {
96             
$tip[$key][] = $value;
97         }
98     }
99     
100     
101     
/**
102      * Removes all session values with the provided prefix
103      * 
104      * This method will not remove session variables used by this class, which
105      * are prefixed with `SESSION::`.
106      * 
107      * @param  string $prefix  The prefix to clear all session values for
108      * @return void
109      */
110     
public static function clear($prefix NULL) {
111         
self::open();
112         
113         
$session_type $_SESSION['SESSION::type'];
114         
$session_expires $_SESSION['SESSION::expires'];
115         
116         if (
$prefix) {
117             foreach (
$_SESSION as $key => $value) {
118                 if (
strpos($key$prefix) === 0) {
119                     unset(
$_SESSION[$key]);
120                 }
121             }
122         } else {
123             
$_SESSION = array();        
124         }
125         
126         
$_SESSION['SESSION::type'] = $session_type;
127         
$_SESSION['SESSION::expires'] = $session_expires;
128     }
129     
130     
131     
/**
132      * Closes the session for writing, allowing other pages to open the session
133      * 
134      * @return void
135      */
136     
public static function close() {
137         if ( ! 
self::$_open) { 
138             return; 
139         }
140         
141         
session_write_close();
142         unset(
$_SESSION);
143         
self::$_open FALSE;
144     }
145     
146     
147     
/**
148      * Deletes a value from the session
149      * 
150      * @param  string $key            The key of the value to delete - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
151      * @param  mixed  $default_value  The value to return if the `$key` is not set
152      * @return mixed  The value of the `$key` that was deleted
153      */
154     
public static function delete($key$default_value NULL) {
155         
self::open();
156         
157         
$value $default_value;
158         
159         if (
$bracket_pos strpos($key'[')) {
160             
$original_key $key;
161             
$array_dereference substr($key$bracket_pos);
162             
$key substr($key0$bracket_pos);
163             
164             if ( ! isset(
$_SESSION[$key])) {
165                 return 
$value;
166             }
167             
168             
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#'$array_dereference$array_keysPREG_SET_ORDER);
169             
$array_keys array_map('current'$array_keys);
170             
171             
$tip =& $_SESSION[$key];
172             
173             foreach (
array_slice($array_keys0, -1) as $array_key) {
174                 if ( ! isset(
$tip[$array_key])) {
175                     return 
$value;
176                 } elseif ( ! 
is_array($tip[$array_key])) {
177                     
halt('A System Error Was Encountered'"Session::delete() was called for an element, {$original_key}, which is not an array"'sys_error');
178                 }
179                 
$tip =& $tip[$array_key];
180             }
181             
182             
$key end($array_keys);
183             
184         } else {
185             
$tip =& $_SESSION;
186         }
187         
188         if (isset(
$tip[$key])) {
189             
$value $tip[$key];
190             unset(
$tip[$key]);
191         }
192         
193         return 
$value;
194     }
195     
196     
197     
/**
198      * Destroys the session, removing all values
199      * 
200      * @return void
201      */
202     
public static function destroy() {
203         
self::open();
204         
$_SESSION = array();
205         if (isset(
$_COOKIE[session_name()])) {
206             
$params session_get_cookie_params();
207             
setcookie(session_name(), ''time() - 43200$params['path'], $params['domain'], $params['secure']);
208         }
209         
session_destroy();
210         
self::regenerate_id();
211     }
212     
213     
214     
/**
215      * Changed the session to use a time-based cookie instead of a session-based cookie
216      * 
217      * The length of the time-based cookie is controlled by ::set_length(). When
218      * this method is called, a time-based cookie is used to store the session
219      * ID. This means the session can persist browser restarts. Normally, a
220      * session-based cookie is used, which is wiped when a browser restart
221      * occurs.
222      * 
223      * This method should be called during the login process and will normally
224      * be controlled by a checkbox or similar where the user can indicate if
225      * they want to stay logged in for an extended period of time.
226      * 
227      * @return void
228      */
229     
public static function enable_persistence() {
230         if (
self::$_persistent_timespan === NULL) {
231             
halt('A System Error Was Encountered'"The method Session::init() must be called with the '$_persistent_timespan' parameter before calling Session::enable_persistence()"'sys_error');
232         }
233         
234         
$current_params session_get_cookie_params();
235         
236         
$params = array(
237             
self::$_persistent_timespan,
238             
$current_params['path'],
239             
$current_params['domain'],
240             
$current_params['secure']
241         );
242         
243         
call_user_func_array('session_set_cookie_params'$params);
244         
245         
self::open();
246         
247         
$_SESSION['SESSION::type'] = 'persistent';
248         
249         
session_regenerate_id();
250         
self::$_regenerated TRUE;
251     }
252     
253     
254     
/**
255      * Gets data from the `$_SESSION` superglobal
256      * 
257      * @param  string $key            The name to get the value for - array elements can be accessed via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
258      * @param  mixed  $default_value  The default value to use if the requested key is not set
259      * @return mixed  The data element requested
260      */
261     
public static function get($key$default_value NULL) {
262         
self::open();
263         
264         
$array_dereference NULL;
265         if (
$bracket_pos strpos($key'[')) {
266             
$array_dereference substr($key$bracket_pos);
267             
$key substr($key0$bracket_pos);
268         }
269         
270         if ( ! isset(
$_SESSION[$key])) {
271             return 
$default_value;
272         }
273         
$value $_SESSION[$key];
274         
275         if (
$array_dereference) {
276             
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#'$array_dereference$array_keysPREG_SET_ORDER);
277             
$array_keys array_map('current'$array_keys);
278             foreach (
$array_keys as $array_key) {
279                 if ( ! 
is_array($value) || ! isset($value[$array_key])) {
280                     
$value $default_value;
281                     break;
282                 }
283                 
$value $value[$array_key];
284             }
285         }
286         
287         return 
$value;
288     }
289     
290     
291     
/**
292      * Sets the session to run on the main domain, not just the specific subdomain currently being accessed
293      * By default PHP will only allow access to the $_SESSION superglobal values by pages on the same subdomain, 
294      * such that www.example.com could access the session, but example.com could not. 
295      * Calling ignore_subdomain() removes that restriction and allows access to any subdomain.
296      * 
297      * This method should be called after any calls to
298      * [http://php.net/session_set_cookie_params `session_set_cookie_params()`].
299      * 
300      * @return void
301      */
302     
public static function ignore_subdomain() {
303         if (
self::$_open || isset($_SESSION)) {
304             
halt('A System Error Was Encountered'"Session::ignore_subdomain() must be called before any of Session::add(), Session::clear(), Session::enable_persistence(), Session::get(), Session::open(), Session::set(), session_start()"'sys_error');
305         }
306         
307         
$current_params session_get_cookie_params();
308         
309         if (isset(
$_SERVER['SERVER_NAME'])) {
310             
$domain $_SERVER['SERVER_NAME'];
311         } elseif (isset(
$_SERVER['HTTP_HOST'])) {
312             
$domain $_SERVER['HTTP_HOST'];
313         } else {
314             
halt('A System Error Was Encountered'"The domain name could not be found in ['SERVER_NAME'] or ['HTTP_HOST']. Please set one of these keys to use Session::ignore_subdomain()."'sys_error');
315         }
316         
317         
$params = array(
318             
$current_params['lifetime'],
319             
$current_params['path'],
320             
preg_replace('#.*?([a-z0-9\\-]+\.[a-z]+)$#iD''.\1'$domain),
321             
$current_params['secure']
322         );
323         
324         
call_user_func_array('session_set_cookie_params'$params);
325     }
326     
327     
328     
/**
329      * Opens the session for writing, is automatically called by ::clear(), ::get() and ::set()
330      * 
331      * A `Cannot send session cache limiter` warning will be triggered if this,
332      * ::add(), ::clear(), ::delete(), ::get() or ::set() is called after output
333      * has been sent to the browser. To prevent such a warning, explicitly call
334      * this method before generating any output.
335      * 
336      * @param  boolean $cookie_only_session_id  If the session id should only be allowed via cookie - this is a security issue and should only be set to `FALSE` when absolutely necessary 
337      * @return void
338      */
339     
public static function open($cookie_only_session_id TRUE) {
340         if (
self::$_open) { 
341             return; 
342         }
343         
344         
self::$_open TRUE;
345         
346         if (
self::$_normal_timespan === NULL) {
347             
self::$_normal_timespan ini_get('session.gc_maxlifetime');    
348         }
349         
350         
// If the session is already open, we just piggy-back without setting options
351         
if ( ! isset($_SESSION)) {
352             if (
$cookie_only_session_id) {
353                 
ini_set('session.use_cookies'1);
354                 
ini_set('session.use_only_cookies'1);
355             }
356             
session_start();
357         }
358         
359         
// If the session has existed for too long, reset it
360         
if (isset($_SESSION['SESSION::expires']) && $_SESSION['SESSION::expires'] < $_SERVER['REQUEST_TIME']) {
361             
$_SESSION = array();
362             
self::regenerate_id();
363         }
364         
365         if ( ! isset(
$_SESSION['SESSION::type'])) {
366             
$_SESSION['SESSION::type'] = 'normal';    
367         }
368         
369         
// We store the expiration time for a session to allow for both normal and persistent sessions
370         
if ($_SESSION['SESSION::type'] == 'persistent' && self::$_persistent_timespan) {
371             
$_SESSION['SESSION::expires'] = $_SERVER['REQUEST_TIME'] + self::$_persistent_timespan;
372         } else {
373             
$_SESSION['SESSION::expires'] = $_SERVER['REQUEST_TIME'] + self::$_normal_timespan;    
374         }
375     }
376     
377     
378     
/**
379      * Regenerates the session ID, but only once per script execution
380      * 
381      * @internal
382      * 
383      * @return void
384      */
385     
public static function regenerate_id() {
386         if ( ! 
self::$_regenerated){
387             
session_regenerate_id();
388             
self::$_regenerated TRUE;
389         }
390     }
391     
392     
393     
/**
394      * Removes and returns the value from the end of an array value
395      *
396      * @param  string  $key        The name of the element to remove the value from - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
397      * @param  boolean $beginning  If the value should be removed to the beginning
398      * @return mixed  The value that was removed
399      */
400     
public static function remove($key$beginning FALSE) {
401         
self::open();
402         
$tip =& $_SESSION;
403         
404         if (
$bracket_pos strpos($key'[')) {
405             
$original_key $key;
406             
$array_dereference substr($key$bracket_pos);
407             
$key substr($key0$bracket_pos);
408             
409             
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#'$array_dereference$array_keysPREG_SET_ORDER);
410             
$array_keys array_map('current'$array_keys);
411             
array_unshift($array_keys$key);
412             
413             foreach (
array_slice($array_keys0, -1) as $array_key) {
414                 if ( ! isset(
$tip[$array_key])) {
415                     return 
NULL;
416                 } elseif ( ! 
is_array($tip[$array_key])) {
417                     
halt('A System Error Was Encountered'"Session::remove() was called for the key, {$original_key}, which is not an array"'sys_error');
418                 }
419                 
$tip =& $tip[$array_key];
420             }
421             
$key end($array_keys);
422         }
423         
424         if ( ! isset(
$tip[$key])) {
425             return 
NULL;
426         } elseif ( ! 
is_array($tip[$key])) {
427             
halt('A System Error Was Encountered'"Session::remove() was called for the key, {$key}, which is not an array"'sys_error');
428         }
429         
430         if (
$beginning) {
431             return 
array_shift($tip[$key]);
432         }
433         
434         return 
array_pop($tip[$key]);
435     }    
436     
437     
438     
/**
439      * Resets the configuration of the class
440      * 
441      * @internal
442      * 
443      * @return void
444      */
445     
public static function reset() {
446         
self::$_normal_timespan NULL;
447         
self::$_persistent_timespan NULL;
448         
self::$_regenerated FALSE;
449         
self::$destroy();
450         
self::$close();    
451     }
452     
453     
454     
/**
455      * Sets data to the `$_SESSION` superglobal
456      * 
457      * @param  string $key     The name to save the value under - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
458      * @param  mixed  $value   The value to store
459      * @return void
460      */
461     
public static function set($key$value) {
462         
self::open();
463         
$tip =& $_SESSION;
464         
465         if (
$bracket_pos strpos($key'[')) {
466             
$array_dereference substr($key$bracket_pos);
467             
$key substr($key0$bracket_pos);
468             
469             
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#'$array_dereference$array_keysPREG_SET_ORDER);
470             
$array_keys array_map('current'$array_keys);
471             
array_unshift($array_keys$key);
472             
473             foreach (
array_slice($array_keys0, -1) as $array_key) {
474                 if ( ! isset(
$tip[$array_key]) || ! is_array($tip[$array_key])) {
475                     
$tip[$array_key] = array();
476                 }
477                 
$tip =& $tip[$array_key];
478             }
479             
$tip[end($array_keys)] = $value;        
480         } else {
481             
$tip[$key] = $value;
482         }
483     }
484
485     
/**
486      * Sets the path to store session files in and 
487      * Sets the minimum length of a session
488      * (PHP might not clean up the session data right away once this timespan has elapsed)
489      * 
490      * You should always be called with a non-standard directory to ensure that 
491      * another site on the server doesn't garbage collect the session files for this site.
492      * 
493      * Standard session directories usually include `/tmp` and `/var/tmp`. 
494      * 
495      * Both of the timespan can accept either a integer timespan in seconds,
496      * or an english description of a timespan (e.g. `'30 minutes'`, `'1 hour'`, `'1 day 2 hours'`).
497      * 
498      * To enable a user to stay logged in for the whole $persistent_timespan and to stay logged in 
499      * across browser restarts, the static method ::enable_persistence() must be called when they log in.
500      * 
501      * @param  string $dir  The directory to store session files in
502      * @param  string|integer $normal_timespan      The normal, session-based cookie, length for the session
503      * @param  string|integer $persistent_timespan  The persistent, timed-based cookie, length for the session - this is enabled by calling ::enabled_persistence() during login
504      * @return void
505      */
506     
public static function init($dir$normal_timespan$persistent_timespan NULL) {
507         if (
self::$_open || isset($_SESSION)) {
508             
halt('A System Error Was Encountered'"Session::init() must be called before any of Session::add(), Session::clear(), Session::enable_persistence(), Session::get(), Session::open(), Session::set(), session_start()"'sys_error');
509         }
510
511         if ( ! 
is_writable($dir)) {
512             
halt('A System Error Was Encountered''The session file directory specified is not writable''sys_error');
513         }
514         
515         
// Set the path of the current directory used to save session data.
516         
session_save_path($dir);
517
518         
$seconds = ( ! is_numeric($normal_timespan)) ? strtotime($normal_timespan) - time() : $normal_timespan;
519         
self::$_normal_timespan $seconds;
520         
521         if (
$persistent_timespan) {
522             
$seconds = ( ! is_numeric($persistent_timespan)) ? strtotime($persistent_timespan) - time() : $persistent_timespan;    
523             
self::$_persistent_timespan $seconds;
524         }
525         
526         
ini_set('session.gc_maxlifetime'$seconds);
527         
528         
// Marks the cookie as accessible only through the HTTP protocol. 
529         // This means that the cookie won't be accessible by scripting languages, such as JavaScript. 
530         // This setting can effectively help to reduce identity theft through XSS attacks 
531         // (although it is not supported by all browsers).
532         
ini_set('session.cookie_httponly'1);
533     }
534     
535 }
536
537
/* End of file: ./system/core/session.php */

Page URI: http://www.infopotato.com/index.php/code/core/session/