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 © 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($key, 0, $bracket_pos);
69
70
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_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_keys, 0, -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($key, 0, $bracket_pos);
163
164
if ( ! isset($_SESSION[$key])) {
165
return $value;
166
}
167
168
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
169
$array_keys = array_map('current', $array_keys);
170
171
$tip =& $_SESSION[$key];
172
173
foreach (array_slice($array_keys, 0, -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($key, 0, $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_keys, PREG_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($key, 0, $bracket_pos);
408
409
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
410
$array_keys = array_map('current', $array_keys);
411
array_unshift($array_keys, $key);
412
413
foreach (array_slice($array_keys, 0, -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($key, 0, $bracket_pos);
468
469
preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
470
$array_keys = array_map('current', $array_keys);
471
array_unshift($array_keys, $key);
472
473
foreach (array_slice($array_keys, 0, -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/
