Finish Horde_Prefs rewrite
authorMichael M Slusarz <slusarz@curecanti.org>
Mon, 8 Nov 2010 22:49:57 +0000 (15:49 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Mon, 15 Nov 2010 23:41:24 +0000 (16:41 -0700)
Main focus: ability to to stack storage drivers to allow for multiple
drivers to build a preferences scope.

Horde now uses 3 storage drivers to build the final prefs hash: the
configuration file, prefs backend, and hooks.

Caching needed to be separated from storage since the two objects do
*not* store the same data.  Caching is intended to store the entire
preference scope, storage only handles discrete preference values.

Default/dirty bits are now entirely handled internally by the Scope
object.

Remove all external charset conversion API for prefs.  Charset
conversion, if needed, should be done entirely within the storage
driver (prefs now assumes all in-memory prefs are in UTF-8).

31 files changed:
framework/Core/lib/Horde/Core/Factory/Prefs.php
framework/Core/lib/Horde/Core/Prefs.php [deleted file]
framework/Core/lib/Horde/Core/Prefs/Cache/Session.php [new file with mode: 0644]
framework/Core/lib/Horde/Core/Prefs/Storage/Configuration.php [new file with mode: 0644]
framework/Core/lib/Horde/Core/Prefs/Storage/Hooks.php [new file with mode: 0644]
framework/Core/lib/Horde/Core/Prefs/Storage/Session.php [deleted file]
framework/Core/package.xml
framework/Prefs/lib/Horde/Prefs.php
framework/Prefs/lib/Horde/Prefs/Cache.php [new file with mode: 0644]
framework/Prefs/lib/Horde/Prefs/Cache/Null.php [new file with mode: 0644]
framework/Prefs/lib/Horde/Prefs/Cache/Session.php [new file with mode: 0644]
framework/Prefs/lib/Horde/Prefs/Identity.php
framework/Prefs/lib/Horde/Prefs/Scope.php [new file with mode: 0644]
framework/Prefs/lib/Horde/Prefs/Storage.php
framework/Prefs/lib/Horde/Prefs/Storage/File.php
framework/Prefs/lib/Horde/Prefs/Storage/Imsp.php
framework/Prefs/lib/Horde/Prefs/Storage/KolabImap.php
framework/Prefs/lib/Horde/Prefs/Storage/Ldap.php
framework/Prefs/lib/Horde/Prefs/Storage/Null.php
framework/Prefs/lib/Horde/Prefs/Storage/Session.php [deleted file]
framework/Prefs/lib/Horde/Prefs/Storage/Sql.php
framework/Prefs/package.xml
hermes/entry.php
hermes/lib/Application.php
hermes/start.php
horde/config/hooks.php.dist
horde/config/prefs.php.dist
horde/lib/LoginTasks/SystemTask/UpgradeFromHorde3.php [deleted file]
imp/lib/Compose/Stationery.php
imp/lib/LoginTasks/SystemTask/UpgradeFromImp4.php
ingo/lib/Storage/Prefs.php

index 4d0fec0..8d72ead 100644 (file)
@@ -55,20 +55,14 @@ class Horde_Core_Factory_Prefs
      * Return the Horde_Prefs:: instance.
      *
      * @param string $scope  The scope for this set of preferences.
-     * @param array $opts    See Horde_Prefs::__construct(). Additional
-     *                       options:
-     * <pre>
-     * 'session' - (boolean) Use the session driver.
-     *             DEFAULT: false
-     * </pre>
+     * @param array $opts    See Horde_Prefs::__construct().
      *
      * @return Horde_Prefs  The singleton instance.
      */
     public function create($scope = 'horde', array $opts = array())
     {
-        if (empty($GLOBALS['conf']['prefs']['driver']) ||
-            !empty($opts['session'])) {
-            $driver = 'Horde_Core_Prefs_Storage_Session';
+        if (empty($GLOBALS['conf']['prefs']['driver'])) {
+            $driver = null;
             $params = array();
         } else {
             $driver = 'Horde_Prefs_Storage_' . ucfirst($GLOBALS['conf']['prefs']['driver']);
@@ -77,7 +71,6 @@ class Horde_Core_Factory_Prefs
 
         $opts = array_merge(array(
             'cache' => true,
-            'charset' => 'UTF-8',
             'logger' => $this->_injector->getInstance('Horde_Log_Logger'),
             'password' => '',
             'sizecallback' => ((isset($GLOBALS['conf']['prefs']['maxsize'])) ? array($this, 'sizeCallback') : null),
@@ -108,22 +101,31 @@ class Horde_Core_Factory_Prefs
 
             case 'Horde_Prefs_Storage_Sql':
                 $params['db'] = $this->_injector->getInstance('Horde_Db_Adapter');
-                $opts['charset'] = $params['db']->getOption('charset');
                 break;
             }
 
-            $drivers = array(
-                new $driver($opts['user'], $params)
-            );
+            $config_driver = new Horde_Core_Prefs_Storage_Configuration($opts['user']);
+            $hooks_driver = new Horde_Core_Prefs_Storage_Hooks($opts['user'], array('conf_ob' => $config_driver));
+
+            $drivers = $driver
+                ? array(
+                      $config_driver,
+                      new $driver($opts['user'], $params),
+                      $hooks_driver
+                  )
+                : array(
+                      $config_driver,
+                      $hooks_driver
+                  );
 
             if ($opts['cache']) {
-                $opts['cache'] = new Horde_Core_Prefs_Storage_Session($opts['user']);
+                $opts['cache'] = new Horde_Core_Prefs_Cache_Session($opts['user']);
             } else {
                 unset($opts['cache']);
             }
 
             try {
-                $this->_instances[$sig] = new Horde_Core_Prefs($scope, $drivers, $opts);
+                $this->_instances[$sig] = new Horde_Prefs($scope, $drivers, $opts);
             } catch (Horde_Prefs_Exception $e) {
                 if (!$GLOBALS['session']->get('horde', 'no_prefs')) {
                     $GLOBALS['session']->set('horde', 'no_prefs', true);
@@ -131,8 +133,10 @@ class Horde_Core_Factory_Prefs
                         $GLOBALS['notification']->push(Horde_Core_Translation::t("The preferences backend is currently unavailable and your preferences have not been loaded. You may continue to use the system with default preferences."));
                     }
                 }
-                unset($opts['cache']);
-                $this->_instances[$sig] = new Horde_Core_Prefs($scope, new Horde_Core_Prefs_Storage_Session($opts['user']), $opts);
+
+                /* Store data in the cached session object. */
+                $opts['cache'] = new Horde_Core_Prefs_Cache_Session($opts['user']);
+                $this->_instances[$sig] = new Horde_Prefs($scope, array($config_driver, $hooks_driver), $opts);
             }
         }
 
diff --git a/framework/Core/lib/Horde/Core/Prefs.php b/framework/Core/lib/Horde/Core/Prefs.php
deleted file mode 100644 (file)
index 0ff57b4..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-/**
- * The Horde_Core_Prefs class extends the base Horde_Prefs class by adding
- * support for the prefs.php configuration file and Horde hooks.
- *
- * Copyright 2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @package  Core
- */
-class Horde_Core_Prefs extends Horde_Prefs
-{
-    /**
-     * Hash holding preferences with hook functions defined.
-     *
-     * @var array
-     */
-    protected $_hooks = array();
-
-    /**
-     */
-    protected function _setValue($pref, $val, $convert = true)
-    {
-        if (!parent::_setValue($pref, $val, $convert)) {
-            return false;
-        }
-
-        /* If this preference has a change hook, call it now. */
-        try {
-            Horde::callHook('prefs_change', array($pref), $this->_getPrefScope($pref));
-        } catch (Horde_Exception_HookNotSet $e) {}
-
-        return true;
-    }
-
-    /**
-     * Populates the $_scopes hash with the default values as loaded from
-     * an application's prefs.php file.
-     */
-    protected function _loadScopePre($scope)
-    {
-        /* Read the configuration file. The $_prefs array holds the default
-         * values. */
-        try {
-            $result = Horde::loadConfiguration('prefs.php', array('_prefs'), $scope);
-            if (empty($result) || !isset($result['_prefs'])) {
-                return;
-            }
-        } catch (Horde_Exception $e) {
-            return;
-        }
-
-        foreach ($result['_prefs'] as $name => $pref) {
-            if (!isset($pref['value'])) {
-                continue;
-            }
-
-            $name = str_replace('.', '_', $name);
-
-            $mask = self::IS_DEFAULT;
-            if (!empty($pref['locked'])) {
-                $mask |= self::LOCKED;
-            }
-
-            $this->_scopes[$scope][$name] = array(
-                'm' => $mask,
-                'v' => $pref['value']
-            );
-
-            if (!empty($pref['hook'])) {
-                $this->_hooks[$scope][] = $name;
-            }
-        }
-    }
-
-    /**
-     * After preferences have been loaded, set any locked or empty
-     * preferences that have hooks to the result of the hook.
-     */
-    protected function _loadScopePost($scope)
-    {
-        if (empty($this->_hooks[$scope])) {
-            return;
-        }
-
-        foreach ($this->_hooks[$scope] as $name) {
-            if ($this->isLocked($name) ||
-                $this->isDefault($name) ||
-                empty($this->_scopes[$scope][$name]['v'])) {
-                try {
-                    $val = Horde::callHook('prefs_init', array($name, $this->getUser()), $scope);
-                } catch (Horde_Exception_HookNotSet $e) {
-                    continue;
-                }
-
-                $this->_scopes[$scope][$name]['v'] = $this->isDefault($name)
-                    ? $val
-                    : $this->convertToDriver($val);
-
-                if (!$this->isLocked($name)) {
-                    $this->setDirty($pref, true);
-                }
-            }
-        }
-    }
-
-}
diff --git a/framework/Core/lib/Horde/Core/Prefs/Cache/Session.php b/framework/Core/lib/Horde/Core/Prefs/Cache/Session.php
new file mode 100644 (file)
index 0000000..6bb8fdb
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Cache storage implementation using Horde_Session.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Core
+ */
+class Horde_Core_Prefs_Cache_Session extends Horde_Prefs_Cache
+{
+    const SESS_KEY = 'prefs_cache/';
+
+    /**
+     */
+    public function get($scope)
+    {
+        global $session;
+
+        return $session->exists('horde', self::SESS_KEY . $scope)
+            ? $session->get('horde', self::SESS_KEY . $scope)
+            : false;
+    }
+
+    /**
+     */
+    public function store($scope_ob)
+    {
+        $GLOBALS['session']->set('horde', self::SESS_KEY . $scope_ob->scope, $scope_ob);
+    }
+
+    /**
+     */
+    public function remove($scope = null)
+    {
+        $GLOBALS['session']->remove('horde', self::SESS_KEY . strval($scope));
+    }
+
+}
diff --git a/framework/Core/lib/Horde/Core/Prefs/Storage/Configuration.php b/framework/Core/lib/Horde/Core/Prefs/Storage/Configuration.php
new file mode 100644 (file)
index 0000000..d3f5a53
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Preferences storage implementation that loads the default values from
+ * the configuration files.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Core
+ */
+class Horde_Core_Prefs_Storage_Configuration extends Horde_Prefs_Storage
+{
+    /**
+     * The list of preference hooks defined.
+     *
+     * @var array
+     */
+    public $hooks = array();
+
+    /**
+     */
+    public function get($scope_ob)
+    {
+        /* Read the configuration file.
+         * Values are in the $_prefs array. */
+        try {
+            $result = Horde::loadConfiguration('prefs.php', array('_prefs'), $scope_ob->scope);
+            if (empty($result) || !isset($result['_prefs'])) {
+                return false;
+            }
+        } catch (Horde_Exception $e) {
+            return false;
+        }
+
+        foreach ($result['_prefs'] as $name => $pref) {
+            if (!isset($pref['value'])) {
+                continue;
+            }
+
+            $scope_ob->set($name, $pref['value']);
+            if (!empty($pref['locked'])) {
+                $scope_ob->setLocked($name, true);
+            }
+
+            if (!empty($pref['hook'])) {
+                $this->hooks[$scope_ob->scope][] = $name;
+            }
+        }
+
+        return $scope_ob;
+    }
+
+    /**
+     */
+    public function store($scope_ob)
+    {
+        // Configuration files are read-only.
+    }
+
+    /**
+     */
+    public function remove($scope = null, $pref = null)
+    {
+        // Configuration files are read-only.
+    }
+
+}
diff --git a/framework/Core/lib/Horde/Core/Prefs/Storage/Hooks.php b/framework/Core/lib/Horde/Core/Prefs/Storage/Hooks.php
new file mode 100644 (file)
index 0000000..6332abe
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Preferences storage implementation that adds support for Horde hooks to
+ * manipulate preference values.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Core
+ */
+class Horde_Core_Prefs_Storage_Hooks extends Horde_Prefs_Storage
+{
+    /**
+     */
+    public function get($scope_ob)
+    {
+        $conf_ob = $this->_params['conf_ob'];
+
+        if (empty($conf_ob->hooks[$scope_ob->scope])) {
+            return $scope_ob;
+        }
+
+        foreach ($conf_ob->_hooks[$scope_ob->scope] as $name) {
+            try {
+                $scope_ob->set($name, Horde::callHook('prefs_init', array($name, $this->_params['user']), $scope_ob->scope));
+            } catch (Horde_Exception_HookNotSet $e) {}
+        }
+
+        return $scope_ob;
+    }
+
+    /**
+     */
+    public function store($scope_ob)
+    {
+        // Hooks are read-only.
+    }
+
+    /**
+     */
+    public function onChange($scope, $pref)
+    {
+        try {
+            Horde::callHook('prefs_change', array($pref), $scope);
+        } catch (Horde_Exception_HookNotSet $e) {}
+    }
+
+    /**
+     */
+    public function remove($scope = null, $pref = null)
+    {
+        // Hooks are read-only.
+    }
+
+}
diff --git a/framework/Core/lib/Horde/Core/Prefs/Storage/Session.php b/framework/Core/lib/Horde/Core/Prefs/Storage/Session.php
deleted file mode 100644 (file)
index 47040a6..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/**
- * Preferences session storage implementation using Horde_Session.
- *
- * Copyright 2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @package  Core
- */
-class Horde_Core_Prefs_Storage_Session extends Horde_Prefs_Storage
-{
-    const SESS_KEY = 'prefs_session/';
-
-    /**
-     */
-    public function get($scope)
-    {
-        global $session;
-
-        return $session->exists('horde', self::SESS_KEY . $scope)
-            ? $session->get('horde', self::SESS_KEY . $scope)
-            : false;
-    }
-
-    /**
-     */
-    public function store($prefs)
-    {
-        foreach ($prefs as $scope => $vals) {
-            if (($old_vals = $this->get($scope)) === false) {
-                $old_vals = array();
-            }
-            $GLOBALS['session']->set('horde', self::SESS_KEY . $scope, array_merge($old_vals, $vals));
-        }
-    }
-
-    /**
-     */
-    public function remove($scope = null, $pref = null)
-    {
-        global $session;
-
-        if (is_null($scope)) {
-            $session->remove('horde', self::SESS_KEY);
-        } elseif (is_null($pref)) {
-            $session->remove('horde', self::SESS_KEY . $this->_scope);
-        } elseif ((($vals = $this->get($scope)) !== false) &&
-                  isset($vals[$pref])) {
-            unset($vals[$pref]);
-            $session->set('horde', self::SESS_KEY . $scope, $vals);
-        }
-    }
-
-}
index 17f49b0..47c3b0b 100644 (file)
@@ -195,8 +195,12 @@ Application Framework.</description>
        <file name="Ui.php" role="php" />
       </dir> <!-- /lib/Horde/Core/Perms -->
       <dir name="Prefs">
-       <dir name="Storage">
+       <dir name="Cache">
         <file name="Session.php" role="php" />
+       </dir> <!-- /lib/Horde/Core/Prefs/Cache -->
+       <dir name="Storage">
+        <file name="Configuration.php" role="php" />
+        <file name="Hooks.php" role="php" />
        </dir> <!-- /lib/Horde/Core/Prefs/Storage -->
        <dir name="Ui">
         <file name="Widgets.php" role="php" />
@@ -237,7 +241,6 @@ Application Framework.</description>
        <file name="Widget.php" role="php" />
       </dir> <!-- /lib/Horde/Core/Ui -->
       <file name="Browser.php" role="php" />
-      <file name="Prefs.php" role="php" />
       <file name="Sidebar.php" role="php" />
       <file name="Translation.php" role="php">
        <tasks:replace from="@data_dir@" to="data_dir" type="pear-config" />
@@ -712,7 +715,6 @@ Application Framework.</description>
    <install as="Horde/Themes.php" name="lib/Horde/Themes.php" />
    <install as="Horde/Config/Form.php" name="lib/Horde/Config/Form.php" />
    <install as="Horde/Core/Browser.php" name="lib/Horde/Core/Browser.php" />
-   <install as="Horde/Core/Prefs.php" name="lib/Horde/Core/Prefs.php" />
    <install as="Horde/Core/Sidebar.php" name="lib/Horde/Core/Sidebar.php" />
    <install as="Horde/Core/Translation.php" name="lib/Horde/Core/Translation.php" />
    <install as="Horde/Core/ActiveSync/Connector.php" name="lib/Horde/Core/ActiveSync/Connector.php" />
@@ -796,7 +798,9 @@ Application Framework.</description>
    <install as="Horde/Core/Perms/Ui.php" name="lib/Horde/Core/Perms/Ui.php" />
    <install as="Horde/Core/Prefs/Identity.php" name="lib/Horde/Core/Prefs/Identity.php" />
    <install as="Horde/Core/Prefs/Ui.php" name="lib/Horde/Core/Prefs/Ui.php" />
-   <install as="Horde/Core/Prefs/Storage/Session.php" name="lib/Horde/Core/Prefs/Storage/Session.php" />
+   <install as="Horde/Core/Prefs/Cache/Session.php" name="lib/Horde/Core/Prefs/Cache/Session.php" />
+   <install as="Horde/Core/Prefs/Storage/Configuration.php" name="lib/Horde/Core/Prefs/Storage/Configuration.php" />
+   <install as="Horde/Core/Prefs/Storage/Hooks.php" name="lib/Horde/Core/Prefs/Storage/Hooks.php" />
    <install as="Horde/Core/Prefs/Ui/Widgets.php" name="lib/Horde/Core/Prefs/Ui/Widgets.php" />
    <install as="Horde/Core/Share/Driver.php" name="lib/Horde/Core/Share/Driver.php" />
    <install as="Horde/Core/Share/FactoryCallback.php" name="lib/Horde/Core/Share/FactoryCallback.php" />
index d911c1b..c5cd30b 100644 (file)
  */
 class Horde_Prefs implements ArrayAccess
 {
-    /** Preference is administratively locked. */
-    const LOCKED = 1;
-
-    /** Preference value is the application default.
-     *  DEFAULT is a reserved PHP constant. */
-    const IS_DEFAULT = 2;
-
     /* The default scope name. */
     const DEFAULT_SCOPE = 'horde';
 
     /**
      * Caching object.
      *
-     * @var Horde_Prefs_Storage
+     * @var Horde_Prefs_Cache
      */
     protected $_cache;
 
     /**
-     * List of dirty prefs.
-     *
-     * @var array
-     */
-    protected $_dirty = array();
-
-    /**
      * General library options.
      *
      * @var array
@@ -56,23 +42,16 @@ class Horde_Prefs implements ArrayAccess
 
     /**
      * String containing the name of the current scope. This is used
-     * to differentiate between sets of preferences.  By default, preferences
-     * belong to the "global" (Horde) scope.
+     * to differentiate between sets of preferences. By default, preferences
+     * belong to this scope.
      *
      * @var string
      */
     protected $_scope = self::DEFAULT_SCOPE;
 
     /**
-     * Preferences list.  Stored by scope name.  Each preference has the
-     * following format:
-     * <pre>
-     * [pref_name] => array(
-     *     [d] => (string) Default value
-     *     [m] => (integer) Pref mask
-     *     [v] => (string) Current pref value
-     * )
-     * </pre>
+     * Scope list.  Keys are scope names, values are Horde_Prefs_Scope
+     * objects.
      *
      * @var array
      */
@@ -94,14 +73,8 @@ class Horde_Prefs implements ArrayAccess
      *                        objects.
      * @param array $opts     Additional confguration options:
      * <pre>
-     * REQUIRED:
-     * ---------
-     * charset - (string) Default charset.
-     *
-     * OPTIONAL:
-     * ---------
-     * cache - (string) The class name defining the cache driver to use.
-     *         DEFAULT: Caching is not used
+     * cache - (Horde_Prefs_Cache) The cache driver to use.
+     *         DEFAULT: No caching.
      * logger - (Horde_Log_Logger) Logging object.
      *          DEFAULT: NONE
      * password - (string) The password associated with 'user'.
@@ -112,31 +85,23 @@ class Horde_Prefs implements ArrayAccess
      * user - (string) The name of the user who owns this set of preferences.
      *        DEFAULT: NONE
      * </pre>
-     *
-     * @throws InvalidArgumentException
      */
     public function __construct($scope, $storage = null, array $opts = array())
     {
-        if (!isset($opts['charset'])) {
-            throw new InvalidArgumentException(__CLASS__ . ': Missing charset parameter.');
-        }
-
         $this->_opts = array_merge($this->_opts, $opts);
 
-        $default = __CLASS__ . '_Storage_Null';
-
         $this->_cache = isset($this->_opts['cache'])
             ? $this->_opts['cache']
-            : new $default($this->getUser());
+            : new Horde_Prefs_Cache_Null($this->getUser());
+
         $this->_scope = $scope;
+
         if (is_null($storage)) {
-            $this->_storage = array(new $default($this->getUser()));
-        } else {
-            if (!is_array($storage)) {
-                $storage = array($storage);
-            }
-            $this->_storage = $storage;
+            $storage = array(new Horde_Prefs_Storage_Null($this->getUser()));
+        } elseif (!is_array($storage)) {
+            $storage = array($storage);
         }
+        $this->_storage = $storage;
 
         register_shutdown_function(array($this, 'store'));
 
@@ -144,16 +109,6 @@ class Horde_Prefs implements ArrayAccess
     }
 
     /**
-     * Returns the charset used by the concrete preference backend.
-     *
-     * @return string  The preference backend's charset.
-     */
-    public function getCharset()
-    {
-        return $this->_opts['charset'];
-    }
-
-    /**
      * Return the user who owns these preferences.
      *
      * @return string  The user these preferences are for.
@@ -174,40 +129,14 @@ class Horde_Prefs implements ArrayAccess
     }
 
     /**
-     * Change scope without explicitly retrieving preferences.
-     *
-     * @param string $scope  The new scope.
-     */
-    public function setScope($scope)
-    {
-        $this->_scope = $scope;
-    }
-
-    /**
      * Removes a preference entry from the $prefs hash.
      *
      * @param string $pref  The name of the preference to remove.
      */
     public function remove($pref)
     {
-        $scope = $this->_getPrefScope($pref);
-        unset(
-            $this->_dirty[$scope][$pref],
-            $this->_scopes[$scope][$pref]
-        );
-
-        foreach ($this->_storage as $storage) {
-            try {
-                $storage->remove($scope, $pref);
-            } catch (Horde_Prefs_Exception $e) {
-                // TODO: logging
-            }
-        }
-
-        try {
-            $this->_cache->remove($scope, $pref);
-        } catch (Horde_Prefs_Exception $e) {
-            // TODO: logging
+        if ($scope = $this->_getScope($pref)) {
+            $this->_scopes[$scope]->remove($pref);
         }
     }
 
@@ -215,96 +144,40 @@ class Horde_Prefs implements ArrayAccess
      * Sets the given preference to the specified value if the preference is
      * modifiable.
      *
-     * @param string $pref      The name of the preference to modify.
-     * @param string $val       The new value for this preference.
-     * @param boolean $convert  If true the preference value gets converted
-     *                          from the current charset to the backend's
-     *                          charset.
+     * @param string $pref  The preference name to modify.
+     * @param string $val   The preference value (UTF-8).
+     * @param array $opts   Additional options:
+     * <pre>
+     * nosave - (boolean) If true, the preference will not be saved to the
+     *          storage backend(s).
+     * </pre>
      *
      * @return boolean  True if the value was successfully set, false on a
      *                  failure.
      * @throws Horde_Prefs_Exception
      */
-    public function setValue($pref, $val, $convert = true)
+    public function setValue($pref, $val, array $opts = array())
     {
-        $scope = $this->_getPrefScope($pref);
-
-        /* Exit early if this preference is locked or doesn't exist. */
-        if (!isset($this->_scopes[$scope][$pref]) || $this->isLocked($pref)) {
+        /* Exit early if preference doesn't exist or is locked. */
+        if (!($scope = $this->_getScope($pref)) ||
+            $this->_scopes[$scope]->isLocked($pref)) {
             return false;
         }
 
-        if (!$this->_setValue($pref, $val, $convert)) {
-            return false;
-        }
-
-        $this->_cache->store(array(
-            $scope => array(
-                $pref => $this->_scopes[$scope][$pref]
-            )
-        ));
-
-        return true;
-    }
-
-    /**
-     * Shortcut to setValue().
-     */
-    public function __set($name, $value)
-    {
-        return $this->setValue($name, $value);
-    }
-
-    /**
-     * Sets the given preferences ($pref) to the specified value
-     * ($val), whether or not the preference is user-modifiable, unset
-     * the default bit, and set the dirty bit.
-     *
-     * @param string $pref      The name of the preference to modify.
-     * @param string $val       The new value for this preference.
-     * @param boolean $convert  If true the preference value gets converted
-     *                          from the current charset to the backend's
-     *                          charset.
-     *
-     * @return boolean  True if the value was successfully set, false on a
-     *                  failure.
-     */
-    protected function _setValue($pref, $val, $convert = true)
-    {
-        if ($convert) {
-            $val = $this->convertToDriver($val);
-        }
-
-        $scope = $this->_getPrefScope($pref);
-
-        // If the preference's value is already equal to $val, don't
-        // bother changing it. Changing it would set the "dirty" bit,
-        // causing an unnecessary update later.
-        if (isset($this->_scopes[$scope][$pref]) &&
-            (($this->_scopes[$scope][$pref]['v'] == $val) &&
-             !$this->isDefault($pref))) {
-            return true;
-        }
-
-        // Check to see if the value exceeds the allowable storage
-        // limit.
+        // Check to see if the value exceeds the allowable storage limit.
         if ($this->_opts['sizecallback'] &&
             call_user_func($this->_opts['sizecallback'], $pref, strlen($val))) {
             return false;
         }
 
-        // Assign the new value, unset the "default" bit, and set the
-        // "dirty" bit.
-        $ptr = &$this->_scopes[$scope][$pref];
-        if (empty($ptr['m'])) {
-            $ptr['m'] = 0;
+        $this->_scopes[$scope]->set($pref, $val);
+        if (!empty($opts['nosave'])) {
+            $this->_scopes[$scope]->setDirty($pref, false);
         }
-        if (!isset($ptr['d'])) {
-            $ptr['d'] = $ptr['v'];
+
+        foreach ($this->_storage as $storage) {
+            $storage->onChange($scope, $pref);
         }
-        $ptr['v'] = $val;
-        $this->setDefault($pref, false);
-        $this->setDirty($pref, true);
 
         if ($this->_opts['logger']) {
             $this->_opts['logger']->log(__CLASS__ . ': Storing preference value (' . $pref . ')', 'DEBUG');
@@ -314,32 +187,26 @@ class Horde_Prefs implements ArrayAccess
     }
 
     /**
+     * Shortcut to setValue().
+     */
+    public function __set($name, $value)
+    {
+        return $this->setValue($name, $value);
+    }
+
+    /**
      * Returns the value of the requested preference.
      *
-     * @param string $pref      The name of the preference to retrieve.
-     * @param boolean $convert  If true the preference value gets converted
-     *                          from the backend's charset to the current
-     *                          charset.
+     * @param string $pref  The preference name.
      *
-     * @return string  The value of the preference, null if it doesn't exist.
+     * @return string  The value of the preference (UTF-8), null if it doesn't
+     *                 exist.
      */
-    public function getValue($pref, $convert = true)
+    public function getValue($pref)
     {
-        $scope = $this->_getPrefScope($pref);
-
-        $value = isset($this->_scopes[$scope][$pref]['v'])
-            ? $this->_scopes[$scope][$pref]['v']
+        return ($scope = $this->_getScope($pref))
+            ? $this->_scopes[$scope]->get($pref)
             : null;
-
-        if ($convert &&
-            !is_null($value) &&
-            !$this->isDefault($pref)) {
-            /* Default values have the current UI charset.
-             * Stored values have the backend charset. */
-            $value = $this->convertFromDriver($value);
-        }
-
-        return $value;
     }
 
     /**
@@ -351,67 +218,44 @@ class Horde_Prefs implements ArrayAccess
     }
 
     /**
-     * Modifies the "locked" bit for the given preference.
+     * Mark a preference as locked.
      *
-     * @param string $pref   The name of the preference to modify.
-     * @param boolean $bool  The new boolean value for the "locked" bit.
+     * @param string $pref     The preference name.
+     * @param boolean $locked  Is the preference locked?
      */
     public function setLocked($pref, $bool)
     {
-        $this->_setMask($pref, $bool, self::LOCKED);
+        if ($scope = $this->_getScope($pref)) {
+            $this->_scopes[$scope]->setLocked($pref, $bool);
+        }
     }
 
     /**
-     * Returns the state of the "locked" bit for the given preference.
+     * Is a preference locked?
      *
-     * @param string $pref  The name of the preference to check.
+     * @param string $pref  The preference name.
      *
-     * @return boolean  The boolean state of $pref's "locked" bit.
+     * @return boolean  Whether the preference is locked.
      */
     public function isLocked($pref)
     {
-        return $this->_getMask($pref, self::LOCKED);
-    }
-
-    /**
-     * Modifies the "dirty" bit for the given preference.
-     *
-     * @param string $pref   The name of the preference to modify.
-     * @param boolean $bool  The new boolean value for the "dirty" bit.
-     */
-    public function setDirty($pref, $bool)
-    {
-        $scope = $this->_getPrefScope($pref);
-
-        if ($bool) {
-            $this->_dirty[$scope][$pref] = $this->_scopes[$scope][$pref];
-        } else {
-            unset($this->_dirty[$scope][$pref]);
-        }
+        return ($scope = $this->_getScope($pref))
+            ? $this->_scopes[$scope]->isLocked($pref)
+            : false;
     }
 
     /**
-     * Returns the state of the "dirty" bit for the given preference.
+     * Is a preference marked dirty?
      *
-     * @param string $pref  The name of the preference to check.
+     * @param string $pref  The preference name.
      *
-     * @return boolean  The boolean state of $pref's "dirty" bit.
+     * @return boolean  True if the preference is marked dirty.
      */
     public function isDirty($pref)
     {
-        $scope = $this->_getPrefScope($pref);
-        return isset($this->_dirty[$scope][$pref]);
-    }
-
-    /**
-     * Modifies the "default" bit for the given preference.
-     *
-     * @param string $pref   The name of the preference to modify.
-     * @param boolean $bool  The new boolean value for the "default" bit.
-     */
-    public function setDefault($pref, $bool)
-    {
-        $this->_setMask($pref, $bool, self::IS_DEFAULT);
+        return ($scope = $this->_getScope($pref))
+            ? $this->_scopes[$scope]->isDirty($pref)
+            : false;
     }
 
     /**
@@ -423,11 +267,9 @@ class Horde_Prefs implements ArrayAccess
      */
     public function getDefault($pref)
     {
-        $scope = $this->_getPrefScope($pref);
-
-        return isset($this->_scopes[$scope][$pref]['d'])
-            ? $this->_scopes[$scope][$pref]['d']
-            : '';
+        return ($scope = $this->_getScope($pref))
+            ? $this->_scopes[$scope]->getDefault($pref)
+            : null;
     }
 
     /**
@@ -440,59 +282,29 @@ class Horde_Prefs implements ArrayAccess
      */
     public function isDefault($pref)
     {
-        return $this->_getMask($pref, self::IS_DEFAULT);
-    }
-
-    /**
-     * Sets the value for a given mask.
-     *
-     * @param string $pref   The name of the preference to modify.
-     * @param boolean $bool  The new boolean value for the "default" bit.
-     * @param integer $mask  The mask to add.
-     */
-    protected function _setMask($pref, $bool, $mask)
-    {
-        $scope = $this->_getPrefScope($pref);
-
-        if (isset($this->_scopes[$scope][$pref]) &&
-            ($bool != $this->_getMask($pref, $mask))) {
-            if ($bool) {
-                $this->_scopes[$scope][$pref]['m'] |= $mask;
-            } else {
-                $this->_scopes[$scope][$pref]['m'] &= ~$mask;
-            }
-        }
-    }
-
-    /**
-     * Gets the boolean state for a given mask.
-     *
-     * @param string $pref   The name of the preference to modify.
-     * @param integer $mask  The mask to get.
-     *
-     * @return boolean  The boolean state for the given mask.
-     */
-    protected function _getMask($pref, $mask)
-    {
-        $scope = $this->_getPrefScope($pref);
-
-        return isset($this->_scopes[$scope][$pref]['m'])
-            ? (bool)($this->_scopes[$scope][$pref]['m'] & $mask)
+        return ($scope = $this->_getScope($pref))
+            ? $this->_scopes[$scope]->isDefault($pref)
             : false;
     }
 
     /**
-     * Returns the scope of the given preference.
+     * Returns the scope of a preference.
      *
-     * @param string $pref  The name of the preference to examine.
+     * @param string $pref  The preference name.
      *
-     * @return string  The scope of the $pref.
+     * @return mixed  The scope of the preference, or null if it doesn't
+     *                exist.
      */
-    protected function _getPrefScope($pref)
+    protected function _getScope($pref)
     {
-        return (isset($this->_scopes[$this->_scope][$pref]) || !isset($this->_scopes[self::DEFAULT_SCOPE][$pref]))
-            ? $this->_scope
-            : self::DEFAULT_SCOPE;
+        if ($this->_scopes[$this->_scope]->exists($pref)) {
+            return $this->_scope;
+        } elseif (($this->_scope != self::DEFAULT_SCOPE) &&
+            ($this->_scopes[self::DEFAULT_SCOPE]->exists($pref))) {
+            return self::DEFAULT_SCOPE;
+        }
+
+        return null;
     }
 
     /**
@@ -506,7 +318,7 @@ class Horde_Prefs implements ArrayAccess
         if (is_null($scope)) {
             $scope = $this->getScope();
         } else {
-            $this->setScope($scope);
+            $this->_scope = $scope;
         }
 
         $this->_loadScope(self::DEFAULT_SCOPE);
@@ -527,9 +339,6 @@ class Horde_Prefs implements ArrayAccess
             return;
         }
 
-        // Basic initialization so _something_ is always set.
-        $this->_scopes[$scope] = array();
-
         // Now check the prefs cache for existing values.
         try {
             if (($cached = $this->_cache->get($scope)) !== false) {
@@ -538,127 +347,57 @@ class Horde_Prefs implements ArrayAccess
             }
         } catch (Horde_Prefs_Exception $e) {}
 
-        $this->_loadScopePre($scope);
+        $scope_ob = new Horde_Prefs_Scope($scope);
+        $scope_ob->init = true;
 
         foreach ($this->_storage as $storage) {
-            if (($prefs = $storage->get($scope)) !== false) {
-                foreach ($prefs as $name => $val) {
-                    if (isset($this->_scopes[$scope][$name])) {
-                        if ($this->isDefault($name)) {
-                            $this->_scopes[$scope][$name]['d'] = $this->_scopes[$scope][$name]['v'];
-                        }
-                    } else {
-                        $this->_scopes[$scope][$name] = array(
-                            'm' => 0
-                        );
-                    }
-                    $this->_scopes[$scope][$name]['v'] = $val;
-                    $this->setDefault($name, false);
-                }
-            }
+            $scope_ob = $storage->get($scope_ob);
         }
 
-        $this->_loadScopePost($scope);
-
-        /* Update the cache. */
-        $this->_cache->store(array($scope => $this->_scopes[$scope]));
-    }
-
-    /**
-     * Actions to perform before a scope is loaded from storage.
-     *
-     * @param string $scope  The scope to load.
-     */
-    protected function _loadScopePre($scope)
-    {
-    }
+        $scope_ob->init = false;
 
-    /**
-     * Actions to perform after a scope is loaded from storage.
-     *
-     * @param string $scope  The loaded scope.
-     */
-    protected function _loadScopePost($scope)
-    {
+        $this->_scopes[$scope] = $scope_ob;
+        $this->_cache->store($scope_ob);
     }
 
     /**
      * This function will be run at the end of every request as a shutdown
-     * function (registered by the constructor).  All prefs with the
-     * dirty bit set will be saved to the storage backend.
+     * function (registered by the constructor).  All dirty prefs will be
+     * saved to the storage backend.
      */
     public function store()
     {
-        if (!empty($this->_dirty)) {
-            foreach ($this->_storage as $storage) {
-                try {
-                    $storage->store($this->_dirty);
-
-                    /* Clear the dirty flag. */
-                    foreach ($this->_dirty as $k => $v) {
-                        foreach (array_keys($v) as $name) {
-                            $this->setDirty($name, false);
-                        }
-                    }
-                } catch (Horde_Prefs_Exception $e) {}
+        foreach ($this->_scopes as $scope) {
+            if ($scope->isDirty()) {
+                foreach ($this->_storage as $storage) {
+                    $storage->store($scope);
+                }
+
+                $this->_cache->store($scope);
             }
         }
     }
 
     /**
-     * This function provides common cleanup functions for all of the driver
-     * implementations.
+     * Cleanup (e.g. remove) scope(s).
      *
-     * @param boolean $all  Clean up all Horde preferences.
+     * @param boolean $all  Cleanup all scopes. If false, clean present scope
+     *                      only.
      */
     public function cleanup($all = false)
     {
-        /* Perform a Horde-wide cleanup? */
         if ($all) {
-            /* Destroy the contents of the preferences hash. */
-            $this->_dirty = $this->_scopes = array();
-
-            /* Destroy the contents of the preferences cache. */
-            try {
-                $this->_cache->remove();
-            } catch (Horde_Prefs_Exception $e) {}
+            /* Destroy all scopes. */
+            $this->_scopes = array();
+            $scope = null;
         } else {
-            $scope = $this->getScope();
-
-            $this->_dirty[$scope] = $this->_scopes[$scope] = array();
-            /* Remove this scope from the preferences cache. */
-            try {
-                $this->_cache->remove($scope);
-            } catch (Horde_Prefs_Exception $e) {}
+            unset($this->_scopes[$this->_scope]);
+            $scope = $this->_scope;
         }
-    }
 
-    /**
-     * Converts a value from the driver's charset to the specified charset.
-     *
-     * @param mixed $value  A value to convert.
-     *
-     * @return mixed  The converted value.
-     */
-    public function convertFromDriver($value)
-    {
-        return is_bool($value)
-            ? $value
-            : Horde_String::convertCharset($value, $this->getCharset(), 'UTF-8');
-    }
-
-    /**
-     * Converts a value from the specified charset to the driver's charset.
-     *
-     * @param mixed $value  A value to convert.
-     *
-     * @return mixed  The converted value.
-     */
-    public function convertToDriver($value)
-    {
-        return is_bool($value)
-            ? $value
-            : Horde_String::convertCharset($value, 'UTF-8', $this->getCharset());
+        try {
+            $this->_cache->remove($scope);
+        } catch (Horde_Prefs_Exception $e) {}
     }
 
     /* ArrayAccess methods. */
diff --git a/framework/Prefs/lib/Horde/Prefs/Cache.php b/framework/Prefs/lib/Horde/Prefs/Cache.php
new file mode 100644 (file)
index 0000000..89e84ad
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Cache driver for the preferences system.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Prefs
+ */
+abstract class Horde_Prefs_Cache
+{
+    /**
+     * Configuration parameters.
+     * 'user' is always available as an entry.
+     *
+     * @var string
+     */
+    protected $_params = array();
+
+    /**
+     * Constructor.
+     *
+     * @param string $user   The username.
+     * @param array $params  Additional configuration parameters.
+     */
+    public function __construct($user, array $params = array())
+    {
+        $this->_params = array_merge($this->_params, $params);
+        $this->_params['user'] = $user;
+    }
+
+    /**
+     * Retrieves the requested preferences scope from the cache backend.
+     *
+     * @param string $scope  Scope specifier.
+     *
+     * @return mixed  Returns false if no data is available, otherwise the
+     *                Horde_Prefs_Scope object.
+     * @throws Horde_Prefs_Exception
+     */
+    abstract public function get($scope);
+
+    /**
+     * Stores preferences in the cache backend.
+     *
+     * @param Horde_Prefs_Scope $scope_ob  The scope object to store.
+     *
+     * @throws Horde_Prefs_Exception
+     */
+    abstract public function store($scope_ob);
+
+    /**
+     * Removes preferences from the cache.
+     *
+     * @param string $scope  The scope to remove. If null, clears entire
+     *                       cache.
+     *
+     * @throws Horde_Prefs_Exception
+     */
+    abstract public function remove($scope = null);
+
+}
diff --git a/framework/Prefs/lib/Horde/Prefs/Cache/Null.php b/framework/Prefs/lib/Horde/Prefs/Cache/Null.php
new file mode 100644 (file)
index 0000000..cfe6562
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Null cache implementation for the preferences system.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Prefs
+ */
+class Horde_Prefs_Cache_Null extends Horde_Prefs_Cache
+{
+    /**
+     */
+    public function get($scope)
+    {
+        return false;
+    }
+
+    /**
+     */
+    public function store($scope_ob)
+    {
+    }
+
+    /**
+     */
+    public function remove($scope = null)
+    {
+    }
+
+}
diff --git a/framework/Prefs/lib/Horde/Prefs/Cache/Session.php b/framework/Prefs/lib/Horde/Prefs/Cache/Session.php
new file mode 100644 (file)
index 0000000..0d2cb6a
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Session cache implementation for the preferences system.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Prefs
+ */
+class Horde_Prefs_Cache_Session extends Horde_Prefs_Cache
+{
+    /**
+     * Session key.
+     *
+     * @var string
+     */
+    protected $_key;
+
+    /**
+     */
+    public function __construct($user, array $params = array())
+    {
+        parent::__construct($user, $params);
+
+        $this->_key = 'horde_prefs_cache_' . $this->_params['user'];
+    }
+
+    /**
+     */
+    public function get($scope)
+    {
+        return isset($_SESSION[$this->_key][$scope])
+            ? $_SESSION[$this->_key][$scope]
+            : false;
+    }
+
+    /**
+     */
+    public function store($scope_ob)
+    {
+        $_SESSION[$this->_key][$scope_ob->getScope()] = $scope_ob;
+    }
+
+    /**
+     */
+    public function remove($scope = null)
+    {
+        if (is_null($scope)) {
+            unset($_SESSION[$this->_key]);
+        } else {
+            unset($_SESSION[$this->_key][$scope]);
+        }
+    }
+
+}
index b4ce7bf..c8b5bdf 100644 (file)
@@ -91,10 +91,8 @@ class Horde_Prefs_Identity
         $this->_prefs = $params['prefs'];
         $this->_user = $params['user'];
 
-        if (!($this->_identities = @unserialize($this->_prefs->getValue($this->_prefnames['identities'], false)))) {
+        if (!($this->_identities = @unserialize($this->_prefs->getValue($this->_prefnames['identities'])))) {
             $this->_identities = $this->_prefs->getDefault($this->_prefnames['identities']);
-        } elseif (is_array($this->_identities)) {
-            $this->_identities = $this->_prefs->convertFromDriver($this->_identities);
         }
 
         $this->setDefault($this->_prefs->getValue($this->_prefnames['default_identity']));
@@ -124,8 +122,7 @@ class Horde_Prefs_Identity
                 if (is_array($value)) {
                     $value = implode("\n", $value);
                 }
-                $this->_prefs->setValue($key, $value);
-                $this->_prefs->setDirty($key, false);
+                $this->_prefs->setValue($key, $value, array('nosave' => true));
             }
         }
     }
diff --git a/framework/Prefs/lib/Horde/Prefs/Scope.php b/framework/Prefs/lib/Horde/Prefs/Scope.php
new file mode 100644 (file)
index 0000000..0bf1c27
--- /dev/null
@@ -0,0 +1,271 @@
+<?php
+/**
+ * This class provides the storage for a preference scope.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Prefs
+ */
+class Horde_Prefs_Scope implements Serializable
+{
+    /**
+     * Is the object being initialized?
+     *
+     * @var boolean
+     */
+    public $init = false;
+
+    /**
+     * The scope name.
+     *
+     * @var string
+     */
+    public $scope;
+
+    /**
+     * List of dirty prefs.
+     *
+     * @var array
+     */
+    protected $_dirty = array();
+
+    /**
+     * Preferences list.  Each preference has the following format:
+     * <pre>
+     * [pref_name] => array(
+     *     [d] => (string) Default value
+     *            If not present, 'v' is the default value.
+     *     [l] => (boolean) Locked
+     *            If not present, pref is not locked.
+     *     [v] => (string) Current pref value
+     * )
+     * </pre>
+     *
+     * @var array
+     */
+    protected $_prefs = array();
+
+    /**
+     * Constructor.
+     *
+     * @param string $scope  The scope for this set of preferences.
+     */
+    public function __construct($scope)
+    {
+        $this->scope = $scope;
+    }
+
+    /**
+     * Removes a preference entry.
+     *
+     * @param string $pref  The name of the preference to remove.
+     *
+     * @return boolean  True if preference was removed.
+     */
+    public function remove($pref)
+    {
+        if (!isset($this->_prefs[$pref])) {
+            return false;
+        }
+
+        unset($this->_prefs[$pref]);
+        if (!$this->init) {
+            $this->setDirty($pref, true);
+        }
+
+        return true;
+    }
+
+    /**
+     * Sets the value for a preference.
+     *
+     * @param string $pref  The preference name.
+     * @param string $val   The preference value.
+     */
+    public function set($pref, $val)
+    {
+        if (isset($this->_prefs[$pref])) {
+            $ptr = &$this->_prefs[$pref];
+
+            if ($val != $ptr['v']) {
+                if (isset($ptr['d']) && ($val == $ptr['d'])) {
+                    unset($ptr['d']);
+                } else {
+                    $ptr['d'] = $ptr['v'];
+                }
+                $ptr['v'] = $val;
+
+                if (!$this->init) {
+                    $this->setDirty($pref, true);
+                }
+            }
+        } else {
+            $this->_prefs[$pref] = array(
+                'v' => $val
+            );
+
+            if (!$this->init) {
+                $this->setDirty($pref, true);
+            }
+        }
+    }
+
+    /**
+     * Does a preference exist in this scope?
+     *
+     * @return boolean  True if the preference exists.
+     */
+    public function exists($pref)
+    {
+        return isset($this->_prefs[$pref]);
+    }
+
+    /**
+     * Returns the value of a preference.
+     *
+     * @param string $pref  The preference name to retrieve.
+     *
+     * @return string  The value of the preference, null if it doesn't exist.
+     */
+    public function get($pref)
+    {
+        return isset($this->_prefs[$pref]['v'])
+            ? $this->_prefs[$pref]['v']
+            : null;
+    }
+
+    /**
+     * Mark a preference as locked.
+     *
+     * @param string $pref     The preference name.
+     * @param boolean $locked  Is the preference locked?
+     */
+    public function setLocked($pref, $locked)
+    {
+        if (isset($this->_prefs[$pref])) {
+            $ptr = &$this->_prefs[$pref];
+
+            if ($locked) {
+                if (!isset($ptr['l'])) {
+                    $ptr['l'] = true;
+                    if (!$this->init) {
+                        $this->setDirty($pref, true);
+                    }
+                }
+            } elseif (isset($ptr['l'])) {
+                unset($ptr['l']);
+                if (!$this->init) {
+                    $this->setDirty($pref, true);
+                }
+            }
+        }
+    }
+
+    /**
+     * Is a preference locked?
+     *
+     * @param string $pref  The preference name.
+     *
+     * @return boolean  Whether the preference is locked.
+     */
+    public function isLocked($pref)
+    {
+        return !empty($this->_prefs[$pref]['l']);
+    }
+
+    /**
+     * Is a preference's value the default?
+     *
+     * @param string $pref  The preference name.
+     *
+     * @return boolean  True if the preference contains the default value.
+     */
+    public function isDefault($pref)
+    {
+        return !isset($this->_prefs[$pref]['d']);
+    }
+
+    /**
+     * Returns the default value of a preference.
+     *
+     * @param string $pref  The preference name.
+     *
+     * @return string  The preference's default value.
+     */
+    public function getDefault($pref)
+    {
+        if (!$this->isDefault($pref)) {
+            return $this->_prefs[$pref]['d'];
+        }
+
+        return isset($this->_prefs[$pref])
+            ? $this->_prefs[$pref]['v']
+            : null;
+    }
+
+    /**
+     * Get the list of dirty preferences.
+     *
+     * @return array  The list of dirty preferences.
+     */
+    public function getDirty()
+    {
+        return array_keys($this->_dirty);
+    }
+
+    /**
+     * Is a preference marked dirty?
+     *
+     * @param mixed $pref  The preference name.  If null, will return true if
+     *                     scope contains at least one dirty pref.
+     *
+     * @return boolean  True if the preference is marked dirty.
+     */
+    public function isDirty($pref = null)
+    {
+        return is_null($pref)
+            ? !empty($this->_dirty)
+            : isset($this->_dirty[$pref]);
+    }
+
+    /**
+     * Set the dirty flag for a preference
+     *
+     * @param string $pref    The preference name.
+     * @param boolean $dirty  True to mark the pref as dirty.
+     */
+    public function setDirty($pref, $dirty)
+    {
+        if ($dirty) {
+            $this->_dirty[$pref] = true;
+        } else {
+            unset($this->_dirty[$pref]);
+        }
+    }
+
+    /* Serializable methods. */
+
+    /**
+     */
+    public function serialize()
+    {
+        return serialize(array(
+            $this->scope,
+            $this->_prefs
+        ));
+    }
+
+    /**
+     */
+    public function unserialize($data)
+    {
+        list($this->scope, $this->_prefs) = unserialize($data);
+    }
+
+}
index a2c28bd..2f99ebd 100644 (file)
@@ -37,28 +37,37 @@ abstract class Horde_Prefs_Storage
     /**
      * Retrieves the requested preferences scope from the storage backend.
      *
-     * @param string $scope  Scope specifier.
+     * @param Horde_Prefs_Scope $scope_ob  The scope object.
      *
-     * @return mixed  Keys are pref names, values are pref values. Returns
-     *                false if no data is available.
-     * @throws Horde_Db_Exception
+     * @return Horde_Prefs_Scope  The modified scope object.
+     * @throws Horde_Prefs_Exception
      */
-    abstract public function get($scope);
+    abstract public function get($scope_ob);
 
     /**
-     * Stores preferences in the storage backend.
+     * Stores changed preferences in the storage backend.
      *
-     * @param array $prefs  The preference list.
+     * @param Horde_Prefs_Scope $scope_ob  The scope object.
      *
-     * @throws Horde_Db_Exception
+     * @throws Horde_Prefs_Exception
+     */
+    abstract public function store($scope_ob);
+
+    /**
+     * Called whenever a preference value is changed.
+     *
+     * @param string $scope  Scope specifier.
+     * @param string $pref   The preference name.
      */
-    abstract public function store($prefs);
+    public function onChange($scope, $pref)
+    {
+    }
 
     /**
      * Removes preferences from the backend.
      *
      * @param string $scope  The scope of the prefs to clear. If null, clears
-     *                       entire cache.
+     *                       all scopes.
      * @param string $pref   The pref to clear. If null, clears the entire
      *                       scope.
      *
index e10a30f..a94c82f 100644 (file)
@@ -59,18 +59,36 @@ class Horde_Prefs_Storage_File extends Horde_Prefs_Storage
 
     /**
      */
-    public function get($scope)
+    public function get($scope_ob)
     {
-        if (!isset($this->_params['directory'])) {
-            return false;
+        if ($this->_loadFileCache() &&
+            isset($this->_fileCache[$scope_ob->scope])) {
+            foreach ($this->_fileCache[$scope_ob->scope] as $name => $val) {
+                $scope_ob->set($name, $val);
+            }
         }
 
+        return $scope_ob;
+    }
+
+    /**
+     * Load the preferences from the files.
+     *
+     * @return boolean  True on success.
+     * @throws Horde_Prefs_Exception
+     */
+    protected function _loadFileCache()
+    {
         if (is_null($this->_fileCache)) {
             // Try to read
             if (!file_exists($this->_fullpath)) {
+                $this->_fileCache = array(
+                    '__file_version' => self::VERSION
+                );
                 return false;
             }
-            $this->_fileCache = unserialize(file_get_contents($this->_fullpath));
+
+            $this->_fileCache = @unserialize(file_get_contents($this->_fullpath));
 
             // Check version number. We can call format transformations hooks
             // in the future.
@@ -78,44 +96,29 @@ class Horde_Prefs_Storage_File extends Horde_Prefs_Storage
                 !array_key_exists('__file_version', $this->_fileCache) ||
                 !($this->_fileCache['__file_version'] == self::VERSION)) {
                 if ($this->_fileCache['__file_version'] == 1) {
-                    $this->transformV1V2();
+                    $this->updateFileFormat();
                 } else {
                     throw new Horde_Prefs_Exception(sprintf('Wrong version number found: %s (should be %d)', $this->_fileCache['__file_version'], self::VERSION));
                 }
             }
         }
 
-        // Check if the scope exists
-        if (empty($scope) || !isset($this->_fileCache[$scope])) {
-            return false;
-        }
-
-        $ret = array();
-
-        foreach ($this->_fileCache[$scope] as $name => $val) {
-            $ret[$name] = $val;
-        }
-
-        return $ret;
+        return true;
     }
 
     /**
      */
-    public function store($prefs)
+    public function store($scope_ob)
     {
-        if (!isset($this->_params['directory'])) {
-            return;
-        }
-
-        // Read in all existing preferences, if any.
-        $this->get('');
-        if (!is_array($this->_fileCache)) {
-            $this->_fileCache = array('__file_version' => self::VERSION);
-        }
-
-        foreach ($prefs as $scope => $p) {
-            foreach ($p as $name => $val) {
-                $this->_fileCache[$scope][$name] = $pref['v'];
+        $this->_loadFileCache();
+
+        /* Driver has no support for storing locked status. */
+        foreach ($scope_ob->getDirty() as $name) {
+            $value = $scope_ob->get($name);
+            if (is_null($value)) {
+                unset($this->_fileCache[$scope_ob->scope][$name]);
+            } else {
+                $this->_fileCache[$scope_ob->scope][$name] = $value;
             }
         }
 
@@ -137,20 +140,20 @@ class Horde_Prefs_Storage_File extends Horde_Prefs_Storage
     /* Helper functions. */
 
     /**
-     * Transforms the broken version 1 format into version 2.
+     * Updates format of file.
      */
-    public function transformV1V2()
+    public function updateFileFormat()
     {
-        $version2 = array('__file_version' => 2);
+        $new_vers = array('__file_version' => self::VERSION);
+        unset($this->_fileCache['__file_version']);
+
         foreach ($this->_fileCache as $scope => $prefs) {
-            if ($scope != '__file_version') {
-                foreach ($prefs as $name => $pref) {
-                    $version2[$scope][$name] = $pref['v'];
-                }
+            foreach ($prefs as $name => $pref) {
+                $new_vers[$scope][$name] = $pref['v'];
             }
         }
 
-        $this->_fileCache = $version2;
+        $this->_fileCache = $new_vers;
     }
 
 }
index 805cff2..a64a2c9 100644 (file)
@@ -29,42 +29,37 @@ class Horde_Prefs_Storage_Imsp extends Horde_Prefs_Storage
 
     /**
      */
-    public function get($scope)
+    public function get($scope_ob)
     {
         $this->_connect();
 
-        $prefs = $this->_imsp->get($scope . '.*');
+        $prefs = $this->_imsp->get($scope_ob->scope . '.*');
         if ($prefs instanceof PEAR_Error) {
             throw new Horde_Prefs_Exception($prefs);
         }
 
-        $ret = array();
-
         foreach ($prefs as $name => $val) {
-            $name = str_replace($scope . '.', '', $name);
+            $name = str_replace($scope_ob->scope . '.', '', $name);
             if ($val != '-') {
-                $ret[$name] = $val;
+                $scope_ob->set($name, $val);
             }
         }
+
+        return $scope_ob;
     }
 
     /**
      */
-    public function store($prefs)
+    public function store($scope_ob)
     {
         $this->_connect();
 
-        foreach ($prefs as $scope => $p) {
-            foreach ($p as $name => $pref) {
-                $value = $pref['v'];
-                if (empty($value)) {
-                    $value = '-';
-                }
-
-                $result = $this->_imsp->set($scope . '.' . $name, $value);
-                if ($result instanceof PEAR_Error) {
-                    throw new Horde_Prefs_Exception($result);
-                }
+        /* Driver has no support for storing locked status. */
+        foreach ($scope_ob->getDirty() as $name) {
+            $value = $scope_ob->get($name);
+            $result = $this->_imsp->set($scope_ob->scope . '.' . $name, $value ? $value : '-');
+            if ($result instanceof PEAR_Error) {
+                throw new Horde_Prefs_Exception($result);
             }
         }
     }
index cac031e..d33c2b9 100644 (file)
@@ -9,6 +9,7 @@
  *
  * @author   Gunnar Wrobel <p@rdus.de>
  * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
  * @package  Prefs
  */
 class Horde_Prefs_Storage_KolabImap extends Horde_Prefs_Storage
@@ -29,32 +30,32 @@ class Horde_Prefs_Storage_KolabImap extends Horde_Prefs_Storage
 
     /**
      */
-    public function get($scope)
+    public function get($scope_ob)
     {
         $this->_connect();
 
-        $pref = $this->_getPref($scope);
+        $pref = $this->_getPref($scope_ob->scope);
 
         if (is_null($pref)) {
             /* No preferences saved yet. */
             return false;
         }
 
-        $ret = array();
-
         foreach ($pref['pref'] as $prefstr) {
             // If the string doesn't contain a colon delimiter, skip it.
             if (strpos($prefstr, ':') !== false) {
                 // Split the string into its name:value components.
                 list($name, $val) = explode(':', $prefstr, 2);
-                $ret[$name] = base64_decode($val);
+                $pref_ob->set($name, base64_decode($val));
             }
         }
+
+        return $pref_ob;
     }
 
     /**
      */
-    public function store($prefs)
+    public function store($scope_ob)
     {
         $this->_connect();
 
@@ -62,33 +63,33 @@ class Horde_Prefs_Storage_KolabImap extends Horde_Prefs_Storage
         // to be stored on the IMAP server. Because we have to update
         // all of the values of a multi-value entry wholesale, we
         // can't just pick out the dirty preferences; we must update
-        // every scope that has dirty preferences.
-        foreach ($prefs as $scope => $vals) {
-            $new_values = array();
-            foreach ($vals as $name => $pref) {
-                $new_values[] = $name . ':' . base64_encode($pref['v']);
-            }
+        // the entire dirty scope.
+        $new_vals = array();
 
-            $pref = $this->_getPref($scope);
+        /* Driver does not support storing locked status. */
+        foreach ($scope_ob->getDirty() as $name) {
+            $new_vals[] = $name . ':' . base64_encode($scope_ob->get($name));
+        }
 
-            if (is_null($pref)) {
-                $old_uid = null;
-                $prefs_uid = $this->_connection->_storage->generateUID();
-            } else {
-                $old_uid = $pref['uid'];
-                $prefs_uid = $pref['uid'];
-            }
+        $pref = $this->_getPref($scope_ob->scope);
+
+        if (is_null($pref)) {
+            $old_uid = null;
+            $prefs_uid = $this->_connection->_storage->generateUID();
+        } else {
+            $old_uid = $pref['uid'];
+            $prefs_uid = $pref['uid'];
+        }
 
-            $object = array(
-                'uid' => $prefs_uid,
-                'application' => $scope,
-                'pref' => $new_values
-            );
+        $object = array(
+            'application' => $scope_ob->scope,
+            'pref' => $new_vals,
+            'uid' => $prefs_uid
+        );
 
-            $result = $this->_connection->_storage->save($object, $old_uid);
-            if ($result instanceof PEAR_Error) {
-                throw new Horde_Prefs_Exception($result);
-            }
+        $result = $this->_connection->_storage->save($object, $old_uid);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Prefs_Exception($result);
         }
     }
 
index 9dc0735..de154ff 100644 (file)
@@ -74,15 +74,17 @@ class Horde_Prefs_Storage_Ldap extends Horde_Prefs_Storage
 
     /**
      */
-    public function get($scope)
+    public function get($scope_ob)
     {
         $this->_connect();
 
         // Search for the multi-valued field containing the array of
         // preferences.
-        $search = @ldap_search($this->_connection, $this->_params['basedn'],
-                              $this->_params['uid'] . '=' . $this->params['user'],
-                              array($scope . 'Prefs'));
+        $search = @ldap_search(
+            $this->_connection,
+            $this->_params['basedn'],
+            $this->_params['uid'] . '=' . $this->params['user'],
+            array($scope_ob->scope . 'Prefs'));
         if ($search === false) {
             throw new Horde_Prefs_Exception(sprintf('Error while searching for the user\'s prefs: [%d]: %s', @ldap_errno($this->_connection), @ldap_error($this->_connection)));
         }
@@ -94,31 +96,29 @@ class Horde_Prefs_Storage_Ldap extends Horde_Prefs_Storage
 
         // Preferences are stored as colon-separated name:value pairs.
         // Each pair is stored as its own attribute off of the multi-
-        // value attribute named in: $scope . 'Prefs'
+        // value attribute named in: $scope_ob->scope . 'Prefs'
 
         // ldap_get_entries() converts attribute indexes to lowercase.
-        $field = Horde_String::lower($scope . 'prefs');
+        $field = Horde_String::lower($scope_ob->scope . 'prefs');
         $prefs = isset($result[0][$field])
             ? $result[0][$field]
             : array();
 
-        $ret = array();
-
         foreach ($prefs as $prefstr) {
             // If the string doesn't contain a colon delimiter, skip it.
             if (strpos($prefstr, ':') !== false) {
                 // Split the string into its name:value components.
                 list($name, $val) = explode(':', $prefstr, 2);
-                $ret[$name] = base64_decode($val);
+                $scope_ob->set($name, base64_decode($val));
             }
         }
 
-        return $ret;
+        return $scope_ob;
     }
 
     /**
      */
-    public function store($prefs)
+    public function store($scope_ob)
     {
         $this->_connect();
 
@@ -126,20 +126,23 @@ class Horde_Prefs_Storage_Ldap extends Horde_Prefs_Storage
         // to be stored on the LDAP server. Because we have to update
         // all of the values of a multi-value entry wholesale, we
         // can't just pick out the dirty preferences; we must update
-        // every scope that has dirty preferences.
-        $new_values = array();
-        foreach ($prefs as $scope => $vals) {
-            foreach ($vals as $name => $pref) {
-                $new_values[$scope . 'Prefs'][] = $name . ':' . base64_encode($pref['v']);
-            }
+        // the entire dirty scope.
+        $new_vals = array();
+
+        /* Driver has no support for storing locked status. */
+        foreach ($scope_ob->getDirty() as $name) {
+            $new_vals[$scope_ob->scope . 'Prefs'][] = $name . ':' . base64_encode($scope_ob->get($name));
         }
 
         // Entries must have the objectclasses 'top' and 'hordeperson'
         // to successfully store LDAP prefs. Check for both of them,
         // and add them if necessary.
-        $search = @ldap_search($this->_connection, $this->_params['basedn'],
-                              $this->_params['uid'] . '=' . $this->prefs['user'],
-                              array('objectclass'));
+        $search = @ldap_search(
+            $this->_connection,
+            $this->_params['basedn'],
+            $this->_params['uid'] . '=' . $this->prefs['user'],
+            array('objectclass')
+        );
         if ($search === false) {
             throw new Horde_Prefs_Exception(sprintf('Error searching the directory for required objectClasses: [%d] %s', @ldap_errno($this->_connection), @ldap_error($this->_connection)));
         }
@@ -150,10 +153,9 @@ class Horde_Prefs_Storage_Ldap extends Horde_Prefs_Storage
         }
 
         if ($result['count'] > 0) {
-            $top = false;
-            $hordeperson = false;
+            $hordeperson = $top = false;
 
-            for ($i = 0; $i < $result[0]['objectclass']['count']; $i++) {
+            for ($i = 0; $i < $result[0]['objectclass']['count']; ++$i) {
                 if ($result[0]['objectclass'][$i] == 'top') {
                     $top = true;
                 } elseif ($result[0]['objectclass'][$i] == 'hordePerson') {
@@ -172,8 +174,7 @@ class Horde_Prefs_Storage_Ldap extends Horde_Prefs_Storage
         }
 
         // Send the hash to the LDAP server.
-        $result = @ldap_mod_replace($this->_connection, $this->_dn,
-                                    $new_values);
+        $result = @ldap_mod_replace($this->_connection, $this->_dn, $new_vals);
         if ($result === false) {
             throw new Horde_Prefs_Exception(sprintf('Unable to modify user\'s objectClass for preferences: [%d] %s', @ldap_errno($this->_connection), @ldap_error($this->_connection)));
         }
index b082fde..de833f9 100644 (file)
@@ -16,14 +16,14 @@ class Horde_Prefs_Storage_Null extends Horde_Prefs_Storage
 {
     /**
      */
-    public function get($scope)
+    public function get($scope_ob)
     {
-        return false;
+        return $scope_ob;
     }
 
     /**
      */
-    public function store($prefs)
+    public function store($scope_ob)
     {
     }
 
diff --git a/framework/Prefs/lib/Horde/Prefs/Storage/Session.php b/framework/Prefs/lib/Horde/Prefs/Storage/Session.php
deleted file mode 100644 (file)
index 265320e..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-/**
- * Session storage driver for the preferences system.
- *
- * Copyright 2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @package  Prefs
- */
-class Horde_Prefs_Storage_Session extends Horde_Prefs_Storage
-{
-    /**
-     * Session key.
-     *
-     * @var string
-     */
-    protected $_key;
-
-    /**
-     */
-    public function __construct($user, array $params = array())
-    {
-        parent::__construct($user, $params);
-
-        $this->_key = 'horde_prefs_' . $this->_params['user'];
-    }
-
-    /**
-     */
-    public function get($scope)
-    {
-        return isset($_SESSION[$this->_key][$scope])
-            ? $_SESSION[$this->_key][$scope]
-            : false;
-    }
-
-    /**
-     */
-    public function store($prefs)
-    {
-        foreach ($prefs as $scope => $vals) {
-            $_SESSION[$this->_key][$scope] = isset($_SESSION[$this->_key][$scope])
-                ? array_merge($_SESSION[$this->_key][$scope], $vals)
-                : array();
-        }
-    }
-
-    /**
-     */
-    public function remove($scope = null, $pref = null)
-    {
-        if (is_null($scope)) {
-            unset($_SESSION[$this->_key]);
-        } elseif (is_null($pref)) {
-            unset($_SESSION[$this->_key][$scope]);
-        } else {
-            unset($_SESSION[$this->_key][$scope][$pref]);
-        }
-    }
-
-}
index fdca35d..9c5f338 100644 (file)
@@ -52,12 +52,13 @@ class Horde_Prefs_Storage_Sql extends Horde_Prefs_Storage
 
     /**
      */
-    public function get($scope)
+    public function get($scope_ob)
     {
+        $charset = $this->_db->getOption('charset');
         $query = 'SELECT pref_scope, pref_name, pref_value FROM ' .
             $this->_params['table'] . ' ' .
             'WHERE pref_uid = ? AND pref_scope = ?';
-        $values = array($this->_params['user'], $scope);
+        $values = array($this->_params['user'], $scope_ob->scope);
 
         try {
             $result = $this->_db->selectAll($query, $values);
@@ -65,8 +66,6 @@ class Horde_Prefs_Storage_Sql extends Horde_Prefs_Storage
             throw Horde_Prefs_Exception($e);
         }
 
-        $ret = array();
-
         foreach ($result as $row) {
             $name = trim($row['pref_name']);
 
@@ -82,33 +81,48 @@ class Horde_Prefs_Storage_Sql extends Horde_Prefs_Storage
                 break;
             }
 
-            $ret[$name] = $row['pref_value'];
+            $scope_ob->set($name, Horde_String::convertCharset($row['pref_value'], $charset, 'UTF-8'));
         }
 
-        return $ret;
+        return $scope_ob;
     }
 
     /**
      */
-    public function store($prefs)
+    public function store($scope_ob)
     {
+        $charset = $this->_db->getOption('charset');
+
         // For each preference, check for an existing table row and
         // update it if it's there, or create a new one if it's not.
-        foreach ($prefs as $scope => $p) {
-            foreach ($p as $name => $pref) {
+        foreach ($scope_ob->getDirty() as $name) {
+            $value = $scope_ob->get($name);
+            $values = array($this->_params['user'], $name, $scope_ob->scope);
+
+            if (is_null($value)) {
+                $query = 'DELETE FROM ' . $this->_params['table'] .
+                    ' WHERE pref_uid = ? AND pref_name = ?' .
+                    ' AND pref_scope = ?';
+
+                try {
+                    $this->_db->delete($query, $values);
+                } catch (Horde_Db_Exception $e) {
+                    throw new Horde_Prefs_Exception($e);
+                }
+            } else {
                 // Does a row already exist for this preference?
                 $query = 'SELECT 1 FROM ' . $this->_params['table'] .
                     ' WHERE pref_uid = ? AND pref_name = ?' .
                     ' AND pref_scope = ?';
-                $values = array($this->_params['user'], $name, $scope);
 
                 try {
                     $check = $this->_db->selectValue($query, $values);
                 } catch (Horde_Db_Exception $e) {
-                    throw Horde_Prefs_Exception($e);
+                    throw new Horde_Prefs_Exception($e);
                 }
 
-                $value = strval(isset($pref['v']) ? $pref['v'] : null);
+                /* Driver has no support for storing locked status. */
+                $value = Horde_String::convertCharset($value, 'UTF-8', $charset);
 
                 switch ($this->_db->adapterName()) {
                 case 'PDO_PostgreSQL':
@@ -124,7 +138,7 @@ class Horde_Prefs_Storage_Sql extends Horde_Prefs_Storage
                         '(?, ?, ?, ?)';
                     $values = array(
                         $this->_params['user'],
-                        $scope,
+                        $scope_ob->scope,
                         $name,
                         $value
                     );
@@ -145,7 +159,7 @@ class Horde_Prefs_Storage_Sql extends Horde_Prefs_Storage
                         $value,
                         $this->_params['user'],
                         $name,
-                        $scope
+                        $scope_ob->scope
                     );
 
                     try {
index d0330ee..3f7898b 100644 (file)
@@ -21,7 +21,8 @@
   <api>beta</api>
  </stability>
  <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes> * Removed Horde_Prefs_Storage_Kolab driver.
+ <notes>* Abstract caching code into Horde_Prefs_Cache.
+ * Removed Horde_Prefs_Storage_Kolab driver.
  * Abstract storage code into Horde_Prefs_Storage.
  * Add array access API to Horde_Prefs.
  * Remove dependency on horde/Core.
    <dir name="lib">
     <dir name="Horde">
      <dir name="Prefs">
+      <dir name="Cache">
+       <file name="Null.php" role="php" />
+       <file name="Session.php" role="php" />
+      </dir> <!-- /lib/Horde/Prefs/Cache -->
       <dir name="Storage">
        <file name="File.php" role="php" />
        <file name="Imsp.php" role="php" />
        <file name="KolabImap.php" role="php" />
        <file name="Ldap.php" role="php" />
        <file name="Null.php" role="php" />
-       <file name="Session.php" role="php" />
        <file name="Sql.php" role="php" />
       </dir> <!-- /lib/Horde/Prefs/Storage -->
+      <file name="Cache.php" role="php" />
       <file name="CategoryManager.php" role="php" />
       <file name="Exception.php" role="php" />
       <file name="Identity.php" role="php" />
+      <file name="Scope.php" role="php" />
       <file name="Storage.php" role="php" />
       <file name="Translation.php" role="php">
        <tasks:replace from="@data_dir@" to="data_dir" type="pear-config" />
  <phprelease>
   <filelist>
    <install as="Horde/Prefs.php" name="lib/Horde/Prefs.php" />
+   <install as="Horde/Prefs/Cache.php" name="lib/Horde/Prefs/Cache.php" />
    <install as="Horde/Prefs/CategoryManager.php" name="lib/Horde/Prefs/CategoryManager.php" />
    <install as="Horde/Prefs/Exception.php" name="lib/Horde/Prefs/Exception.php" />
    <install as="Horde/Prefs/Identity.php" name="lib/Horde/Prefs/Identity.php" />
+   <install as="Horde/Prefs/Scope.php" name="lib/Horde/Prefs/Scope.php" />
    <install as="Horde/Prefs/Storage.php" name="lib/Horde/Prefs/Storage.php" />
    <install as="Horde/Prefs/Translation.php" name="lib/Horde/Prefs/Translation.php" />
+   <install as="Horde/Prefs/Cache/Null.php" name="lib/Horde/Prefs/Cache/Null.php" />
+   <install as="Horde/Prefs/Cache/Session.php" name="lib/Horde/Prefs/Cache/Session.php" />
    <install as="Horde/Prefs/Storage/File.php" name="lib/Horde/Prefs/Storage/File.php" />
    <install as="Horde/Prefs/Storage/Imsp.php" name="lib/Horde/Prefs/Storage/Imsp.php" />
    <install as="Horde/Prefs/Storage/KolabImap.php" name="lib/Horde/Prefs/Storage/KolabImap.php" />
    <install as="Horde/Prefs/Storage/Ldap.php" name="lib/Horde/Prefs/Storage/Ldap.php" />
    <install as="Horde/Prefs/Storage/Null.php" name="lib/Horde/Prefs/Storage/Null.php" />
-   <install as="Horde/Prefs/Storage/Session.php" name="lib/Horde/Prefs/Storage/Session.php" />
    <install as="Horde/Prefs/Storage/Sql.php" name="lib/Horde/Prefs/Storage/Sql.php" />
    <install as="locale/Horde_Prefs.pot" name="locale/Horde_Prefs.pot" />
    <install as="locale/ar/LC_MESSAGES/Horde_Prefs.mo" name="locale/ar/LC_MESSAGES/Horde_Prefs.mo" />
index 061bc8c..0c4fb2c 100644 (file)
@@ -17,9 +17,9 @@ $vars = Horde_Variables::getDefaultVariables();
 
 if (!$vars->exists('id') && $vars->exists('timer')) {
     $timer_id = $vars->get('timer');
-    $timers = @unserialize($prefs->getValue('running_timers', false));
+    $timers = @unserialize($prefs->getValue('running_timers'));
     if ($timers && isset($timers[$timer_id])) {
-        $tname = Horde_String::convertCharset($timers[$timer_id]['name'], $prefs->getCharset(), 'UTF-8');
+        $tname = $timers[$timer_id]['name'];
         $tformat = $prefs->getValue('twentyFour') ? 'G:i' : 'g:i a';
         $vars->set('hours', round((float)(time() - $timer_id) / 3600, 2));
         if ($prefs->getValue('add_description')) {
@@ -27,7 +27,7 @@ if (!$vars->exists('id') && $vars->exists('timer')) {
         }
         $notification->push(sprintf(_("The stop watch \"%s\" has been stopped."), $tname), 'horde.success');
         unset($timers[$timer_id]);
-        $prefs->setValue('running_timers', serialize($timers), false);
+        $prefs->setValue('running_timers', serialize($timers));
     }
 }
 
index b01cf4e..3a09611 100644 (file)
@@ -138,13 +138,13 @@ class Hermes_Application extends Horde_Registry_Application
                 )
             );
 
-            if ($timers = @unserialize($GLOBALS['prefs']->getValue('running_timers', false))) {
+            if ($timers = @unserialize($GLOBALS['prefs']->getValue('running_timers'))) {
                 foreach ($timers as $i => $timer) {
                     $hours = round((float)(time() - $i) / 3600, 2);
                     $tree->addNode(
                         $parent . '__timer_' . $i,
                         $parent,
-                        Horde_String::convertCharset($timer['name'], $prefs->getCharset(), 'UTF-8') . sprintf(" (%s)", $hours),
+                        $timer['name'] . sprintf(" (%s)", $hours),
                         1,
                         false,
                         array(
index fb34255..e068416 100644 (file)
@@ -18,7 +18,7 @@ $form = new Horde_Form($vars, _("Stop Watch"));
 $form->addVariable(_("Stop watch description"), 'description', 'text', true);
 
 if ($form->validate($vars)) {
-    $timers = $prefs->getValue('running_timers', false);
+    $timers = $prefs->getValue('running_timers');
     if (empty($timers)) {
         $timers = array();
     } else {
@@ -28,11 +28,9 @@ if ($form->validate($vars)) {
         }
     }
     $now = time();
-    $timers[$now] = array('name' => Horde_String::convertCharset($vars->get('description'),
-                                                                 'UTF-8',
-                                                                 $prefs->getCharset()),
+    $timers[$now] = array('name' => $vars->get('description'),
                           'time' => $now);
-    $prefs->setValue('running_timers', serialize($timers), false);
+    $prefs->setValue('running_timers', serialize($timers));
 
     echo Horde::wrapInlineScript(array(
         'alert(\'' . addslashes(sprintf(_("The stop watch \"%s\" has been started and will appear in the sidebar at the next refresh."), $vars->get('description'))),
index 34db3d1..09f29a7 100644 (file)
  *
  * Setting value
  * -------------
- * If
- *   'hook' => true
- * exists for a preference (config/prefs.php), the prefs_init hook will be run
- * on login to allow alteration of the default value. This hook receives the
- * preference name and the username as parameters and uses the return value
- * from the hook as the new preference value.
+ * If the 'hook' parameter is non-empty for a preference (config/prefs.php),
+ * the prefs_init hook will be run on login to allow alteration of the value.
+ * This hook receives the preference name and the username as parameters and
+ * uses the return value from the hook as the new preference value.
  *
  * This hook is ONLY executed on login and preferences are cached during a
  * users' session.
  *
- * Any preference that is NOT LOCKED, that is set by a hook, WILL BE SAVED
- * WITH THAT VALUE. This means:
- * 1) Users will get the results of the hook set for them in their
- *    preferences.
- * 2) The next time they login and load their preferences, the hook will NOT
- *    be called. However, if the preference is locked, the result of the hook
- *    will never be saved.
- *
  * Authentication/Login Hooks
  * ==========================
  * There are three special hooks called during the initial authentication
index e5ec937..634f9e0 100644 (file)
  *              false: Basic preference; shown regardless of preferences mode.
  *            DEFAULT: false
  *
- * locked - (boolean) Allow preference to be changed from the preferences screen?
- *          VALUES:
- *            true: Do not show this preference in the UI and don't allow
- *                  changing by any mechanism.
- *            false: Show this preference in the UI and allow changing.
- *          DEFAULT: false
- *
  * help - (string) The help file identifier for this preference.
  *        VALUES:
  *          If present, a help icon will be displayed next to the preference.
  *          a popup window.
  *        DEFAULT: No help icon is displayed
  *
+ * hook - (boolean) If true, the prefs_init hook will be run for this entry.
+ *        VALUES: true/false
+ *        DEFAULT: false
+ *
+ * locked - (boolean) Allow preference to be changed from the preferences
+ *          screen?
+ *          VALUES:
+ *            true: Do not show this preference in the UI and don't allow
+ *                  changing by any mechanism.
+ *            false: Show this preference in the UI and allow changing.
+ *          DEFAULT: false
+ *
  * The UI display for a preference is controlled by the 'type' key. This key
  * controls how the preference is displayed on the preferences screen. If this
  * key is not present, the preference is treated as type 'implict'. The
diff --git a/horde/lib/LoginTasks/SystemTask/UpgradeFromHorde3.php b/horde/lib/LoginTasks/SystemTask/UpgradeFromHorde3.php
deleted file mode 100644 (file)
index 9e02e2a..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-/**
- * Login system task for automated upgrade tasks.
- *
- * Copyright 2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author  Michael Slusarz <slusarz@horde.org>
- * @package Horde
- */
-class Horde_LoginTasks_SystemTask_UpgradeFromHorde3 extends Horde_LoginTasks_SystemTask
-{
-    /**
-     * The interval at which to run the task.
-     *
-     * @var integer
-     */
-    public $interval = Horde_LoginTasks::ONCE;
-
-    /**
-     * Perform all functions for this task.
-     */
-    public function execute()
-    {
-        $this->_upgradeIdentityPrefs();
-    }
-
-    /**
-     * Upgrade to the new identity preferences.
-     */
-    protected function _upgradeIdentityPrefs()
-    {
-        global $prefs;
-
-        if (!$prefs->isDefault('identities') &&
-            (!($this->_identities = @unserialize($prefs->getValue('identities', false))))) {
-            $identities = @unserialize($prefs->getValue('identities'));
-            if (!is_array($identities)) {
-                $identities = $prefs->getDefault('identities');
-            }
-            $prefs->setValue('identities', serialize($identities), false);
-        }
-    }
-
-}
index 4dc3d51..08c3dd6 100644 (file)
@@ -32,9 +32,9 @@ class IMP_Compose_Stationery implements ArrayAccess, Countable, Iterator
      */
     public function __construct()
     {
-        $slist = @unserialize($GLOBALS['prefs']->getValue('stationery', false));
+        $slist = @unserialize($GLOBALS['prefs']->getValue('stationery'));
         $this->_stationery = is_array($slist)
-            ? Horde_String::convertCharset($slist, $GLOBALS['prefs']->getCharset(), 'UTF-8')
+            ? $slist
             : array();
     }
 
@@ -84,7 +84,7 @@ class IMP_Compose_Stationery implements ArrayAccess, Countable, Iterator
      */
     protected function _save()
     {
-        $GLOBALS['prefs']->setValue('stationery', serialize(Horde_String::convertCharset($this->_stationery, 'UTF-8', $GLOBALS['prefs']->getCharset())), false);
+        $GLOBALS['prefs']->setValue('stationery', serialize($this->_stationery));
     }
 
     /* ArrayAccess methods. */
index e79da1a..f16c7d3 100644 (file)
@@ -98,7 +98,6 @@ class IMP_LoginTasks_SystemTask_UpgradeFromImp4 extends Horde_LoginTasks_SystemT
 
         default:
             $prefs->setValue('forward_default', 'attach');
-            $prefs->setDefault('forward_default', true);
             break;
         }
     }
index 729b54f..64448b0 100644 (file)
@@ -42,8 +42,7 @@ class Ingo_Storage_Prefs extends Ingo_Storage
         switch ($field) {
         case self::ACTION_BLACKLIST:
             $ob = new Ingo_Storage_Blacklist();
-            $data = @unserialize($prefs->getValue('blacklist'));
-            if ($data) {
+            if ($data = @unserialize($prefs->getValue('blacklist'))) {
                 $ob->setBlacklist($data['a'], false);
                 $ob->setBlacklistFolder($data['f']);
             }
@@ -51,30 +50,21 @@ class Ingo_Storage_Prefs extends Ingo_Storage
 
         case self::ACTION_WHITELIST:
             $ob = new Ingo_Storage_Whitelist();
-            $data = @unserialize($prefs->getValue('whitelist'));
-            if ($data) {
+            if ($data = @unserialize($prefs->getValue('whitelist'))) {
                 $ob->setWhitelist($data, false);
             }
             break;
 
         case self::ACTION_FILTERS:
             $ob = new Ingo_Storage_Filters();
-            $data = @unserialize($prefs->getValue('rules', false));
-            if ($data === false) {
-                /* Convert rules from the old format. */
-                $data = @unserialize($prefs->getValue('rules'));
-            } else {
-                $data = Horde_String::convertCharset($data, $prefs->getCharset(), 'UTF-8');
-            }
-            if ($data) {
+            if ($data = @unserialize($prefs->getValue('rules'))) {
                 $ob->setFilterlist($data);
             }
             break;
 
         case self::ACTION_FORWARD:
             $ob = new Ingo_Storage_Forward();
-            $data = @unserialize($prefs->getValue('forward'));
-            if ($data) {
+            if ($data = @unserialize($prefs->getValue('forward'))) {
                 $ob->setForwardAddresses($data['a'], false);
                 $ob->setForwardKeep($data['k']);
             }
@@ -82,14 +72,7 @@ class Ingo_Storage_Prefs extends Ingo_Storage
 
         case self::ACTION_VACATION:
             $ob = new Ingo_Storage_Vacation();
-            $data = @unserialize($prefs->getValue('vacation', false));
-            if ($data === false) {
-                /* Convert vacation from the old format. */
-                $data = unserialize($prefs->getValue('vacation'));
-            } elseif (is_array($data)) {
-                $data = $prefs->convertFromDriver($data);
-            }
-            if ($data) {
+            if ($data = @unserialize($prefs->getValue('vacation'))) {
                 $ob->setVacationAddresses($data['addresses'], false);
                 $ob->setVacationDays($data['days']);
                 $ob->setVacationExcludes($data['excludes'], false);
@@ -107,8 +90,7 @@ class Ingo_Storage_Prefs extends Ingo_Storage
 
         case self::ACTION_SPAM:
             $ob = new Ingo_Storage_Spam();
-            $data = @unserialize($prefs->getValue('spam'));
-            if ($data) {
+            if ($data = @unserialize($prefs->getValue('spam'))) {
                 $ob->setSpamFolder($data['folder']);
                 $ob->setSpamLevel($data['level']);
             }
@@ -145,7 +127,7 @@ class Ingo_Storage_Prefs extends Ingo_Storage
             return $prefs->setValue('blacklist', serialize($data));
 
         case self::ACTION_FILTERS:
-            return $prefs->setValue('rules', serialize(Horde_String::convertCharset($ob->getFilterList(), 'UTF-8', $prefs->getCharset())), false);
+            return $prefs->setValue('rules', serialize($ob->getFilterList()));
 
         case self::ACTION_FORWARD:
             $data = array(
@@ -165,7 +147,7 @@ class Ingo_Storage_Prefs extends Ingo_Storage
                 'start' => $ob->getVacationStart(),
                 'end' => $ob->getVacationEnd(),
             );
-            return $prefs->setValue('vacation', serialize($prefs->convertToDriver($data)), false);
+            return $prefs->setValue('vacation', serialize($data));
 
         case self::ACTION_WHITELIST:
             return $prefs->setValue('whitelist', serialize($ob->getWhitelist()));