Index: trunk/lib/Prefs.inc.php
===================================================================
--- trunk/lib/Prefs.inc.php	(revision 136)
+++ trunk/lib/Prefs.inc.php	(revision 146)
@@ -4,5 +4,6 @@
  * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
  *
- * Prefs provides an API for saving arbitrary values in a user's session.
+ * Prefs provides an API for saving arbitrary values in a user's session. 
+ * Session prefs can be stored into a database with the optional save() and load() methods.
  *
  * @author  Quinn Comendant <quinn@strangecode.com>
@@ -14,4 +15,20 @@
     var $_ns;
 
+    // Configuration of this object.
+    var $_params = array(
+        
+        // Enable database storage.
+        'enable_db' => true,
+        
+        // Name of database table to store prefs.
+        'db_table' => 'prefs_tbl',
+
+        // Automatically create table and verify columns. Better set to false after site launch.
+        'create_table' => true,
+        
+        // The current user_id for which to load/save preferences.
+        'user_id' => null,
+    );
+
     /**
      * Prefs constructor.
@@ -19,9 +36,88 @@
     function Prefs($namespace='')
     {
+		$app =& App::getInstance();
+
         $this->_ns = '_prefs_' . $namespace;
         
         // Initialized the prefs array.
         if (!isset($_SESSION[$this->_ns])) {
-            $_SESSION[$this->_ns] = array();
+            $_SESSION[$this->_ns] = array('loaded' => false, 'data' => array());
+        }
+
+        // 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   04 Jun 2006 16:41:42
+     */
+    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 " . $db->escapeString($this->getParam('db_table')) . " (
+                user_id VARCHAR(32) NOT NULL DEFAULT '',
+                pref_namespace VARCHAR(32) NOT NULL DEFAULT '',
+                pref_key VARCHAR(64) NOT NULL DEFAULT '',
+                pref_value TEXT,
+                PRIMARY KEY (user_id, pref_namespace, pref_key)
+            )");
+
+            if (!$db->columnExists($this->getParam('db_table'), array(
+                'user_id',
+                'pref_namespace',
+                'pref_key',
+                'pref_value',
+            ), 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_DEBUG, __FILE__, __LINE__);
+            return null;
         }
     }
@@ -34,9 +130,9 @@
      * @param  string $val       The new value for this preference.
      */
-    function setDefault($key, $val)
-    {
-        // Set it only if not set already.
-        if (!isset($_SESSION[$this->_ns][$key])) {
-            $_SESSION[$this->_ns][$key] = $val;
+    function setDefaults($defaults)
+    {
+        if (isset($defaults) && is_array($defaults)) {
+            // Apply defaults to the session, setting only non-existing values.
+            $_SESSION[$this->_ns]['data'] = array_merge($defaults, $_SESSION[$this->_ns]['data']);
         }
     }
@@ -50,5 +146,5 @@
     function set($key, $val)
     {
-        $_SESSION[$this->_ns][$key] = $val;
+        $_SESSION[$this->_ns]['data'][$key] = $val;
     }
 
@@ -62,5 +158,5 @@
     function get($key)
     {
-        return (isset($_SESSION[$this->_ns][$key])) ? $_SESSION[$this->_ns][$key] : null;
+        return (isset($_SESSION[$this->_ns]['data'][$key])) ? $_SESSION[$this->_ns]['data'][$key] : null;
     }
 
@@ -75,5 +171,5 @@
     function exists($key)
     {
-        return isset($_SESSION[$this->_ns][$key]);
+        return isset($_SESSION[$this->_ns]['data'][$key]);
     }
 
@@ -81,15 +177,13 @@
      * Clear a set preference value.
      *
-     * @param string $key       The name of the preference to check.
+     * @param string $key       The name of the preference to delete.
      */
     function delete($key)
     {
-        if (isset($_SESSION[$this->_ns][$key])) {
-            unset($_SESSION[$this->_ns][$key]);
-        }
-    }
-
-    /**
-     * Perform cleanup operations.
+        unset($_SESSION[$this->_ns]['data'][$key]);
+    }
+
+    /**
+     * Empty the $_SESSION cache.
      */
     function clear()
@@ -97,4 +191,121 @@
         $_SESSION[$this->_ns] = array();
     }
+    
+    /*
+    * Retreives all prefs from the database and stores them in the $_SESSION.
+    *
+    * @access   public
+    * @return   bool    True if loading succeeded.
+    * @author   Quinn Comendant <quinn@strangecode.com>
+    * @version  1.0
+    * @since    04 Jun 2006 16:56:53
+    */
+    function load()
+    {
+        $app =& App::getInstance();
+		$db =& DB::getInstance();
+		
+		// Skip this method if not using the db.
+		if (true !=== $this->getParam('enable_db')) {
+		    return true;
+		}
+
+        // Prefs already loaded for this session.
+		if ($this->_isLoaded()) {
+		    return true;
+		}
+
+        // User_id must not be empty.
+        if ('' == $this->getParam('user_id')) {
+            $app->logMsg(sprintf('Cannot save prefs because user_id not set.', null), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        }
+
+        $this->initDB();
+        
+        // Retreive all prefs for this user and namespace.
+		$qid = $db->query("
+		    SELECT pref_key, pref_value
+		    FROM " . $db->escapeString($this->getParam('db_table')) . "
+		    WHERE user_id = '" . $db->escapeString($this->getParam('user_id')) . "'
+		    AND pref_namespace = '" . $db->escapeString($this->_ns) . "'
+		    LIMIT 10000
+		");
+		while (list($key, $val) = mysql_fetch_row($qid)) {
+            $_SESSION[$this->_ns]['data'][$key] = $val;
+		}
+		
+		// Data loaded only once per session.
+		$_SESSION[$this->_ns]['loaded'] = true;
+		
+		return true;
+    }
+    
+    /*
+    *   Returns true if the prefs had been loaded from the database into the $_SESSION.
+    * 
+    * @access   private
+    * @return   bool    True if prefs are loaded.
+    * @author   Quinn Comendant <quinn@strangecode.com>
+    * @version  1.0
+    * @since    04 Jun 2006 17:12:44
+    */
+    function _isLoaded()
+    {
+        return isset($_SESSION[$this->_ns]['loaded']) && true === $_SESSION[$this->_ns]['loaded'];
+    }
+    
+    /*
+    * Saves all prefs stored in the $_SESSION into the database.
+    *
+    * @access   public
+    * @return   bool    True if prefs exist and were saved.
+    * @author   Quinn Comendant <quinn@strangecode.com>
+    * @version  1.0
+    * @since    04 Jun 2006 17:19:56
+    */
+    function save()
+    {
+        $app =& App::getInstance();
+		$db =& DB::getInstance();
+		
+		// Skip this method if not using the db.
+		if (true !=== $this->getParam('enable_db')) {
+		    return true;
+		}
+        
+        // User_id must not be empty.
+        if ('' == $this->getParam('user_id')) {
+            $app->logMsg(sprintf('Cannot save prefs because user_id not set.', null), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        }
+
+        $this->initDB();
+
+        if (isset($_SESSION[$this->_ns]['data']) && is_array($_SESSION[$this->_ns]['data'])) {
+            // Delete old prefs from database.
+            $db->query("
+                DELETE FROM " . $db->escapeString($this->getParam('db_table')) . "
+    		    WHERE user_id = '" . $db->escapeString($this->getParam('user_id')) . "'
+    		    AND pref_namespace = '" . $db->escapeString($this->_ns) . "'
+            ");
+            
+            // Insert new prefs.
+            $insert_values = array();
+            foreach ($_SESSION[$this->_ns]['data'] as $key => $val) {
+                $insert_values[] = sprintf("('%s', '%s', '%s', '%s')", DB::escapeString($this->getParam('user_id')), DB::escapeString($this->_ns), DB::escapeString($key), DB::escapeString($val));
+            }
+            $db->query("
+                INSERT LOW_PRIORITY INTO " . $db->escapeString($this->getParam('db_table')) . " 
+                (user_id, pref_namespace, pref_key, pref_value)
+                VALUES " . join(', ', $insert_values) . "
+            ");
+            
+            $app->logMsg(sprintf('Saved %s preferences to database.', sizeof($insert_values)), LOG_DEBUG, __FILE__, __LINE__);
+            return true;
+        }
+        
+        return false;
+    }
 }
 
