From 8887166f7f16b778d9c1570e858f42afd282c427 Mon Sep 17 00:00:00 2001 From: Jason Woofenden Date: Mon, 30 Mar 2015 22:04:51 -0400 Subject: [PATCH] API CHANGE: new DB structure for sessions Also, session_set() doesn't convert things to strings Added session_sets() session_clear() with no args clears all --- examples/session.sql | 14 ++- session.php | 238 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 177 insertions(+), 75 deletions(-) diff --git a/examples/session.sql b/examples/session.sql index aa44c5a..3771c9a 100644 --- a/examples/session.sql +++ b/examples/session.sql @@ -2,12 +2,8 @@ drop table if exists wfpl_sessions; create table wfpl_sessions ( id int unique auto_increment, session_key varchar(16), - length int, - expires int); - -drop table if exists wfpl_session_data; -create table wfpl_session_data ( - id int unique auto_increment, - session_id int, - name varchar(100), - value text); + idle_timeout int, + expires int, + expires_max int, + value text +) CHARSET=utf8; diff --git a/session.php b/session.php index bcf2357..87cbfd5 100644 --- a/session.php +++ b/session.php @@ -16,22 +16,32 @@ # along with this program. If not, see . -# you'll need these database tables: -# create table wfpl_sessions (id int unique auto_increment, session_key varchar(16), length int, expires int); -# create table wfpl_session_data (id int unique auto_increment, session_id int, name varchar(100), value text); -# run this command to install/clear the tables: -# mysql DATABASE_NAME < inc/wfpl/examples/session.sql -# note: you may need these parameters for mysql: -u USERNAME -p - -# GLOSSARY +# The functions in this file assume that you have this database table: +# drop table if exists wfpl_sessions; +# create table wfpl_sessions ( +# id int unique auto_increment, +# session_key varchar(16), +# idle_timeout int, +# expires int, +# expires_max int, +# value text +# ) CHARSET=utf8; + +# You'll want to use these: # -# session_key 16 digit string identifying the session -# session_id integer id of the record in the "wfpl_sessions" table of the database -# UNTIL_CLOSE a constant passed as session length to indicate "until browser window closes" - +# session_exists() +# session_new('timeout', 'max_len') +# session_set('key', 'value') +# session_sets(['key': 'value', 'key2': 'val2']) +# session_get('key') +# session_clear() # removes all set() values +# session_clear('key') +# session_kill() +# +# All session data is cached in globals, so: +# 1. don't set large amonuts of data +# 2. session_get() is very fast (no db access) -# session_id is kept in $GLOBALS -# session_key is sent as a cookie, and thus appears in $_REQUEST. The clean version is in $GLOBALS # generate a new random 16-character string function session_generate_key() { @@ -50,28 +60,65 @@ function session_generate_key() { # track this user with a session cookie (ie a cookie that goes away when the # user closes the browser). The timestamp is how long to track the session in # the database. Defaults to one day. -function session_new($length = 86400) { +function session_new($idle_timeout = 86400, $max_timeout = 'same_as_idle') { + if ($max_timeout === 'same_as_idle') { + $max_timeout = $idle_timeout; + } + kill_session(); + $session_key = session_generate_key(); - db_insert('wfpl_sessions', 'session_key,length', $session_key, $length); - $GLOBALS['session_id'] = db_auto_id(); - $GLOBALS['session_key'] = $session_key; - $_COOKIE['session_key'] = $session_key; #just in case someone calls session_exists() after session_new() - session_touch($length); - return $GLOBALS['session_key']; + $now = time(); + $row = array( + 'session_key' => $session_key, + 'idle_timeout' => $idle_timeout, + 'expires' => $now + $idle_timeout, + 'expires_max' => $now + $max_timeout, + 'value' => '{}' + ); + + db_insert_assoc('wfpl_sessions', $row); + $session_id = db_auto_id(); + $GLOBALS['wfpl_session'] = array( + 'exists' => true, + 'id' => $session_id, + 'key' => $session_key, + 'idle_timeout' => $row['idle_timeout'], + 'expires' => $row['expires'], + 'expires_max' => $row['expires_max'], + 'value' => array() + ); + session_set_cookie(); + return $session_key; } -# call to renew the timeout for the session. -# assumes there's a session. call init_session() if you'd like one auto-create one if not found. -function session_touch($length = false) { - if(!$length) { - $length = db_get_value('wfpl_sessions', 'length', 'where id=%i', $GLOBALS['session_id']); +function session_set_cookie() { + if (session_exists()) { + if (!isset($GLOBALS['wfpl_session']['cookie_set'])) { + $GLOBALS['wfpl_session']['cookie_set'] = true; + header('Set-Cookie: session_key=' . $GLOBALS['wfpl_session']['key'] . '; Path=/'); + } } - $expires = time() + $length; - - header('Set-Cookie: session_key=' . $GLOBALS['session_key'] . '; Path=/'); +} - db_update('wfpl_sessions', 'expires', $expires, 'where id=%i', $GLOBALS['session_id']); +# update the idle_timeout +function session_touch() { + if(!session_exists()) { + return; + } + # is the session extendable? + if ($GLOBALS['wfpl_session']['expires'] < $GLOBALS['wfpl_session']['expires_max']) { + # would this extend the session by at least 10%? + $now = time(); + $session_start = $GLOBALS['wfpl_session']['expires'] - $GLOBALS['wfpl_session']['idle_timeout']; + if ($now > $session_start + ceil(0.1 * $GLOBALS['wfpl_session']['idle_timeout'])) { + $expires = max( + $GLOBALS['wfpl_session']['expires_max'], + $now + $GLOBALS['wfpl_session']['idle_timeout'] + ); + db_update('wfpl_sessions', 'expires', $expires, 'where id=%i', $GLOBALS['wfpl_session']['id']); + } + } } # delete the current session @@ -79,32 +126,25 @@ function kill_session() { if(!session_exists()) { return; } - _kill_session($GLOBALS['session_id']); -} - -# for internal use. use kill_session() above -function _kill_session($id) { - db_delete('wfpl_session_data', 'where session_id=%i', $id); - db_delete('wfpl_sessions', 'where id=%i', $id); + db_delete('wfpl_sessions', 'where id=%i', $GLOBALS['wfpl_session']['id']); + $GLOBALS['wfpl_session'] = array('exists' => false); } # delete expired sessions from database function session_purge_old() { - $now = time(); - $expired_sessions = db_get_column('wfpl_sessions', 'id', 'where expires < %i', $now); - if($expired_sessions) foreach($expired_sessions as $expired_session) { - _kill_session($expired_session); - } + db_delete('wfpl_sessions', 'where expires < %i', time()); } # return true if a session exists function session_exists() { - if(!isset($_COOKIE['session_key'])) { - return false; + if (isset($GLOBALS['wfpl_session'])) { + return $GLOBALS['wfpl_session']['exists']; } - if(isset($GLOBALS['session_id'])) { - return true; + $GLOBALS['wfpl_session'] = array('exists' => false); + + if(!isset($_COOKIE['session_key'])) { + return false; } $session_key = ereg_replace('[^a-zA-Z0-9]', '', $_COOKIE['session_key']); @@ -113,39 +153,48 @@ function session_exists() { return false; } - $GLOBALS['session_key'] = $session_key; - session_purge_old(); - $id = db_get_value('wfpl_sessions', 'id', 'where session_key=%"', $session_key); - if($id === false) { + $row = db_get_assoc('wfpl_sessions', 'id,idle_timeout,expires,expires_max,value', 'where session_key=%"', $session_key); + if($row === false) { return false; } - $GLOBALS['session_id'] = $id; - return true; -} + $GLOBALS['wfpl_session']['exists'] = true; + $GLOBALS['wfpl_session']['id'] = $row['id']; + $GLOBALS['wfpl_session']['idle_timeout'] = $row['idle_timeout']; + $GLOBALS['wfpl_session']['expires'] = $row['expires']; + $GLOBALS['wfpl_session']['expires_max'] = $row['expires_max']; + $GLOBALS['wfpl_session']['key'] = $session_key; + + if ($row['value'] && is_array($parsed = json_decode($row['value'], true))) { + $GLOBALS['wfpl_session']['value'] = $parsed; + } else { + $GLOBALS['wfpl_session']['value'] = array(); + } -# depricated -function session_exists_and_authed() { - return logged_in(); + # this session is not idle (extend if it's extendable) + session_touch(); + + return true; } # generate a random password using only letters and numbers that look # particularly unique function new_readable_password($length = 8) { - $character_set = "ABCDEFHJKLMNPQRTUVWXY34789"; + $character_set = "ABCDEFHJKLMNPQRTUWXY34789"; $code = ""; # PHP 4.2.0 and up seed the random number generator for you. # Lets hope that it seeds with something harder to guess than the clock. while($length--) { - $code .= $character_set{mt_rand(0, 25)}; # inclusive + $code .= $character_set{mt_rand(0, 24)}; # inclusive } return $code; } +# depricated # return username if a session exists and is authenticated function logged_in() { if(!session_exists()) { @@ -156,7 +205,13 @@ function logged_in() { } +# depricated +function session_exists_and_authed() { + return logged_in(); +} + +# depricated # return true if a session exists and is authenticated function logged_in_as_admin() { if(!session_exists()) { @@ -177,20 +232,71 @@ function init_session() { } } -# save a variable into the session +# internal use only (write session cache to db) +function _sync_session() { + db_update('wfpl_sessions', + 'value', json_encode($GLOBALS['wfpl_session']['value']), + 'where id=%i', $GLOBALS['wfpl_session']['id'] + ); +} + +# save data into the session +# $value can be anything json_encode()able function session_set($name, $value) { - session_clear($name); - db_insert('wfpl_session_data', 'session_id,name,value', $GLOBALS['session_id'], $name, $value); + init_session(); + if (isset($GLOBALS['wfpl_session']['value'][$name])) { + if ($GLOBALS['wfpl_session']['value'][$name] === $value) { + return; + } + } + $GLOBALS['wfpl_session']['value'][$name] = $value; + _sync_session(); +} + +# save data into the session +# values can be anything json_encode()able +function session_sets($assoc) { + init_session(); + $dirty = false; + foreach ($assoc as $name => &$value) { + if (isset($GLOBALS['wfpl_session']['value'][$name])) { + if ($GLOBALS['wfpl_session']['value'][$name] === $value) { + continue; + } + } + $GLOBALS['wfpl_session']['value'][$name] = $value; + $dirty = true; + } + if ($dirty) { + _sync_session(); + } } # remove variable from the session -function session_clear($name) { - db_delete('wfpl_session_data', 'where session_id=%i && name=%"', $GLOBALS['session_id'], $name); +# with no args: clear all +function session_clear($name = -1) { + if(!session_exists()) { + return; + } + if ($name === -1) { + if (count($GLOBALS['wfpl_session']['value']) > 0) { + $GLOBALS['wfpl_session']['value'] = array(); + _sync_session(); + } + } elseif (isset($GLOBALS['wfpl_session']['value'][$name])) { + unset($GLOBALS['wfpl_session']['value'][$name]); + _sync_session(); + } } # get a variable into the session function session_get($name) { - return db_get_value('wfpl_session_data', 'value', 'where session_id=%i && name=%"', $GLOBALS['session_id'], $name); + if(!session_exists()) { + return false; + } + if (isset($GLOBALS['wfpl_session']['value'][$name])) { + return $GLOBALS['wfpl_session']['value'][$name]; + } else { + return false; + } } - -?> -- 1.7.10.4