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

Last change on this file since 396 was 396, checked in by anonymous, 12 years ago

Updated copyright date; comments elaboration; spelling fixes.

File size: 15.3 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])) {
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 persistent preference if...
204        // - there isn't a default.
205        // - the new value is different than the default
206        // - there is a previously existing persistent key.
207        if (!isset($_SESSION['_prefs'][$this->_ns]['defaults'][$key]) || $_SESSION['_prefs'][$this->_ns]['defaults'][$key] != $val || isset($_SESSION['_prefs'][$this->_ns]['persistent'][$key])) {
208            $_SESSION['_prefs'][$this->_ns]['persistent'][$key] = $val;           
209            $app->logMsg(sprintf('Setting preference %s => %s', $key, truncate(getDump($val, true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
210        } else {
211            $app->logMsg(sprintf('Not setting preference %s => %s', $key, truncate(getDump($val, true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
212        }
213    }
214
215    /**
216     * Returns the value of the requested preference. Persistent values take precedence, but if none is set
217     * a default value is returned, or if not that, null.
218     *
219     * @param string $key       The name of the preference to retrieve.
220     *
221     * @return string           The value of the preference.
222     */
223    function get($key)
224    {
225        $app =& App::getInstance();
226        if (isset($_SESSION['_prefs'][$this->_ns]['persistent']) && array_key_exists($key, $_SESSION['_prefs'][$this->_ns]['persistent'])) {
227            $app->logMsg(sprintf('Found %s in persistent', $key), LOG_DEBUG, __FILE__, __LINE__);
228            return $_SESSION['_prefs'][$this->_ns]['persistent'][$key];
229        } else if (isset($_SESSION['_prefs'][$this->_ns]['defaults']) && array_key_exists($key, $_SESSION['_prefs'][$this->_ns]['defaults'])) {
230            $app->logMsg(sprintf('Found %s in defaults', $key), LOG_DEBUG, __FILE__, __LINE__);
231            return $_SESSION['_prefs'][$this->_ns]['defaults'][$key];
232        } else {
233            $app->logMsg(sprintf('Key not found in prefs cache: %s', $key), LOG_DEBUG, __FILE__, __LINE__);
234            return null;
235        }
236    }
237
238    /**
239     * To see if a preference has been set.
240     *
241     * @param string $key       The name of the preference to check.
242     * @return boolean          True if the preference isset and not empty false otherwise.
243     */
244    function exists($key)
245    {
246        return array_key_exists($key, $_SESSION['_prefs'][$this->_ns]['persistent']);
247    }
248
249    /**
250     * Clear a set preference value. This will also remove the value from the database.
251     *
252     * @param string $key       The name of the preference to delete.
253     */
254    function delete($key)
255    {
256        unset($_SESSION['_prefs'][$this->_ns]['persistent'][$key]);
257    }
258
259    /**
260     * Resets the $_SESSION cache. This should be executed with the same consideration
261     * as $auth->clear(), such as when logging out.
262     */
263    function clear($focus='all')
264    {
265        switch ($focus) {
266        case 'all' :
267            $_SESSION['_prefs'][$this->_ns] = array(
268                'loaded' => false,
269                'load_datetime' => '1970-01-01',
270                'defaults' => array(),
271                'persistent' => array(),
272            );
273            break;
274
275        case 'defaults' :
276            $_SESSION['_prefs'][$this->_ns]['defaults'] = array();
277            break;
278
279        case 'persistent' :
280            $_SESSION['_prefs'][$this->_ns]['persistent'] = array();
281            break;
282        }
283    }
284   
285    /*
286    * Retrieves all prefs from the database and stores them in the $_SESSION.
287    *
288    * @access   public
289    * @param    bool    $force  Set to always load from database, regardless if _isLoaded() or not.
290    * @return   bool    True if loading succeeded.
291    * @author   Quinn Comendant <quinn@strangecode.com>
292    * @version  1.0
293    * @since    04 Jun 2006 16:56:53
294    */
295    function load($force=false)
296    {
297        $app =& App::getInstance();
298        $db =& DB::getInstance();
299       
300        // Skip this method if not using the db.
301        if (true !== $this->getParam('persistent')) {
302            return true;
303        }
304
305        $this->initDB();
306
307        // Prefs already loaded for this session.
308        if (!$force && $this->_isLoaded()) {
309            return true;
310        }
311
312        // User_id must not be empty.
313        if ('' == $this->getParam('user_id')) {
314            $app->logMsg(sprintf('Cannot save prefs because user_id not set.', null), LOG_WARNING, __FILE__, __LINE__);
315            return false;
316        }
317       
318        // Clear existing cache.
319        $this->clear('persistent');
320       
321        // Retrieve all prefs for this user and namespace.
322        $qid = $db->query("
323            SELECT pref_key, pref_value
324            FROM " . $db->escapeString($this->getParam('db_table')) . "
325            WHERE user_id = '" . $db->escapeString($this->getParam('user_id')) . "'
326            AND pref_namespace = '" . $db->escapeString($this->_ns) . "'
327            LIMIT 10000
328        ");
329        while (list($key, $val) = mysql_fetch_row($qid)) {
330            $_SESSION['_prefs'][$this->_ns]['persistent'][$key] = unserialize($val);
331        }
332       
333        $app->logMsg(sprintf('Loaded %s prefs from database.', mysql_num_rows($qid)), LOG_DEBUG, __FILE__, __LINE__);
334       
335        // Data loaded only once per session.
336        $_SESSION['_prefs'][$this->_ns]['loaded'] = true;
337        $_SESSION['_prefs'][$this->_ns]['load_datetime'] = date('Y-m-d H:i:s');
338       
339        return true;
340    }
341   
342    /*
343    * Returns true if the prefs had been loaded from the database into the $_SESSION recently.
344    * This function is simply a check so the database isn't access every page load.
345    *
346    * @access   private
347    * @return   bool    True if prefs are loaded.
348    * @author   Quinn Comendant <quinn@strangecode.com>
349    * @version  1.0
350    * @since    04 Jun 2006 17:12:44
351    */
352    function _isLoaded()
353    {
354        if (isset($_SESSION['_prefs'][$this->_ns]['load_datetime'])
355        && strtotime($_SESSION['_prefs'][$this->_ns]['load_datetime']) > time() - $this->getParam('load_timeout')
356        && isset($_SESSION['_prefs'][$this->_ns]['loaded']) 
357        && true === $_SESSION['_prefs'][$this->_ns]['loaded']) {
358            return true;
359        } else {
360            return false;
361        }
362    }
363   
364    /*
365    * Saves all prefs stored in the $_SESSION into the database.
366    *
367    * @access   public
368    * @return   bool    True if prefs exist and were saved.
369    * @author   Quinn Comendant <quinn@strangecode.com>
370    * @version  1.0
371    * @since    04 Jun 2006 17:19:56
372    */
373    function save()
374    {
375        $app =& App::getInstance();
376        $db =& DB::getInstance();
377       
378        // Skip this method if not using the db.
379        if (true !== $this->getParam('persistent')) {
380            return true;
381        }
382       
383        // User_id must not be empty.
384        if ('' == $this->getParam('user_id')) {
385            $app->logMsg(sprintf('Cannot save prefs because user_id not set.', null), LOG_WARNING, __FILE__, __LINE__);
386            return false;
387        }
388
389        $this->initDB();
390
391        if (isset($_SESSION['_prefs'][$this->_ns]['persistent']) && is_array($_SESSION['_prefs'][$this->_ns]['persistent']) && !empty($_SESSION['_prefs'][$this->_ns]['persistent'])) {
392            // Delete old prefs from database.
393            $db->query("
394                DELETE FROM " . $db->escapeString($this->getParam('db_table')) . "
395                WHERE user_id = '" . $db->escapeString($this->getParam('user_id')) . "'
396                AND pref_namespace = '" . $db->escapeString($this->_ns) . "'
397            ");
398           
399            // Insert new prefs.
400            $insert_values = array();
401            foreach ($_SESSION['_prefs'][$this->_ns]['persistent'] as $key => $val) {
402                $insert_values[] = sprintf("('%s', '%s', '%s', '%s')", 
403                    $db->escapeString($this->getParam('user_id')), 
404                    $db->escapeString($this->_ns), 
405                    $db->escapeString($key), 
406                    $db->escapeString(serialize($val))
407                );
408            }
409            // TODO: after MySQL 5.0.23 is released this query could benefit from INSERT DELAYED.
410            $db->query("
411                INSERT INTO " . $db->escapeString($this->getParam('db_table')) . "
412                (user_id, pref_namespace, pref_key, pref_value)
413                VALUES " . join(', ', $insert_values) . "
414            ");
415           
416            $app->logMsg(sprintf('Saved %s prefs to database.', sizeof($insert_values)), LOG_DEBUG, __FILE__, __LINE__);
417            return true;
418        }
419       
420        return false;
421    }
422}
423
424
425?>
Note: See TracBrowser for help on using the repository browser.