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

Last change on this file since 463 was 463, checked in by anonymous, 10 years ago

Check for new 'saved' key to determine if reset is needed. This helps users with old sessions when a site is upgraded.

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