source: trunk/lib/Prefs.inc.php @ 358

Last change on this file since 358 was 334, checked in by quinn, 16 years ago

Fixed lots of misplings. I'm so embarrassed! ;P

File size: 14.4 KB
RevLine 
[1]1<?php
2/**
[136]3 * Prefs.inc.php
4 * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
[1]5 *
[146]6 * Prefs provides an API for saving arbitrary values in a user's session.
7 * Session prefs can be stored into a database with the optional save() and load() methods.
[136]8 *
[1]9 * @author  Quinn Comendant <quinn@strangecode.com>
[136]10 * @version 2.1
[150]11 *
12 * Example of use:
13---------------------------------------------------------------------
[151]14// Load preferences for the user's session.
[150]15require_once 'codebase/lib/Prefs.inc.php';
[153]16$prefs = new Prefs('my-namespace');
[150]17$prefs->setParam(array(
[152]18    'persistent' => $auth->isLoggedIn(),
[150]19    'user_id' => $auth->get('user_id'),
20));
21$prefs->setDefaults(array(
22    'search_num_results' => 25,
23    'datalog_num_entries' => 25,
24));
25$prefs->load();
[151]26
27// Update preferences. Make sure to validate this input first!
28$prefs->set('search_num_results', getFormData('search_num_results'));
29$prefs->set('datalog_num_entries', getFormData('datalog_num_entries'));
30$prefs->save();
31
[150]32---------------------------------------------------------------------
[1]33 */
34class Prefs {
35
[136]36    // Namespace of this instance of Prefs.
37    var $_ns;
[1]38
[152]39    // Configuration parameters for this object.
[146]40    var $_params = array(
41       
[152]42        // Enable database storage. If this is false, all prefs will live only as long as the session.
43        'persistent' => false,
44       
45        // The current user_id for which to load/save persistent preferences.
[149]46        'user_id' => null,
47       
[152]48        // How long before we force a reload of the persistent prefs data? 3600 = once every hour.
[149]49        'load_timeout' => 3600,
50       
[152]51        // Name of database table to store persistent prefs.
[147]52        'db_table' => 'pref_tbl',
[146]53
54        // Automatically create table and verify columns. Better set to false after site launch.
55        'create_table' => true,
56    );
57
[1]58    /**
59     * Prefs constructor.
60     */
[136]61    function Prefs($namespace='')
[1]62    {
[146]63        $app =& App::getInstance();
64
[154]65        $this->_ns = $namespace;
[136]66       
67        // Initialized the prefs array.
[154]68        if (!isset($_SESSION['_prefs'][$this->_ns])) {
[150]69            $this->clear();
[136]70        }
[146]71
72        // Get create tables config from global context.
73        if (!is_null($app->getParam('db_create_tables'))) {
74            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
75        }
[1]76    }
77
78    /**
[146]79     * Setup the database table for this class.
80     *
81     * @access  public
82     * @author  Quinn Comendant <quinn@strangecode.com>
83     * @since   04 Jun 2006 16:41:42
84     */
85    function initDB($recreate_db=false)
86    {
87        $app =& App::getInstance();
88        $db =& DB::getInstance();
89
90        static $_db_tested = false;
91
92        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
93            if ($recreate_db) {
94                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
[201]95                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_INFO, __FILE__, __LINE__);
[146]96            }
97            $db->query("CREATE TABLE IF NOT EXISTS " . $db->escapeString($this->getParam('db_table')) . " (
98                user_id VARCHAR(32) NOT NULL DEFAULT '',
99                pref_namespace VARCHAR(32) NOT NULL DEFAULT '',
100                pref_key VARCHAR(64) NOT NULL DEFAULT '',
101                pref_value TEXT,
102                PRIMARY KEY (user_id, pref_namespace, pref_key)
103            )");
104
105            if (!$db->columnExists($this->getParam('db_table'), array(
106                'user_id',
107                'pref_namespace',
108                'pref_key',
109                'pref_value',
110            ), false, false)) {
111                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
112                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
113            }
114        }
115        $_db_tested = true;
116    }
117
118    /**
119     * Set the params of this object.
120     *
121     * @param  array $params   Array of param keys and values to set.
122     */
123    function setParam($params=null)
124    {
125        if (isset($params) && is_array($params)) {
126            // Merge new parameters with old overriding only those passed.
127            $this->_params = array_merge($this->_params, $params);
128        }
129    }
130
131    /**
132     * Return the value of a parameter, if it exists.
133     *
134     * @access public
135     * @param string $param        Which parameter to return.
136     * @return mixed               Configured parameter value.
137     */
138    function getParam($param)
139    {
140        $app =& App::getInstance();
141   
142        if (isset($this->_params[$param])) {
143            return $this->_params[$param];
144        } else {
145            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
146            return null;
147        }
148    }
149
150    /**
[151]151     * Sets the default values for preferences. If a preference is not explicitly
152     * set, the value set here will be used. Can be called multiple times to merge additional
153     * defaults together.
[1]154     *
[151]155     * @param  array $defaults  Array of key-value pairs
[1]156     */
[146]157    function setDefaults($defaults)
[1]158    {
[146]159        if (isset($defaults) && is_array($defaults)) {
[154]160            $_SESSION['_prefs'][$this->_ns]['defaults'] = array_merge($_SESSION['_prefs'][$this->_ns]['defaults'], $defaults);
[1]161        }
162    }
163
164    /**
[151]165     * Store a key-value pair in the session. If the value is different than what is set by setDefaults
166     * the value will be scheduled to be saved in the database.
[275]167     * This function determines what data is saved to the database. Ensure clean values!
[1]168     *
[152]169     * @param  string $key          The name of the preference to modify.
170     * @param  string $val          The new value for this preference.
171     * @param  bool   $persistent   Save this value forever? Set to false and value will exist as long as the session is in use.
[1]172     */
[136]173    function set($key, $val)
[1]174    {
[151]175        $app =& App::getInstance();
[152]176
[151]177        if ('' == $key) {
178            $app->logMsg(sprintf('Key is empty (provided with value: %s)', $val), LOG_NOTICE, __FILE__, __LINE__);
179            return false;
[149]180        }
[152]181       
[334]182        // Set a persistent preference if...
[159]183        // - there isn't a default.
184        // - the new value is different than the default
[254]185        // - there is a previously existing persistent key.
[154]186        if (!isset($_SESSION['_prefs'][$this->_ns]['defaults'][$key]) || $_SESSION['_prefs'][$this->_ns]['defaults'][$key] != $val || isset($_SESSION['_prefs'][$this->_ns]['persistent'][$key])) {
187            $_SESSION['_prefs'][$this->_ns]['persistent'][$key] = $val;           
[331]188            $app->logMsg(sprintf('Setting preference %s => %s', $key, truncate(getDump($val, true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
[159]189        } else {
[331]190            $app->logMsg(sprintf('Not setting preference %s => %s', $key, truncate(getDump($val, true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
[151]191        }
[1]192    }
[42]193
[1]194    /**
[151]195     * Returns the value of the requested preference. Persistent values take precedence, but if none is set
196     * a default value is returned, or if not that, null.
[1]197     *
[136]198     * @param string $key       The name of the preference to retrieve.
[1]199     *
200     * @return string           The value of the preference.
201     */
[136]202    function get($key)
[1]203    {
[151]204        $app =& App::getInstance();
[242]205        if (isset($_SESSION['_prefs'][$this->_ns]['persistent']) && array_key_exists($key, $_SESSION['_prefs'][$this->_ns]['persistent'])) {
[241]206            $app->logMsg(sprintf('Found %s in persistent', $key), LOG_DEBUG, __FILE__, __LINE__);
[154]207            return $_SESSION['_prefs'][$this->_ns]['persistent'][$key];
[242]208        } else if (isset($_SESSION['_prefs'][$this->_ns]['defaults']) && array_key_exists($key, $_SESSION['_prefs'][$this->_ns]['defaults'])) {
[241]209            $app->logMsg(sprintf('Found %s in defaults', $key), LOG_DEBUG, __FILE__, __LINE__);
[154]210            return $_SESSION['_prefs'][$this->_ns]['defaults'][$key];
[151]211        } else {
[154]212            $app->logMsg(sprintf('Key not found in prefs cache: %s', $key), LOG_DEBUG, __FILE__, __LINE__);
[151]213            return null;
214        }
[1]215    }
[42]216
[1]217    /**
[152]218     * To see if a preference has been set.
[1]219     *
[136]220     * @param string $key       The name of the preference to check.
[151]221     * @return boolean          True if the preference isset and not empty false otherwise.
[1]222     */
[136]223    function exists($key)
[1]224    {
[154]225        return array_key_exists($key, $_SESSION['_prefs'][$this->_ns]['persistent']);
[1]226    }
[42]227
[1]228    /**
[151]229     * Clear a set preference value. This will also remove the value from the database.
[1]230     *
[146]231     * @param string $key       The name of the preference to delete.
[1]232     */
[136]233    function delete($key)
[1]234    {
[154]235        unset($_SESSION['_prefs'][$this->_ns]['persistent'][$key]);
[1]236    }
237
238    /**
[151]239     * Resets the $_SESSION cache. This should be executed with the same consideration
240     * as $auth->clear(), such as when logging out.
[1]241     */
[241]242    function clear($focus='all')
[1]243    {
[241]244        switch ($focus) {
245        case 'all' :
246            $_SESSION['_prefs'][$this->_ns] = array(
247                'loaded' => false,
248                'load_datetime' => '1970-01-01',
249                'defaults' => array(),
250                'persistent' => array(),
251            );
252            break;
253
254        case 'defaults' :
255            $_SESSION['_prefs'][$this->_ns]['defaults'] = array();
256            break;
257
258        case 'persistent' :
259            $_SESSION['_prefs'][$this->_ns]['persistent'] = array();
260            break;
261        }
[1]262    }
[146]263   
264    /*
[151]265    * Retrieves all prefs from the database and stores them in the $_SESSION.
[146]266    *
267    * @access   public
[150]268    * @param    bool    $force  Set to always load from database, regardless if _isLoaded() or not.
[146]269    * @return   bool    True if loading succeeded.
270    * @author   Quinn Comendant <quinn@strangecode.com>
271    * @version  1.0
272    * @since    04 Jun 2006 16:56:53
273    */
[150]274    function load($force=false)
[146]275    {
276        $app =& App::getInstance();
277        $db =& DB::getInstance();
278       
279        // Skip this method if not using the db.
[152]280        if (true !== $this->getParam('persistent')) {
[146]281            return true;
282        }
283
[150]284        $this->initDB();
285
[146]286        // Prefs already loaded for this session.
[150]287        if (!$force && $this->_isLoaded()) {
[146]288            return true;
289        }
290
291        // User_id must not be empty.
292        if ('' == $this->getParam('user_id')) {
[151]293            $app->logMsg(sprintf('Cannot save prefs because user_id not set.', null), LOG_WARNING, __FILE__, __LINE__);
[146]294            return false;
295        }
296       
[150]297        // Clear existing cache.
[241]298        $this->clear('persistent');
[150]299       
[151]300        // Retrieve all prefs for this user and namespace.
[146]301        $qid = $db->query("
302            SELECT pref_key, pref_value
303            FROM " . $db->escapeString($this->getParam('db_table')) . "
304            WHERE user_id = '" . $db->escapeString($this->getParam('user_id')) . "'
305            AND pref_namespace = '" . $db->escapeString($this->_ns) . "'
306            LIMIT 10000
307        ");
308        while (list($key, $val) = mysql_fetch_row($qid)) {
[158]309            $_SESSION['_prefs'][$this->_ns]['persistent'][$key] = unserialize($val);
[146]310        }
311       
[241]312        $app->logMsg(sprintf('Loaded %s prefs from database.', mysql_num_rows($qid)), LOG_DEBUG, __FILE__, __LINE__);
[150]313       
[146]314        // Data loaded only once per session.
[154]315        $_SESSION['_prefs'][$this->_ns]['loaded'] = true;
316        $_SESSION['_prefs'][$this->_ns]['load_datetime'] = date('Y-m-d H:i:s');
[146]317       
318        return true;
319    }
320   
321    /*
[149]322    * Returns true if the prefs had been loaded from the database into the $_SESSION recently.
323    * This function is simply a check so the database isn't access every page load.
[146]324    *
325    * @access   private
326    * @return   bool    True if prefs are loaded.
327    * @author   Quinn Comendant <quinn@strangecode.com>
328    * @version  1.0
329    * @since    04 Jun 2006 17:12:44
330    */
331    function _isLoaded()
332    {
[154]333        if (isset($_SESSION['_prefs'][$this->_ns]['load_datetime'])
334        && strtotime($_SESSION['_prefs'][$this->_ns]['load_datetime']) > time() - $this->getParam('load_timeout')
335        && isset($_SESSION['_prefs'][$this->_ns]['loaded']) 
336        && true === $_SESSION['_prefs'][$this->_ns]['loaded']) {
[149]337            return true;
338        } else {
339            return false;
340        }
[146]341    }
342   
343    /*
344    * Saves all prefs stored in the $_SESSION into the database.
345    *
346    * @access   public
347    * @return   bool    True if prefs exist and were saved.
348    * @author   Quinn Comendant <quinn@strangecode.com>
349    * @version  1.0
350    * @since    04 Jun 2006 17:19:56
351    */
352    function save()
353    {
354        $app =& App::getInstance();
355        $db =& DB::getInstance();
356       
357        // Skip this method if not using the db.
[152]358        if (true !== $this->getParam('persistent')) {
[146]359            return true;
360        }
361       
362        // User_id must not be empty.
363        if ('' == $this->getParam('user_id')) {
[151]364            $app->logMsg(sprintf('Cannot save prefs because user_id not set.', null), LOG_WARNING, __FILE__, __LINE__);
[146]365            return false;
366        }
367
368        $this->initDB();
369
[159]370        if (isset($_SESSION['_prefs'][$this->_ns]['persistent']) && is_array($_SESSION['_prefs'][$this->_ns]['persistent']) && !empty($_SESSION['_prefs'][$this->_ns]['persistent'])) {
[146]371            // Delete old prefs from database.
372            $db->query("
373                DELETE FROM " . $db->escapeString($this->getParam('db_table')) . "
374                WHERE user_id = '" . $db->escapeString($this->getParam('user_id')) . "'
375                AND pref_namespace = '" . $db->escapeString($this->_ns) . "'
376            ");
377           
378            // Insert new prefs.
379            $insert_values = array();
[154]380            foreach ($_SESSION['_prefs'][$this->_ns]['persistent'] as $key => $val) {
[158]381                $insert_values[] = sprintf("('%s', '%s', '%s', '%s')", 
382                    $db->escapeString($this->getParam('user_id')), 
383                    $db->escapeString($this->_ns), 
384                    $db->escapeString($key), 
385                    $db->escapeString(serialize($val))
386                );
[146]387            }
[159]388            // TODO: after MySQL 5.0.23 is released this query could benefit from INSERT DELAYED.
[146]389            $db->query("
[159]390                INSERT INTO " . $db->escapeString($this->getParam('db_table')) . "
[146]391                (user_id, pref_namespace, pref_key, pref_value)
392                VALUES " . join(', ', $insert_values) . "
393            ");
394           
[151]395            $app->logMsg(sprintf('Saved %s prefs to database.', sizeof($insert_values)), LOG_DEBUG, __FILE__, __LINE__);
[146]396            return true;
397        }
398       
399        return false;
400    }
[1]401}
402
403
404?>
Note: See TracBrowser for help on using the repository browser.