Commits

dchill42 committed fab10de

Added Session driver library

Comments (0)

Files changed (1)

system/libraries/Session/Session.php

+<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package     CodeIgniter
+ * @author      ExpressionEngine Dev Team
+ * @copyright   Copyright (c) 2008 - 2010, EllisLab, Inc.
+ * @license     http://codeigniter.com/user_guide/license.html
+ * @link        http://codeigniter.com
+ * @since       Version 2.0
+ * @filesource
+ */
+
+
+/**
+ * SessionDriver Interface
+ *
+ * Implement this interface to make a new Session driver.
+ * A Session driver basically manages an array of name/value pairs with some sort of storage mechanism.
+ * To make a new driver, implement a constructor where session data is read or created, a save handler to write
+ * changed data to storage, a destroy handler to remove deleted data, and an access handler to expose the data.
+ * 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 Session_<name>, where <name> is capitalized, and your filename must be Session_<name>.EXT,
+ * preferably also capitalized. (e.g.: 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 Session
+ * object. (e.g.: $config['sess_driver'] = 'foo'; OR $CI->load->driver('session', array('sess_driver' => 'foo')); )
+ * Included in this file 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      Darren Hill (DChill)
+ */
+interface SessionDriver {
+    /**
+     * SessionDriver constructor
+     *
+     * Initialize session array, loading data from storage or creating a new instance
+     *
+     * @access  public
+     * @param   $params array - Array of parameters
+     */
+    public function __construct(array $params = array());
+
+    /**
+     * Save the session data
+     *
+     * Data in the array has changed - perform any storage synchronization necessary
+     *
+     * @access  public
+     * @return  void
+     */
+    public function sess_save();
+
+    /**
+     * Destroy the current session
+     *
+     * Clean up storage for this session - it has been terminated
+     *
+     * @access  public
+     * @return  void
+     */
+    public function sess_destroy();
+
+    /**
+     * Get a reference to user data array
+     *
+     * Give array access to the main Session object
+     *
+     * @access  public
+     * @return  array - Reference to userdata
+     */
+    public function &get_userdata();
+}
+// END SessionDriver Interface
+
+
+/**
+ * Session Class
+ *
+ * The user interface defined by EllisLabs, now with puggable drivers to manage different storage mechanisms.
+ * Although these classes are not derived from the CI_Driver and CI_Driver_Library classes, they do follow the
+ * driver loading conventions. Instead of loading the CI_Session library, use this object by loading the Session driver.
+ * (e.g.: $CI->load->driver('session'); )
+ * By default, the Native PHP session driver will load, but the 'sess_driver' config/param item (see above) can be
+ * used to specify the 'Cookie' 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).
+ *
+ * @package     CodeIgniter
+ * @subpackage  Libraries
+ * @category    Sessions
+ * @author      Darren Hill (DChill)
+ * @link        http://codeigniter.com/user_guide/libraries/sessions.html
+ */
+final class Session {
+    private $driver = null;
+    private $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;
+
+    /**
+     * 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.
+     *
+     * @access  public
+     * @param   $params array - Configuration parameters
+     */
+    public function __construct(array $params = array()) {
+        log_message('debug', 'Session Class Initialized');
+
+        // Get the super object and driver name
+        $CI =& get_instance();
+        $key = 'sess_driver';
+        $driver = (isset($params[$key])) ? $params[$key] : $CI->config->item($key);
+        if (!$driver) $driver = 'Native';
+
+        // Load driver and get array reference
+        $this->_load_driver($driver, $CI->load->_ci_library_paths, $params);
+        $this->userdata =& $this->driver->get_userdata();
+
+        // 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', 'Session routines successfully run');
+    }
+
+    /**
+     * Destroy the current session
+     *
+     * @access  public
+     * @return  void
+     */
+    public function sess_destroy() {
+        // Just call destroy on driver
+        $this->driver->sess_destroy();
+    }
+
+    /**
+     * Fetch a specific item from the session array
+     *
+     * @access  public
+     * @param   $item   string - Item key
+     * @return  string - Item value
+     */
+    public function userdata($item) {
+        // Return value or FALSE if not found
+        return (!isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
+    }
+
+    /**
+     * Fetch all session data
+     *
+     * @access    public
+     * @return    array - User data array
+     */
+    public function all_userdata() {
+        // Return entire array
+        return (!isset($this->userdata)) ? FALSE : $this->userdata;
+    }
+
+    /**
+     * Add or change data in the "userdata" array
+     *
+     * @access  public
+     * @param   $newdata    mixed - Item name or array of items
+     * @param   $newval     string - Item value or empty string
+     * @return  void
+     */
+    public function set_userdata($newdata = array(), $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->driver->sess_save();
+    }
+
+    /**
+     * Delete a session variable from the "userdata" array
+     *
+     * @access  public
+     * @param   $newdata    mixed - Item name or array of item names
+     * @return  void
+     */
+    public function unset_userdata($newdata = array()) {
+        // Wrap single name as array
+        if (is_string($newdata)) {
+            $newdata = array($newdata => '');
+        }
+
+        // Unset each item name
+        if (count($newdata) > 0) {
+            foreach ($newdata as $key => $val) {
+                unset($this->userdata[$key]);
+            }
+        }
+
+        // Tell driver data changed
+        $this->driver->sess_save();
+    }
+
+    /**
+     * Determine if an item exists
+     *
+     * @access  public
+     * @param   $item   string - Item name
+     * @return  boolean
+     */
+    public function has_userdata($item) {
+        // Check for item name
+        return isset($this->userdata[$item]);
+    }
+
+    /**
+     * Add or change flashdata, only available
+     * until the next request
+     *
+     * @access  public
+     * @param   $newdata    mixed - Item name or array of items
+     * @param   $newval     string - Item value or empty string
+     * @return  void
+     */
+    public function set_flashdata($newdata = array(), $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.
+     *
+     * @access  public
+     * @param   $key    string - Item key
+     * @return  void
+     */
+    public function keep_flashdata($key) {
+        // 'old' flashdata gets removed.  Here we mark all
+        // flashdata as 'new' to preserve it from _flashdata_sweep()
+        $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
+     *
+     * @access  public
+     * @param   $key    string - Item key
+     * @return  string
+     */
+    public function flashdata($key) {
+        // Prepend key and retrieve value
+        $flashdata_key = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$key;
+        return $this->userdata($flashdata_key);
+    }
+
+    /**
+     * Add or change tempdata, only available
+     * until expiration
+     *
+     * @access  public
+     * @param   $newdata    mixed - Item name or array of items
+     * @param   $newval     string - Item value or empty string
+     * @param   $expire     int - Item lifetime in seconds or 0 for default
+     * @return  void
+     */
+    public function set_tempdata($newdata = array(), $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
+     *
+     * @access  public
+     * @param   $newdata    mixed - Item name or array of item names
+     * @return  void
+     */
+    public function unset_tempdata($newdata = array()) {
+        // Get expirations list
+        $expirations = $this->userdata(self::EXPIRATION_KEY);
+        if (!$expirations || !count($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 ($newdata as $key => $val) {
+                $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
+     *
+     * @access  public
+     * @param   $key    string - Item key
+     * @return  string
+     */
+    public function tempdata($key) {
+        // Prepend key and return value
+        $tempdata_key = self::FLASHDATA_KEY.self::FLASHDATA_EXP.$key;
+        return $this->userdata($tempdata_key);
+    }
+
+    /**
+     * Identifies flashdata as 'old' for removal
+     * when _flashdata_sweep() runs.
+     *
+     * @access    private
+     * @return    void
+     */
+    private function _flashdata_mark() {
+        $userdata = $this->all_userdata();
+        foreach ($userdata as $name => $value) {
+            $parts = explode(self::FLASHDATA_NEW, $name);
+            if (is_array($parts) && count($parts) === 2) {
+                $new_name = self::FLASHDATA_KEY.self::FLASHDATA_OLD.$parts[1];
+                $this->set_userdata($new_name, $value);
+                $this->unset_userdata($name);
+            }
+        }
+    }
+
+    /**
+     * Removes all flashdata marked as 'old'
+     *
+     * @access    private
+     * @return    void
+     */
+    private function _flashdata_sweep() {
+        $userdata = $this->all_userdata();
+        foreach ($userdata as $key => $value) {
+            if (strpos($key, self::FLASHDATA_OLD)) {
+                $this->unset_userdata($key);
+            }
+        }
+    }
+
+    /**
+     * Removes all expired tempdata
+     *
+     * @access    private
+     * @return    void
+     */
+    private function _tempdata_sweep() {
+        // Get expirations list
+        $expirations = $this->userdata(self::EXPIRATION_KEY);
+        if (!$expirations || !count($expirations)) {
+            // Nothing to do
+            return;
+        }
+
+        // Unset expired elements
+        $now = time();
+        $userdata = $this->all_userdata();
+        foreach ($userdata as $key => $value) {
+            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);
+    }
+
+    /**
+     * Loads session storage driver
+     *
+     * @access  private
+     * @param   $child  string - Driver classname
+     * @param   $paths  array - Array of loader paths
+     * @param   $params array - Configuration parameters
+     * @return  void
+     */
+	private function _load_driver($child, array $paths, array $params) {
+		// The class will be prefixed with the parent lib
+        $lib_name = get_class($this);
+		$child_class = $lib_name.'_'.ucfirst($child);
+        $lower = FALSE;
+
+        // Determine if driver is already loaded
+        if (class_exists($child_class)) {
+            // Identify non-lowercase name
+            $lower = FALSE;
+        }
+        else if (class_exists(strtolower($child_class))) {
+            // Identify lowercase name
+            $lower = TRUE;
+        }
+        else {
+            // Iterate loader library paths
+            foreach ($paths as $path) {
+                // Try case variations for parent lib
+                foreach (array(ucfirst($lib_name), strtolower($lib_name)) as $lib) {
+                    // ...and child class
+                    foreach (array(ucfirst($child_class), strtolower($child_class)) as $file) {
+                        $filepath = $path.'libraries/'.$lib.'/drivers/'.$file.EXT;
+
+                        // Check for file
+                        if (file_exists($filepath)) {
+                            include_once $filepath;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // See if class exists now
+            if (class_exists($child_class)) {
+                // Identify non-lowercase name
+                $lower = FALSE;
+            }
+            else if (class_exists(strtolower($child_class))) {
+                // Identify lowercase name
+                $lower = TRUE;
+            }
+            else {
+                // Report driver file not found
+                log_message('error', 'Unable to load the requested driver: '.$child_class);
+                show_error('Unable to load the requested driver: '.$child_class);
+            }
+        }
+
+        // See if class is a valid driver
+        if ($lower) {
+            // Lowercase name to check
+            $child_class = strtolower($child_class);
+        }
+        if (!in_array('SessionDriver', class_implements($child_class))) {
+            // Report invalid driver class
+            log_message('error', 'The requested driver is not a Session Driver: '.$child_class);
+            show_error('The requested driver is not a Session Driver: '.$child_class);
+        }
+
+        // Instantiate driver object
+        $this->driver = new $child_class($params);
+	}
+}
+// END Session Class
+
+
+/**
+ * Native PHP session management driver
+ *
+ * This is the driver that uses the native PHP $_SESSION array through the Session driver library above.
+ *
+ * @package     CodeIgniter
+ * @subpackage  Libraries
+ * @category    Sessions
+ * @author      Darren Hill (DChill)
+ */
+class Session_Native implements SessionDriver {
+    /**
+     * Native Session Driver constructor
+     *
+     * Initializes session driver object
+     *
+     * @access  public
+     * @param   $params array - Array of parameters
+     */
+    public function __construct(array $params = array()) {
+        // Get config parameters
+        $CI =& get_instance();
+        foreach (array('sess_cookie_name', 'sess_expire_on_close', 'sess_expiration', 'sess_match_ip',
+        'sess_match_useragent', 'cookie_prefix', 'cookie_path', 'cookie_domain') as $key) {
+            $config[$key] = isset($params[$key]) ? $params[$key] : $CI->config->item($key);
+        }
+
+        // Set session name, if specified
+        if ($config['sess_cookie_name']) {
+            $name = $config['sess_cookie_name'];
+            if ($config['cookie_prefix']) {
+                // Prepend cookie prefix
+                $name = $config['cookie_prefix'].$name;
+            }
+            session_name($name);
+        }
+
+        // Set expiration, path, and domain
+        $expire = 7200;
+        $path = '/';
+        $domain = '';
+        if ($config['sess_expiration'] !== FALSE) {
+            // Default to 2 years if expiration is "0"
+            $expire = ($config['sess_expiration'] == 0) ? (60*60*24*365*2) : $config['sess_expiration'];
+        }
+        if ($config['cookie_path']) {
+            // Use specified path
+            $path = $config['cookie_path'];
+        }
+        if ($config['cookie_domain']) {
+            // Use specified domain
+            $domain = $config['cookie_domain'];
+        }
+        session_set_cookie_params($config['sess_expire_on_close'] ? 0 : $expire, $path, $domain);
+
+        // Start session
+        session_start();
+
+        // Check session expiration, ip, and agent
+        $now = time();
+        $destroy = FALSE;
+        if (isset($_SESSION['last_activity']) && ($_SESSION['last_activity'] + $expire) < $now) {
+            // Expired - destroy
+            $destroy = TRUE;
+        }
+        else if ($config['sess_match_ip'] == TRUE && isset($_SESSION['ip_address']) &&
+        $_SESSION['ip_address'] != $CI->input->ip_address()) {
+            // IP doesn't match - destroy
+            $destroy = TRUE;
+        }
+        else if ($config['sess_match_useragent'] == TRUE && isset($_SESSION['user_agent']) &&
+        $_SESSION['user_agent'] != trim(substr($CI->input->user_agent(), 0, 50))) {
+            // Agent doesn't match - destroy
+            $destroy = TRUE;
+        }
+
+        // Destroy expired or invalid session
+        if ($destroy) {
+            // Clear old session and start new
+            $this->sess_destroy();
+            session_start();
+        }
+
+        // Set activity time
+        $_SESSION['last_activity'] = $now;
+
+        // Set matching values as required
+        if ($config['sess_match_ip'] == TRUE && !isset($_SESSION['ip_address'])) {
+            // Store user IP address
+            $_SESSION['ip_address'] = $CI->input->ip_address();
+        }
+        if ($config['sess_match_useragent'] == TRUE && !isset($_SESSION['user_agent'])) {
+            // Store user agent string
+            $_SESSION['user_agent'] = trim(substr($CI->input->user_agent(), 0, 50));
+        }
+    }
+
+    /**
+     * Save the session data
+     *
+     * @access  public
+     * @return  void
+     */
+    public function sess_save() {
+        // Nothing to do - changes to $_SESSION are automatically saved
+    }
+
+    /**
+     * Destroy the current session
+     *
+     * @access  public
+     * @return  void
+     */
+    public function sess_destroy() {
+        // Cleanup session
+        $_SESSION = array();
+        $name = session_name();
+        if (isset($_COOKIE[$name])) {
+            // Clear session cookie
+            $params = session_get_cookie_params();
+            setcookie($name, '', time() - 42000, $params['path'], $params['domain']);
+            unset($_COOKIE[$name]);
+        }
+        session_destroy();
+    }
+
+    /**
+     * Get a reference to user data array
+     *
+     * @access  public
+     * @return  array - Reference to userdata
+     */
+    public function &get_userdata() {
+        // Just return reference to $_SESSION
+        return $_SESSION;
+    }
+}
+// END Session_Native Class
+
+
+/**
+ * Cookie-based session management driver
+ *
+ * This is the CI_Session functionality, as written by EllisLab, abstracted out to a driver.
+ * I have done a little updating for PHP5, and made minor changes to extract this functionality from
+ * the public interface (now in Session, above), but effectively this code is unchanged.
+ *
+ * @package     CodeIgniter
+ * @subpackage  Libraries
+ * @category    Sessions
+ * @author      ExpressionEngine Dev Team and Darren Hill (DChill)
+ */
+class Session_Cookie implements SessionDriver {
+    private $sess_encrypt_cookie    = FALSE;
+    private $sess_use_database      = FALSE;
+    private $sess_table_name        = '';
+	private $sess_expiration        = 7200;
+    private $sess_expire_on_close   = FALSE;
+    private $sess_match_ip          = FALSE;
+    private $sess_match_useragent   = TRUE;
+	private $sess_cookie_name       = 'ci_session';
+	private $cookie_prefix          = '';
+    private $cookie_path            = '';
+    private $cookie_domain          = '';
+    private $sess_time_to_update    = 300;
+    private $encryption_key         = '';
+	private $time_reference         = 'time';
+    private $userdata               = array();
+    private $CI                     = null;
+    private $now                    = 0;
+
+    const gc_probability            = 5;
+
+    /**
+     * Cookie Session Driver Constructor
+     *
+     * @access  public
+     * @param   $params array - Array of parameters
+     */
+    public function __construct(array $params = array()) {
+        // Set the super object to a local variable for use throughout the class
+        $this->CI =& get_instance();
+
+        // Set all the session preferences, which can either be set
+        // manually via the $params array above or via the config file
+        foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration',
+        'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path',
+        'cookie_domain', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key) {
+            $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
+        }
+
+        if ($this->encryption_key == '') {
+            show_error('In order to use the Cookie Session driver you are required to set an encryption key '.
+                'in your config file.');
+        }
+
+        // Load the string helper so we can use the strip_slashes() function
+        $this->CI->load->helper('string');
+
+        // Do we need encryption? If so, load the encryption class
+        if ($this->sess_encrypt_cookie == TRUE) {
+            $this->CI->load->library('encrypt');
+        }
+
+        // Are we using a database?  If so, load it
+        if ($this->sess_use_database === TRUE && $this->sess_table_name != '') {
+            $this->CI->load->database();
+        }
+
+		// Set the "now" time.  Can either be GMT or server time, based on the config prefs.
+        // We use this to set the "last activity" time
+		$this->now = $this->_get_time();
+
+		// Set the session length. If the session expiration is
+		// set to zero we'll set the expiration two years from now.
+		if ($this->sess_expiration == 0) {
+			$this->sess_expiration = (60*60*24*365*2);
+		}
+		
+		// Set the cookie name
+		$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
+
+        // Run the Session routine. If a session doesn't exist we'll
+        // create a new one.  If it does, we'll update it.
+        if ( ! $this->_sess_read()) {
+            $this->_sess_create();
+        }
+        else {
+            $this->_sess_update();
+        }
+
+        // Delete expired sessions if necessary
+        $this->_sess_gc();
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Write the session data
+     *
+     * @access  public
+     * @return  void
+     */
+    public function sess_save() {
+        // Are we saving custom data to the DB?  If not, all we do is update the cookie
+        if ($this->sess_use_database === FALSE) {
+            $this->_set_cookie();
+            return;
+        }
+
+        // set the custom userdata, the session data we will set in a second
+        $custom_userdata = $this->all_userdata();
+        $cookie_userdata = array();
+
+        // Before continuing, we need to determine if there is any custom data to deal with.
+        // Let's determine this by removing the default indexes to see if there's anything left in the array
+        // and set the session data while we're at it
+        foreach (array('session_id','ip_address','user_agent','last_activity') as $val) {
+            unset($custom_userdata[$val]);
+            $cookie_userdata[$val] = $this->userdata($val);
+        }
+
+        // Did we find any custom data?  If not, we turn the empty array into a string
+        // since there's no reason to serialize and store an empty array in the DB
+        if (count($custom_userdata) === 0) {
+            $custom_userdata = '';
+        }
+        else {
+            // Serialize the custom data array so we can store it
+            $custom_userdata = $this->_serialize($custom_userdata);
+        }
+
+        // Run the update query
+        $this->CI->db->where('session_id', $this->userdata('session_id'));
+        $this->CI->db->update($this->sess_table_name,
+            array('last_activity' => $this->userdata('last_activity'), 'user_data' => $custom_userdata));
+
+        // Write the cookie.  Notice that we manually pass the cookie data array to the
+        // _set_cookie() function. Normally that function will store $this->userdata, but
+        // in this case that array contains custom data, which we do not want in the cookie.
+        $this->_set_cookie($cookie_userdata);
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Destroy the current session
+     *
+     * @access  public
+     * @return  void
+     */
+    public function sess_destroy() {
+        // Kill the session DB row
+        if ($this->sess_use_database === TRUE && $this->has_userdata('session_id')) {
+            $this->CI->db->where('session_id', $this->userdata['session_id']);
+            $this->CI->db->delete($this->sess_table_name);
+        }
+
+        // Kill the cookie
+        setcookie($this->sess_cookie_name, addslashes(serialize(array())), ($this->now - 31500000),
+            $this->cookie_path, $this->cookie_domain, 0);
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Get a reference to user data array
+     *
+     * @access  public
+     * @return  array - Reference to userdata
+     */
+    public function &get_userdata() {
+        // Return reference to array
+        return $this->userdata;
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Fetch the current session data if it exists
+     *
+     * @access  private
+     * @return  bool
+     */
+    private function _sess_read() {
+        // Fetch the cookie
+        $session = $this->CI->input->cookie($this->sess_cookie_name);
+
+        // No cookie?  Goodbye cruel world!...
+        if ($session === FALSE) {
+            log_message('debug', 'A session cookie was not found.');
+            return FALSE;
+        }
+
+        // Decrypt the cookie data
+        if ($this->sess_encrypt_cookie == TRUE) {
+            $session = $this->CI->encrypt->decode($session);
+        }
+        else {
+            // encryption was not used, so we need to check the md5 hash
+            $hash    = substr($session, strlen($session)-32); // get last 32 chars
+            $session = substr($session, 0, strlen($session)-32);
+
+            // Does the md5 hash match?  This is to prevent manipulation of session data in userspace
+            if ($hash !==  md5($session.$this->encryption_key)) {
+                log_message('error', 'The session cookie data did not match what was expected. '.
+                    'This could be a possible hacking attempt.');
+                $this->sess_destroy();
+                return FALSE;
+            }
+        }
+
+        // Unserialize the session array
+        $session = $this->_unserialize($session);
+
+        // Is the session data we unserialized an array with the correct format?
+        if ( ! is_array($session) || ! isset($session['session_id']) || ! isset($session['ip_address']) ||
+        ! isset($session['user_agent']) || ! isset($session['last_activity'])) {
+            $this->sess_destroy();
+            return FALSE;
+        }
+
+        // Is the session current?
+        if (($session['last_activity'] + $this->sess_expiration) < $this->now()) {
+            $this->sess_destroy();
+            return FALSE;
+        }
+
+        // Does the IP Match?
+        if ($this->sess_match_ip == TRUE && $session['ip_address'] != $this->CI->input->ip_address()) {
+            $this->sess_destroy();
+            return FALSE;
+        }
+
+        // Does the User Agent Match?
+        if ($this->sess_match_useragent == TRUE &&
+        trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50))) {
+            $this->sess_destroy();
+            return FALSE;
+        }
+
+        // Is there a corresponding session in the DB?
+        if ($this->sess_use_database === TRUE) {
+            $this->CI->db->where('session_id', $session['session_id']);
+
+            if ($this->sess_match_ip == TRUE)
+            {
+                $this->CI->db->where('ip_address', $session['ip_address']);
+            }
+
+            if ($this->sess_match_useragent == TRUE)
+            {
+                $this->CI->db->where('user_agent', $session['user_agent']);
+            }
+
+            $query = $this->CI->db->get($this->sess_table_name);
+
+            // No result?  Kill it!
+            if ($query->num_rows() == 0)
+            {
+                $this->sess_destroy();
+                return FALSE;
+            }
+
+            // Is there custom data?  If so, add it to the main session array
+            $row = $query->row();
+            if (isset($row->user_data) && $row->user_data != '')
+            {
+                $custom_data = $this->_unserialize($row->user_data);
+
+                if (is_array($custom_data))
+                {
+                    foreach ($custom_data as $key => $val)
+                    {
+                        $session[$key] = $val;
+                    }
+                }
+            }
+        }
+
+        // Session is valid!
+        $this->userdata = $session;
+        unset($session);
+
+        return TRUE;
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Create a new session
+     *
+     * @access  private
+     * @return  void
+     */
+    private function _sess_create() {
+        $sessid = '';
+        while (strlen($sessid) < 32) {
+            $sessid .= mt_rand(0, mt_getrandmax());
+        }
+
+        // To make the session ID even more secure we'll combine it with the user's IP
+        $sessid .= $this->CI->input->ip_address();
+
+        $this->set_userdata('session_id', md5(uniqid($sessid, TRUE)));
+        $this->set_userdata('ip_address', $this->CI->input->ip_address());
+        $this->set_userdata('user_agent', substr($this->CI->input->user_agent(), 0, 50));
+        $this->set_userdata('last_activity',$this->now());
+
+
+        // Save the data to the DB if needed
+        if ($this->sess_use_database === TRUE) {
+            $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->all_userdata()));
+        }
+
+        // Write the cookie
+        $this->_set_cookie();
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Update an existing session
+     *
+     * @access  private
+     * @return  void
+     */
+    private function _sess_update() {
+        // We only update the session every five minutes by default
+        if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now()) {
+            return;
+        }
+
+        // Save the old session id so we know which record to
+        // update in the database if we need it
+        $old_sessid = $this->userdata['session_id'];
+        $new_sessid = '';
+        while (strlen($new_sessid) < 32) {
+            $new_sessid .= mt_rand(0, mt_getrandmax());
+        }
+
+        // To make the session ID even more secure we'll combine it with the user's IP
+        $new_sessid .= $this->CI->input->ip_address();
+
+        // Turn it into a hash
+        $new_sessid = md5(uniqid($new_sessid, TRUE));
+
+        // Update the session data in the session data array
+        $this->set_userdata('session_id', $new_sessid);
+        $this->set_userdata('last_activity', $this->now());
+
+        // _set_cookie() will handle this for us if we aren't using database sessions
+        // by pushing all userdata to the cookie.
+        $cookie_data = NULL;
+
+        // Update the session ID and last_activity field in the DB if needed
+        if ($this->sess_use_database === TRUE) {
+            // set cookie explicitly to only have our session data
+            $cookie_data = array();
+            foreach (array('session_id','ip_address','user_agent','last_activity') as $val) {
+                $cookie_data[$val] = $this->userdata[$val];
+            }
+
+            $this->CI->db->query($this->CI->db->update_string($this->sess_table_name,
+                array('last_activity' => $this->now(), 'session_id' => $new_sessid),
+                array('session_id' => $old_sessid)));
+        }
+
+        // Write the cookie
+        $this->_set_cookie($cookie_data);
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Get the "now" time
+     *
+     * @access  private
+     * @return  int
+     */
+    private function _get_time() {
+        if (strtolower($this->time_reference) == 'gmt') {
+            $now = time();
+            $time = mktime(gmdate('H', $now), gmdate('i', $now), gmdate('s', $now), gmdate('m', $now),
+                gmdate('d', $now), gmdate('Y', $now));
+        }
+        else {
+            $time = time();
+        }
+
+        return $time;
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Write the session cookie
+     *
+     * @access  private
+     * @param   $cookie_data    array - Cookie name/value pairs
+     * @return  void
+     */
+    private function _set_cookie(array $cookie_data = NULL) {
+        if (is_null($cookie_data)) {
+            $cookie_data = $this->all_userdata();
+        }
+
+        // Serialize the userdata for the cookie
+        $cookie_data = $this->_serialize($cookie_data);
+
+        if ($this->sess_encrypt_cookie == TRUE) {
+            $cookie_data = $this->CI->encrypt->encode($cookie_data);
+        }
+        else {
+            // if encryption is not used, we provide an md5 hash to prevent userside tampering
+            $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
+        }
+
+        $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
+
+        // Set the cookie
+        setcookie($this->sess_cookie_name, $cookie_data, $expire, $this->cookie_path, $this->cookie_domain, 0);
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Serialize an array
+     *
+     * This function first converts any slashes found in the array to a temporary
+     * marker, so when it gets unserialized the slashes will be preserved
+     *
+     * @access  private
+     * @param   $data   mixed - Data to serialize
+     * @return  string
+     */
+    private function _serialize($data) {
+        if (is_array($data)) {
+            foreach ($data as $key => $val) {
+                if (is_string($val)) {
+                    $data[$key] = str_replace('\\', '{{slash}}', $val);
+                }
+            }
+        }
+        else {
+            if (is_string($data)) {
+                $data = str_replace('\\', '{{slash}}', $data);
+            }
+        }
+
+        return serialize($data);
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Unserialize
+     *
+     * This function unserializes a data string, then converts any
+     * temporary slash markers back to actual slashes
+     *
+     * @access  private
+     * @param   $data   string - Data to unserialize
+     * @return  mixed
+     */
+    private function _unserialize($data) {
+        $data = @unserialize(strip_slashes($data));
+
+        if (is_array($data)) {
+            foreach ($data as $key => $val) {
+                if (is_string($val)) {
+                    $data[$key] = str_replace('{{slash}}', '\\', $val);
+                }
+            }
+
+            return $data;
+        }
+
+        return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
+    }
+
+    // --------------------------------------------------------------------
+
+    /**
+     * Garbage collection
+     *
+     * This deletes expired session rows from database
+     * if the probability percentage is met
+     *
+     * @access  private
+     * @return  void
+     */
+    private function _sess_gc() {
+        if ($this->sess_use_database != TRUE) {
+            return;
+        }
+
+        srand(time());
+        if ((rand() % 100) < self::gc_probability) {
+            $expire = $this->now() - $this->sess_expiration;
+
+            $this->CI->db->where('last_activity < '.$expire);
+            $this->CI->db->delete($this->sess_table_name);
+
+            log_message('debug', 'Session garbage collection performed.');
+        }
+    }
+}
+// END Session_Cookie Class
+
+/* End of file Session.php */
+/* Location: ./system/libraries/Session/Session.php */
+?>