Skip to content
Derek Jones edited this page Jul 5, 2012 · 30 revisions

Category:Library::Session

Native Session makes good use of PHP's native session handling abilities, but it does not allow the use of a database for session storage. KNDB Session (which stands for Kirilisa's Native Database session) is a hybrid which is based upon Native Session (with some notable changes) but allows use of databases via PHP's ability to store sessions in a DB.

Overview

  • KNDB Session is based on Native Session but has database functionality built in.
  • Should be compatible with Codeignitor version 1.54 and 1.6.
  • Designed as drop-in replacement for CI's bundled session library.
  • Allows you to access values from single-dimensional arrays & objects stored in the session
  • Config options and flash data are supported (with exception of session encryption).
  • When using with a database, only the session_id is stored in a cookie. Any other data is stored in the database.
  • When using without a database, only the session_id is stored in a cookie. Any other data is stored in a file on the server (as PHP does natively).
  • KNDB Session is written for PHP5 but could be slightly altered to work with PHP4 (most notably the regenerate_id() method)

Differences between KNDB Session and Native Session

  • Allows use of a database for session storage
  • Keeps track of both sess_expiration and sess_time_to_update (in config) and thus distinguishes between session id needing regeneration and the session actually expiring (Native session considered sess_expiration as the time needed to regenerate a session id and never expires the session)
  • Added method all_userdata() to get all session data: this method is in CI's bundled session library but missing from Native Session
  • Allows you to access values from single-dimensional arrays & objects stored in the session
  • Made some changes to regenerate_id() method to shorten code and a fix potential bug there

Required Database Structure

CREATE TABLE sessions (                                                                                                                           
session_id varchar(32) NOT NULL,                                                                                                                  
session_last_access int(10) unsigned,                                                                                                             
session_data text,                                                                                                                                
PRIMARY KEY (session_id)                                                                                                                          
);

Example Configuration (/system/application/config/config.php)

$config['sess_cookie_name']            = 'CISESSION';
$config['sess_expiration']            = 7200;
$config['sess_encrypt_cookie']        = FALSE;
$config['sess_table_name']            = 'sessions';
$config['sess_match_ip']            = TRUE;
$config['sess_match_useragent']        = TRUE;
$config['sess_use_database']        = TRUE;
$config['sess_time_to_update']        = 300; 

Usage

  • Place the code in a file named Session.php in your /system/application/libraries/ directory.
  • Use this lib as if you would CI's bundled session library.

Changelog

March 31, 2008: Added ability for values to be retrieved from single-dimensional arrays in the session April 10, 2008: Added ability for values to be retrieved from objects in the session

Code

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
 * Code Igniter
 *
 * An open source application development framework for PHP 4.3.2 or newer
 *
 * @package     CodeIgniter
 * @author      Elise Bosse
 * @copyright   Copyright (c) 2008, E.Bosse
 * @license     http://www.codeignitor.com/user_guide/license.html
 * @link        http://www.codeigniter.com
 * @since       Version 1.2
 * @filesource
 */

// ------------------------------------------------------------------------

/**
 * Session class using native PHP session features and hardened against session fixation.
 * Non-database part is based upon Dariusz Debowczyk's Native session library with some updates.
 * The DB part makes use of PHP's session_set_save_handler() functionality
 * This library is written for PHP 5 but it could be altered a bit to make it work in PHP4
 *
 * @package     CodeIgniter
 * @subpackage  Libraries
 * @category    Sessions
 * @author      Elise Bosse
 * @link        http://www.codeigniter.com/user_guide/libraries/sessions.html
 * @class link  http://codeigniter.com/wiki/KNDB_Session/
 */

/**
 * If using a database, create the following database table and make sure config file is set properly
 *
 * CREATE TABLE sessions (
 * session_id varchar(32) NOT NULL,
 * session_last_access int(10) unsigned,
 * session_data text,
 * PRIMARY KEY (session_id)
 * );
*/

class CI_Session {

  private $_lifetime; 
  private $_sess_id_ttl;
  private $_match_ip;
  private $_match_useragent;
  private $_sess_db;  
  private $_useDB;
  private $_sess_table;
  private $_flash_key = 'flash'; // prefix for "flash" variables (eg. flash:new:message)

  function __construct()
  {
    $this->object =& get_instance();

    // set config variables
    $this->_lifetime = $this->object->config->item('sess_expiration');
    $this->_sess_id_ttl = $this->object->config->item('sess_time_to_update');
    $this->_match_ip = $this->object->config->item('sess_match_ip');
    $this->_match_useragent = $this->object->config->item('sess_match_useragent');
    $this->_useDB = $this->object->config->item('sess_use_database');
    $this->_sess_table = $this->object->config->item('sess_table_name');

    log_message('debug', "Session Class Initialized");

    $this->_sess_run();
  }

  /**
   * Starts up the session system for current request
   */
  function _sess_run()
  {
    // Set session table and register this object as the session handler, if using databases
    if ($this->_useDB == TRUE) {
      session_set_save_handler(array(&$this, "_open"), array(&$this, "_close"), array(&$this, "_read"),
                   array(&$this, "_write"),array(&$this, "_destroy"),array(&$this, "_gc"));
    }

    session_start();

    // if no lifetime set in config, set to 2 years
    if (!is_numeric($this->_lifetime) || $this->_lifetime <= 0) {
      $this->_lifetime = (60*60*24*365*2);
    }

    // if no session ID regeneration time set in config, set to 30 minutes
    if (!is_numeric($this->_sess_id_ttl) || $this->_sess_id_ttl <= 0) {
      $this->_sess_id_ttl = 1800;
    }

    // check if session has expired
    if ($this->_session_expired()) {
      $this->sess_destroy();
      return FALSE;
    }

    // match IP address if necessary
    if ($this->_match_ip == TRUE) {
      if ($this->_ips_match() == FALSE) {
    $this->sess_destroy();
    return FALSE;
      }
    }
    
    // match user agent if necessary
    if ($this->_match_useragent == TRUE) {
      if ($this->_useragents_match() == FALSE) {
    $this->sess_destroy();
    return FALSE;
      }
    }

    // regenerate session id if necessary
    // session data stays the same, but old session storage is destroyed
    if ( $this->_sess_id_expired() ) {      
      $this->regenerate_id();
    }

    // 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();

    // finally, set last access time to now
    $this->set_userdata('sess_last_access', time());
  }

  /**
   * Checks if session has expired
   */
  function _session_expired()
  {
    // if this is the first time coming in, initialize access time
    if (!$this->userdata('sess_last_access')) {
      $this->set_userdata('sess_last_access', time());
      return FALSE;
    }

    $delta = time() - $this->userdata('sess_last_access');

    if ($delta  >=  $this->_lifetime ) {
      return true; 
    }

    return false;
  }

  /**
   * Checks if stored IP matches current IP
   */
  function _ips_match() {
    // if this is the first time coming in, initialize IP address
    if (!$this->userdata('sess_ip_address')) {
      $this->set_userdata('sess_ip_address',  $this->object->input->ip_address());
      return TRUE;
    }    

    return $this->userdata('sess_ip_address') == $this->object->input->ip_address();
  }

  /**
   * Checks if stored user agent matches current user agent
   */
  function _useragents_match() {
    // if this is the first time coming in, initialize user agent
    if (!$this->userdata('sess_useragent')) {
      $this->set_userdata('sess_useragent', trim(substr($this->object->input->user_agent(), 0, 50)));
      return TRUE;
    }    

    return $this->userdata('sess_useragent') == trim(substr($this->object->input->user_agent(), 0, 50));
  }


  /**
   * Checks if session id needs regenerating
   */
  function _sess_id_expired()
  {
    // if this is the first time coming in, initialize regenerated time
    if (!$this->userdata('sess_last_regenerated')) {
      $this->set_userdata('sess_last_regenerated', time());
      return false;
    }

    $delta = time() - $this->userdata('sess_last_regenerated');

    if ( $delta >=  $this->_sess_id_ttl ) { 
      return true;
    }

    return false;
  }


  /**
   * Regenerates session id
   */
  function regenerate_id()
  {
    // regenerate session id and store it
    // $delete_old_session parameter works in PHP5 only! 
    session_regenerate_id(TRUE);

    // update the session generation time
    $this->set_userdata('sess_last_regenerated', time());
  }


  /**
   * Destroys the session and erases session storage
   */
  function sess_destroy()
  {
    session_unset();
    if ( isset( $_COOKIE[session_name()] ) )
      {
    //@@@ was having trouble just using setcookie() because it wasn't unsetting fast enough
    unset($_COOKIE[session_name()]);
    setcookie(session_name(), '', time()-42000, '/'); //@@@
      }

    session_destroy();
  }


  /**                                                                                                                                             
   * returns the session id of the current session                                                                                                
   */
  function get_sess_id() {
    return session_id();
  }


  /**
   * Reads given session attribute value: single variable, element of single dimensional array, or property of object
   * I was kind of of two minds about whether the object bit should be implemented
   * so you can take out that logic if you wish
   */    
  function userdata($item, $subitem=null)
  {
    // this item is in an array
    if ($subitem) {
      if ($subitem == 'session_id'){ //added for backward-compatibility                                                                           
        return session_id();
      } else {
        // array vs. object: handled differently
        if (isset($_SESSION[$item])) {
          if (is_array($_SESSION[$item])) return (!isset($_SESSION[$item][$subitem])) ? false : $_SESSION[$item][$subitem];
          if (is_object($_SESSION[$item])) return (!isset($_SESSION[$item]->$subitem)) ? false : $_SESSION[$item]->$subitem;
          return false;
        }
      }
    }

    // this item is not in an array
    else {
      if($item == 'session_id'){ //added for backward-compatibility
        return session_id();
      } else {
        return ( ! isset($_SESSION[$item])) ? false : $_SESSION[$item];
      }
    }

  }

  /**
   * Returns all session data
   */    
  function all_userdata()
  {
    if (isset($_SESSION['session_id'])) { //added for backward-compatibility
      $_SESSION['session_id'] = session_id();
    }
    return $_SESSION;
  }

  /**
   * Sets session attributes to the given values
   */
  function set_userdata($newdata = array(), $newval = '')
  {
    if (is_string($newdata))
      {
    $newdata = array($newdata => $newval);
      }

    if (count($newdata) > 0)
      {
    foreach ($newdata as $key => $val)
      {
        $_SESSION[$key] = $val;
      }
      }
  }

  /**
   * Erases given session attributes
   */
  function unset_userdata($newdata = array())
  {
    if (is_string($newdata))
      {
    $newdata = array($newdata => '');
      }

    if (count($newdata) > 0)
      {
    foreach ($newdata as $key => $val)
      {
        unset($_SESSION[$key]);
      }
      }
  }


/**
* Sets "flash" data which will be available only in next request (then it will
* be deleted from session). You can use it to implement "Save succeeded" messages
* after redirect.
*/
  function set_flashdata($key, $value)
  {
    $flash_key = $this->_flash_key.':new:'.$key;
    $this->set_userdata($flash_key, $value);
  }

  /**
   * Keeps existing "flash" data available to next request.
   */
  function keep_flashdata($key)
  {
    $old_flash_key = $this->_flash_key.':old:'.$key;
    $value = $this->userdata($old_flash_key);

    $new_flash_key = $this->_flash_key.':new:'.$key;
    $this->set_userdata($new_flash_key, $value);
  }

  /**
   * Returns "flash" data for the given key.
   */
  function flashdata($key)
  {
    $flash_key = $this->_flash_key.':old:'.$key;
    return $this->userdata($flash_key);
  }

  /**
   * PRIVATE: Internal method - marks "flash" session attributes as 'old'
   */
  function _flashdata_mark()
  {
    foreach ($_SESSION as $name => $value)
      {
    $parts = explode(':new:', $name);
    if (is_array($parts) && count($parts) == 2)
      {
        $new_name = $this->_flash_key.':old:'.$parts[1];
        $this->set_userdata($new_name, $value);
        $this->unset_userdata($name);
      }
      }
  }

  /**
   * PRIVATE: Internal method - removes "flash" session marked as 'old'
   */
  function _flashdata_sweep()
  {
    foreach ($_SESSION as $name => $value)
      {
    $parts = explode(':old:', $name);
    if (is_array($parts) && count($parts) == 2 && $parts[0] == $this->_flash_key)
      {
        $this->unset_userdata($name);
      }
      }
  }




  /************* DATABASE METHODS ***************/
  function _open()
  {
    if ($this->_sess_db = mysql_connect($this->object->db->hostname, 
                    $this->object->db->username, 
                    $this->object->db->password)) {
      return mysql_select_db($this->object->db->database, $this->_sess_db);
    }
    
    return FALSE;
  }

  function _close()
  {
    if ($this->_sess_db) {
      return mysql_close($this->_sess_db);
    }

    return TRUE;
  }
  
  function _read($id)
  {
    $id = mysql_real_escape_string($id);
    
    $sql = "SELECT session_data FROM $this->_sess_table WHERE session_id = '$id'";
    
    if ($result = mysql_query($sql, $this->_sess_db)) {
      if (mysql_num_rows($result)) {
    $record = mysql_fetch_assoc($result);
    
    return $record['session_data'];
      }
    }
    
    return '';
  }
  
  function _write($id, $data)
  {
    $access = time();

    $id = mysql_real_escape_string($id);
    $access = mysql_real_escape_string($access);
    $data = mysql_real_escape_string($data);
    
    $sql = "REPLACE INTO $this->_sess_table VALUES ('$id', '$access', '$data')";
    
    return mysql_query($sql, $this->_sess_db);
  }
  
  function _destroy($id)
  {
    $id = mysql_real_escape_string($id);
    
    $sql = "DELETE FROM $this->_sess_table WHERE session_id = '$id'";

    return mysql_query($sql, $this->_sess_db) or die("failed to delete<br>");
  }
  
  function _gc($max)
  {
    $old = time() - $max;
    $old = mysql_real_escape_string($old);
    
    $sql = "DELETE FROM $this->_sess_table WHERE session_last_access < '$old'";
    
    return mysql_query($sql, $this->_sess_db);
  }


}
?&gt;
Clone this wiki locally