Version: 0.3.0
Type: Class
Category: Databases
License: GNU General Public License
Description: MySQL Database session management
<?php # +----------------------------------------------------------------------------+ # | Copyright (c) 2002 - 2003 Undone, Labs. | # +----------------------------------------------------------------------------| # | This library is free software; you can redistribute it and/or modify it | # | under the terms of the GNU Lesser General Public License as published by | # | the Free Software Foundation; either version 2.1 of the License, or | # | (at your option) any later version. | # | | # | This library is distributed in the hope that it will be useful, but | # | WITHOUT ANY WARRANTY; without even the implied warranty of | # | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser | # | General Public License for more details. | # | | # | You should have received a copy of the GNU Lesser General Public | # | License along with this library; if not, write to the Free Software | # | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | # | | # | For new version, support, bugs, commercial use or related subjects, | # | please contact the author. | # +----------------------------------------------------------------------------| # | Author: Minc <[email protected]> | # +----------------------------------------------------------------------------+ define('SESSION_VIA_COOKIE', 1); define('SESSION_VIA_GET', 2); define('SESSION_VIA_PATH', 3); /** * Session management using MySQL Database * * @author Minc <[email protected]> $ * @version $id: Session_MySQL.php,v 0.3.0 2003/08/29 17:06:56 minc Exp $ * @package session */ class Session_MySQL { // {{{ Properties // ---BEGIN CLASS CONFIGURATION VARIABLES--- /** * Default session method, one of * SESSION_VIA_COOKIE, SESSION_VIA_GET, SESSION_VIA_PATH * @var integer * @access private */ var $method = SESSION_VIA_GET; /** * Fallback on failed or die(), this property actually needed if using cookie * @var boolean TRUE if fallback, else FALSE * @access private */ var $fallback_on_failed = false; /** * Default fallback method, this property actually needed if using cookie * @var integer * @access private */ var $fallback_method = SESSION_VIA_GET; /** * Session name (only use if method via cookie or via get) * @var string * @access private */ var $name = 'sid'; /** * An array contains register variables * @var array * @access private */ var $vars = array(); /** * Use to save session id * @var string * @access private */ var $id = null; /** * Storeable vars value delimiter * @var string * @access private */ var $delimiter_value = '=>'; /** * Storeable vars delimiter * @var string * @access private */ var $delimiter = '|'; /** * Object variable delimiter * @var string * @access private */ var $ob_delimiter = '>>'; /** * Method for userialize object * @var string * @access private */ var $incmethod = 'include'; // ---END CLASS CONFIGURATION VARIABLES--- // ---BEGIN MySQL CONFIGURATION VARIABLES--- /** * MySQL username * @var string * @access private */ var $mysql_username = 'session'; /** * MySQL user password * @var string * @access private */ var $mysql_password = ''; /** * MySQL server address * @var string * @access private */ var $mysql_server = 'localhost'; /** * MySQL link resource * @var resource * @access private */ var $mysql_link = null; /** * MySQL database name * @var string * @access private */ var $mysql_dbname = null; /** * MySQL table name * @var string * @access private */ var $mysql_tbname = 'session'; // ---END MySQL CONFIGURATION VARIABLES--- // ---BEGIN SESSION CONFIGURATION VARIABLES--- /** * number of minutes when data will seen as garbage * @var integer default is 24 hours * @access private */ var $gc_maxlifetime = 1440; /** * Cookie lifetime (use when using session via cookie) * @var integer default is 0 seconds or until the end of session * @access private */ var $cookie_lifetime = 0; /** * Cookie path * @var string * @access private */ var $cookie_path = "/"; /** * Cookie domain * @var string * @access private */ var $cookie_domain = ""; /** * Cookie secure * @var integer * @access private */ var $cookie_secure = 0; /** * Cache limiter * @var string one of 'private', 'public', 'no' * @access private */ var $cache_limiter = 'private'; /** * Cache expire * @var integer number of minutes * @access private */ var $cache_expire = 1440; // ---END SESSION CONFIGURATION VARIABLES--- // }}} // {{{ Constructor /** * Create new instance Session object * * @param string $mysql_dbname * @param (optional) string $mysql_username * @param (optional) string $mysql_password * @param (optional) string $mysql_hostname * @param (optional) mixed $mysql_port * @param (optional) string $name * @param (optional) integer $method * @param (optional) string $save_path * * @access public */ function Session_MySQL($mysql_dbname, $mysql_username = null, $mysql_password = null, $mysql_hostname = null, $mysql_port = null) { $this->mysql_dbname = $mysql_dbname; if (isset($mysql_username)) $this->mysql_username = $mysql_username; if (isset($mysql_password)) $this->mysql_password = $mysql_password; if (isset($mysql_host) ) $this->mysql_server = $mysql_hostname; if (isset($mysql_port) ) $this->mysql_server .= ':' . $mysql_port; } // }}} /** * Sets session parameters * * @param (optional) string $name * @param (optional) integer $method * @param (optional) integer $fallback_method * * @access public */ function setParameters($name = null, $method = null, $fallback_method = null) { if (!empty($name) ) $this->name = $name; if (!empty($method) ) $this->method = $method; if (!empty($fallback_method)) $this->fallback_method = $fallback_method; } /** * Register vars to session * * @param string $varname Variable name * @access public */ function register($varname) { if (!empty($varname)) $this->vars[$varname] = null; } /** * Find the whether var is registered * * @param string $varname Variable name * @return boolean * @access public */ function is_registered($varname) { if (isset($this->vars[$varname])) return true; return false; } /** * Unregister vars from the session * * @param string $varname Variable name * @access public */ function unregister($varname) { if (!empty($varname) && isset($this->vars[$varname])) unset($this->vars[$varname]); } /** * Open new session * * @access private */ function open() { // try to get session id $session_id = $this->getID($this->method); if ($session_id == null) { $this->id = $this->randID(); } else { $this->id = $session_id; } @ini_set('track_errors', true); $this->mysql_link = @mysql_connect($this->mysql_server, $this->mysql_username, $this->mysql_password); @ini_restore('track_errors'); if (empty($this->mysql_link)) { $err = (@mysql_error($this->mysql_link) != '') ? $err : $php_errormsg; $this->halt(E_USER_ERROR, @mysql_errno($this->mysql_link), $err); } if (!@mysql_select_db($this->mysql_dbname, $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } if (!$this->is_table_exist($this->mysql_dbname, $this->mysql_tbname)) { $this->halt(E_USER_ERROR, 1, 'Session table is not exists, please create before'); } else { $this->tbintrospection(); } return true; } /** * Insert into database table * * @param string $sess_data Session data * @access private */ function write($sess_data) { if ($this->is_session_exists($this->id)) { $sql = "UPDATE " . $this->mysql_tbname . " SET session_data='" . $sess_data . "', session_time=" . $this->timestamp2mysql(time()) . " " ."WHERE session_id='" . $this->id . "'"; } else { $sql = "INSERT INTO " . $this->mysql_tbname . " VALUES('" . $this->id . "', '" . $sess_data . "', " . $this->timestamp2mysql(time()) . ")"; } if (!@mysql_query($sql, $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } /** * Read data from database table * * @access private */ function read() { if (($result = @mysql_query("SELECT session_data FROM " . $this->mysql_tbname . " WHERE session_id='" . $this->id . "'", $this->mysql_link)) === false) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } $row = mysql_fetch_assoc($result); return $row['session_data']; } /** * Find the whether table is already exist * * @param string $dbname Database name * @param string $tbname Table name * * @access private */ function is_table_exist($dbname, $tbname) { if (($result = mysql_list_tables($dbname, $this->mysql_link)) === false) { return false; } while ($row = mysql_fetch_row($result)) { if ($row[0] == $tbname) { mysql_free_result($result); return true; } } return false; } /** * Get fields table structure information * * @param string $tbname * @access private */ function getTbStructure($tbname) { if (($result = @mysql_query("SELECT * FROM " . $tbname, $this->mysql_link)) === false) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } $fields = array(); for ($i = 0; $i < mysql_num_fields($result); $i++) { $name = mysql_field_name($result, $i); $fields[$name]['type'] = mysql_field_type($result, $i); $fields[$name]['len'] = mysql_field_len($result, $i); $fields[$name]['flag'] = mysql_field_flags($result, $i); } return $fields; } /** * Introspection existing session table structure * * @access private */ function tbintrospection() { $fields = $this->getTbStructure($this->mysql_tbname); if (!isset($fields['session_id'])) { if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " ADD session_id VARCHAR(32) NOT NULL FIRST")) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " ADD UNIQUE(session_id)", $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } else { if ($fields['session_id']['type'] != 'string' || $fields['session_id']['len'] != 32 || !ereg('not_null', $fields['session_id']['flag'])) { if(!mysql_query("ALTER TABLE " . $this->mysql_tbname . " CHANGE session_id session_id VARCHAR(32) NOT NULL", $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } if(!ereg('primary_key', $fields['session_id']['flag'])) { if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " ADD UNIQUE (session_id)", $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } } } if (!isset($fields['session_data'])) { if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " ADD session_data TEXT AFTER session_id", $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } else { if ($fields['session_data']['type'] != 'blob') { if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " CHANGE session_data session_data TEXT")) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } } if (!isset($fields['session_time'])) { if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " ADD session_time TIMESTAMP(14) AFTER session_data", $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } else { if ($fields['session_time']['type'] != 'timestamp' || $fields['session_time']['len'] != 14) { if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " CHANGE session_time session_time TIMESTAMP(14)", $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } } $fields = $this->getTbStructure($this->mysql_tbname); if (count($fields != 3)) { reset($fields); for($i = 0; $i < 3; $i++) { next($fields); } while(list($field, $atts) = each($fields)) { if (!@mysql_query("ALTER TABLE " . $this->mysql_tbname . " DROP " . $field, $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } } } /** * find the wheter session is exists * * @access private */ function is_session_exists($id) { if (($result = @mysql_query("SELECT session_id FROM " . $this->mysql_tbname . " WHERE session_id='" . $this->id . "'", $this->mysql_link)) === false) { $this->halt(E_USER_WARNING, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } if (mysql_num_rows($result) != 0) { mysql_free_result($result); return true; } mysql_free_result($result); return false; } /** * Convert php timestamp to mysql timestamp * * @param integer $ts * @access private */ function timestamp2mysql($ts) { $d=getdate($ts); $yr=$d["year"]; $mo=$d["mon"]; $da=$d["mday"]; $hr=$d["hours"]; $mi=$d["minutes"]; $se=$d["seconds"]; return sprintf("%04d%02d%02d%02d%02d%02d",$yr,$mo,$da,$hr,$mi,$se); } /** * Start session via cookie * * @access private */ function start_via_cookie() { $session_id = $this->getID(SESSION_VIA_COOKIE); if ($session_id == null) { return $this->sendcookie($this->name, $this->id, $this->cookie_lifetime, $this->cookie_path, $this->cookie_domain, $this->cookie_secure); } return true; } /** * Start session via get * * @access private */ function start_via_get() { $session_id = $this->getID(SESSION_VIA_GET); if ($session_id == null) { $getvars = array(); if (!empty($_GET)) { $getvars = $_GET; } $getvars[$this->name] = $this->id; $newquery = $this->array2querystring($getvars); $destination = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . '?' . $newquery; $this->redirect($destination); } return true; } /** * Start session via path * * @access private */ function start_via_path() { $session_id = $this->getID(SESSION_VIA_PATH); if ($session_id == null) { $destination = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . '/' . $this->id; $this->redirect($destination); } return true; } /** * Generate random id * * @return string */ function randID() { return( md5( uniqid( mt_rand() ) ) ); } /** * Make hexadecimal 32 bit for this session id * * @return string Hexadecimal representation * @access private */ function key4id() { $digest = pack('H*', $this->id); $result = array(); for ($i = 0; $i < 4; $i++) { $result[$i] = $digest[$i + 1] ^ $digest[($i + 1) + 4]; $result[$i] = $result[$i] ^ $digest[($i + 1) + 8]; } return(bin2hex(implode('', $result))); } /** * Get session id, if sets * * @param integer $method Session via method * @access private */ function getID($method) { $id = null; switch($method) { case SESSION_VIA_COOKIE: if (isset($_COOKIE[$this->name]) && ereg("[0-9a-z]{32}", $_COOKIE[$this->name])) $id = $_COOKIE[$this->name]; break; case SESSION_VIA_PATH: if (ereg("/([0-9a-z]{32})", $_SERVER['REQUEST_URI'], $regs)) $id = $regs[1]; break; default: if (isset($_GET[$this->name]) && ereg("[0-9a-z]{32}", $_GET[$this->name])) $id = $_GET[$this->name]; break; } return $id; } /** * Put the session ID via method * * @access private * @return boolean */ function putID() { switch ($this->method) { case SESSION_VIA_COOKIE: return $this->start_via_cookie(); break; case SESSION_VIA_PATH: return $this->start_via_path(); break; default: return $this->start_via_get(); break; } } /** * Turn in array to query string * * @param array $array * @return string * @access private */ function array2querystring($array) { if (!is_array($array)) return; reset($array); $queries = array(); while(list($key, $val) = each($array)) { $queries[] = sprintf("%s=%s", $key, urlencode($val)); } return(implode('&', $queries)); } /** * Browser redirect * * @param string $url * * @access private */ function redirect($url) { $serversoftware = ''; // against a IIS 5.0 bug if (isset($_SERVER) && !empty($_SERVER['SERVER_SOFTWARE'])) { $serversoftware = $_SERVER['SERVER_SOFTWARE']; } elseif (isset($HTTP_SERVER_VARS) && !empty($HTTP_SERVER_VARS['SERVER_SOFTWARE'])) { $serversoftware = $HTTP_SERVER_VARS['SERVER_SOFTWARE']; } if (!empty($serversoftware) && $serversoftware == 'Microsoft-IIS/5.0') { header('Refresh=0; url=' . $url); } else { header('Location: ' . $url); } } /** * Send a cookie variable and try to get information about cookie * * @param string $name * @param string $value * @param integer $expire * @param string $path * @param string $domain * @param integer $secure * * @access private * @return boolean True if cookie accepted, otherwise false * @see setcookie() * @see redirect() * @see array2querystring() */ function sendcookie($name, $value = null, $expire = 0, $path = null, $domain = null , $secure = 0) { $status = false; if (!isset($_GET['cookiesent'])) { setcookie($name, $value, $expire, $path, $domain, $secure); $getvars = array(); if (!empty($_GET)) { $getvars = $_GET; } $getvars['cookiesent'] = 1; $newquery = $this->array2querystring($getvars); $destination = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] .'?' . $newquery; $this->redirect($destination); } if (isset($_GET['cookiesent'])) { if (isset($_COOKIE[$name])) { $status = true; } } return $status; } /** * Encode session data using php serialized * * @access public */ function encode() { reset($this->vars); $data = ""; $i = 0; while(list($key, $val) = each($this->vars)) { if (is_object($this->vars[$key])) { $s = $this->serializeObject($val, $this->ob_delimiter); } else { $s = serialize($val); } $data .= sprintf("%s%s%s", $key, $this->delimiter_value, $s); if ($i < count($this->vars) - 1) $data .= $this->delimiter; $i++; } echo $data; return($data); } /** * Decode session data from string * * @param string $data * @access public */ function decode($data) { $vars = explode($this->delimiter, trim($data)); $data = array(); while(list($key, $val) = each($vars)) { list($name, $value) = explode($this->delimiter_value, $val); if (substr($value, 0, 1) == 'O') { $data[$name] = $this->unserializeObject($value, $this->ob_delimiter, $this->incmethod); } else { $data[$name] = unserialize($value); } } return($data); } /** * Get class defined files of the object * Searching all included files where the object class * was defined * * @param object $ob * * @return string or FALSE on failed * @access private */ function getClassFiles($ob) { if (!is_object($ob)) return false; $included = get_included_files(); reset($included); while (list($i, $file) = each($included)) { if(($fp = @fopen($file, 'r')) === false) return false; while (!feof($fp)) { $line = fgets($fp); $regex = '/(classs+' . get_class($ob) . ')(s+extendss+[w]*s*{|s*{)/i'; if(preg_match($regex, $line, $matches)) { fclose($fp); return $file; } } fclose($fp); } } /** * Custom serialize function for the object * The string output is serialized object concatenated * with files where the class of object defined * * @param object $ob * @param string $delimiter * * @return string or FALSE on failed * @access private */ function serializeObject($ob, $delimiter) { if (!is_object($ob)) return false; if (($src = $this->getClassFiles($ob)) === false) return false; $s = serialize($ob); $s .= $delimiter . $src; return($s); } /** * Unserialize custom serialized object * This function will automatic include a file * that concatenated in string after delimiter * * @param string $s custom serialized object * @param string $delimiter * @param string $incmethod include method, if "require" * file will include using require_once() * * @return object or FALSE on failed * @access private * @see serializeObject() */ function unserializeObject($s, $delimiter, $incmethod = 'include') { if (substr($s, 0, 1) != 'O' || substr($s, -1, 1) == '}') return false; $buffer = strrev($s); $pos = strlen($s) - strpos($buffer, $delimiter); $src = substr($s, $pos); $ob = substr($s, 0, $pos - strlen($delimiter)); if ($src != __FILE__) { if ($incmethod == 'require') { require_once($src); } else { include_once($src); } } return(unserialize($ob)); } /** * Put all register variable into GLOBALS variables * * @access private */ function put2globals() { while(list($name, $value) = each($this->vars)) { $GLOBALS[$name] = $value; } } /** * Send the HTTP header * * @access private */ function sendHeader() { $now = gmdate('D, d M Y H:i:s') . ' GMT'; $lastmod = gmdate("D, d M Y H:i:s", getlastmod()) . " GMT"; header('Expires: ' . $now); header('Last-Modified: ' . $lastmod); switch ($this->cache_limiter) { case 'private': header('Cache-Control: private'); header('Cache-Control: max-age=' . $this->cache_expire * 60); header('Cache-Control: pre-check=' . $this->cache_expire * 60); break; case 'public': header("Cache-Control: public"); header("Cache-Control: max-age=" . $this->cache_expire * 60); break; default: header("Cache-Control: no-cache, no-store"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0, max-age=0"); header("Pragma: no-cache"); break; } } /** * Save session data for future use * * @access public */ function save() { $this->put2globals(); $sess_data = $this->encode(); include_once 'Crypt/HCEMD5.php'; $hcemd5 = new Crypt_HCEMD5($this->key4id()); $sess_data = $hcemd5->encodeMimeSelfRand($sess_data); $this->write($sess_data); } /** * Load session data for use * * @access public */ function load() { $sess_data = $this->read(); include_once 'Crypt/HCEMD5.php'; $hcemd5 = new Crypt_HCEMD5($this->key4id()); $sess_data = $hcemd5->DecodeMimeSelfRand($sess_data); $this->vars = $this->decode($sess_data); $this->put2globals(); } /** * Detroy all session data * * @access public */ function destroy() { if (!mysql_query("DELETE FROM " . $this->mysql_tbname . " WHERE session_id='" . $this-> id . "'", $this->mysql_link)) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } /** * Garbage collection, destroy all session older than gc_maxlifetime * * @access private */ function gc() { $timeago = $this->timestamp2mysql(time() - ($this->gc_maxlifetime * 60)); if (($result = @mysql_query("DELETE FROM " . $this->mysql_tbname . " WHERE session_time <= " . $timeago, $this->mysql_link)) === false) { $this->halt(E_USER_ERROR, mysql_errno($this->mysql_link), mysql_error($this->mysql_link)); } } /** * Start the session * * @access public */ function start() { ob_start(); $this->open(); if (!$this->putID()) { if ($this->fallback_on_failed) { $this->method = $this->fallback_method; $this->putID(); } else { die; } } else { $this->sendHeader(); $this->gc(); } ob_end_flush(); } /** * Print an error and exit * * @param integer $errno * @param string $errmsg * * @access private */ function halt($errtype = E_USER_NOTICE, $errno = -1, $errmsg = 'Unknown error') { $formatted = sprintf("[%d]: %s", $errno, $errmsg); switch ($errtype) { case E_USER_ERROR: die('Fatal Error ' . $formatted); break; case E_USER_WARNING: print('Warning ' . $formatted); break; case E_USER_NOTICE: print('Notice ' . $formatted); break; } } } ?>