blob: 3b26a2f170800811b722372a220056f086674873 [file] [log] [blame]
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.2.4 or newer
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014, British Columbia Institute of Technology
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
* @copyright Copyright (c) 2014, British Columbia Institute of Technology (http://bcit.ca/)
* @license http://opensource.org/licenses/MIT MIT License
* @link http://codeigniter.com
* @since Version 2.0.0
* @filesource
*/
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* CodeIgniter Session Class
*
* The user interface defined by EllisLabs, now with puggable drivers to manage different storage mechanisms.
* By default, the cookie session driver will load, but the 'sess_driver' config/param item (see above) can be
* used to specify the 'native' driver, or any other you might create.
* Once loaded, this driver setup is a drop-in replacement for the former CI_Session library, taking its place as the
* 'session' member of the global controller framework (e.g.: $CI->session or $this->session).
* In keeping with the CI_Driver methodology, multiple drivers may be loaded, although this might be a bit confusing.
* The CI_Session library class keeps track of the most recently loaded driver as "current" to call for driver methods.
* Ideally, one driver is loaded and all calls go directly through the main library interface. However, any methods
* called through the specific driver will switch the "current" driver to itself before invoking the library method
* (which will then call back into the driver for low-level operations). So, alternation between two drivers can be
* achieved by specifying which driver to use for each call (e.g.: $this->session->native->set_userdata('foo', 'bar');
* $this->session->cookie->userdata('foo'); $this->session->native->unset_userdata('foo');). Notice in the previous
* example that the _native_ userdata value 'foo' would be set to 'bar', which would NOT be returned by the call for
* the _cookie_ userdata 'foo', nor would the _cookie_ value be unset by the call to unset the _native_ 'foo' value.
*
* @package CodeIgniter
* @subpackage Libraries
* @category Sessions
* @author EllisLab Dev Team
* @link http://codeigniter.com/user_guide/libraries/sessions.html
*/
class CI_Session extends CI_Driver_Library {
/**
* Initialization parameters
*
* @var array
*/
public $params = array();
/**
* Valid drivers list
*
* @var array
*/
public $valid_drivers = array('native', 'cookie');
/**
* Current driver in use
*
* @var string
*/
public $current = NULL;
/**
* User data
*
* @var array
*/
protected $userdata = array();
// ------------------------------------------------------------------------
const FLASHDATA_KEY = 'flash';
const FLASHDATA_NEW = ':new:';
const FLASHDATA_OLD = ':old:';
const FLASHDATA_EXP = ':exp:';
const EXPIRATION_KEY = '__expirations';
const TEMP_EXP_DEF = 300;
// ------------------------------------------------------------------------
/**
* CI_Session constructor
*
* The constructor loads the configured driver ('sess_driver' in config.php or as a parameter), running
* routines in its constructor, and manages flashdata aging.
*
* @param array Configuration parameters
* @return void
*/
public function __construct(array $params = array())
{
$_config =& get_instance()->config;
// No sessions under CLI
if (is_cli())
{
return;
}
log_message('debug', 'CI_Session Class Initialized');
// Add possible extra entries to our valid drivers list
$drivers = isset($params['sess_valid_drivers']) ? $params['sess_valid_drivers'] : $_config->item('sess_valid_drivers');
if ( ! empty($drivers))
{
$drivers = array_map('strtolower', (array) $drivers);
$this->valid_drivers = array_merge($this->valid_drivers, array_diff($drivers, $this->valid_drivers));
}
// Get driver to load
$driver = isset($params['sess_driver']) ? $params['sess_driver'] : $_config->item('sess_driver');
if ( ! $driver)
{
log_message('debug', "Session: No driver name is configured, defaulting to 'cookie'.");
$driver = 'cookie';
}
if ( ! in_array($driver, $this->valid_drivers))
{
log_message('error', 'Session: Configured driver name is not valid, aborting.');
return;
}
// Save a copy of parameters in case drivers need access
$this->params = $params;
// Load driver and get array reference
$this->load_driver($driver);
// Delete 'old' flashdata (from last request)
$this->_flashdata_sweep();
// Mark all new flashdata as old (data will be deleted before next request)
$this->_flashdata_mark();
// Delete expired tempdata
$this->_tempdata_sweep();
log_message('debug', 'CI_Session routines successfully run');
}
// ------------------------------------------------------------------------
/**
* Loads session storage driver
*
* @param string Driver classname
* @return object Loaded driver object
*/
public function load_driver($driver)
{
// Save reference to most recently loaded driver as library default and sync userdata
$this->current = parent::load_driver($driver);
$this->userdata =& $this->current->get_userdata();
return $this->current;
}
// ------------------------------------------------------------------------
/**
* Select default session storage driver
*
* @param string Driver name
* @return void
*/
public function select_driver($driver)
{
// Validate driver name
$prefix = (string) get_instance()->config->item('subclass_prefix');
$child = strtolower(str_replace(array('CI_', $prefix, $this->lib_name.'_'), '', $driver));
if (in_array($child, array_map('strtolower', $this->valid_drivers)))
{
// See if driver is loaded
if (isset($this->$child))
{
// See if driver is already current
if ($this->$child !== $this->current)
{
// Make driver current and sync userdata
$this->current = $this->$child;
$this->userdata =& $this->current->get_userdata();
}
}
else
{
// Load new driver
$this->load_driver($child);
}
}
}
// ------------------------------------------------------------------------
/**
* Destroy the current session
*
* @return void
*/
public function sess_destroy()
{
// Just call destroy on driver
$this->current->sess_destroy();
}
// ------------------------------------------------------------------------
/**
* Regenerate the current session
*
* @param bool Destroy session data flag (default: false)
* @return void
*/
public function sess_regenerate($destroy = FALSE)
{
// Call regenerate on driver and resync userdata
$this->current->sess_regenerate($destroy);
$this->userdata =& $this->current->get_userdata();
}
// ------------------------------------------------------------------------
/**
* Fetch a specific item from the session array
*
* @param string Item key
* @return string Item value or NULL if not found
*/
public function userdata($item = NULL)
{
if (isset($item))
{
return isset($this->userdata[$item]) ? $this->userdata[$item] : NULL;
}
return isset($this->userdata) ? $this->userdata : array();
}
// ------------------------------------------------------------------------
/**
* Fetch all session data
*
* @deprecated 3.0.0 Use userdata() with no parameters instead
* @return array User data array
*/
public function all_userdata()
{
return isset($this->userdata) ? $this->userdata : array();
}
// ------------------------------------------------------------------------
/**
* Add or change data in the "userdata" array
*
* @param mixed Item name or array of items
* @param string Item value or empty string
* @return void
*/
public function set_userdata($newdata, $newval = '')
{
// Wrap params as array if singular
if (is_string($newdata))
{
$newdata = array($newdata => $newval);
}
// Set each name/value pair
if (count($newdata) > 0)
{
foreach ($newdata as $key => $val)
{
$this->userdata[$key] = $val;
}
}
// Tell driver data changed
$this->current->sess_save();
}
// ------------------------------------------------------------------------
/**
* Delete a session variable from the "userdata" array
*
* @param mixed Item name or array of item names
* @return void
*/
public function unset_userdata($newdata)
{
// Wrap single name as array
if (is_string($newdata))
{
$newdata = array($newdata => '');
}
// Unset each item name
if (count($newdata) > 0)
{
foreach (array_keys($newdata) as $key)
{
unset($this->userdata[$key]);
}
}
// Tell driver data changed
$this->current->sess_save();
}
// ------------------------------------------------------------------------
/**
* Determine if an item exists
*
* @param string Item name
* @return bool
*/
public function has_userdata($item)
{
return isset($this->userdata[$item]);
}
// ------------------------------------------------------------------------
/**
* Add or change flashdata, only available until the next request
*
* @param mixed Item name or array of items
* @param string Item value or empty string
* @return void
*/
public function set_flashdata($newdata, $newval = '')
{
// Wrap item as array if singular
if (is_string($newdata))
{
$newdata = array($newdata => $newval);
}
// Prepend each key name and set value
if (count($newdata) > 0)
{
foreach ($newdata as $key => $val)
{
$flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key;
$this->set_userdata($flashdata_key, $val);
}
}
}
// ------------------------------------------------------------------------
/**
* Keeps existing flashdata available to next request.
*
* @param mixed Item key(s)
* @return void
*/
public function keep_flashdata($key)
{
if (is_array($key))
{
foreach ($key as $k)
{
$this->keep_flashdata($k);
}
return;
}
// 'old' flashdata gets removed. Here we mark all flashdata as 'new' to preserve it from _flashdata_sweep()
// Note the function will return NULL if the $key provided cannot be found
$old_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key;
$value = $this->userdata($old_flashdata_key);
$new_flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_NEW.$key;
$this->set_userdata($new_flashdata_key, $value);
}
// ------------------------------------------------------------------------
/**
* Fetch a specific flashdata item from the session array
*
* @param string Item key
* @return string
*/
public function flashdata($key = NULL)
{
if (isset($key))
{
return $this->userdata(self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key);
}
// Get our flashdata items from userdata
$out = array();
foreach ($this->userdata() as $key => $val)
{
if (strpos($key, self::FLASHDATA_KEY.self::FLASHDATA_OLD) !== FALSE)
{
$key = str_replace(self::FLASHDATA_KEY.self::FLASHDATA_OLD, '', $key);
$out[$key] = $val;
}
}
return $out;
}
// ------------------------------------------------------------------------
/**
* Add or change tempdata, only available until expiration
*
* @param mixed Item name or array of items
* @param string Item value or empty string
* @param int Item lifetime in seconds or 0 for default
* @return void
*/
public function set_tempdata($newdata, $newval = '', $expire = 0)
{
// Set expiration time
$expire = time() + ($expire ? $expire : self::TEMP_EXP_DEF);
// Wrap item as array if singular
if (is_string($newdata))
{
$newdata = array($newdata => $newval);
}
// Get or create expiration list
$expirations = $this->userdata(self::EXPIRATION_KEY);
if ( ! $expirations)
{
$expirations = array();
}
// Prepend each key name and set value
if (count($newdata) > 0)
{
foreach ($newdata as $key => $val)
{
$tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
$expirations[$tempdata_key] = $expire;
$this->set_userdata($tempdata_key, $val);
}
}
// Update expiration list
$this->set_userdata(self::EXPIRATION_KEY, $expirations);
}
// ------------------------------------------------------------------------
/**
* Delete a temporary session variable from the "userdata" array
*
* @param mixed Item name or array of item names
* @return void
*/
public function unset_tempdata($newdata)
{
// Get expirations list
$expirations = $this->userdata(self::EXPIRATION_KEY);
if (empty($expirations))
{
// Nothing to do
return;
}
// Wrap single name as array
if (is_string($newdata))
{
$newdata = array($newdata => '');
}
// Prepend each item name and unset
if (count($newdata) > 0)
{
foreach (array_keys($newdata) as $key)
{
$tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
unset($expirations[$tempdata_key]);
$this->unset_userdata($tempdata_key);
}
}
// Update expiration list
$this->set_userdata(self::EXPIRATION_KEY, $expirations);
}
// ------------------------------------------------------------------------
/**
* Fetch a specific tempdata item from the session array
*
* @param string Item key
* @return string
*/
public function tempdata($key = NULL)
{
if (isset($key))
{
return $this->userdata(self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key);
}
// Get our tempdata items from userdata
$out = array();
foreach ($this->userdata() as $key => $val)
{
if (strpos($key, self::FLASHDATA_KEY.self::FLASHDATA_EXP) !== FALSE)
{
$key = str_replace(self::FLASHDATA_KEY.self::FLASHDATA_EXP, '', $key);
$out[$key] = $val;
}
}
return $out;
}
// ------------------------------------------------------------------------
/**
* Identifies flashdata as 'old' for removal
* when _flashdata_sweep() runs.
*
* @return void
*/
protected function _flashdata_mark()
{
foreach ($this->userdata() as $name => $value)
{
$parts = explode(self::FLASHDATA_NEW, $name);
if (count($parts) === 2)
{
$this->set_userdata(self::FLASHDATA_KEY.self::FLASHDATA_OLD.$parts[1], $value);
$this->unset_userdata($name);
}
}
}
// ------------------------------------------------------------------------
/**
* Removes all flashdata marked as 'old'
*
* @return void
*/
protected function _flashdata_sweep()
{
$userdata = $this->userdata();
foreach (array_keys($userdata) as $key)
{
if (strpos($key, self::FLASHDATA_OLD))
{
$this->unset_userdata($key);
}
}
}
// ------------------------------------------------------------------------
/**
* Removes all expired tempdata
*
* @return void
*/
protected function _tempdata_sweep()
{
// Get expirations list
$expirations = $this->userdata(self::EXPIRATION_KEY);
if (empty($expirations))
{
// Nothing to do
return;
}
// Unset expired elements
$now = time();
$userdata = $this->userdata();
foreach (array_keys($userdata) as $key)
{
if (strpos($key, self::FLASHDATA_EXP) && $expirations[$key] < $now)
{
unset($expirations[$key]);
$this->unset_userdata($key);
}
}
// Update expiration list
$this->set_userdata(self::EXPIRATION_KEY, $expirations);
}
}
// ------------------------------------------------------------------------
/**
* CI_Session_driver Class
*
* Extend this class to make a new CI_Session driver.
* A CI_Session driver basically manages an array of name/value pairs with some sort of storage mechanism.
* To make a new driver, derive from (extend) CI_Session_driver. Overload the initialize method and read or create
* session data. Then implement a save handler to write changed data to storage (sess_save), a destroy handler
* to remove deleted data (sess_destroy), and an access handler to expose the data (get_userdata).
* Put your driver in the libraries/Session/drivers folder anywhere in the loader paths. This includes the
* application directory, the system directory, or any path you add with $CI->load->add_package_path().
* Your driver must be named CI_Session_<name>, and your filename must be Session_<name>.php,
* preferably also capitalized. (e.g.: CI_Session_foo in libraries/Session/drivers/Session_foo.php)
* Then specify the driver by setting 'sess_driver' in your config file or as a parameter when loading the CI_Session
* object. (e.g.: $config['sess_driver'] = 'foo'; OR $CI->load->driver('session', array('sess_driver' => 'foo')); )
* Already provided are the Native driver, which manages the native PHP $_SESSION array, and
* the Cookie driver, which manages the data in a browser cookie, with optional extra storage in a database table.
*
* @package CodeIgniter
* @subpackage Libraries
* @category Sessions
* @author EllisLab Dev Team
*/
abstract class CI_Session_driver extends CI_Driver {
/**
* CI Singleton
*
* @see get_instance()
* @var object
*/
protected $CI;
// ------------------------------------------------------------------------
/**
* Constructor
*
* Gets the CI singleton, so that individual drivers
* don't have to do it separately.
*
* @return void
*/
public function __construct()
{
$this->CI =& get_instance();
}
// ------------------------------------------------------------------------
/**
* Decorate
*
* Decorates the child with the parent driver lib's methods and properties
*
* @param object Parent library object
* @return void
*/
public function decorate($parent)
{
// Call base class decorate first
parent::decorate($parent);
// Call initialize method now that driver has access to $this->_parent
$this->initialize();
}
// ------------------------------------------------------------------------
/**
* __call magic method
*
* Handles access to the parent driver library's methods
*
* @param string Library method name
* @param array Method arguments (default: none)
* @return mixed
*/
public function __call($method, $args = array())
{
// Make sure the parent library uses this driver
$this->_parent->select_driver(get_class($this));
return parent::__call($method, $args);
}
// ------------------------------------------------------------------------
/**
* Initialize driver
*
* @return void
*/
protected function initialize()
{
// Overload this method to implement initialization
}
// ------------------------------------------------------------------------
/**
* Save the session data
*
* Data in the array has changed - perform any storage synchronization
* necessary. The child class MUST implement this abstract method!
*
* @return void
*/
abstract public function sess_save();
// ------------------------------------------------------------------------
/**
* Destroy the current session
*
* Clean up storage for this session - it has been terminated.
* The child class MUST implement this abstract method!
*
* @return void
*/
abstract public function sess_destroy();
// ------------------------------------------------------------------------
/**
* Regenerate the current session
*
* Regenerate the session ID.
* The child class MUST implement this abstract method!
*
* @param bool Destroy session data flag (default: false)
* @return void
*/
abstract public function sess_regenerate($destroy = FALSE);
// ------------------------------------------------------------------------
/**
* Get a reference to user data array
*
* Give array access to the main CI_Session object.
* The child class MUST implement this abstract method!
*
* @return array Reference to userdata
*/
abstract public function &get_userdata();
}
/* End of file Session.php */
/* Location: ./system/libraries/Session/Session.php */