blob: 7aaf706a1c2b4f83f12085a2eb9767c6a97c1344 [file] [log] [blame]
Darren Hillc4e266b2011-08-30 15:40:27 -04001<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 5.1.6 or newer
6 *
7 * @package CodeIgniter
8 * @author ExpressionEngine Dev Team
9 * @copyright Copyright (c) 2008 - 2010, EllisLab, Inc.
10 * @license http://codeigniter.com/user_guide/license.html
11 * @link http://codeigniter.com
12 * @since Version 2.0
13 * @filesource
14 */
15
16
17/**
18 * Session Class
19 *
20 * The user interface defined by EllisLabs, now with puggable drivers to manage different storage mechanisms.
21 * By default, the Native PHP session driver will load, but the 'sess_driver' config/param item (see above) can be
22 * used to specify the 'Cookie' driver, or any other you might create.
23 * Once loaded, this driver setup is a drop-in replacement for the former CI_Session library, taking its place as the
24 * 'session' member of the global controller framework (e.g.: $CI->session or $this->session).
25 * In keeping with the CI_Driver methodology, multiple drivers may be loaded, although this might be a bit confusing.
26 * The Session library class keeps track of the most recently loaded driver as "current" to call for driver methods.
27 * Ideally, one driver is loaded and all calls go directly through the main library interface. However, any methods
28 * called through the specific driver will switch the "current" driver to itself before invoking the library method
29 * (which will then call back into the driver for low-level operations). So, alternation between two drivers can be
30 * achieved by specifying which driver to use for each call (e.g.: $this->session->native->set_userdata('foo', 'bar');
31 * $this->session->cookie->userdata('foo'); $this->session->native->unset_userdata('foo');). Notice in the previous
32 * example that the _native_ userdata value 'foo' would be set to 'bar', which would NOT be returned by the call for
33 * the _cookie_ userdata 'foo', nor would the _cookie_ value be unset by the call to unset the _native_ 'foo' value.
34 *
35 * @package CodeIgniter
36 * @subpackage Libraries
37 * @category Sessions
38 * @author Darren Hill (DChill)
39 * @link http://codeigniter.com/user_guide/libraries/sessions.html
40 */
41final class Session extends CI_Driver_Library {
42 public $params = array();
43 private $current = null;
44 private $userdata = array();
45
46 const FLASHDATA_KEY = 'flash';
47 const FLASHDATA_NEW = ':new:';
48 const FLASHDATA_OLD = ':old:';
49 const FLASHDATA_EXP = ':exp:';
50 const EXPIRATION_KEY = '__expirations';
51 const TEMP_EXP_DEF = 300;
52
53 /**
54 * Session constructor
55 *
56 * The constructor loads the configured driver ('sess_driver' in config.php or as a parameter), running
57 * routines in its constructor, and manages flashdata aging.
58 *
59 * @param array Configuration parameters
60 */
61 public function __construct(array $params = array())
62 {
63 log_message('debug', 'Session Class Initialized');
64
65 // Get valid drivers list
66 $CI =& get_instance();
67 $this->valid_drivers = array('Session_Native', 'Session_Cookie');
68 $key = 'sess_valid_drivers';
69 $drivers = (isset($params[$key])) ? $params[$key] : $CI->config->item($key);
70 if ($drivers)
71 {
72 if (!is_array($drivers)) $drivers = array($drivers);
73
74 // Add driver names to valid list
75 foreach ($drivers as $driver)
76 {
77 if (!in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers)))
78 {
79 $this->valid_drivers[] = $driver;
80 }
81 }
82 }
83
84 // Get driver to load
85 $key = 'sess_driver';
86 $driver = (isset($params[$key])) ? $params[$key] : $CI->config->item($key);
87 if (!$driver) $driver = 'Native';
88 if (!in_array('session_'.strtolower($driver), array_map('strtolower', $this->valid_drivers)))
89 {
90 $this->valid_drivers[] = 'Session_'.$driver;
91 }
92
93 // Save a copy of parameters in case drivers need access
94 $this->params = $params;
95
96 // Load driver and get array reference
97 $this->load_driver($driver);
98 $this->userdata =& $this->current->get_userdata();
99
100 // Delete 'old' flashdata (from last request)
101 $this->_flashdata_sweep();
102
103 // Mark all new flashdata as old (data will be deleted before next request)
104 $this->_flashdata_mark();
105
106 // Delete expired tempdata
107 $this->_tempdata_sweep();
108
109 log_message('debug', 'Session routines successfully run');
110 }
111
112 /**
113 * Loads session storage driver
114 *
115 * @param string Driver classname
116 * @return object Loaded driver object
117 */
118 public function load_driver($driver)
119 {
120 // Save reference to most recently loaded driver as library default
121 $this->current = parent::load_driver($driver);
122 return $this->current;
123 }
124
125 /**
126 * Select default session storage driver
127 *
128 * @param string Driver classname
129 * @return void
130 */
131 public function select_driver($driver)
132 {
133 // Validate driver name
134 $lowername = strtolower($driver);
135 if (in_array($lowername, array_map('strtolower', $this->valid_drivers)))
136 {
137 // See if regular or lowercase variant is loaded
138 if (class_exists($driver))
139 {
140 $this->current = $this->$driver;
141 }
142 else if (class_exists($lowername))
143 {
144 $this->current = $this->$lowername;
145 }
146 else
147 {
148 $this->load_driver($driver);
149 }
150 }
151 }
152
153 /**
154 * Destroy the current session
155 *
156 * @return void
157 */
158 public function sess_destroy()
159 {
160 // Just call destroy on driver
161 $this->current->sess_destroy();
162 }
163
164 /**
165 * Regenerate the current session
166 *
167 * @param boolean Destroy session data flag (default: false)
168 * @return void
169 */
170 public function sess_regenerate($destroy = false)
171 {
172 // Just call regenerate on driver
173 $this->current->sess_regenerate($destroy);
174 }
175
176 /**
177 * Fetch a specific item from the session array
178 *
179 * @param string Item key
180 * @return string Item value
181 */
182 public function userdata($item)
183 {
184 // Return value or FALSE if not found
185 return (!isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
186 }
187
188 /**
189 * Fetch all session data
190 *
191 * @return array User data array
192 */
193 public function all_userdata()
194 {
195 // Return entire array
196 return (!isset($this->userdata)) ? FALSE : $this->userdata;
197 }
198
199 /**
200 * Add or change data in the "userdata" array
201 *
202 * @param mixed Item name or array of items
203 * @param string Item value or empty string
204 * @return void
205 */
206 public function set_userdata($newdata = array(), $newval = '')
207 {
208 // Wrap params as array if singular
209 if (is_string($newdata))
210 {
211 $newdata = array($newdata => $newval);
212 }
213
214 // Set each name/value pair
215 if (count($newdata) > 0)
216 {
217 foreach ($newdata as $key => $val)
218 {
219 $this->userdata[$key] = $val;
220 }
221 }
222
223 // Tell driver data changed
224 $this->current->sess_save();
225 }
226
227 /**
228 * Delete a session variable from the "userdata" array
229 *
230 * @param mixed Item name or array of item names
231 * @return void
232 */
233 public function unset_userdata($newdata = array())
234 {
235 // Wrap single name as array
236 if (is_string($newdata))
237 {
238 $newdata = array($newdata => '');
239 }
240
241 // Unset each item name
242 if (count($newdata) > 0)
243 {
244 foreach ($newdata as $key => $val)
245 {
246 unset($this->userdata[$key]);
247 }
248 }
249
250 // Tell driver data changed
251 $this->current->sess_save();
252 }
253
254 /**
255 * Determine if an item exists
256 *
257 * @param string Item name
258 * @return boolean
259 */
260 public function has_userdata($item)
261 {
262 // Check for item name
263 return isset($this->userdata[$item]);
264 }
265
266 /**
267 * Add or change flashdata, only available until the next request
268 *
269 * @param mixed Item name or array of items
270 * @param string Item value or empty string
271 * @return void
272 */
273 public function set_flashdata($newdata = array(), $newval = '')
274 {
275 // Wrap item as array if singular
276 if (is_string($newdata))
277 {
278 $newdata = array($newdata => $newval);
279 }
280
281 // Prepend each key name and set value
282 if (count($newdata) > 0)
283 {
284 foreach ($newdata as $key => $val)
285 {
286 $flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key;
287 $this->set_userdata($flashdata_key, $val);
288 }
289 }
290 }
291
292 /**
293 * Keeps existing flashdata available to next request.
294 *
295 * @param string Item key
296 * @return void
297 */
298 public function keep_flashdata($key)
299 {
300 // 'old' flashdata gets removed. Here we mark all
301 // flashdata as 'new' to preserve it from _flashdata_sweep()
302 $old_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key;
303 $value = $this->userdata($old_flashdata_key);
304
305 $new_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key;
306 $this->set_userdata($new_flashdata_key, $value);
307 }
308
309 /**
310 * Fetch a specific flashdata item from the session array
311 *
312 * @param string Item key
313 * @return string
314 */
315 public function flashdata($key)
316 {
317 // Prepend key and retrieve value
318 $flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key;
319 return $this->userdata($flashdata_key);
320 }
321
322 /**
323 * Add or change tempdata, only available
324 * until expiration
325 *
326 * @param mixed Item name or array of items
327 * @param string Item value or empty string
328 * @param int Item lifetime in seconds or 0 for default
329 * @return void
330 */
331 public function set_tempdata($newdata = array(), $newval = '', $expire = 0)
332 {
333 // Set expiration time
334 $expire = time() + ($expire ? $expire : self::TEMP_EXP_DEF);
335
336 // Wrap item as array if singular
337 if (is_string($newdata))
338 {
339 $newdata = array($newdata => $newval);
340 }
341
342 // Get or create expiration list
343 $expirations = $this->userdata(self::EXPIRATION_KEY);
344 if (!$expirations)
345 {
346 $expirations = array();
347 }
348
349 // Prepend each key name and set value
350 if (count($newdata) > 0)
351 {
352 foreach ($newdata as $key => $val)
353 {
354 $tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
355 $expirations[$tempdata_key] = $expire;
356 $this->set_userdata($tempdata_key, $val);
357 }
358 }
359
360 // Update expiration list
361 $this->set_userdata(self::EXPIRATION_KEY, $expirations);
362 }
363
364 /**
365 * Delete a temporary session variable from the "userdata" array
366 *
367 * @param mixed Item name or array of item names
368 * @return void
369 */
370 public function unset_tempdata($newdata = array())
371 {
372 // Get expirations list
373 $expirations = $this->userdata(self::EXPIRATION_KEY);
374 if (!$expirations || !count($expirations))
375 {
376 // Nothing to do
377 return;
378 }
379
380 // Wrap single name as array
381 if (is_string($newdata))
382 {
383 $newdata = array($newdata => '');
384 }
385
386 // Prepend each item name and unset
387 if (count($newdata) > 0)
388 {
389 foreach ($newdata as $key => $val)
390 {
391 $tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
392 unset($expirations[$tempdata_key]);
393 $this->unset_userdata($tempdata_key);
394 }
395 }
396
397 // Update expiration list
398 $this->set_userdata(self::EXPIRATION_KEY, $expirations);
399 }
400
401 /**
402 * Fetch a specific tempdata item from the session array
403 *
404 * @param string Item key
405 * @return string
406 */
407 public function tempdata($key)
408 {
409 // Prepend key and return value
410 $tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
411 return $this->userdata($tempdata_key);
412 }
413
414 /**
415 * Identifies flashdata as 'old' for removal
416 * when _flashdata_sweep() runs.
417 *
418 * @access private
419 * @return void
420 */
421 private function _flashdata_mark()
422 {
423 $userdata = $this->all_userdata();
424 foreach ($userdata as $name => $value)
425 {
426 $parts = explode(self::FLASHDATA_NEW, $name);
427 if (is_array($parts) && count($parts) === 2)
428 {
429 $new_name = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$parts[1];
430 $this->set_userdata($new_name, $value);
431 $this->unset_userdata($name);
432 }
433 }
434 }
435
436 /**
437 * Removes all flashdata marked as 'old'
438 *
439 * @access private
440 * @return void
441 */
442 private function _flashdata_sweep()
443 {
444 $userdata = $this->all_userdata();
445 foreach ($userdata as $key => $value)
446 {
447 if (strpos($key, self::FLASHDATA_OLD))
448 {
449 $this->unset_userdata($key);
450 }
451 }
452 }
453
454 /**
455 * Removes all expired tempdata
456 *
457 * @access private
458 * @return void
459 */
460 private function _tempdata_sweep()
461 {
462 // Get expirations list
463 $expirations = $this->userdata(self::EXPIRATION_KEY);
464 if (!$expirations || !count($expirations))
465 {
466 // Nothing to do
467 return;
468 }
469
470 // Unset expired elements
471 $now = time();
472 $userdata = $this->all_userdata();
473 foreach ($userdata as $key => $value)
474 {
475 if (strpos($key, self::FLASHDATA_EXP) && $expirations[$key] < $now)
476 {
477 unset($expirations[$key]);
478 $this->unset_userdata($key);
479 }
480 }
481
482 // Update expiration list
483 $this->set_userdata(self::EXPIRATION_KEY, $expirations);
484 }
485}
486// END Session Class
487
488
489/**
490 * SessionDriver Class
491 *
492 * Extend this class to make a new Session driver.
493 * A Session driver basically manages an array of name/value pairs with some sort of storage mechanism.
494 * To make a new driver, derive from (extend) SessionDriver. Overload the initialize method and read or create
495 * session data. Then implement a save handler to write changed data to storage (sess_save), a destroy handler
496 * to remove deleted data (sess_destroy), and an access handler to expose the data (get_userdata).
497 * Put your driver in the libraries/Session/drivers folder anywhere in the loader paths. This includes the application
498 * directory, the system directory, or any path you add with $CI->load->add_package_path().
499 * Your driver must be named Session_<name>, where <name> is capitalized, and your filename must be Session_<name>.EXT,
500 * preferably also capitalized. (e.g.: Session_Foo in libraries/Session/drivers/Session_Foo.php)
501 * Then specify the driver by setting 'sess_driver' in your config file or as a parameter when loading the Session
502 * object. (e.g.: $config['sess_driver'] = 'foo'; OR $CI->load->driver('session', array('sess_driver' => 'foo')); )
503 * Already provided are the Native driver, which manages the native PHP $_SESSION array, and
504 * the Cookie driver, which manages the data in a browser cookie, with optional extra storage in a database table.
505 *
506 * @package CodeIgniter
507 * @subpackage Libraries
508 * @category Sessions
509 * @author Darren Hill (DChill)
510 */
511abstract class SessionDriver extends CI_Driver {
512 /**
513 * Decorate
514 *
515 * Decorates the child with the parent driver lib's methods and properties
516 *
517 * @param object Parent library object
518 * @return void
519 */
520 public function decorate($parent)
521 {
522 // Call base class decorate first
523 parent::decorate($parent);
524
525 // Call initialize method now that driver has access to $this->parent
526 $this->initialize();
527 }
528
529 /**
530 * __call magic method
531 *
532 * Handles access to the parent driver library's methods
533 *
534 * @param string Library method name
535 * @param array Method arguments (default: none)
536 * @return mixed
537 */
538 public function __call($method, $args = array())
539 {
540 // Make sure the parent library uses this driver
541 $this->parent->select_driver(get_class($this));
542 return parent::__call($method, $args);
543 }
544
545 /**
546 * Initialize driver
547 *
548 * @return void
549 */
550 protected function initialize()
551 {
552 // Overload this method to implement initialization
553 }
554
555 /**
556 * Save the session data
557 *
558 * Data in the array has changed - perform any storage synchronization necessary
559 * The child class MUST implement this abstract method!
560 *
561 * @return void
562 */
563 abstract public function sess_save();
564
565 /**
566 * Destroy the current session
567 *
568 * Clean up storage for this session - it has been terminated
569 * The child class MUST implement this abstract method!
570 *
571 * @return void
572 */
573 abstract public function sess_destroy();
574
575 /**
576 * Regenerate the current session
577 *
578 * Regenerate the session id
579 * The child class MUST implement this abstract method!
580 *
581 * @param boolean Destroy session data flag (default: false)
582 * @return void
583 */
584 abstract public function sess_regenerate($destroy = false);
585
586 /**
587 * Get a reference to user data array
588 *
589 * Give array access to the main Session object
590 * The child class MUST implement this abstract method!
591 *
592 * @return array Reference to userdata
593 */
594 abstract public function &get_userdata();
595}
596// END SessionDriver Class
597
598
599/* End of file Session.php */
600/* Location: ./system/libraries/Session/Session.php */
601?>