Index: trunk/lib/Cache.inc.php
===================================================================
--- trunk/lib/Cache.inc.php	(revision 137)
+++ trunk/lib/Cache.inc.php	(revision 137)
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Cache.inc.php
+ * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
+ *
+ * Provides an API for storing a limited amount of data
+ * intended to have a short lifetime in a user's session.
+ *
+ * @author  Quinn Comendant <quinn@strangecode.com>
+ * @version 2.1
+ * @since   2001
+ */
+ 
+// Flags.
+define('CACHE_IGNORE_SIZE', 1);
+
+class Cache {
+
+    var $_params = array(
+        'enabled' => true,
+        'soft_limit' => 204800,
+        'hard_limit' => 4194304,
+        'min_items' => 3,
+    );
+
+    /**
+     * This method enforces the singleton pattern for this class.
+     *
+     * @return  object  Reference to the global Cache object.
+     * @access  public
+     * @static
+     */
+    function &getInstance()
+    {
+        static $instance = null;
+
+        if ($instance === null) {
+            $instance = new Cache();
+        }
+
+        return $instance;
+    }
+
+    /**
+     * Set (or overwrite existing) parameters by passing an array of new parameters.
+     *
+     * @access public
+     * @param  array    $params     Array of parameters (key => val pairs).
+     */
+    function setParam($params)
+    {
+		$app =& App::getInstance();
+
+        if (isset($params) && is_array($params)) {
+            // Merge new parameters with old overriding only those passed.
+            $this->_params = array_merge($this->_params, $params);
+        } else {
+            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
+        }
+    }
+
+    /**
+     * Return the value of a parameter, if it exists.
+     *
+     * @access public
+     * @param string $param        Which parameter to return.
+     * @return mixed               Configured parameter value.
+     */
+    function getParam($param)
+    {
+		$app =& App::getInstance();
+    
+        if (isset($this->_params[$param])) {
+            return $this->_params[$param];
+        } else {
+            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
+            return null;
+        }
+    }
+
+    /**
+     * Stores a new variable in the session cache. The $key is is md5'ed
+     * because if a variable id is a very large integer, the array_shift function
+     * will reset the key to the next largest int key. Weird behavior I can't
+     * understand. $session_cache[32341234123] will become $session_cache[0]
+     * for example. Usage warning: if the variable is too big to fit, or is
+     * old and discarded, you must provide alternative ways of accessing the data.
+     *
+     * @param str   $key        An identifier for the cached object.
+     * @param mixed $var          The var to store in the session cache.
+     * @param bool  $flags      If we have something really big that we
+     *                            still want to cache, setting this to
+     *                            CACHE_IGNORE_SIZE allows this.
+     *
+     * @return bool               True on success, false otherwise.
+     */
+    function set($key, $var, $flags=0)
+    {
+		$app =& App::getInstance();
+
+        if (!$this->getParam('enabled')) {
+            $app->logMsg(sprintf('Cache not enabled, not saving data.', null), LOG_DEBUG, __FILE__, __LINE__);
+            return false;
+        }
+
+        $key = md5($key);
+        $serialized_var = serialize($var);
+        $serialized_var_len = strlen($serialized_var);
+
+        if ($flags & CACHE_IGNORE_SIZE > 0 && $serialized_var_len >= $this->getParam('soft_limit')) {
+            $app->logMsg(sprintf('Serialized variable (%s bytes) more than soft_limit (%s bytes).', $serialized_var_len, $this->getParam('soft_limit')), LOG_NOTICE, __FILE__, __LINE__);
+            return false;
+        }
+
+        if ($serialized_var_len >= $this->getParam('hard_limit')) {
+            $app->logMsg(sprintf('Serialized variable (%s bytes) more than hard_limit (%s bytes).', $serialized_var_len, $this->getParam('hard_limit')), LOG_NOTICE, __FILE__, __LINE__);
+            return false;
+        }
+
+        if (!isset($_SESSION['_session_cache'])) {
+            $_SESSION['_session_cache'] = array();
+        } else {
+            unset($_SESSION['_session_cache'][$key]);
+            // Continue to prune the cache if it's length is too long for the new variable to fit, but keep at least MIN_ITEMS at least.
+            while (strlen(serialize($_SESSION['_session_cache'])) + $serialized_var_len >= $this->getParam('soft_limit')
+            && sizeof($_SESSION['_session_cache']) >= $this->getParam('min_items')) {
+                array_shift($_SESSION['_session_cache']);
+            }
+        }
+        $_SESSION['_session_cache'][$key] =& $serialized_var;
+
+        if ($serialized_var_len >= 1024000) {
+            $app->logMsg(sprintf('Successfully cached oversized variable (%s bytes).', $serialized_var_len), LOG_DEBUG, __FILE__, __LINE__);
+        }
+
+        return true;
+    }
+
+    /**
+     * Retrives an object from the session cache and returns it unserialized.
+     * It also moves it to the top of the stack, which makes it such that the
+     * cache flushing mechanism of putCache deletes the oldest referenced items
+     * first.
+     *
+     * @param string $key  The key for the datum to retrieve.
+     *
+     * @return mixed          The requested datum, or false on failure.
+     */
+    function get($key)
+    {
+        if (!$this->getParam('enabled')) {
+            return false;
+        }
+
+        $key = md5($key);
+        if (isset($_SESSION['_session_cache'][$key])) {
+            // Move the accessed cached datum to the top of the stack. Maybe somebody knows a better way to do this?
+            $tmp =& $_SESSION['_session_cache'][$key];
+            unset($_SESSION['_session_cache'][$key]);
+            $_SESSION['_session_cache'][$key] =& $tmp;
+            // Return the unserialized datum.
+            return unserialize($_SESSION['_session_cache'][$key]);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Tells you if the object is cached.
+     *
+     * @param string $key  The key of the object to check.
+     *
+     * @return bool           The return from isset().
+     */
+    function exists($key)
+    {
+        if (!$this->getParam('enabled')) {
+            return false;
+        }
+
+        $key = md5($key);
+        return isset($_SESSION['_session_cache'][$key]);
+    }
+
+    /**
+     * Tells you if the object is cached.
+     *
+     * @param string $key  The key of the object to check.
+     *
+     * @return bool           The return from isset().
+     */
+    function delete($key)
+    {
+        $key = md5($key);
+        if (isset($_SESSION['_session_cache'][$key])) {
+            unset($_SESSION['_session_cache'][$key]);
+        }
+    }
+
+// END Cache
+}
+
+?>
Index: trunk/lib/Lock.inc.php
===================================================================
--- trunk/lib/Lock.inc.php	(revision 137)
+++ trunk/lib/Lock.inc.php	(revision 137)
@@ -0,0 +1,425 @@
+<?php
+/**
+ * RecordLock.inc.php
+ * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
+ *
+ * The RecordLock class provides a system for locking abstract DB rows.
+ *
+ * @author  Quinn Comendant <quinn@strangecode.com>
+ * @version 2.1
+ */
+class RecordLock {
+
+    // Configuration of this object.
+    var $_params = array(
+        'timeout' => 600,
+        'auto_timeout' => 1800,
+        'error_url' => '/lock.php',
+        'db_table' => 'lock_tbl',
+        'create_table' => true, // Automatically create table and verify columns. Better set to false after site launch.
+    );
+
+    // Store lock data from DB.
+    var $data = array();
+
+    // Auth_SQL object from which to access a current user_id.
+    var $_auth;
+
+    /**
+     * This method enforces the singleton pattern for this class.
+     *
+     * @return  object  Reference to the global RecordLock object.
+     * @access  public
+     * @static
+     */
+    function &getInstance($auth_object)
+    {
+        static $instance = null;
+
+        if ($instance === null) {
+            $instance = new RecordLock($auth_object);
+        }
+
+        return $instance;
+    }
+
+    /**
+     * Constructor. Pass an Auth object on which to perform user lookups.
+     *
+     * @param mixed  $auth_object  An Auth_SQL or Auth_FILE object.
+     */
+    function RecordLock($auth_object)
+    {
+		$app =& App::getInstance();
+
+        if (!method_exists($auth_object, 'getVal') || !method_exists($auth_object, 'getUsername')) {
+            trigger_error('Constructor not provided a valid Auth_* object.', E_USER_ERROR);
+        }
+
+        $this->_auth = $auth_object;
+
+        // Get create tables config from global context.
+        if (!is_null($app->getParam('db_create_tables'))) {
+            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
+        }
+    }
+
+    /**
+     * Setup the database table for this class.
+     *
+     * @access  public
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   26 Aug 2005 17:09:36
+     */
+    function initDB($recreate_db=false)
+    {
+		$app =& App::getInstance();
+		$db =& DB::getInstance();
+
+        static $_db_tested = false;
+
+        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
+            if ($recreate_db) {
+                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
+                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_DEBUG, __FILE__, __LINE__);
+            }
+            $db->query("CREATE TABLE IF NOT EXISTS " . $this->getParam('db_table') . " (
+                lock_id int NOT NULL auto_increment,
+                record_table varchar(255) NOT NULL default '',
+                record_key varchar(255) NOT NULL default '',
+                record_val varchar(255) NOT NULL default '',
+                title varchar(255) NOT NULL default '',
+                set_by_admin_id smallint(11) NOT NULL default '0',
+                lock_datetime datetime NOT NULL default '0000-00-00 00:00:00',
+                PRIMARY KEY (lock_id),
+                KEY record_table (record_table),
+                KEY record_key (record_key),
+                KEY record_val (record_val)
+            )");
+
+            if (!$db->columnExists($this->getParam('db_table'), array(
+                'lock_id',
+                'record_table',
+                'record_key',
+                'record_val',
+                'title',
+                'set_by_admin_id',
+                'lock_datetime',
+            ), false, false)) {
+                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
+                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
+            }
+        }
+        $_db_tested = true;
+    }
+
+    /**
+     * Set the params of this object.
+     *
+     * @param  array $params   Array of param keys and values to set.
+     */
+    function setParam($params=null)
+    {
+        if (isset($params) && is_array($params)) {
+            // Merge new parameters with old overriding only those passed.
+            $this->_params = array_merge($this->_params, $params);
+        }
+    }
+
+    /**
+     * Return the value of a parameter, if it exists.
+     *
+     * @access public
+     * @param string $param        Which parameter to return.
+     * @return mixed               Configured parameter value.
+     */
+    function getParam($param)
+    {
+		$app =& App::getInstance();
+    
+        if (isset($this->_params[$param])) {
+            return $this->_params[$param];
+        } else {
+            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
+            return null;
+        }
+    }
+
+    /**
+     * Select the lock to manipulate.
+     *
+     * @param mixed  $record_table_or_lock_id  The table containing the record to lock,
+     *                                         or a numeric lock_id.
+     * @param string $record_key  The key column for the record to lock.
+     * @param string $record_val  The value of the key column for the record to lock.
+     * @param string $title       A title to apply to the lock, for display purposes.
+     */
+    function select($record_table_or_lock_id, $record_key=null, $record_val=null)
+    {
+		$app =& App::getInstance();
+		$db =& DB::getInstance();
+
+        $this->initDB();
+
+        // Expire old locks.
+        $this->_auto_timeout();
+
+        if (is_numeric($record_table_or_lock_id) && !isset($record_key) && !isset($record_val)) {
+            // Get lock data by lock_id.
+            $qid = $db->query("
+                SELECT * FROM " . $this->getParam('db_table') . "
+                WHERE lock_id = '" . $db->escapeString($record_table_or_lock_id) . "'
+            ");
+        } else {
+            // Get lock data by record specs
+            $qid = $db->query("
+                SELECT * FROM " . $this->getParam('db_table') . "
+                WHERE record_table = '" . $db->escapeString($record_table_or_lock_id) . "'
+                AND record_key = '" . $db->escapeString($record_key) . "'
+                AND record_val = '" . $db->escapeString($record_val) . "'
+            ");
+        }
+        if ($this->data = mysql_fetch_assoc($qid)) {
+            $app->logMsg(sprintf('Selecting %slocked record: %s %s %s', ($this->data['set_by_admin_id'] == $this->_auth->getVal('user_id') ? 'self-' : ''), $record_table_or_lock_id, $record_key, $record_val), LOG_DEBUG, __FILE__, __LINE__);
+            /// FIX ME: What if admin set lock, but public user is current lock user?
+            $this->data['editor'] = $this->_auth->getUsername($this->data['set_by_admin_id']);
+            return true;
+        } else {
+            $app->logMsg(sprintf('No locked record: %s %s %s', $record_table_or_lock_id, $record_key, $record_val), LOG_DEBUG, __FILE__, __LINE__);
+            return false;
+        }
+    }
+
+    /**
+     * Returns true if the record we instantiated with is locked.
+     *
+     * @return bool            True if locked.
+     */
+    function isLocked()
+    {
+        return isset($this->data['lock_id']);
+    }
+
+    /**
+     * Returns the status of who set the lock. Use this to ignore locks set by
+     * the current user.
+     *
+     * @return bool            True if current user set the lock.
+     */
+    function isMine()
+    {
+		$db =& DB::getInstance();
+	
+        $this->initDB();
+
+        if (isset($this->data['lock_id'])) {
+            $qid = $db->query("SELECT * FROM " . $this->getParam('db_table') . " WHERE lock_id = '" . $db->escapeString($this->data['lock_id']) . "'");
+            if ($lock = mysql_fetch_assoc($qid)) {
+                return ($lock['set_by_admin_id'] == $this->_auth->getVal('user_id'));
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Create a new lock for the specified table/key/value.
+     *
+     * @param string $record_table  The table containing the record to lock.
+     * @param string $record_key  The key column for the record to lock.
+     * @param string $record_val  The value of the key column for the record to lock.
+     * @param string $title       A title to apply to the lock, for display purposes.
+     *
+     * @return int            The id for the lock (mysql last insert id).
+     */
+    function set($record_table, $record_key, $record_val, $title='')
+    {
+		$db =& DB::getInstance();
+	
+        $this->initDB();
+
+        // Expire old locks.
+        $this->_auto_timeout();
+
+        // Remove previous locks if exist. Is this better than using a REPLACE INTO?
+        $db->query("
+            DELETE FROM " . $this->getParam('db_table') . "
+            WHERE record_table = '" . $db->escapeString($record_table) . "'
+            AND record_key = '" . $db->escapeString($record_key) . "'
+            AND record_val = '" . $db->escapeString($record_val) . "'
+        ");
+
+        // Set new lock.
+        $db->query("
+            INSERT INTO " . $this->getParam('db_table') . " (
+                record_table,
+                record_key,
+                record_val,
+                title,
+                set_by_admin_id,
+                lock_datetime
+            ) VALUES (
+                '" . $db->escapeString($record_table) . "',
+                '" . $db->escapeString($record_key) . "',
+                '" . $db->escapeString($record_val) . "',
+                '" . $db->escapeString($title) . "',
+                '" . $db->escapeString($this->_auth->getVal('user_id')) . "',
+                NOW()
+            )
+        ");
+        $lock_id = mysql_insert_id($db->getDBH());
+
+        // Must register this locked record as the current.
+        $this->select($lock_id);
+
+        return $lock_id;
+    }
+
+    /**
+     * Unlock the currently selected record.
+     */
+    function remove()
+    {
+		$app =& App::getInstance();
+		$db =& DB::getInstance();
+
+        $this->initDB();
+
+        // Expire old locks.
+        $this->_auto_timeout();
+
+        // Delete a specific lock.
+        $db->query("
+            DELETE FROM " . $this->getParam('db_table') . "
+            WHERE lock_id = '" . $db->escapeString($this->data['lock_id']) . "'
+        ");
+
+        $app->logMsg(sprintf('Removing lock: %s', $this->data['lock_id']), LOG_DEBUG, __FILE__, __LINE__);
+    }
+
+    /**
+     * Unlock all records, or all records for a specified user.
+     */
+    function removeAll($user_id=null)
+    {
+		$app =& App::getInstance();
+		$db =& DB::getInstance();
+
+        $this->initDB();
+
+        // Expire old locks.
+        $this->_auto_timeout();
+
+        if (isset($user_id)) {
+            // Delete specific user's locks.
+            $db->query("DELETE FROM " . $this->getParam('db_table') . " WHERE set_by_admin_id = '" . $db->escapeString($user_id) . "'");
+            $app->logMsg(sprintf('Record locks owned by %s %s have been deleted', $this->_auth->getVal('auth_name'), $this->_auth->getUsername($user_id)), LOG_DEBUG, __FILE__, __LINE__);
+        } else {
+            // Delete ALL locks.
+            $db->query("DELETE FROM " . $this->getParam('db_table') . "");
+            $app->logMsg(sprintf('All record locks deleted by %s %s', $this->_auth->getVal('auth_name'), $this->_auth->getVal('username')), LOG_DEBUG, __FILE__, __LINE__);
+        }
+    }
+
+    /**
+     * Delete's all locks that are older than auto_timeout.
+     */
+    function _auto_timeout()
+    {
+		$db =& DB::getInstance();
+	
+        static $_timeout_run = false;
+
+        $this->initDB();
+
+        if (!$_timeout_run) {
+            // Delete all old locks.
+            $db->query("
+                DELETE FROM " . $this->getParam('db_table') . "
+                WHERE DATE_ADD(lock_datetime, INTERVAL '" . $this->getParam('auto_timeout') . "' SECOND) < NOW()
+            ");
+            $_timeout_run = true;
+        }
+    }
+
+    /**
+     * Redirect to record lock error page.
+     */
+    function dieErrorPage()
+    {
+		$app =& App::getInstance();
+
+        $app->dieURL(sprintf('%s?lock_id=%s&boomerang=%s', $this->getParam('error_url'), $this->data['lock_id'], urlencode(absoluteMe())));
+    }
+
+    /**
+     * Print error page.
+     */
+    function printErrorHTML()
+    {
+		$app =& App::getInstance();
+
+        ?>
+        <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
+        <?php $app->printHiddenSession() ?>
+        <input type="hidden" name="lock_id" value="<?php echo $this->getID(); ?>" />
+
+        <p><?php
+        printf(_("The record <strong>%s</strong> is currently being edited by <strong>%s</strong> (%d minutes elapsed). You cannot modify the record while it is locked by another user."),
+            $this->getTitle(),
+            $this->getEditor(),
+            date('i', $this->getSecondsElapsed() + 60)
+        );
+        ?></p>
+
+        <?php if ($this->getSecondsElapsed() > $this->getParam('timeout')) { ?>
+        <p><?php printf(_("You can forcibly unlock the record if you believe the editing session has expired. You might want to confirm with %s before doing this."), $this->getEditor()) ?></p>
+        <input type="submit" name="unlock" value="<?php echo _("Unlock"); ?>" />
+        <?php } ?>
+
+        <input type="submit" name="cancel" value="<?php echo _("Cancel"); ?>" />
+        </form>
+        <?php
+    }
+
+    /**
+     * Return lock_id of locked record.
+     */
+    function getID()
+    {
+        return $this->data['lock_id'];
+    }
+
+    /**
+     * Return title of locked record.
+     */
+    function getTitle()
+    {
+        return $this->data['title'];
+    }
+
+    /**
+     * Return administrator username for locked record.
+     */
+    function getEditor()
+    {
+        return $this->data['editor'];
+    }
+
+    /**
+     * Return total seconds since the record was locked.
+     */
+    function getSecondsElapsed()
+    {
+        if (isset($this->data['lock_datetime']) && $this->data['lock_datetime'] < time()) {
+            return time() - strtotime($this->data['lock_datetime']);
+        } else {
+            return 0;
+        }
+    }
+
+
+} // End of class.
+?>
Index: trunk/lib/RecordLock.inc.php
===================================================================
--- trunk/lib/RecordLock.inc.php	(revision 136)
+++ 	(revision )
@@ -1,425 +1,0 @@
-<?php
-/**
- * RecordLock.inc.php
- * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
- *
- * The RecordLock class provides a system for locking abstract DB rows.
- *
- * @author  Quinn Comendant <quinn@strangecode.com>
- * @version 2.1
- */
-class RecordLock {
-
-    // Configuration of this object.
-    var $_params = array(
-        'timeout' => 600,
-        'auto_timeout' => 1800,
-        'error_url' => '/lock.php',
-        'db_table' => 'lock_tbl',
-        'create_table' => true, // Automatically create table and verify columns. Better set to false after site launch.
-    );
-
-    // Store lock data from DB.
-    var $data = array();
-
-    // Auth_SQL object from which to access a current user_id.
-    var $_auth;
-
-    /**
-     * This method enforces the singleton pattern for this class.
-     *
-     * @return  object  Reference to the global RecordLock object.
-     * @access  public
-     * @static
-     */
-    function &getInstance($auth_object)
-    {
-        static $instance = null;
-
-        if ($instance === null) {
-            $instance = new RecordLock($auth_object);
-        }
-
-        return $instance;
-    }
-
-    /**
-     * Constructor. Pass an Auth object on which to perform user lookups.
-     *
-     * @param mixed  $auth_object  An Auth_SQL or Auth_FILE object.
-     */
-    function RecordLock($auth_object)
-    {
-		$app =& App::getInstance();
-
-        if (!method_exists($auth_object, 'getVal') || !method_exists($auth_object, 'getUsername')) {
-            trigger_error('Constructor not provided a valid Auth_* object.', E_USER_ERROR);
-        }
-
-        $this->_auth = $auth_object;
-
-        // Get create tables config from global context.
-        if (!is_null($app->getParam('db_create_tables'))) {
-            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
-        }
-    }
-
-    /**
-     * Setup the database table for this class.
-     *
-     * @access  public
-     * @author  Quinn Comendant <quinn@strangecode.com>
-     * @since   26 Aug 2005 17:09:36
-     */
-    function initDB($recreate_db=false)
-    {
-		$app =& App::getInstance();
-		$db =& DB::getInstance();
-
-        static $_db_tested = false;
-
-        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
-            if ($recreate_db) {
-                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
-                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_DEBUG, __FILE__, __LINE__);
-            }
-            $db->query("CREATE TABLE IF NOT EXISTS " . $this->getParam('db_table') . " (
-                lock_id int NOT NULL auto_increment,
-                record_table varchar(255) NOT NULL default '',
-                record_key varchar(255) NOT NULL default '',
-                record_val varchar(255) NOT NULL default '',
-                title varchar(255) NOT NULL default '',
-                set_by_admin_id smallint(11) NOT NULL default '0',
-                lock_datetime datetime NOT NULL default '0000-00-00 00:00:00',
-                PRIMARY KEY (lock_id),
-                KEY record_table (record_table),
-                KEY record_key (record_key),
-                KEY record_val (record_val)
-            )");
-
-            if (!$db->columnExists($this->getParam('db_table'), array(
-                'lock_id',
-                'record_table',
-                'record_key',
-                'record_val',
-                'title',
-                'set_by_admin_id',
-                'lock_datetime',
-            ), false, false)) {
-                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
-                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
-            }
-        }
-        $_db_tested = true;
-    }
-
-    /**
-     * Set the params of this object.
-     *
-     * @param  array $params   Array of param keys and values to set.
-     */
-    function setParam($params=null)
-    {
-        if (isset($params) && is_array($params)) {
-            // Merge new parameters with old overriding only those passed.
-            $this->_params = array_merge($this->_params, $params);
-        }
-    }
-
-    /**
-     * Return the value of a parameter, if it exists.
-     *
-     * @access public
-     * @param string $param        Which parameter to return.
-     * @return mixed               Configured parameter value.
-     */
-    function getParam($param)
-    {
-		$app =& App::getInstance();
-    
-        if (isset($this->_params[$param])) {
-            return $this->_params[$param];
-        } else {
-            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
-            return null;
-        }
-    }
-
-    /**
-     * Select the lock to manipulate.
-     *
-     * @param mixed  $record_table_or_lock_id  The table containing the record to lock,
-     *                                         or a numeric lock_id.
-     * @param string $record_key  The key column for the record to lock.
-     * @param string $record_val  The value of the key column for the record to lock.
-     * @param string $title       A title to apply to the lock, for display purposes.
-     */
-    function select($record_table_or_lock_id, $record_key=null, $record_val=null)
-    {
-		$app =& App::getInstance();
-		$db =& DB::getInstance();
-
-        $this->initDB();
-
-        // Expire old locks.
-        $this->_auto_timeout();
-
-        if (is_numeric($record_table_or_lock_id) && !isset($record_key) && !isset($record_val)) {
-            // Get lock data by lock_id.
-            $qid = $db->query("
-                SELECT * FROM " . $this->getParam('db_table') . "
-                WHERE lock_id = '" . $db->escapeString($record_table_or_lock_id) . "'
-            ");
-        } else {
-            // Get lock data by record specs
-            $qid = $db->query("
-                SELECT * FROM " . $this->getParam('db_table') . "
-                WHERE record_table = '" . $db->escapeString($record_table_or_lock_id) . "'
-                AND record_key = '" . $db->escapeString($record_key) . "'
-                AND record_val = '" . $db->escapeString($record_val) . "'
-            ");
-        }
-        if ($this->data = mysql_fetch_assoc($qid)) {
-            $app->logMsg(sprintf('Selecting %slocked record: %s %s %s', ($this->data['set_by_admin_id'] == $this->_auth->getVal('user_id') ? 'self-' : ''), $record_table_or_lock_id, $record_key, $record_val), LOG_DEBUG, __FILE__, __LINE__);
-            /// FIX ME: What if admin set lock, but public user is current lock user?
-            $this->data['editor'] = $this->_auth->getUsername($this->data['set_by_admin_id']);
-            return true;
-        } else {
-            $app->logMsg(sprintf('No locked record: %s %s %s', $record_table_or_lock_id, $record_key, $record_val), LOG_DEBUG, __FILE__, __LINE__);
-            return false;
-        }
-    }
-
-    /**
-     * Returns true if the record we instantiated with is locked.
-     *
-     * @return bool            True if locked.
-     */
-    function isLocked()
-    {
-        return isset($this->data['lock_id']);
-    }
-
-    /**
-     * Returns the status of who set the lock. Use this to ignore locks set by
-     * the current user.
-     *
-     * @return bool            True if current user set the lock.
-     */
-    function isMine()
-    {
-		$db =& DB::getInstance();
-	
-        $this->initDB();
-
-        if (isset($this->data['lock_id'])) {
-            $qid = $db->query("SELECT * FROM " . $this->getParam('db_table') . " WHERE lock_id = '" . $db->escapeString($this->data['lock_id']) . "'");
-            if ($lock = mysql_fetch_assoc($qid)) {
-                return ($lock['set_by_admin_id'] == $this->_auth->getVal('user_id'));
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Create a new lock for the specified table/key/value.
-     *
-     * @param string $record_table  The table containing the record to lock.
-     * @param string $record_key  The key column for the record to lock.
-     * @param string $record_val  The value of the key column for the record to lock.
-     * @param string $title       A title to apply to the lock, for display purposes.
-     *
-     * @return int            The id for the lock (mysql last insert id).
-     */
-    function set($record_table, $record_key, $record_val, $title='')
-    {
-		$db =& DB::getInstance();
-	
-        $this->initDB();
-
-        // Expire old locks.
-        $this->_auto_timeout();
-
-        // Remove previous locks if exist. Is this better than using a REPLACE INTO?
-        $db->query("
-            DELETE FROM " . $this->getParam('db_table') . "
-            WHERE record_table = '" . $db->escapeString($record_table) . "'
-            AND record_key = '" . $db->escapeString($record_key) . "'
-            AND record_val = '" . $db->escapeString($record_val) . "'
-        ");
-
-        // Set new lock.
-        $db->query("
-            INSERT INTO " . $this->getParam('db_table') . " (
-                record_table,
-                record_key,
-                record_val,
-                title,
-                set_by_admin_id,
-                lock_datetime
-            ) VALUES (
-                '" . $db->escapeString($record_table) . "',
-                '" . $db->escapeString($record_key) . "',
-                '" . $db->escapeString($record_val) . "',
-                '" . $db->escapeString($title) . "',
-                '" . $db->escapeString($this->_auth->getVal('user_id')) . "',
-                NOW()
-            )
-        ");
-        $lock_id = mysql_insert_id($db->getDBH());
-
-        // Must register this locked record as the current.
-        $this->select($lock_id);
-
-        return $lock_id;
-    }
-
-    /**
-     * Unlock the currently selected record.
-     */
-    function remove()
-    {
-		$app =& App::getInstance();
-		$db =& DB::getInstance();
-
-        $this->initDB();
-
-        // Expire old locks.
-        $this->_auto_timeout();
-
-        // Delete a specific lock.
-        $db->query("
-            DELETE FROM " . $this->getParam('db_table') . "
-            WHERE lock_id = '" . $db->escapeString($this->data['lock_id']) . "'
-        ");
-
-        $app->logMsg(sprintf('Removing lock: %s', $this->data['lock_id']), LOG_DEBUG, __FILE__, __LINE__);
-    }
-
-    /**
-     * Unlock all records, or all records for a specified user.
-     */
-    function removeAll($user_id=null)
-    {
-		$app =& App::getInstance();
-		$db =& DB::getInstance();
-
-        $this->initDB();
-
-        // Expire old locks.
-        $this->_auto_timeout();
-
-        if (isset($user_id)) {
-            // Delete specific user's locks.
-            $db->query("DELETE FROM " . $this->getParam('db_table') . " WHERE set_by_admin_id = '" . $db->escapeString($user_id) . "'");
-            $app->logMsg(sprintf('Record locks owned by %s %s have been deleted', $this->_auth->getVal('auth_name'), $this->_auth->getUsername($user_id)), LOG_DEBUG, __FILE__, __LINE__);
-        } else {
-            // Delete ALL locks.
-            $db->query("DELETE FROM " . $this->getParam('db_table') . "");
-            $app->logMsg(sprintf('All record locks deleted by %s %s', $this->_auth->getVal('auth_name'), $this->_auth->getVal('username')), LOG_DEBUG, __FILE__, __LINE__);
-        }
-    }
-
-    /**
-     * Delete's all locks that are older than auto_timeout.
-     */
-    function _auto_timeout()
-    {
-		$db =& DB::getInstance();
-	
-        static $_timeout_run = false;
-
-        $this->initDB();
-
-        if (!$_timeout_run) {
-            // Delete all old locks.
-            $db->query("
-                DELETE FROM " . $this->getParam('db_table') . "
-                WHERE DATE_ADD(lock_datetime, INTERVAL '" . $this->getParam('auto_timeout') . "' SECOND) < NOW()
-            ");
-            $_timeout_run = true;
-        }
-    }
-
-    /**
-     * Redirect to record lock error page.
-     */
-    function dieErrorPage()
-    {
-		$app =& App::getInstance();
-
-        $app->dieURL(sprintf('%s?lock_id=%s&boomerang=%s', $this->getParam('error_url'), $this->data['lock_id'], urlencode(absoluteMe())));
-    }
-
-    /**
-     * Print error page.
-     */
-    function printErrorHTML()
-    {
-		$app =& App::getInstance();
-
-        ?>
-        <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
-        <?php $app->printHiddenSession() ?>
-        <input type="hidden" name="lock_id" value="<?php echo $this->getID(); ?>" />
-
-        <p><?php
-        printf(_("The record <strong>%s</strong> is currently being edited by <strong>%s</strong> (%d minutes elapsed). You cannot modify the record while it is locked by another user."),
-            $this->getTitle(),
-            $this->getEditor(),
-            date('i', $this->getSecondsElapsed() + 60)
-        );
-        ?></p>
-
-        <?php if ($this->getSecondsElapsed() > $this->getParam('timeout')) { ?>
-        <p><?php printf(_("You can forcibly unlock the record if you believe the editing session has expired. You might want to confirm with %s before doing this."), $this->getEditor()) ?></p>
-        <input type="submit" name="unlock" value="<?php echo _("Unlock"); ?>" />
-        <?php } ?>
-
-        <input type="submit" name="cancel" value="<?php echo _("Cancel"); ?>" />
-        </form>
-        <?php
-    }
-
-    /**
-     * Return lock_id of locked record.
-     */
-    function getID()
-    {
-        return $this->data['lock_id'];
-    }
-
-    /**
-     * Return title of locked record.
-     */
-    function getTitle()
-    {
-        return $this->data['title'];
-    }
-
-    /**
-     * Return administrator username for locked record.
-     */
-    function getEditor()
-    {
-        return $this->data['editor'];
-    }
-
-    /**
-     * Return total seconds since the record was locked.
-     */
-    function getSecondsElapsed()
-    {
-        if (isset($this->data['lock_datetime']) && $this->data['lock_datetime'] < time()) {
-            return time() - strtotime($this->data['lock_datetime']);
-        } else {
-            return 0;
-        }
-    }
-
-
-} // End of class.
-?>
Index: trunk/lib/RecordVersion.inc.php
===================================================================
--- trunk/lib/RecordVersion.inc.php	(revision 136)
+++ 	(revision )
@@ -1,442 +1,0 @@
-<?php
-/**
- * RecordVersion.inc.php
- * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
- *
- * The RecordVersion class provides a system for saving, reviewing, and
- * restoring versions of a record of any DB table. All the data in the record is
- * serialized, compressed, and saved in a blob in the version_tbl. Restoring a
- * version simply does a REPLACE INTO of the data. It is very simple, and works
- * with multiple database tables, but the drawback is that relationships for
- * a record cannot be retained. For example, an article from an article_tbl can
- * be saved, but not categories associated to the record in a category_article_tbl.
- * The restored article will simple retain the relationships that the previous
- * current article had.
- *
- * @author  Quinn Comendant <quinn@strangecode.com>
- * @version 2.1
- */
-class RecordVersion {
-
-    // Configuration of this object.
-    var $_params = array(
-        'max_qty' => 100, // Never have more than this many versions of each record.
-        'min_qty' => 25, // Keep at least this many versions of each record.
-        'min_days' => 7, // Keep ALL versions within this many days, even if MORE than min_qty.
-        'db_table' => 'version_tbl',
-        'create_table' => true, // Automatically create table and verify columns. Better set to false after site launch.
-        'db_schema_strict' => true, // If true, makes an exact comparison of saved vs. live table schemas. If false, just checks that the saved columns are available.
-    );
-
-    // Auth_SQL object from which to access a current user_id.
-    var $_auth;
-
-    /**
-     * This method enforces the singleton pattern for this class.
-     *
-     * @return  object  Reference to the global RecordLock object.
-     * @access  public
-     * @static
-     */
-    function &getInstance($auth_object)
-    {
-        static $instance = null;
-
-        if ($instance === null) {
-            $instance = new RecordVersion($auth_object);
-        }
-
-        return $instance;
-    }
-
-    /**
-     * Constructor. Pass an Auth object on which to perform user lookups.
-     *
-     * @param mixed  $auth_object  An Auth_SQL object.
-     */
-    function RecordVersion($auth_object)
-    {
-		$app =& App::getInstance();
-
-        if (!method_exists($auth_object, 'getVal') || !method_exists($auth_object, 'getUsername')) {
-            trigger_error('Constructor not provided a valid Auth_* object.', E_USER_ERROR);
-        }
-
-        $this->_auth = $auth_object;
-
-        // Get create tables config from global context.
-        if (!is_null($app->getParam('db_create_tables'))) {
-            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
-        }
-    }
-
-    /**
-     * Setup the database table for this class.
-     *
-     * @access  public
-     * @author  Quinn Comendant <quinn@strangecode.com>
-     * @since   26 Aug 2005 17:09:36
-     */
-    function initDB($recreate_db=false)
-    {
-		$app =& App::getInstance();
-		$db =& DB::getInstance();
-
-        static $_db_tested = false;
-
-        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
-            if ($recreate_db) {
-                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
-                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_DEBUG, __FILE__, __LINE__);
-            }
-            $db->query("CREATE TABLE IF NOT EXISTS " . $this->getParam('db_table') . " (
-                version_id int NOT NULL auto_increment,
-                record_table varchar(255) NOT NULL default '',
-                record_key varchar(255) NOT NULL default '',
-                record_val varchar(255) NOT NULL default '',
-                version_data mediumblob NOT NULL,
-                version_title varchar(255) NOT NULL default '',
-                version_notes varchar(255) NOT NULL default '',
-                saved_by_admin_id smallint(11) NOT NULL default '0',
-                version_datetime datetime NOT NULL default '0000-00-00 00:00:00',
-                PRIMARY KEY (version_id),
-                KEY record_table (record_table),
-                KEY record_key (record_key),
-                KEY record_val (record_val)
-            )");
-
-            if (!$db->columnExists($this->getParam('db_table'), array(
-                'version_id',
-                'record_table',
-                'record_key',
-                'record_val',
-                'version_data',
-                'version_title',
-                'version_notes',
-                'saved_by_admin_id',
-                'version_datetime',
-            ), false, false)) {
-                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
-                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
-            }
-        }
-        $_db_tested = true;
-    }
-
-    /**
-     * Set the params of this object.
-     *
-     * @param  array $params   Array of param keys and values to set.
-     */
-    function setParam($params=null)
-    {
-        if (isset($params) && is_array($params)) {
-            // Merge new parameters with old overriding only those passed.
-            $this->_params = array_merge($this->_params, $params);
-        }
-    }
-
-    /**
-     * Return the value of a parameter, if it exists.
-     *
-     * @access public
-     * @param string $param        Which parameter to return.
-     * @return mixed               Configured parameter value.
-     */
-    function getParam($param)
-    {
-		$app =& App::getInstance();
-    
-        if (isset($this->_params[$param])) {
-            return $this->_params[$param];
-        } else {
-            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
-            return null;
-        }
-    }
-
-    /**
-     * Saves a version of the current record into the version table.
-     *
-     * @param string $record_table  The table containing the record.
-     * @param string $record_key    The key column for the record.
-     * @param string $record_val    The value of the key column for the record.
-     * @param string $title         The title of this record. Only used for human presentation.
-     *
-     * @return int                  The id for the version (mysql last insert id).
-     */
-    function create($record_table, $record_key, $record_val, $title='', $notes='')
-    {
-		$app =& App::getInstance();
-		$db =& DB::getInstance();
-
-        $this->initDB();
-
-        // Get current record.
-        if (!$record = $this->getCurrent($record_table, $record_key, $record_val)) {
-            $app->logMsg(sprintf('Could not create %s version, record not found: %s, %s, %s.', $title, $record_table, $record_key, $record_val), LOG_ERR, __FILE__, __LINE__);
-            return false;
-        }
-
-        // Clean-up old versions.
-        $this->deleteOld($record_table, $record_key, $record_val);
-
-        // Save as new version.
-        $db->query("
-            INSERT INTO " . $this->getParam('db_table') . " (
-                record_table,
-                record_key,
-                record_val,
-                version_data,
-                version_title,
-                version_notes,
-                saved_by_admin_id,
-                version_datetime
-            ) VALUES (
-                '" . $db->escapeString($record_table) . "',
-                '" . $db->escapeString($record_key) . "',
-                '" . $db->escapeString($record_val) . "',
-                '" . $db->escapeString(gzcompress(serialize($record), 9)) . "',
-                '" . $db->escapeString($title) . "',
-                '" . $db->escapeString($notes) . "',
-                '" . $db->escapeString($this->_auth->getVal('user_id')) . "',
-                NOW()
-            )
-        ");
-
-        return mysql_insert_id($db->getDBH());
-    }
-
-    /**
-     * Copy a version back into it's original table.
-     *
-     * @param string $version_id    The id of the version to restore.
-     *
-     * @return int                  The id for the version (mysql last insert id).
-     */
-    function restore($version_id)
-    {
-		$app =& App::getInstance();
-		$db =& DB::getInstance();
-
-        $this->initDB();
-
-        // Get version data.
-        $qid = $db->query("
-            SELECT * FROM " . $this->getParam('db_table') . "
-            WHERE version_id = '" . $db->escapeString($version_id) . "'
-        ");
-        if (!$record = mysql_fetch_assoc($qid)) {
-            $app->raiseMsg(sprintf(_("Version ID %s%s not found."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')')), MSG_WARNING, __FILE__, __LINE__);
-            $app->logMsg(sprintf(_("Version ID %s%s not found."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')')), LOG_WARNING, __FILE__, __LINE__);
-            return false;
-        }
-        $data = unserialize(gzuncompress($record['version_data']));
-
-        // Ensure saved db columns match current table schema.
-        if (!$db->columnExists($record['record_table'], array_keys($data), $this->getParam('db_schema_strict'))) {
-            $app->raiseMsg(sprintf(_("Version ID %s%s is not compatible with the current database table."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')')), MSG_ERR, __FILE__, __LINE__);
-            $app->logMsg(sprintf(_("Version ID %s%s restoration failed, DB schema does not match for table %s."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')'), $record['record_table']), LOG_ALERT, __FILE__, __LINE__);
-            return false;
-        }
-
-        // SQLize the keys of the specified versioned record.
-        $replace_keys = join(",\n", array_map(array($db, 'escapeString'), array_keys($data)));
-
-        // SQLize the keys of the values of the specified versioned record. (These are more complex because we need to account for SQL null values.)
-        $replace_values = '';
-        $comma = '';
-        foreach ($data as $v) {
-            $replace_values .= is_null($v) ? "$comma\nNULL" : "$comma\n'" . $db->escapeString($v) . "'";
-            $comma = ',';
-        }
-
-        // Replace current record with specified versioned record.
-        $db->query("
-            REPLACE INTO " . $record['record_table'] . " (
-                $replace_keys
-            ) VALUES (
-                $replace_values
-            )
-        ");
-
-        return $record;
-    }
-
-    /**
-     * Version garbage collection. Deletes versions older than min_days
-     * when quantity of versions exceeds min_qty. If quantity
-     * exceeds 100 within min_days, the oldest are deleted to bring the
-     * quantity back down to min_qty.
-     *
-     * @param string $record_table  The table containing the record.
-     * @param string $record_key    The key column for the record.
-     * @param string $record_val    The value of the key column for the record.
-     *
-     * @return mixed                Array of versions, or false if none.
-     */
-    function deleteOld($record_table, $record_key, $record_val)
-    {
-		$db =& DB::getInstance();
-	
-        $this->initDB();
-
-        // Get total number of versions for this record.
-        $qid = $db->query("
-            SELECT COUNT(*) FROM " . $this->getParam('db_table') . "
-            WHERE record_table = '" . $db->escapeString($record_table) . "'
-            AND record_key = '" . $db->escapeString($record_key) . "'
-            AND record_val = '" . $db->escapeString($record_val) . "'
-        ");
-        list($v_count) = mysql_fetch_row($qid);
-
-        if ($v_count > $this->getParam('min_qty')) {
-            if ($v_count > $this->getParam('max_qty')) {
-                // To prevent a record bomb, limit max number of versions to max_qty.
-                // First query for oldest records, selecting enough to bring total number down to min_qty.
-                $qid = $db->query("
-                    SELECT version_id FROM " . $this->getParam('db_table') . "
-                    WHERE record_table = '" . $db->escapeString($record_table) . "'
-                    AND record_key = '" . $db->escapeString($record_key) . "'
-                    AND record_val = '" . $db->escapeString($record_val) . "'
-                    ORDER BY version_datetime ASC
-                    LIMIT " . ($v_count - $this->getParam('min_qty')) . "
-                ");
-                while (list($old_id) = mysql_fetch_row($qid)) {
-                    $old_versions[] = $old_id;
-                }
-                $db->query("
-                    DELETE FROM " . $this->getParam('db_table') . "
-                    WHERE version_id IN ('" . join("','", $old_versions) . "')
-                ");
-            } else {
-                // Delete versions older than min_days, while still keeping min_qty.
-                $qid = $db->query("
-                    SELECT version_id FROM " . $this->getParam('db_table') . "
-                    WHERE record_table = '" . $db->escapeString($record_table) . "'
-                    AND record_key = '" . $db->escapeString($record_key) . "'
-                    AND record_val = '" . $db->escapeString($record_val) . "'
-                    AND DATE_ADD(version_datetime, INTERVAL '" . $this->getParam('min_days') . "' DAY) < NOW()
-                    ORDER BY version_datetime ASC
-                    LIMIT " . ($v_count - $this->getParam('min_qty')) . "
-                ");
-                while (list($old_id) = mysql_fetch_row($qid)) {
-                    $old_versions[] = $old_id;
-                }
-                if (sizeof($old_versions) > 0) {
-                    $db->query("
-                        DELETE FROM " . $this->getParam('db_table') . "
-                        WHERE version_id IN ('" . join("','", $old_versions) . "')
-                    ");
-                }
-            }
-        }
-    }
-
-    /**
-     * Get a list of versions of specified record.
-     *
-     * @param string $record_table  The table containing the record.
-     * @param string $record_key    The key column for the record.
-     * @param string $record_val    The value of the key column for the record.
-     *
-     * @return mixed                Array of versions, or false if none.
-     */
-    function getList($record_table, $record_key, $record_val)
-    {
-		$db =& DB::getInstance();
-	
-        $this->initDB();
-
-        // Get versions of this record.
-        $qid = $db->query("
-            SELECT version_id, saved_by_admin_id, version_datetime, version_title
-            FROM " . $this->getParam('db_table') . "
-            WHERE record_table = '" . $db->escapeString($record_table) . "'
-            AND record_key = '" . $db->escapeString($record_key) . "'
-            AND record_val = '" . $db->escapeString($record_val) . "'
-            ORDER BY version_datetime DESC
-        ");
-        $versions = array();
-        while ($row = mysql_fetch_assoc($qid)) {
-            // Get admin usernames.
-            $row['editor'] = $this->_auth->getVal('auth_type') . ' ' . $this->_auth->getUsername($row['saved_by_admin_id']);
-            $versions[] = $row;
-        }
-        return $versions;
-    }
-
-    /**
-     * Get the version record for a specified version id.
-     *
-     * @param string $version_id    The id of the version to restore.
-     *
-     * @return mixed                Array of data saved in version, or false if none.
-     */
-    function getVerson($version_id)
-    {
-		$db =& DB::getInstance();
-	
-        $this->initDB();
-
-        // Get version data.
-        $qid = $db->query("
-            SELECT * FROM " . $this->getParam('db_table') . "
-            WHERE version_id = '" . $db->escapeString($version_id) . "'
-        ");
-        return mysql_fetch_assoc($qid);
-    }
-
-    /**
-     * Get the data stored for a specified version id.
-     *
-     * @param string $version_id    The id of the version to restore.
-     *
-     * @return mixed                Array of data saved in version, or false if none.
-     */
-    function getData($version_id)
-    {
-		$db =& DB::getInstance();
-	
-        $this->initDB();
-
-        // Get version data.
-        $qid = $db->query("
-            SELECT * FROM " . $this->getParam('db_table') . "
-            WHERE version_id = '" . $db->escapeString($version_id) . "'
-        ");
-        $record = mysql_fetch_assoc($qid);
-        if (isset($record['version_data'])) {
-            return unserialize(gzuncompress($record['version_data']));
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Get the current record data from the original table.
-     *
-     * @param string $version_id    The id of the version to restore.
-     *
-     * @return mixed                Array of data saved in version, or false if none.
-     */
-    function getCurrent($record_table, $record_key, $record_val)
-    {
-		$db =& DB::getInstance();
-	
-        $this->initDB();
-
-        $qid = $db->query("
-            SELECT * FROM " . $db->escapeString($record_table) . "
-            WHERE " . $db->escapeString($record_key) . " = '" . $db->escapeString($record_val) . "'
-        ");
-        if ($record = mysql_fetch_assoc($qid)) {
-            return $record;
-        } else {
-            return false;
-        }
-    }
-
-
-} // End of class.
-?>
Index: trunk/lib/SessionCache.inc.php
===================================================================
--- trunk/lib/SessionCache.inc.php	(revision 136)
+++ 	(revision )
@@ -1,203 +1,0 @@
-<?php
-/**
- * Cache.inc.php
- * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
- *
- * Provides an API for storing a limited amount of data
- * intended to have a short lifetime in a user's session.
- *
- * @author  Quinn Comendant <quinn@strangecode.com>
- * @version 2.1
- * @since   2001
- */
- 
-// Flags.
-define('CACHE_IGNORE_SIZE', 1);
-
-class Cache {
-
-    var $_params = array(
-        'enabled' => true,
-        'soft_limit' => 204800,
-        'hard_limit' => 4194304,
-        'min_items' => 3,
-    );
-
-    /**
-     * This method enforces the singleton pattern for this class.
-     *
-     * @return  object  Reference to the global Cache object.
-     * @access  public
-     * @static
-     */
-    function &getInstance()
-    {
-        static $instance = null;
-
-        if ($instance === null) {
-            $instance = new Cache();
-        }
-
-        return $instance;
-    }
-
-    /**
-     * Set (or overwrite existing) parameters by passing an array of new parameters.
-     *
-     * @access public
-     * @param  array    $params     Array of parameters (key => val pairs).
-     */
-    function setParam($params)
-    {
-		$app =& App::getInstance();
-
-        if (isset($params) && is_array($params)) {
-            // Merge new parameters with old overriding only those passed.
-            $this->_params = array_merge($this->_params, $params);
-        } else {
-            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
-        }
-    }
-
-    /**
-     * Return the value of a parameter, if it exists.
-     *
-     * @access public
-     * @param string $param        Which parameter to return.
-     * @return mixed               Configured parameter value.
-     */
-    function getParam($param)
-    {
-		$app =& App::getInstance();
-    
-        if (isset($this->_params[$param])) {
-            return $this->_params[$param];
-        } else {
-            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
-            return null;
-        }
-    }
-
-    /**
-     * Stores a new variable in the session cache. The $key is is md5'ed
-     * because if a variable id is a very large integer, the array_shift function
-     * will reset the key to the next largest int key. Weird behavior I can't
-     * understand. $session_cache[32341234123] will become $session_cache[0]
-     * for example. Usage warning: if the variable is too big to fit, or is
-     * old and discarded, you must provide alternative ways of accessing the data.
-     *
-     * @param str   $key        An identifier for the cached object.
-     * @param mixed $var          The var to store in the session cache.
-     * @param bool  $flags      If we have something really big that we
-     *                            still want to cache, setting this to
-     *                            CACHE_IGNORE_SIZE allows this.
-     *
-     * @return bool               True on success, false otherwise.
-     */
-    function set($key, $var, $flags=0)
-    {
-		$app =& App::getInstance();
-
-        if (!$this->getParam('enabled')) {
-            $app->logMsg(sprintf('Cache not enabled, not saving data.', null), LOG_DEBUG, __FILE__, __LINE__);
-            return false;
-        }
-
-        $key = md5($key);
-        $serialized_var = serialize($var);
-        $serialized_var_len = strlen($serialized_var);
-
-        if ($flags & CACHE_IGNORE_SIZE > 0 && $serialized_var_len >= $this->getParam('soft_limit')) {
-            $app->logMsg(sprintf('Serialized variable (%s bytes) more than soft_limit (%s bytes).', $serialized_var_len, $this->getParam('soft_limit')), LOG_NOTICE, __FILE__, __LINE__);
-            return false;
-        }
-
-        if ($serialized_var_len >= $this->getParam('hard_limit')) {
-            $app->logMsg(sprintf('Serialized variable (%s bytes) more than hard_limit (%s bytes).', $serialized_var_len, $this->getParam('hard_limit')), LOG_NOTICE, __FILE__, __LINE__);
-            return false;
-        }
-
-        if (!isset($_SESSION['_session_cache'])) {
-            $_SESSION['_session_cache'] = array();
-        } else {
-            unset($_SESSION['_session_cache'][$key]);
-            // Continue to prune the cache if it's length is too long for the new variable to fit, but keep at least MIN_ITEMS at least.
-            while (strlen(serialize($_SESSION['_session_cache'])) + $serialized_var_len >= $this->getParam('soft_limit')
-            && sizeof($_SESSION['_session_cache']) >= $this->getParam('min_items')) {
-                array_shift($_SESSION['_session_cache']);
-            }
-        }
-        $_SESSION['_session_cache'][$key] =& $serialized_var;
-
-        if ($serialized_var_len >= 1024000) {
-            $app->logMsg(sprintf('Successfully cached oversized variable (%s bytes).', $serialized_var_len), LOG_DEBUG, __FILE__, __LINE__);
-        }
-
-        return true;
-    }
-
-    /**
-     * Retrives an object from the session cache and returns it unserialized.
-     * It also moves it to the top of the stack, which makes it such that the
-     * cache flushing mechanism of putCache deletes the oldest referenced items
-     * first.
-     *
-     * @param string $key  The key for the datum to retrieve.
-     *
-     * @return mixed          The requested datum, or false on failure.
-     */
-    function get($key)
-    {
-        if (!$this->getParam('enabled')) {
-            return false;
-        }
-
-        $key = md5($key);
-        if (isset($_SESSION['_session_cache'][$key])) {
-            // Move the accessed cached datum to the top of the stack. Maybe somebody knows a better way to do this?
-            $tmp =& $_SESSION['_session_cache'][$key];
-            unset($_SESSION['_session_cache'][$key]);
-            $_SESSION['_session_cache'][$key] =& $tmp;
-            // Return the unserialized datum.
-            return unserialize($_SESSION['_session_cache'][$key]);
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Tells you if the object is cached.
-     *
-     * @param string $key  The key of the object to check.
-     *
-     * @return bool           The return from isset().
-     */
-    function exists($key)
-    {
-        if (!$this->getParam('enabled')) {
-            return false;
-        }
-
-        $key = md5($key);
-        return isset($_SESSION['_session_cache'][$key]);
-    }
-
-    /**
-     * Tells you if the object is cached.
-     *
-     * @param string $key  The key of the object to check.
-     *
-     * @return bool           The return from isset().
-     */
-    function delete($key)
-    {
-        $key = md5($key);
-        if (isset($_SESSION['_session_cache'][$key])) {
-            unset($_SESSION['_session_cache'][$key]);
-        }
-    }
-
-// END Cache
-}
-
-?>
Index: trunk/lib/Version.inc.php
===================================================================
--- trunk/lib/Version.inc.php	(revision 137)
+++ trunk/lib/Version.inc.php	(revision 137)
@@ -0,0 +1,442 @@
+<?php
+/**
+ * Version.inc.php
+ * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
+ *
+ * The Version class provides a system for saving, reviewing, and
+ * restoring versions of a record of any DB table. All the data in the record is
+ * serialized, compressed, and saved in a blob in the version_tbl. Restoring a
+ * version simply does a REPLACE INTO of the data. It is very simple, and works
+ * with multiple database tables, but the drawback is that relationships for
+ * a record cannot be retained. For example, an article from an article_tbl can
+ * be saved, but not categories associated to the record in a category_article_tbl.
+ * The restored article will simple retain the relationships that the previous
+ * current article had.
+ *
+ * @author  Quinn Comendant <quinn@strangecode.com>
+ * @version 2.1
+ */
+class Version {
+
+    // Configuration of this object.
+    var $_params = array(
+        'max_qty' => 100, // Never have more than this many versions of each record.
+        'min_qty' => 25, // Keep at least this many versions of each record.
+        'min_days' => 7, // Keep ALL versions within this many days, even if MORE than min_qty.
+        'db_table' => 'version_tbl',
+        'create_table' => true, // Automatically create table and verify columns. Better set to false after site launch.
+        'db_schema_strict' => true, // If true, makes an exact comparison of saved vs. live table schemas. If false, just checks that the saved columns are available.
+    );
+
+    // Auth_SQL object from which to access a current user_id.
+    var $_auth;
+
+    /**
+     * This method enforces the singleton pattern for this class.
+     *
+     * @return  object  Reference to the global RecordLock object.
+     * @access  public
+     * @static
+     */
+    function &getInstance($auth_object)
+    {
+        static $instance = null;
+
+        if ($instance === null) {
+            $instance = new Version($auth_object);
+        }
+
+        return $instance;
+    }
+
+    /**
+     * Constructor. Pass an Auth object on which to perform user lookups.
+     *
+     * @param mixed  $auth_object  An Auth_SQL object.
+     */
+    function Version($auth_object)
+    {
+		$app =& App::getInstance();
+
+        if (!method_exists($auth_object, 'getVal') || !method_exists($auth_object, 'getUsername')) {
+            trigger_error('Constructor not provided a valid Auth_* object.', E_USER_ERROR);
+        }
+
+        $this->_auth = $auth_object;
+
+        // Get create tables config from global context.
+        if (!is_null($app->getParam('db_create_tables'))) {
+            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
+        }
+    }
+
+    /**
+     * Setup the database table for this class.
+     *
+     * @access  public
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   26 Aug 2005 17:09:36
+     */
+    function initDB($recreate_db=false)
+    {
+		$app =& App::getInstance();
+		$db =& DB::getInstance();
+
+        static $_db_tested = false;
+
+        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
+            if ($recreate_db) {
+                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
+                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_DEBUG, __FILE__, __LINE__);
+            }
+            $db->query("CREATE TABLE IF NOT EXISTS " . $this->getParam('db_table') . " (
+                version_id int NOT NULL auto_increment,
+                record_table varchar(255) NOT NULL default '',
+                record_key varchar(255) NOT NULL default '',
+                record_val varchar(255) NOT NULL default '',
+                version_data mediumblob NOT NULL,
+                version_title varchar(255) NOT NULL default '',
+                version_notes varchar(255) NOT NULL default '',
+                saved_by_admin_id smallint(11) NOT NULL default '0',
+                version_datetime datetime NOT NULL default '0000-00-00 00:00:00',
+                PRIMARY KEY (version_id),
+                KEY record_table (record_table),
+                KEY record_key (record_key),
+                KEY record_val (record_val)
+            )");
+
+            if (!$db->columnExists($this->getParam('db_table'), array(
+                'version_id',
+                'record_table',
+                'record_key',
+                'record_val',
+                'version_data',
+                'version_title',
+                'version_notes',
+                'saved_by_admin_id',
+                'version_datetime',
+            ), false, false)) {
+                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
+                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
+            }
+        }
+        $_db_tested = true;
+    }
+
+    /**
+     * Set the params of this object.
+     *
+     * @param  array $params   Array of param keys and values to set.
+     */
+    function setParam($params=null)
+    {
+        if (isset($params) && is_array($params)) {
+            // Merge new parameters with old overriding only those passed.
+            $this->_params = array_merge($this->_params, $params);
+        }
+    }
+
+    /**
+     * Return the value of a parameter, if it exists.
+     *
+     * @access public
+     * @param string $param        Which parameter to return.
+     * @return mixed               Configured parameter value.
+     */
+    function getParam($param)
+    {
+		$app =& App::getInstance();
+    
+        if (isset($this->_params[$param])) {
+            return $this->_params[$param];
+        } else {
+            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
+            return null;
+        }
+    }
+
+    /**
+     * Saves a version of the current record into the version table.
+     *
+     * @param string $record_table  The table containing the record.
+     * @param string $record_key    The key column for the record.
+     * @param string $record_val    The value of the key column for the record.
+     * @param string $title         The title of this record. Only used for human presentation.
+     *
+     * @return int                  The id for the version (mysql last insert id).
+     */
+    function create($record_table, $record_key, $record_val, $title='', $notes='')
+    {
+		$app =& App::getInstance();
+		$db =& DB::getInstance();
+
+        $this->initDB();
+
+        // Get current record.
+        if (!$record = $this->getCurrent($record_table, $record_key, $record_val)) {
+            $app->logMsg(sprintf('Could not create %s version, record not found: %s, %s, %s.', $title, $record_table, $record_key, $record_val), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        }
+
+        // Clean-up old versions.
+        $this->deleteOld($record_table, $record_key, $record_val);
+
+        // Save as new version.
+        $db->query("
+            INSERT INTO " . $this->getParam('db_table') . " (
+                record_table,
+                record_key,
+                record_val,
+                version_data,
+                version_title,
+                version_notes,
+                saved_by_admin_id,
+                version_datetime
+            ) VALUES (
+                '" . $db->escapeString($record_table) . "',
+                '" . $db->escapeString($record_key) . "',
+                '" . $db->escapeString($record_val) . "',
+                '" . $db->escapeString(gzcompress(serialize($record), 9)) . "',
+                '" . $db->escapeString($title) . "',
+                '" . $db->escapeString($notes) . "',
+                '" . $db->escapeString($this->_auth->getVal('user_id')) . "',
+                NOW()
+            )
+        ");
+
+        return mysql_insert_id($db->getDBH());
+    }
+
+    /**
+     * Copy a version back into it's original table.
+     *
+     * @param string $version_id    The id of the version to restore.
+     *
+     * @return int                  The id for the version (mysql last insert id).
+     */
+    function restore($version_id)
+    {
+		$app =& App::getInstance();
+		$db =& DB::getInstance();
+
+        $this->initDB();
+
+        // Get version data.
+        $qid = $db->query("
+            SELECT * FROM " . $this->getParam('db_table') . "
+            WHERE version_id = '" . $db->escapeString($version_id) . "'
+        ");
+        if (!$record = mysql_fetch_assoc($qid)) {
+            $app->raiseMsg(sprintf(_("Version ID %s%s not found."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')')), MSG_WARNING, __FILE__, __LINE__);
+            $app->logMsg(sprintf(_("Version ID %s%s not found."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')')), LOG_WARNING, __FILE__, __LINE__);
+            return false;
+        }
+        $data = unserialize(gzuncompress($record['version_data']));
+
+        // Ensure saved db columns match current table schema.
+        if (!$db->columnExists($record['record_table'], array_keys($data), $this->getParam('db_schema_strict'))) {
+            $app->raiseMsg(sprintf(_("Version ID %s%s is not compatible with the current database table."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')')), MSG_ERR, __FILE__, __LINE__);
+            $app->logMsg(sprintf(_("Version ID %s%s restoration failed, DB schema does not match for table %s."), $version_id, (empty($record['version_title']) ? '' : ' (' . $record['version_title'] . ')'), $record['record_table']), LOG_ALERT, __FILE__, __LINE__);
+            return false;
+        }
+
+        // SQLize the keys of the specified versioned record.
+        $replace_keys = join(",\n", array_map(array($db, 'escapeString'), array_keys($data)));
+
+        // SQLize the keys of the values of the specified versioned record. (These are more complex because we need to account for SQL null values.)
+        $replace_values = '';
+        $comma = '';
+        foreach ($data as $v) {
+            $replace_values .= is_null($v) ? "$comma\nNULL" : "$comma\n'" . $db->escapeString($v) . "'";
+            $comma = ',';
+        }
+
+        // Replace current record with specified versioned record.
+        $db->query("
+            REPLACE INTO " . $record['record_table'] . " (
+                $replace_keys
+            ) VALUES (
+                $replace_values
+            )
+        ");
+
+        return $record;
+    }
+
+    /**
+     * Version garbage collection. Deletes versions older than min_days
+     * when quantity of versions exceeds min_qty. If quantity
+     * exceeds 100 within min_days, the oldest are deleted to bring the
+     * quantity back down to min_qty.
+     *
+     * @param string $record_table  The table containing the record.
+     * @param string $record_key    The key column for the record.
+     * @param string $record_val    The value of the key column for the record.
+     *
+     * @return mixed                Array of versions, or false if none.
+     */
+    function deleteOld($record_table, $record_key, $record_val)
+    {
+		$db =& DB::getInstance();
+	
+        $this->initDB();
+
+        // Get total number of versions for this record.
+        $qid = $db->query("
+            SELECT COUNT(*) FROM " . $this->getParam('db_table') . "
+            WHERE record_table = '" . $db->escapeString($record_table) . "'
+            AND record_key = '" . $db->escapeString($record_key) . "'
+            AND record_val = '" . $db->escapeString($record_val) . "'
+        ");
+        list($v_count) = mysql_fetch_row($qid);
+
+        if ($v_count > $this->getParam('min_qty')) {
+            if ($v_count > $this->getParam('max_qty')) {
+                // To prevent a record bomb, limit max number of versions to max_qty.
+                // First query for oldest records, selecting enough to bring total number down to min_qty.
+                $qid = $db->query("
+                    SELECT version_id FROM " . $this->getParam('db_table') . "
+                    WHERE record_table = '" . $db->escapeString($record_table) . "'
+                    AND record_key = '" . $db->escapeString($record_key) . "'
+                    AND record_val = '" . $db->escapeString($record_val) . "'
+                    ORDER BY version_datetime ASC
+                    LIMIT " . ($v_count - $this->getParam('min_qty')) . "
+                ");
+                while (list($old_id) = mysql_fetch_row($qid)) {
+                    $old_versions[] = $old_id;
+                }
+                $db->query("
+                    DELETE FROM " . $this->getParam('db_table') . "
+                    WHERE version_id IN ('" . join("','", $old_versions) . "')
+                ");
+            } else {
+                // Delete versions older than min_days, while still keeping min_qty.
+                $qid = $db->query("
+                    SELECT version_id FROM " . $this->getParam('db_table') . "
+                    WHERE record_table = '" . $db->escapeString($record_table) . "'
+                    AND record_key = '" . $db->escapeString($record_key) . "'
+                    AND record_val = '" . $db->escapeString($record_val) . "'
+                    AND DATE_ADD(version_datetime, INTERVAL '" . $this->getParam('min_days') . "' DAY) < NOW()
+                    ORDER BY version_datetime ASC
+                    LIMIT " . ($v_count - $this->getParam('min_qty')) . "
+                ");
+                while (list($old_id) = mysql_fetch_row($qid)) {
+                    $old_versions[] = $old_id;
+                }
+                if (sizeof($old_versions) > 0) {
+                    $db->query("
+                        DELETE FROM " . $this->getParam('db_table') . "
+                        WHERE version_id IN ('" . join("','", $old_versions) . "')
+                    ");
+                }
+            }
+        }
+    }
+
+    /**
+     * Get a list of versions of specified record.
+     *
+     * @param string $record_table  The table containing the record.
+     * @param string $record_key    The key column for the record.
+     * @param string $record_val    The value of the key column for the record.
+     *
+     * @return mixed                Array of versions, or false if none.
+     */
+    function getList($record_table, $record_key, $record_val)
+    {
+		$db =& DB::getInstance();
+	
+        $this->initDB();
+
+        // Get versions of this record.
+        $qid = $db->query("
+            SELECT version_id, saved_by_admin_id, version_datetime, version_title
+            FROM " . $this->getParam('db_table') . "
+            WHERE record_table = '" . $db->escapeString($record_table) . "'
+            AND record_key = '" . $db->escapeString($record_key) . "'
+            AND record_val = '" . $db->escapeString($record_val) . "'
+            ORDER BY version_datetime DESC
+        ");
+        $versions = array();
+        while ($row = mysql_fetch_assoc($qid)) {
+            // Get admin usernames.
+            $row['editor'] = $this->_auth->getVal('auth_type') . ' ' . $this->_auth->getUsername($row['saved_by_admin_id']);
+            $versions[] = $row;
+        }
+        return $versions;
+    }
+
+    /**
+     * Get the version record for a specified version id.
+     *
+     * @param string $version_id    The id of the version to restore.
+     *
+     * @return mixed                Array of data saved in version, or false if none.
+     */
+    function getVerson($version_id)
+    {
+		$db =& DB::getInstance();
+	
+        $this->initDB();
+
+        // Get version data.
+        $qid = $db->query("
+            SELECT * FROM " . $this->getParam('db_table') . "
+            WHERE version_id = '" . $db->escapeString($version_id) . "'
+        ");
+        return mysql_fetch_assoc($qid);
+    }
+
+    /**
+     * Get the data stored for a specified version id.
+     *
+     * @param string $version_id    The id of the version to restore.
+     *
+     * @return mixed                Array of data saved in version, or false if none.
+     */
+    function getData($version_id)
+    {
+		$db =& DB::getInstance();
+	
+        $this->initDB();
+
+        // Get version data.
+        $qid = $db->query("
+            SELECT * FROM " . $this->getParam('db_table') . "
+            WHERE version_id = '" . $db->escapeString($version_id) . "'
+        ");
+        $record = mysql_fetch_assoc($qid);
+        if (isset($record['version_data'])) {
+            return unserialize(gzuncompress($record['version_data']));
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Get the current record data from the original table.
+     *
+     * @param string $version_id    The id of the version to restore.
+     *
+     * @return mixed                Array of data saved in version, or false if none.
+     */
+    function getCurrent($record_table, $record_key, $record_val)
+    {
+		$db =& DB::getInstance();
+	
+        $this->initDB();
+
+        $qid = $db->query("
+            SELECT * FROM " . $db->escapeString($record_table) . "
+            WHERE " . $db->escapeString($record_key) . " = '" . $db->escapeString($record_val) . "'
+        ");
+        if ($record = mysql_fetch_assoc($qid)) {
+            return $record;
+        } else {
+            return false;
+        }
+    }
+
+
+} // End of class.
+?>
