<?php
-# Copyright (C) 2006 Jason Woofenden
+# This program is in the public domain within the United States. Additionally,
+# we waive copyright and related rights in the work worldwide through the CC0
+# 1.0 Universal public domain dedication, which can be found at
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+# 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:
#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-# 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 < code/wfpl/examples/session.sql
-# note: you may need these parameters for mysql: -u USERNAME -p
-
-# GLOSSARY
+# 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()
#
-# 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"
+# 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() {
$character_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- $id = " ";
+ $id = " ";
# 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.
- for($i = 0; $i < 16; ++$i) {
- $id{$i} = $character_set{mt_rand(0, 61)};
- }
+ for($i = 0; $i < 16; ++$i) {
+ $id{$i} = $character_set{mt_rand(0, 61)};
+ }
- return $id;
+ return $id;
}
-# 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) {
+# start a new session, tracked by a browser "session cookie".
+#
+# args:
+# $idle_timeout (seconds) session ends after this much inactivity (or up to 10% less)
+# $max_length (seconds) session ends after this long, regardless of activity
+function session_new($idle_timeout = 129600 /* 36 hours */, $max_length = 604800 /* 1 week */) {
+ 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;
- $_REQUEST['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_length,
+ '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' => $idle_timeout,
+ 'expires' => $now + $idle_timeout,
+ 'expires_max' => $now + $max_length,
+ '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']);
+}
- db_update('wfpl_sessions', 'expires', $expires, 'where id=%i', $GLOBALS['session_id']);
+# this is a helper function. See session_new()
+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();
+ $last_activity = $GLOBALS['wfpl_session']['expires'] - $GLOBALS['wfpl_session']['idle_timeout'];
+ # don't db_update if only a tiny fraction of the idle timeout has passed
+ $db_threshold = ceil(0.1 * $GLOBALS['wfpl_session']['idle_timeout']);
+ if ($now > $last_activity + $db_threshold) {
+ $expires = min(
+ $GLOBALS['wfpl_session']['expires_max'],
+ $now + $GLOBALS['wfpl_session']['idle_timeout']
+ );
+ db_update('wfpl_sessions', 'expires', $expires, 'where id=%i', $GLOBALS['wfpl_session']['id']);
+ $GLOBALS['wfpl_session']['expires'] = $expires;
+ }
+ }
}
# delete the current 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($_REQUEST['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]', '', $_REQUEST['session_key']);
+ $session_key = preg_replace('|[^a-z0-9]|i', '', $_COOKIE['session_key']);
if(!strlen($session_key) == 16) {
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;
+ }
+ $now = time();
+ if ($now >= (int) $row['expires']) {
+ session_purge_old();
return false;
}
- $GLOBALS['session_id'] = $id;
- return true;
-}
+ $GLOBALS['wfpl_session']['exists'] = true;
+ $GLOBALS['wfpl_session']['id'] = $row['id'];
+ $GLOBALS['wfpl_session']['idle_timeout'] = (int) $row['idle_timeout'];
+ $GLOBALS['wfpl_session']['expires'] = (int) $row['expires'];
+ $GLOBALS['wfpl_session']['expires_max'] = (int) $row['expires_max'];
+ $GLOBALS['wfpl_session']['key'] = $session_key;
+
+ if (strlen($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();
+ # mark session as not idle
+ 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()) {
}
+# depricated
+function session_exists_and_authed() {
+ return logged_in();
+}
+
-# return username if a session exists and is authenticated
+# depricated
+# return true if a session exists and is authenticated
function logged_in_as_admin() {
if(!session_exists()) {
return false;
}
-# find existing session, or make one
+# find existing session, or make one (name "session_init" was taken)
function init_session() {
if(!session_exists()) {
session_new();
}
}
-# save a variable into the session
+# internal use only (write session cache to db)
+function _sync_session() {
+ if (count($GLOBALS['wfpl_session']['value']) > 0) {
+ $value = json_encode($GLOBALS['wfpl_session']['value']);
+ } else {
+ $value = '';
+ }
+ db_update('wfpl_sessions', 'value', $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;
+ }
}
-
-?>