*/
public function loadPrefs($app = null)
{
- require_once 'Horde/Prefs.php';
-
if (is_null($app)) {
$app = $this->getApp();
}
- /* If there is no logged in user, return an empty Prefs::
+ /* If there is no logged in user, return an empty Horde_Prefs::
* object with just default preferences. */
if (!Horde_Auth::getAuth()) {
- $GLOBALS['prefs'] = Prefs::factory('session', $app, '', '', null, false);
+ $GLOBALS['prefs'] = Horde_Prefs::factory('Session', $app, '', '', null, false);
} else {
if (!isset($GLOBALS['prefs']) ||
($GLOBALS['prefs']->getUser() != Horde_Auth::getAuth())) {
- $GLOBALS['prefs'] = Prefs::factory($GLOBALS['conf']['prefs']['driver'], $app, Horde_Auth::getAuth(), Horde_Auth::getCredential('password'));
+ $GLOBALS['prefs'] = Horde_Prefs::factory($GLOBALS['conf']['prefs']['driver'], $app, Horde_Auth::getAuth(), Horde_Auth::getCredential('password'));
} else {
$GLOBALS['prefs']->retrieve($app);
}
*/
public function testCategoriesWithPrefs()
{
- @include_once 'Horde.php';
- @include_once 'Horde/Prefs.php';
-
global $registry, $prefs;
- if (class_exists('Prefs')) {
+ if (class_exists('Horde_Prefs')) {
$registry = new DummyRegistry();
- $prefs = Prefs::singleton('session');
+ $prefs = Horde_Prefs::singleton('Session');
/* Monkey patch to allw the value to be set. */
$prefs->_prefs['categories'] = array('v' => '');
0,
array('type' => self::TYPE_DATETIME));
}
-}
\ No newline at end of file
+}
$mailparams = array(), $mod = array(),
$err = array())
{
- require_once 'Horde/Identity.php';
-
/* Set up some variables we use later. */
- $identity = Identity::singleton();
+ $identity = Horde_Prefs_Identity::singleton();
$from_addr = $identity->getDefaultFromAddress();
$to = $this->getMdnReturnAddr();
--- /dev/null
+<?php
+/**
+ * The Horde_Prefs:: class provides a common abstracted interface into the
+ * various preferences storage mediums. It also includes all of the
+ * functions for retrieving, storing, and checking preference values.
+ *
+ * TODO: document the format of the $_prefs hash here
+ *
+ * $_prefs[*pref name*] = array(
+ * 'value' => *Default value*,
+ * 'locked' => *boolean*,
+ * 'shared' => *boolean*,
+ * 'type' => 'checkbox'
+ * 'text'
+ * 'password'
+ * 'textarea'
+ * 'select'
+ * 'number'
+ * 'implicit'
+ * 'special'
+ * 'link' - There must be a field named either 'url'
+ * (internal application link) or 'xurl'
+ * (external application link) if this type is used.
+ * 'enum'
+ * 'enum' => TODO,
+ * 'desc' => _(*Description string*),
+ * 'help' => *Name of the entry in the XML help file*
+ * );
+ *
+ * Copyright 1999-2009 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 Jon Parise <jon@horde.org>
+ * @package Horde_Prefs
+ */
+class Horde_Prefs
+{
+ /** Preference is administratively locked. */
+ const LOCKED = 1;
+
+ /** Preference is shared amongst applications. */
+ const SHARED = 2;
+
+ /** Preference value has been changed. */
+ const DIRTY = 4;
+
+ /** Preference value is the application default.
+ * DEFAULT is a reserved PHP constant. */
+ const PREFS_DEFAULT = 8;
+
+ /**
+ * Singleton instances.
+ *
+ * @var array
+ */
+ static protected $_instances = array();
+
+ /**
+ * Hash holding the current set of preferences. Each preference is
+ * itself a hash, so this will ultimately be multi-dimensional.
+ *
+ * [*pref name*] => Array(
+ * [d] => *default value*
+ * [m] => *pref mask*
+ * [v] => *pref value*
+ * )
+ *
+ * @var array
+ */
+ protected $_prefs = array();
+
+ /**
+ * String containing the name of the current scope. This is used
+ * to differentiate between sets of preferences (multiple
+ * applications can have a "sortby" preference, for example). By
+ * default, all preferences belong to the "global" (Horde) scope.
+ *
+ * @var string
+ */
+ protected $_scope = 'horde';
+
+ /**
+ * Array of loaded scopes. In order to only load what we need, and
+ * to not load things multiple times, we need to maintain a list
+ * of loaded scopes. $this->_prefs will always be the combination
+ * of the current scope and the 'horde' scope (or just the 'horde'
+ * scope).
+ *
+ * @var array
+ */
+ protected $_scopes = array();
+
+ /**
+ * String containing the current username. This indicates the owner of the
+ * preferences.
+ *
+ * @var string
+ */
+ protected $_user = '';
+
+ /**
+ * Boolean indicating whether preference caching should be used.
+ *
+ * @var boolean
+ */
+ protected $_caching = false;
+
+ /**
+ * Array to cache in. Usually a reference to an array in $_SESSION, but
+ * could be overridden by a subclass for testing or other users.
+ *
+ * @var array
+ */
+ protected $_cache = array();
+
+ /**
+ * Hash holding preferences with hook functions defined.
+ *
+ * @var array
+ */
+ protected $_hooks = array();
+
+ /**
+ * Attempts to return a reference to a concrete instance based on $driver.
+ * It will only create a new instance if no instance with the same
+ * parameters currently exists.
+ *
+ * This should be used if multiple preference sources (and, thus,
+ * multiple instances) are required.
+ *
+ * @param mixed $driver The type of concrete subclass to return.
+ * @param string $scope The scope for this set of preferences.
+ * @param string $user The name of the user who owns this set of
+ * preferences.
+ * @param string $password The password associated with $user.
+ * @param array $params A hash containing any additional configuration
+ * or connection parameters a subclass might need.
+ * @param boolean $caching Should caching be used?
+ *
+ * @return Horde_Prefs The concrete reference, or false on an error.
+ * @throws Horde_Exception
+ */
+ static public function singleton($driver, $scope = 'horde', $user = '',
+ $password = '', $params = null,
+ $caching = true)
+ {
+ if (is_null($params)) {
+ $params = Horde::getDriverConfig('prefs', $driver);
+ }
+
+ $signature = serialize(array($driver, $user, $params, $caching));
+ if (!isset(self::$_instances[$signature])) {
+ self::$_instances[$signature] = self::factory($driver, $scope, $user, $password, $params, $caching);
+ }
+
+ /* Preferences may be cached with a different scope. */
+ self::$_instances[$signature]->setScope($scope);
+
+ return self::$_instances[$signature];
+ }
+
+ /**
+ * Attempts to return a concrete instance based on $driver.
+ *
+ * @param mixed $driver The type of concrete subclass to return.
+ * @param string $scope The scope for this set of preferences.
+ * @param string $user The name of the user who owns this set of
+ * preferences.
+ * @param string $password The password associated with $user.
+ * @param array $params A hash containing any additional configuration
+ * or connection parameters a subclass might need.
+ * @param boolean $caching Should caching be used?
+ *
+ * @return Horde_Prefs The newly created concrete instance.
+ * @throws Horde_Exception
+ */
+ static public function factory($driver, $scope = 'horde', $user = '',
+ $password = '', $params = null,
+ $caching = true)
+ {
+ $driver = ucfirst(basename($driver));
+ if (empty($driver) || $driver == 'None') {
+ $driver = 'Session';
+ }
+
+ $class = 'Horde_Prefs_' . $driver;
+ if (!class_exists($class)) {
+ throw new Horde_Exception('Class definition of ' . $class . ' not found.');
+ }
+
+ if (is_null($params)) {
+ $params = Horde::getDriverConfig('prefs', $driver);
+ }
+
+ /* If $params['user_hook'] is defined, use it to retrieve the value to
+ * use for the username ($this->_user). Otherwise, just use the value
+ * passed in the $user parameter. */
+ if (!empty($params['user_hook']) &&
+ function_exists($params['user_hook'])) {
+ $user = call_user_func($params['user_hook'], $user);
+ }
+
+ $prefs = new $class($scope, $user, $password, $params, $caching);
+ $prefs->retrieve($scope);
+
+ return $prefs;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $scope The scope for this set of preferences.
+ * @param string $user The name of the user who owns this set of
+ * preferences.
+ * @param string $password The password associated with $user.
+ * @param array $params A hash containing any additional configuration
+ * or connection parameters a subclass might need.
+ * @param boolean $caching Should caching be used?
+ *
+ */
+ protected function __construct($scope, $user, $password, $params, $caching)
+ {
+ register_shutdown_function(array($this, 'store'));
+
+ $this->_user = $user;
+ $this->_password = $password;
+ $this->_scope = $scope;
+ $this->_params = $params;
+ $this->_caching = $caching;
+
+ // Create a unique key that's safe to use for caching even if we want
+ // another user's preferences later, then register the cache array in
+ // $_SESSION.
+ if ($this->_caching) {
+ $cacheKey = 'horde_prefs_' . hash('sha1', $this->_user);
+
+ // Store a reference to the $_SESSION array.
+ $this->_cache = &$_SESSION[$cacheKey];
+ }
+ }
+
+ /**
+ * Returns the charset used by the concrete preference backend.
+ *
+ * @return string The preference backend's charset.
+ */
+ public function getCharset()
+ {
+ return Horde_Nls::getCharset();
+ }
+
+ /**
+ * Return the user who owns these preferences.
+ *
+ * @return string The user these preferences are for.
+ */
+ public function getUser()
+ {
+ return $this->_user;
+ }
+
+ /**
+ * Get the current scope.
+ *
+ * @return string The current scope (application).
+ */
+ public function getScope()
+ {
+ return $this->_scope;
+ }
+
+ /**
+ * 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)
+ {
+ // FIXME not updated yet.
+ $scope = $this->_getPreferenceScope($pref);
+ unset($this->_prefs[$pref]);
+ unset($this->_cache[$scope][$pref]);
+ }
+
+ /**
+ * Sets the given preferences ($pref) to the specified value
+ * ($val), 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.
+ *
+ * @return boolean True if the value was successfully set, false on a
+ * failure.
+ * @throws Horde_Exception
+ */
+ public function setValue($pref, $val, $convert = true)
+ {
+ /* Exit early if this preference is locked or doesn't exist. */
+ if (!isset($this->_prefs[$pref]) || $this->isLocked($pref)) {
+ return false;
+ }
+
+ $result = $this->_setValue($pref, $val, true, $convert);
+ if ($result && $this->isDirty($pref)) {
+ $scope = $this->_getPreferenceScope($pref);
+ $this->_cacheUpdate($scope, array($pref));
+
+ /* If this preference has a change hook, call it now. */
+ try {
+ Horde::callHook('prefs_change_hook_' . $pref, array(), $scope);
+ } catch (Horde_Exception_HookNotSet $e) {}
+ }
+
+ return $result;
+ }
+
+ 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 $dirty True if we should mark the new value as
+ * dirty (changed).
+ * @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, $dirty = true, $convert = true)
+ {
+ global $conf;
+
+ if ($convert) {
+ $val = $this->convertToDriver($val, Horde_Nls::getCharset());
+ }
+
+ // 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->_prefs[$pref]) &&
+ (($this->_prefs[$pref]['v'] == $val) &&
+ !$this->isDefault($pref))) {
+ return true;
+ }
+
+ // Check to see if the value exceeds the allowable storage
+ // limit.
+ if (isset($GLOBALS['conf']['prefs']['maxsize']) &&
+ (strlen($val) > $GLOBALS['conf']['prefs']['maxsize']) &&
+ isset($GLOBALS['notification'])) {
+ $GLOBALS['notification']->push(sprintf(_("The preference \"%s\" could not be saved because its data exceeded the maximum allowable size"), $pref), 'horde.error');
+ return false;
+ }
+
+ // Assign the new value, unset the "default" bit, and set the
+ // "dirty" bit.
+ if (empty($this->_prefs[$pref]['m'])) {
+ $this->_prefs[$pref]['m'] = 0;
+ }
+ $this->_prefs[$pref]['v'] = $val;
+ $this->setDefault($pref, false);
+ if ($dirty) {
+ $this->setDirty($pref, true);
+ }
+
+ // Finally, copy into the $_scopes array.
+ $this->_scopes[$this->_getPreferenceScope($pref)][$pref] = $this->_prefs[$pref];
+
+ return true;
+ }
+
+ /**
+ * 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.
+ *
+ * @return string The value of the preference, null if it doesn't exist.
+ */
+ public function getValue($pref, $convert = true)
+ {
+ $value = null;
+
+ if (isset($this->_prefs[$pref]['v'])) {
+ if ($convert) {
+ /* Default values have the current UI charset.
+ * Stored values have the backend charset. */
+ $value = $this->isDefault($pref)
+ ? Horde_String::convertCharset($this->_prefs[$pref]['v'], Horde_Nls::getCharset(), Horde_Nls::getCharset())
+ : $this->convertFromDriver($this->_prefs[$pref]['v'], Horde_Nls::getCharset());
+ } else {
+ $value = $this->_prefs[$pref]['v'];
+ }
+ }
+
+ return $value;
+ }
+
+ public function __get($name)
+ {
+ return $this->getValue($name);
+ }
+
+ /**
+ * Modifies the "locked" bit for the given preference.
+ *
+ * @param string $pref The name of the preference to modify.
+ * @param boolean $bool The new boolean value for the "locked" bit.
+ */
+ public function setLocked($pref, $bool)
+ {
+ $this->_setMask($pref, $bool, self::LOCKED);
+ }
+
+ /**
+ * Returns the state of the "locked" bit for the given preference.
+ *
+ * @param string $pref The name of the preference to check.
+ *
+ * @return boolean The boolean state of $pref's "locked" bit.
+ */
+ public function isLocked($pref)
+ {
+ return $this->_getMask($pref, self::LOCKED);
+ }
+
+ /**
+ * Modifies the "shared" bit for the given preference.
+ *
+ * @param string $pref The name of the preference to modify.
+ * @param boolean $bool The new boolean value for the "shared" bit.
+ */
+ public function setShared($pref, $bool)
+ {
+ $this->_setMask($pref, $bool, self::SHARED);
+ }
+
+ /**
+ * Returns the state of the "shared" bit for the given preference.
+ *
+ * @param string $pref The name of the preference to check.
+ *
+ * @return boolean The boolean state of $pref's "shared" bit.
+ */
+ public function isShared($pref)
+ {
+ return $this->_getMask($pref, self::SHARED);
+ }
+
+ /**
+ * 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)
+ {
+ $this->_setMask($pref, $bool, self::DIRTY);
+ }
+
+ /**
+ * Returns the state of the "dirty" bit for the given preference.
+ *
+ * @param string $pref The name of the preference to check.
+ *
+ * @return boolean The boolean state of $pref's "dirty" bit.
+ */
+ public function isDirty($pref)
+ {
+ return $this->_getMask($pref, self::DIRTY);
+ }
+
+ /**
+ * 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::PREFS_DEFAULT);
+ }
+
+ /**
+ * Returns the default value of the given preference.
+ *
+ * @param string $pref The name of the preference to get the default for.
+ *
+ * @return string The preference's default value.
+ */
+ public function getDefault($pref)
+ {
+ return empty($this->_prefs[$pref]['d'])
+ ? ''
+ : $this->_prefs[$pref]['d'];
+ }
+
+ /**
+ * Determines if the current preference value is the default
+ * value from prefs.php or a user defined value
+ *
+ * @param string $pref The name of the preference to check.
+ *
+ * @return boolean True if the preference is the application default
+ * value.
+ */
+ public function isDefault($pref)
+ {
+ return $this->_getMask($pref, self::PREFS_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)
+ {
+ if (isset($this->_prefs[$pref]) &&
+ ($bool != $this->_getMask($pref, $mask))) {
+ if ($bool) {
+ $this->_prefs[$pref]['m'] |= $mask;
+ } else {
+ $this->_prefs[$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)
+ {
+ return isset($this->_prefs[$pref]['m'])
+ ? (bool)($this->_prefs[$pref]['m'] & $mask)
+ : false;
+ }
+
+ /**
+ * Returns the scope of the given preference.
+ *
+ * @param string $pref The name of the preference to examine.
+ *
+ * @return string The scope of the $pref.
+ */
+ protected function _getPreferenceScope($pref)
+ {
+ return $this->isShared($pref) ? 'horde' : $this->_scope;
+ }
+
+ /**
+ * Retrieves preferences for the current scope + the 'horde'
+ * scope.
+ *
+ * @param string $scope Optional scope specifier - if not present the
+ * current scope will be used.
+ */
+ public function retrieve($scope = null)
+ {
+ if (is_null($scope)) {
+ $scope = $this->_scope;
+ } else {
+ $this->_scope = $scope;
+ }
+
+ $this->_loadScope('horde');
+ if ($scope != 'horde') {
+ $this->_loadScope($scope);
+ }
+
+ $this->_prefs = ($scope == 'horde')
+ ? $this->_scopes['horde']
+ : array_merge($this->_scopes['horde'], $this->_scopes[$scope]);
+ }
+
+ /**
+ * Load a specific preference scope.
+ */
+ protected function _loadScope($scope)
+ {
+ // Return if we've already loaded these prefs.
+ if (!empty($this->_scopes[$scope])) {
+ return;
+ }
+
+ // Basic initialization so _something_ is always set.
+ $this->_scopes[$scope] = array();
+
+ // Always set defaults to pick up new default values, etc.
+ $this->_setDefaults($scope);
+
+ // Now check the prefs cache for existing values.
+ if ($this->_cacheLookup($scope)) {
+ return;
+ }
+
+ $this->_retrieve($scope);
+ $this->_callHooks($scope);
+
+ /* Update the session cache. */
+ $this->_cacheUpdate($scope, array_keys($this->_scopes[$scope]));
+ }
+
+ /**
+ * 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 at this time; thus,
+ * there is no need to manually call $prefs->store() every time a
+ * preference is changed.
+ */
+ public function store()
+ {
+ }
+
+ /**
+ * TODO
+ *
+ * @throws Horde_Exception
+ */
+ protected function _retrieve()
+ {
+ }
+
+ /**
+ * This function provides common cleanup functions for all of the driver
+ * implementations.
+ *
+ * @param boolean $all Clean up all Horde preferences.
+ */
+ public function cleanup($all = false)
+ {
+ /* Perform a Horde-wide cleanup? */
+ if ($all) {
+ /* Destroy the contents of the preferences hash. */
+ $this->_prefs = array();
+
+ /* Destroy the contents of the preferences cache. */
+ unset($this->_cache);
+ } else {
+ /* Remove this scope from the preferences cache, if it exists. */
+ unset($this->_cache[$this->_scope]);
+ }
+ }
+
+ /**
+ * Clears all preferences from the backend.
+ */
+ public function clear()
+ {
+ $this->cleanup(true);
+ }
+
+ /**
+ * Converts a value from the driver's charset to the specified charset.
+ *
+ * @param mixed $value A value to convert.
+ * @param string $charset The charset to convert to.
+ *
+ * @return mixed The converted value.
+ */
+ public function convertFromDriver($value, $charset)
+ {
+ return $value;
+ }
+
+ /**
+ * Converts a value from the specified charset to the driver's charset.
+ *
+ * @param mixed $value A value to convert.
+ * @param string $charset The charset to convert from.
+ *
+ * @return mixed The converted value.
+ */
+ public function convertToDriver($value, $charset)
+ {
+ return $value;
+ }
+
+ /**
+ * Return all "dirty" preferences across all scopes.
+ *
+ * @return array The values for all dirty preferences, in a
+ * multi-dimensional array of scope => pref name =>
+ * pref values.
+ */
+ protected function _dirtyPrefs()
+ {
+ $dirty_prefs = array();
+
+ foreach ($this->_scopes as $scope => $prefs) {
+ foreach ($prefs as $pref_name => $pref) {
+ if (isset($pref['m']) && ($pref['m'] & self::DIRTY)) {
+ $dirty_prefs[$scope][$pref_name] = $pref;
+ }
+ }
+ }
+
+ return $dirty_prefs;
+ }
+
+ /**
+ * Updates the session-based preferences cache (if available).
+ *
+ * @param string $scope The scope of the prefs being updated.
+ * @param array $prefs The preferences to update.
+ */
+ protected function _cacheUpdate($scope, $prefs)
+ {
+ if ($this->_caching && isset($this->_cache)) {
+ /* Place each preference in the cache according to its
+ * scope. */
+ foreach ($prefs as $name) {
+ if (isset($this->_scopes[$scope][$name])) {
+ $this->_cache[$scope][$name] = $this->_scopes[$scope][$name];
+ }
+ }
+ }
+ }
+
+ /**
+ * Tries to find the requested preferences in the cache. If they
+ * exist, update the $_scopes hash with the cached values.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ protected function _cacheLookup($scope)
+ {
+ if ($this->_caching && isset($this->_cache[$scope])) {
+ $this->_scopes[$scope] = $this->_cache[$scope];
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Populates the $_scopes hash with new entries and externally
+ * defined default values.
+ *
+ * @param string $scope The scope to load defaults for.
+ */
+ protected function _setDefaults($scope)
+ {
+ /* Read the configuration file. The $_prefs array is assumed to hold
+ * the default values. */
+ try {
+ $result = Horde::loadConfiguration('prefs.php', array('_prefs'), $scope);
+ if (empty($result)) {
+ return;
+ }
+ } catch (Horde_Exception $e) {
+ return;
+ }
+
+ extract($result);
+ if (!isset($_prefs)) {
+ return;
+ }
+
+ foreach ($_prefs as $name => $pref) {
+ if (isset($pref['value']) &&
+ isset($pref['locked']) &&
+ isset($pref['shared']) &&
+ ($pref['type'] != 'link') &&
+ ($pref['type'] != 'special')) {
+ $name = str_replace('.', '_', $name);
+
+ $mask = 0;
+ $mask &= ~self::DIRTY;
+ $mask |= self::PREFS_DEFAULT;
+
+ if ($pref['locked']) {
+ $mask |= self::LOCKED;
+ }
+
+ if ($pref['shared'] || ($scope == 'horde')) {
+ $mask |= self::SHARED;
+ $pref_scope = 'horde';
+ } else {
+ $pref_scope = $scope;
+ }
+
+ if ($pref['shared'] && isset($this->_scopes[$pref_scope][$name])) {
+ // This is a shared preference that was already
+ // retrieved.
+ $this->_scopes[$pref_scope][$name]['m'] = $mask & ~self::PREFS_DEFAULT;
+ $this->_scopes[$pref_scope][$name]['d'] = $pref['value'];
+ } else {
+ $this->_scopes[$pref_scope][$name] = array('v' => $pref['value'], 'm' => $mask, 'd' => $pref['value']);
+ }
+
+ if (!empty($pref['hook'])) {
+ if (!isset($this->_hooks[$scope])) {
+ $this->_hooks[$scope] = array();
+ }
+ $this->_hooks[$scope][$name] = $pref_scope;
+ }
+ }
+ }
+ }
+
+ /**
+ * After preferences have been loaded, set any locked or empty
+ * preferences that have hooks to the result of the hook.
+ *
+ * @param string $scope The preferences scope to call hooks for.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _callHooks($scope)
+ {
+ if (empty($this->_hooks[$scope])) {
+ return;
+ }
+
+ foreach ($this->_hooks[$scope] as $name => $pref_scope) {
+ if ($this->_scopes[$pref_scope][$name]['m'] & self::LOCKED ||
+ empty($this->_scopes[$pref_scope][$name]['v']) ||
+ $this->_scopes[$pref_scope][$name]['m'] & self::PREFS_DEFAULT) {
+
+ try {
+ $val = Horde::callHook('prefs_hook_' . $name, array($this->_user), $scope);
+ } catch (Horde_Exception_HookNotSet $e) {
+ continue;
+ }
+
+ if ($this->_scopes[$pref_scope][$name]['m'] & self::PREFS_DEFAULT) {
+ $this->_scopes[$pref_scope][$name]['v'] = $val;
+ } else {
+ $this->_scopes[$pref_scope][$name]['v'] = $this->convertToDriver($val, Horde_Nls::getCharset());
+ }
+ if (!($this->_scopes[$pref_scope][$name]['m'] & self::LOCKED)) {
+ $this->_scopes[$pref_scope][$name]['m'] |= self::DIRTY;
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Class for handling a list of categories stored in a user's
+ * preferences.
+ *
+ * Copyright 2004-2009 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 Chuck Hagenbuch <chuck@horde.org>
+ * @category Horde
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_CategoryManager
+{
+ /**
+ * Get all categories.
+ */
+ static public function get()
+ {
+ $string = $GLOBALS['prefs']->getValue('categories');
+ if (empty($string)) {
+ return array();
+ }
+
+ $categories = explode('|', $string);
+ asort($categories);
+
+ return $categories;
+ }
+
+ /**
+ * TODO
+ */
+ static public function getSelect($id, $current = null)
+ {
+ $categories = self::get();
+ $colors = self::colors();
+ $fgcolors = self::fgColors();
+
+ $id_html = htmlspecialchars($id);
+ $html = '<select id="' . $id_html . '" name="' . $id_html . '">';
+
+ if (!in_array($current, $categories) && !empty($current)) {
+ $curr_html = htmlspecialchars($current);
+ $html .= '<option value="*new*' . $curr_html . '">'
+ . sprintf(_("Use Current: %s"), $curr_html)
+ . '</option>'
+ . '<option value="" disabled="disabled">- - - - - - - - -</option>';
+ }
+
+ if (!$GLOBALS['prefs']->isLocked('categories')) {
+ $html .= '<option value="*new*">' . _("New Category")
+ . "</option>\n"
+ . '<option value="" disabled="disabled">- - - - - - - - -</option>';
+ }
+
+ // Always add an Unfiled option.
+ $html .= '<option value="" style="background:'
+ . $colors['_unfiled_'] . ';color:' . $fgcolors['_unfiled_'] . '"'
+ . (empty($current) ? ' selected="selected">' : '>')
+ . htmlspecialchars(_("Unfiled")) . '</option>';
+
+ foreach ($categories as $name) {
+ $name_html = htmlspecialchars($name);
+ $html .= '<option value="' . $name_html
+ . '" style="background:' . (isset($colors[$name]) ? $colors[$name] : '#fff')
+ . ';color:' . (isset($fgcolors[$name]) ? $fgcolors[$name] : '#000') . '"'
+ . ($name === $current ? ' selected="selected">' : '>')
+ . $name_html . '</option>';
+ }
+
+ return $html . '</select>';
+ }
+
+ /**
+ * TODO
+ */
+ static public function getJavaScript($formname, $elementname)
+ {
+ $prompt = addslashes(_("Please type the new category name:"));
+ $error = addslashes(_("You must type a new category name."));
+
+ return <<<JAVASCRIPT
+
+<script type="text/javascript">
+<!--
+function checkCategory()
+{
+ if (document.${formname}['$elementname'].value == '*new*') {
+ var category = window.prompt('$prompt', '');
+ if (category != null && category != '') {
+ document.$formname.new_category.value = category;
+ } else {
+ window.alert('$error');
+ return false;
+ }
+ } else if (document.${formname}['$elementname'].value.indexOf('*new*') != -1) {
+ document.$formname.new_category.value = document.${formname}['$elementname'].value.substr(5, document.${formname}['$elementname'].value.length);
+ }
+
+ return true;
+}
+//-->
+</script>
+JAVASCRIPT;
+ }
+
+ /**
+ * Add a new category.
+ *
+ * @param string $category The name of the category to add.
+ *
+ * @return mixed False on failure, or the new category's name.
+ */
+ static public function add($category)
+ {
+ if ($GLOBALS['prefs']->isLocked('categories') || empty($category)) {
+ return false;
+ }
+
+ $categories = self::get();
+ if (in_array($category, $categories)) {
+ return $category;
+ }
+
+ $categories[] = $category;
+ $GLOBALS['prefs']->setValue('categories', implode('|', $categories));
+
+ return $category;
+ }
+
+ /**
+ * Delete a category.
+ *
+ * @param string $category The category to remove.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ static public function remove($category)
+ {
+ if ($GLOBALS['prefs']->isLocked('categories')) {
+ return false;
+ }
+
+ $categories = self::get();
+
+ $key = array_search($category, $categories);
+ if ($key === false) {
+ return $key;
+ }
+
+ unset($categories[$key]);
+ $GLOBALS['prefs']->setValue('categories', implode('|', $categories));
+
+ // Remove any color settings for $category.
+ $colors = self::colors();
+ unset($colors[$category]);
+ self::setColors($colors);
+
+ return true;
+ }
+
+ /**
+ * Returns the color for each of the user's categories.
+ *
+ * @return array A list of colors, key is the category name, value is the
+ * HTML color code.
+ */
+ static public function colors()
+ {
+ /* Default values that can be overridden but must always be
+ * present. */
+ $colors['_default_'] = '#FFFFFF';
+ $colors['_unfiled_'] = '#DDDDDD';
+
+ $pairs = explode('|', $GLOBALS['prefs']->getValue('category_colors'));
+ foreach ($pairs as $pair) {
+ if (!empty($pair)) {
+ list($category, $color) = explode(':', $pair);
+ $colors[$category] = $color;
+ }
+ }
+
+ $colors[''] = $colors['_unfiled_'];
+
+ return $colors;
+ }
+
+ /**
+ * Returns the foreground color for each of the user's categories.
+ *
+ * @return array A list of colors, key is the category name, value is the
+ * HTML color code.
+ */
+ static public function fgColors()
+ {
+ $colors = self::colors();
+ $fgcolors = array();
+ foreach ($colors as $name => $color) {
+ $fgcolors[$name] = Horde_Image::brightness($color) < 128 ? '#f6f6f6' : '#000';
+ }
+
+ return $fgcolors;
+ }
+
+ /**
+ * TODO
+ */
+ static public function setColor($category, $color)
+ {
+ $colors = self::colors();
+ $colors[$category] = $color;
+ self::setColors($colors);
+ }
+
+ /**
+ * TODO
+ */
+ static public function setColors($colors)
+ {
+ $pairs = array();
+ foreach ($colors as $category => $color) {
+ if ($color[0] != '#') {
+ $color = '#' . $color;
+ }
+ if (!empty($category)) {
+ $pairs[] = $category . ':' . $color;
+ }
+ }
+
+ $GLOBALS['prefs']->setValue('category_colors', implode('|', $pairs));
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Class for handling a list of credentials stored in a user's preferences.
+ *
+ * Copyright 2008-2009 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 Jan Schneider <jan@horde.org>
+ * @category Horde
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Credentials
+{
+ /**
+ * Singleton instance.
+ *
+ * @var Horde_Prefs_Credentials
+ */
+ static protected $_instance = null;
+
+ /**
+ * The Horde application currently processed.
+ *
+ * @see singleton()
+ * @var string
+ */
+ protected $app;
+
+ /**
+ * A list of preference field names and their values.
+ *
+ * @var array
+ */
+ protected $_credentials = array();
+
+ /**
+ * Cache for getCredentials().
+ *
+ * @var array
+ */
+ protected $_credentialsCache = null;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $credentials = @unserialize($GLOBALS['prefs']->getValue('credentials'));
+ if ($credentials) {
+ foreach ($credentials as $app => $app_prefs) {
+ foreach ($app_prefs as $name => $value) {
+ $this->_credentials['credentials[' . $app . '][' . $name . ']'] = $value;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a single instance of the Prefs_Credentials class, and sets the
+ * curently processed application.
+ *
+ * @param string $app The current application.
+ *
+ * @return Prefs_Credentials A Prefs_Credentials instance.
+ */
+ static public function singleton($app)
+ {
+ if (is_null(self::$_instance)) {
+ self::$_instance = new Horde_Prefs_Credentials();
+ }
+ self::$_instance->app = $app;
+
+ return self::$_instance;
+ }
+
+ /**
+ * Returns a list of available credentials collected from all Horde
+ * applications.
+ *
+ * @return array A list of Horde applications and their credentials.
+ */
+ public function getCredentials()
+ {
+ if (!is_null($this->_credentialsCache)) {
+ return $this->_credentialsCache;
+ }
+
+ $credentials_prefs = array();
+ foreach ($GLOBALS['registry']->listApps() as $app) {
+ try {
+ $credentials = $GLOBALS['registry']->callAppMethod($app, 'authCredentials');
+ } catch (Horde_Exception $e) {
+ continue;
+ }
+
+ if (!count($credentials)) {
+ continue;
+ }
+
+ $credentials_prefs[$app] = array();
+ foreach ($credentials as $name => $credential) {
+ $pref = 'credentials[' . $app . '][' . $name . ']';
+ $credential['shared'] = true;
+ $credentials_prefs[$app][$pref] = $credential;
+ }
+ }
+
+ return $credentials_prefs;
+ }
+
+ /**
+ * Displays the preference interface for setting all available
+ * credentials.
+ */
+ public function showUi()
+ {
+ $credentials = $this->getCredentials();
+ $vspace = '';
+ foreach ($credentials as $app => $_prefs) {
+ $prefs = Horde_Prefs_Credentials::singleton($app);
+ echo $vspace . '<h2 class="smallheader">';
+ printf(_("%s authentication credentials"),
+ $GLOBALS['registry']->get('name', $app));
+ echo '</h2>';
+ foreach (array_keys($_prefs) as $pref) {
+ $helplink = empty($_prefs[$pref]['help'])
+ ? null
+ : Horde_Help::link(!empty($_prefs[$pref]['shared']) ? 'horde' : $GLOBALS['registry']->getApp(), $_prefs[$pref]['help']);
+ require $GLOBALS['registry']->get('templates') . '/prefs/' . $_prefs[$pref]['type'] . '.inc';
+ }
+ $vspace = '<br />';
+ }
+ }
+
+ /**
+ * Returns the value of a credential for the currently processed
+ * application.
+ *
+ * @see Horde_Prefs::getValue()
+ *
+ * @param string $pref A credential name.
+ *
+ * @return mixed The credential's value, either from the user's
+ * preferences, or from the default value, or null.
+ */
+ public function getValue($pref)
+ {
+ if (isset($this->_credentials[$pref])) {
+ return $this->_credentials[$pref];
+ }
+ $credentials = $this->getCredentials();
+
+ return isset($credentials[$this->app][$pref]['value'])
+ ? $credentials[$this->app][$pref]['value']
+ : null;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Preferences storage implementation using files in a directory
+ *
+ * Copyright 2008-2009 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 Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @category Horde
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_File extends Horde_Prefs
+{
+ /**
+ * Current version number of the data format
+ *
+ * @var int
+ */
+ protected $_version = 2;
+
+ /**
+ * Directory to store the preferences
+ *
+ * @var string
+ */
+ protected $_dirname;
+
+ /**
+ * Full path to the current preference file
+ *
+ * @var string
+ */
+ protected $_fullpath;
+
+ /**
+ * Cached unserialized data of all scopes
+ *
+ * @var array
+ */
+ protected $_fileCache = null;
+
+ /**
+ * Constructor.
+ *
+ * @param string $scope The current preferences scope.
+ * @param string $user The user who owns these preferences.
+ * @param string $password The password associated with $user. (Unused)
+ * @param array $params A hash containing connection parameters.
+ * @param boolean $caching Should caching be used?
+ */
+ public function __construct($scope, $user, $password, $params, $caching)
+ {
+ parent::__construct($scope, $user, $password, $params, $caching);
+
+ // Sanity check for directory
+ $error = false;
+ if (empty($params['directory']) || !is_dir($params['directory'])) {
+ Horde::logMessage(_("Preference storage directory is not available."), __FILE__, __LINE__, PEAR_LOG_ERR);
+ $error = true;
+ } elseif (!is_writable($params['directory'])) {
+ Horde::logMessage(sprintf(_("Directory %s is not writeable"), $params['directory']), __FILE__, __LINE__, PEAR_LOG_ERR);
+ $error = true;
+ }
+
+ if ($error) {
+ $this->_dirname = null;
+ $this->_fullpath = null;
+
+ if (isset($GLOBALS['notification'])) {
+ $GLOBALS['notification']->push(_("The preferences backend is currently unavailable and your preferences have not been loaded. You may continue to use the system with default settings."));
+ }
+ } else {
+ $this->_dirname = $params['directory'];
+ $this->_fullpath = $this->_dirname . '/' . basename($user) . '.prefs';
+ }
+ }
+
+ /**
+ * Retrieves the requested set of preferences from the current session.
+ *
+ * @param string $scope Scope specifier.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _retrieve($scope)
+ {
+ if (is_null($this->_dirname)) {
+ return;
+ }
+
+ if (is_null($this->_fileCache)) {
+ // Try to read
+ $this->_fileCache = $this->_readCache();
+ if (is_null($this->_fileCache)) {
+ return;
+ }
+
+ // Check version number. We can call format transformations hooks
+ // in the future.
+ if (!is_array($this->_fileCache) ||
+ !array_key_exists('__file_version', $this->_fileCache) ||
+ !($this->_fileCache['__file_version'] == $this->_version)) {
+ if ($this->_fileCache['__file_version'] == 1) {
+ $this->transformV1V2();
+ } else {
+ throw new Horde_Exception(sprintf('Wrong version number found: %s (should be %d)', $this->_fileCache['__file_version'], $this->_version));
+ }
+ }
+ }
+
+ // Check if the scope exists
+ if (empty($scope) || !array_key_exists($scope, $this->_fileCache)) {
+ return;
+ }
+
+ // Merge config values
+ foreach ($this->_fileCache[$scope] as $name => $val) {
+ if (isset($this->_scopes[$scope][$name])) {
+ $this->_scopes[$scope][$name]['v'] = $val;
+ $this->_scopes[$scope][$name]['m'] &= ~self::PREFS_DEFAULT;
+ } else {
+ // This is a shared preference.
+ $this->_scopes[$scope][$name] = array('v' => $val,
+ 'm' => 0,
+ 'd' => null);
+ }
+ }
+ }
+
+ /**
+ * Read data from disk.
+ *
+ * @return mixed Data array on success or null on error.
+ */
+ protected function _readCache()
+ {
+ return file_exists($this->_fullpath)
+ ? unserialize(file_get_contents($this->_fullpath))
+ : null;
+ }
+
+ /**
+ * Transforms the broken version 1 format into version 2.
+ */
+ public function transformV1V2()
+ {
+ $version2 = array('__file_version' => 2);
+ foreach ($this->_fileCache as $scope => $prefs) {
+ if ($scope != '__file_version') {
+ foreach ($prefs as $name => $pref) {
+ /* Default values should not have been stored by the
+ * driver. They are being set via the prefs.php files. */
+ if (!($pref['m'] & self::PREFS_DEFAULT)) {
+ $version2[$scope][$name] = $pref['v'];
+ }
+ }
+ }
+ }
+ $this->_fileCache = $version2;
+ }
+
+ /**
+ * Write data to disk
+ *
+ * @return boolean True on success.
+ */
+ protected function _writeCache()
+ {
+ $tmp_file = Horde_Util::getTempFile('PrefsFile', true, $this->_dirname);
+
+ $data = serialize($this->_fileCache);
+
+ if (file_put_contents($tmp_file, $data) === false) {
+ return false;
+ }
+
+ return @rename($tmp_file, $this->_fullpath);
+ }
+
+ /**
+ * Stores preferences in the current session.
+ *
+ * @return boolean True on success.
+ * @throws Horde_Exception
+ */
+ public function store()
+ {
+ if (is_null($this->_dirname)) {
+ return false;
+ }
+
+ // Get the list of preferences that have changed. If there are
+ // none, no need to hit the backend.
+ $dirty_prefs = $this->_dirtyPrefs();
+ if (!$dirty_prefs) {
+ return true;
+ }
+
+ // Read in all existing preferences, if any.
+ $this->_retrieve('');
+ if (!is_array($this->_fileCache)) {
+ $this->_fileCache = array('__file_version' => $this->_version);
+ }
+
+ // Update all values from dirty scope
+ foreach ($dirty_prefs as $scope => $prefs) {
+ foreach ($prefs as $name => $pref) {
+ // Don't store locked preferences.
+ if (!($this->_scopes[$scope][$name]['m'] & self::LOCKED)) {
+ $this->_fileCache[$scope][$name] = $pref['v'];
+
+ // Clean the pref since it was just saved.
+ $this->_scopes[$scope][$name]['m'] &= ~self::DIRTY;
+ }
+ }
+ }
+
+ if ($this->_writeCache() == false) {
+ throw new Horde_Exception('Write of preferences to %s failed', $this->_filename);
+ }
+
+ return true;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * This class provides an interface to all identities a user might have. Its
+ * methods take care of any site-specific restrictions configured in prefs.php
+ * and conf.php.
+ *
+ * @todo Remove notification and gettext references.
+ *
+ * Copyright 2001-2009 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 Jan Schneider <jan@horde.org>
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Identity
+{
+ /**
+ * Singleton instances.
+ *
+ * @var array
+ */
+ static protected $_instances = array();
+
+ /**
+ * Array containing all the user's identities.
+ *
+ * @var array
+ */
+ protected $_identities = array();
+
+ /**
+ * A pointer to the user's standard identity.
+ * This one is used by the methods returning values if no other one is
+ * specified.
+ *
+ * @var integer
+ */
+ protected $_default = 0;
+
+ /**
+ * The user whose identities these are.
+ *
+ * @var string
+ */
+ protected $_user = null;
+
+ /**
+ * Array containing all of the properties in this identity.
+ *
+ * @var array
+ */
+ protected $_properties = array('id', 'fullname', 'from_addr');
+
+ /**
+ * The prefs object that this Identity points to.
+ *
+ * @var Horde_Prefs
+ */
+ protected $_prefs;
+
+ /**
+ * Constructor.
+ *
+ * Reads all the user's identities from the prefs object or builds a new
+ * identity from the standard values given in prefs.php.
+ *
+ * @param string $user If specified, we read another user's identities
+ * instead of the current user.
+ */
+ public function __construct($user = null)
+ {
+ $this->_user = is_null($user)
+ ? Horde_Auth::getAuth()
+ : $user;
+
+ if ((is_null($user) || $user == Horde_Auth::getAuth()) &&
+ isset($GLOBALS['prefs'])) {
+ $this->_prefs = $GLOBALS['prefs'];
+ } else {
+ $this->_prefs = Horde_Prefs::singleton($GLOBALS['conf']['prefs']['driver'], $GLOBALS['registry']->getApp(), $user, '', null, false);
+ $this->_prefs->retrieve();
+ }
+
+ if (!($this->_identities = @unserialize($this->_prefs->getValue('identities', false)))) {
+ /* Convert identities from the old format. */
+ $this->_identities = @unserialize($this->_prefs->getValue('identities'));
+ } elseif (is_array($this->_identities)) {
+ $this->_identities = $this->_prefs->convertFromDriver($this->_identities, Horde_Nls::getCharset());
+ }
+
+ $this->setDefault($this->_prefs->getValue('default_identity'));
+ }
+
+ /**
+ * Creates a default identity if none exists yet and sets the preferences
+ * up if the identities are locked.
+ */
+ public function init()
+ {
+ if (!is_array($this->_identities) || (count($this->_identities) <= 0)) {
+ foreach ($this->_properties as $key) {
+ $identity[$key] = $this->_prefs->getValue($key);
+ }
+ if (empty($identity['id'])) {
+ $identity['id'] = _("Default Identity");
+ }
+
+ $this->_identities = array($identity);
+ $this->verify(0);
+ }
+
+ if ($this->_prefs->isLocked('default_identity')) {
+ foreach ($this->_properties as $key) {
+ $value = $this->getValue($key);
+ if (is_array($value)) {
+ $value = implode("\n", $value);
+ }
+ $this->_prefs->setValue($key, $value);
+ $this->_prefs->setDirty($key, false);
+ }
+ }
+ }
+
+ /**
+ * Saves all identities in the prefs backend.
+ */
+ public function save()
+ {
+ $identities = $this->_identities;
+ if (is_array($identities)) {
+ $identities = $this->_prefs->convertToDriver($identities, Horde_Nls::getCharset());
+ }
+
+ $this->_prefs->setValue('identities', serialize($identities), false);
+ $this->_prefs->setValue('default_identity', $this->_default);
+ }
+
+ /**
+ * Adds a new identity to the array of identities.
+ *
+ * @param array $identity An identity hash to add.
+ *
+ * @return integer The pointer to the created identity
+ */
+ public function add($identity = array())
+ {
+ $this->_identities[] = $identity;
+ return count($this->_identities) - 1;
+ }
+
+ /**
+ * Returns a complete identity hash.
+ *
+ * @param integer $identity The identity to retrieve.
+ *
+ * @return array An identity hash.
+ */
+ public function get($identity = null)
+ {
+ if (is_null($identity) || !isset($this->_identities[$identity])) {
+ $identity = $this->_default;
+ }
+ return $this->_identities[$identity];
+ }
+
+ /**
+ * Removes an identity from the array of identities.
+ *
+ * @param integer $identity The pointer to the identity to be removed
+ *
+ * @return array The removed identity.
+ */
+ public function delete($identity)
+ {
+ $deleted = array_splice($this->_identities, $identity, 1);
+ foreach (array_keys($this->_identities) as $id) {
+ if ($this->setDefault($id)) {
+ break;
+ }
+ }
+ $this->save();
+
+ return $deleted;
+ }
+
+ /**
+ * Returns a pointer to the current default identity.
+ *
+ * @return integer The pointer to the current default identity.
+ */
+ public function getDefault()
+ {
+ return $this->_default;
+ }
+
+ /**
+ * Sets the current default identity.
+ * If the identity doesn't exist, the old default identity stays the same.
+ *
+ * @param integer $identity The pointer to the new default identity.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function setDefault($identity)
+ {
+ if (isset($this->_identities[$identity])) {
+ $this->_default = $identity;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a property from one of the identities. If this value doesn't
+ * exist or is locked, the property is retrieved from the prefs backend.
+ *
+ * @param string $key The property to retrieve.
+ * @param integer $identity The identity to retrieve the property from.
+ *
+ * @return mixed The value of the property.
+ */
+ public function getValue($key, $identity = null)
+ {
+ if (is_null($identity) || !isset($this->_identities[$identity])) {
+ $identity = $this->_default;
+ }
+
+ return (!isset($this->_identities[$identity][$key]) || $this->_prefs->isLocked($key))
+ ? $this->_prefs->getValue($key)
+ : $this->_identities[$identity][$key];
+ }
+
+ /**
+ * Returns an array with the specified property from all existing
+ * identities.
+ *
+ * @param string $key The property to retrieve.
+ *
+ * @return array The array with the values from all identities.
+ */
+ public function getAll($key)
+ {
+ $list = array();
+
+ foreach (array_keys($this->_identities) as $identity) {
+ $list[$identity] = $this->getValue($key, $identity);
+ }
+
+ return $list;
+ }
+
+ /**
+ * Sets a property with a specified value.
+ *
+ * @param string $key The property to set.
+ * @param mixed $val The value to which the property should be
+ * set.
+ * @param integer $identity The identity to set the property in.
+ *
+ * @return boolean True on success, false on failure (property was
+ * locked).
+ */
+ public function setValue($key, $val, $identity = null)
+ {
+ if (is_null($identity)) {
+ $identity = $this->_default;
+ }
+
+ if (!$this->_prefs->isLocked($key)) {
+ $this->_identities[$identity][$key] = $val;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if all properties are locked and therefore nothing in the
+ * identities can be changed.
+ *
+ * @return boolean True if all properties are locked, false otherwise.
+ */
+ public function isLocked()
+ {
+ foreach ($this->_properties as $key) {
+ if (!$this->_prefs->isLocked($key)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the given address belongs to one of the identities.
+ *
+ * @param string $key The identity key to search.
+ * @param string $value The value to search for in $key.
+ *
+ * @return boolean True if the $value was found in $key.
+ */
+ public function hasValue($key, $valueA)
+ {
+ $list = $this->getAll($key);
+
+ foreach ($list as $valueB) {
+ if (!empty($valueB) &&
+ strpos(Horde_String::lower($valueA), Horde_String::lower($valueB)) !== false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Verifies and sanitizes all identity properties.
+ *
+ * @param integer $identity The identity to verify.
+ *
+ * @return bool|object True if the properties are valid.
+ * @throws Horde_Exception
+ */
+ public function verify($identity = null)
+ {
+ if (is_null($identity)) {
+ $identity = $this->_default;
+ }
+
+ if (!$this->getValue('id', $identity)) {
+ $this->setValue('id', _("Unnamed"), $identity);
+ }
+
+ /* RFC 2822 [3.2.5] does not allow the '\' character to be used in the
+ * personal portion of an e-mail string. */
+ if (strpos($this->getValue('fullname', $identity), '\\') !== false) {
+ throw new Horde_Exception('You cannot have the \ character in your full name.');
+ }
+
+ /* Prepare email validator */
+ require_once 'Horde/Form.php';
+ $email = new Horde_Form_Type_email();
+ $vars = new Horde_Variables();
+ $var = new Horde_Form_Variable('', 'replyto_addr', $email, false);
+
+ /* Verify From address. */
+ if ($email->isValid($var, $vars, $this->getValue('from_addr', $identity), $error_message)) {
+ return true;
+ }
+
+ throw new Horde_Exception($error_message);
+ }
+
+ /**
+ * Returns the user's full name.
+ *
+ * @param integer $ident The identity to retrieve the name from.
+ *
+ * @return string The user's full name, or the user name if it doesn't
+ * exist.
+ */
+ public function getName($ident = null)
+ {
+ if (isset($this->_names[$ident])) {
+ return $this->_names[$ident];
+ }
+
+ $this->_names[$ident] = $this->getValue('fullname', $ident);
+ if (!strlen($this->_names[$ident])) {
+ $this->_names[$ident] = $this->_user;
+ }
+
+ return $this->_names[$ident];
+ }
+
+ /**
+ * Generates the from address to use for the default identity.
+ *
+ * @param boolean $fullname Include the fullname information.
+ *
+ * @return string The default from address.
+ */
+ public function getDefaultFromAddress($fullname = false)
+ {
+ $from_addr = '';
+
+ if ($fullname) {
+ $name = $this->getValue('fullname');
+ if (!empty($name)) {
+ $from_addr = $name . ' ';
+ }
+ }
+
+ $addr = $this->getValue('from_addr');
+ if (empty($addr)) {
+ $addr = $this->_user;
+ if (empty($from_addr)) {
+ return $addr;
+ }
+ }
+
+ return $from_addr . '<' . $addr . '>';
+ }
+
+ /**
+ * Sends a message to an email address supposed to be added to the
+ * identity.
+ * A message is send to this address containing a link to confirm that the
+ * address really belongs to that user.
+ *
+ * @param integer $id The identity's ID.
+ * @param string $old_addr The old From: address.
+ *
+ * @return TODO
+ * @throws Horde_Mime_Exception
+ */
+ public function verifyIdentity($id, $old_addr)
+ {
+ global $conf;
+
+ $hash = base_convert(microtime() . mt_rand(), 10, 36);
+
+ $pref = @unserialize($this->_prefs->getValue('confirm_email', false));
+ $pref = $pref
+ ? $this->_prefs->convertFromDriver($pref, Horde_Nls::getCharset())
+ : array();
+ $pref[$hash] = $this->get($id);
+ $pref = $this->_prefs->convertToDriver($pref, Horde_Nls::getCharset());
+ $this->_prefs->setValue('confirm_email', serialize($pref), false);
+
+ $new_addr = $this->getValue('from_addr', $id);
+ $confirm = Horde_Util::addParameter(Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/confirm.php', true, -1), 'h', $hash, false);
+ $message = sprintf(_("You have requested to add the email address \"%s\" to the list of your personal email addresses.\n\nGo to the following link to confirm that this is really your address:\n%s\n\nIf you don't know what this message means, you can delete it."),
+ $new_addr,
+ $confirm);
+
+ $msg_headers = new Horde_Mime_Headers();
+ $msg_headers->addMessageIdHeader();
+ $msg_headers->addUserAgentHeader();
+ $msg_headers->addHeader('Date', date('r'));
+ $msg_headers->addHeader('To', $new_addr);
+ $msg_headers->addHeader('From', $old_addr);
+ $msg_headers->addHeader('Subject', _("Confirm new email address"));
+
+ $body = new Horde_Mime_Part();
+ $body->setType('text/plain');
+ $body->setContents(Horde_String::wrap($message, 76, "\n"));
+ $body->setCharset(Horde_Nls::getCharset());
+
+ $mail_driver = $conf['mailer']['type'];
+ $mail_params = $conf['mailer']['params'];
+ if (($mail_driver == 'smtp') &&
+ $mail_params['auth'] &&
+ empty($mail_params['username'])) {
+ $mail_params['username'] = Horde_Auth::getAuth();
+ $mail_params['password'] = Horde_Auth::getCredential('password');
+ }
+
+ $body->send($new_addr, $msg_headers, $mail_driver, $mail_params);
+
+ return new Horde_Notification_Event(sprintf(_("A message has been sent to \"%s\" to verify that this is really your address. The new email address is activated as soon as you confirm this message."), $new_addr));
+ }
+
+ /**
+ * Checks whether an identity confirmation is valid, and adds the
+ * validated identity.
+ *
+ * @param string $hash The saved hash of the identity being validated.
+ *
+ * @return array A message for the user, and the message level.
+ */
+ public function confirmIdentity($hash)
+ {
+ $confirm = $this->_prefs->getValue('confirm_email', false);
+ if (empty($confirm)) {
+ return array(_("There are no email addresses to confirm."), 'horde.message');
+ }
+
+ $confirm = @unserialize($confirm);
+ if (empty($confirm)) {
+ return array(_("There are no email addresses to confirm."), 'horde.message');
+ } elseif (!isset($confirm[$hash])) {
+ return array(_("Email addresses to confirm not found."), 'horde.message');
+ }
+
+ $identity = $this->_prefs->convertFromDriver($confirm[$hash], Horde_Nls::getCharset());
+ $verified = array();
+ foreach ($identity as $key => $value) {
+ if (!$this->_prefs->isLocked($key)) {
+ $verified[$key] = $value;
+ }
+ }
+ $this->add($verified);
+ $this->save();
+ unset($confirm[$hash]);
+ $this->_prefs->setValue('confirm_email', serialize($confirm), false);
+
+ return array(sprintf(_("The email address %s has been added to your identities. You can close this window now."), $verified['from_addr']), 'horde.success');
+ }
+
+ /**
+ * Attempts to return a concrete instance based on $type.
+ *
+ * @param mixed $driver The type of concrete Identity subclass to return.
+ * This is based on the storage driver. The code is
+ * dynamically included. If $type is an array, then
+ * we will look in $driver[0]/lib/Prefs/Identity/
+ * for the subclass implementation named
+ * $driver[1].php.
+ * @param string $user If specified, we read another user's identities
+ * instead of the current user.
+ *
+ * @return Horde_Prefs_Identity The newly created instance.
+ * @throws Horde_Exception
+ */
+ static public function factory($driver = 'None', $user = null)
+ {
+ if (is_array($driver)) {
+ list($app, $driv_name) = $driver;
+ $driver = basename($driv_name);
+ } else {
+ $driver = basename($driver);
+ }
+
+ /* Return a base Identity object if no driver is specified. */
+ if (empty($driver) || (strcasecmp($driver, 'none') == 0)) {
+ $instance = new Horde_Prefs_Identity($user);
+ $instance->init();
+ return $instance;
+ }
+
+ $class = (empty($app) ? 'Horde' : $app) . '_Prefs_Identity';
+
+ if (class_exists($class)) {
+ $instance = new $class($user);
+ $instance->init();
+ return $instance;
+ }
+
+ throw new Horde_Exception('Class definition of ' . $class . ' not found.');
+ }
+
+ /**
+ * Attempts to return a reference to a concrete instance based on
+ * $type. It will only create a new instance if no instance with
+ * the same parameters currently exists.
+ *
+ * This should be used if multiple types of identities (and, thus,
+ * multiple instances) are required.
+ *
+ * This method must be invoked as:
+ * $var = Horde_Prefs_Identity::singleton()
+ *
+ * @param mixed $type The type of concrete subclass to return.
+ * This is based on the storage driver ($type). The
+ * code is dynamically included. If $type is an array,
+ * then we will look in $type[0]/lib/Prefs/Identity/
+ * for the subclass implementation named
+ * $type[1].php.
+ * @param string $user If specified, we read another user's identities
+ * instead of the current user.
+ *
+ * @return Horde_Prefs_Identity The concrete reference.
+ * @throws Horde_Exception
+ */
+ static public function singleton($type = 'None', $user = null)
+ {
+ $signature = hash('md5', serialize(array($type, $user)));
+ if (!isset(self::$_instances[$signature])) {
+ self::$_instances[$signature] = Horde_Prefs_Identity::factory($type, $user);
+ }
+
+ return self::$_instances[$signature];
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Preference storage implementation for an IMSP server.
+ *
+ * Copyright 2004-2009 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 Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Imsp extends Horde_Prefs
+{
+ /**
+ * Handle for the IMSP server connection.
+ *
+ * @var Net_IMSP
+ */
+ protected $_imsp;
+
+ /**
+ * User password.
+ *
+ * @var string
+ */
+ protected $_password;
+
+ /**
+ * Boolean indicating whether or not we're connected to the IMSP server.
+ *
+ * @var boolean
+ */
+ protected $_connected = false;
+
+ /**
+ * Holds the driver specific parameters.
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Retrieves the requested set of preferences from the IMSP server.
+ *
+ * @param string $scope Scope specifier.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _retrieve($scope)
+ {
+ /* Now connect to the IMSP server. */
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ if (empty($_SESSION['prefs_cache']['unavailable'])) {
+ $_SESSION['prefs_cache']['unavailable'] = true;
+ $GLOBALS['notification']->push(_("The preferences backend is currently unavailable and your preferences have not been loaded. You may continue to use the system with default settings."));
+ }
+ return;
+ }
+
+ $prefs = $this->_imsp->get($scope . '.*');
+ if ($prefs instanceof PEAR_Error) {
+ Horde::logMessage($prefs, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ foreach ($prefs as $name => $val) {
+ $name = str_replace($scope . '.', '', $name);
+ if ($val != '-') {
+ if (isset($this->_scopes[$scope][$name])) {
+ $this->_scopes[$scope][$name]['v'] = $val;
+ $this->_scopes[$scope][$name]['m'] &= ~self::PREFS_DEFAULT;
+ } else {
+ // This is a shared preference.
+ $this->_scopes[$scope][$name] = array('v' => $val,
+ 'm' => 0,
+ 'd' => null);
+ }
+ }
+ }
+ }
+
+ /**
+ * Stores all dirty prefs to IMSP server.
+ */
+ public function store()
+ {
+ // Get the list of preferences that have changed. If there are
+ // none, no need to hit the backend.
+ $dirty_prefs = $this->_dirtyPrefs();
+ if (!$dirty_prefs) {
+ return;
+ }
+
+ $this->_connect();
+
+ foreach ($dirty_prefs as $scope => $prefs) {
+ foreach ($prefs as $name => $pref) {
+ // Don't store locked preferences.
+ if ($this->_scopes[$scope][$name]['m'] & self::LOCKED) {
+ continue;
+ }
+
+ $value = $pref['v'];
+ if (empty($value)) {
+ $value = '-';
+ }
+
+ $result = $this->_imsp->set($scope . '.' . $name, $value);
+ if ($result instanceof PEAR_Error) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ // Clean the pref since it was just saved.
+ $this->_scopes[$scope][$name]['m'] &= ~self::DIRTY;
+ }
+
+ // Update the cache for this scope.
+ $this->_cacheUpdate($scope, array_keys($prefs));
+ }
+ }
+
+ /**
+ * Attempts to set up a connection to the IMSP server.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connected) {
+ return;
+ }
+
+ $this->_params['username'] = preg_match('/(^.*)@/', $this->_user, $matches)
+ ? $matches[1]
+ : $this->_user;
+ $this->_params['password'] = $this->_password;
+
+ if (isset($this->_params['socket'])) {
+ $this->_params['socket'] = $params['socket'] . 'imsp_' . $this->_params['username'] . '.sck';
+ }
+
+ $this->_imsp = Net_IMSP::factory('Options', $this->_params);
+ $result = $this->_imsp->init();
+ if ($result instanceof PEAR_Error) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception($result);
+ }
+
+ $this->_imsp->setLogger($GLOBALS['conf']['log']);
+ $this->_connected = true;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Kolab implementation of the Horde preference system. Derives from the
+ * Prefs_ldap LDAP authentication object, and simply provides parameters to it
+ * based on the global Kolab configuration.
+ *
+ * Copyright 2004-2007 Stuart Binge <s.binge@codefusion.co.za>
+ *
+ * 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 Stuart Binge <s.binge@codefusion.co.za>
+ * @category Horde
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Kolab extends Horde_Prefs_Ldap
+{
+ /**
+ * Constructor.
+ *
+ * @param string $scope The current application scope.
+ * @param string $user The user who owns these preferences.
+ * @param string $password The password associated with $user.
+ * @param array $params A hash containing connection parameters.
+ * @param boolean $caching Should caching be used?
+ */
+ public function __construct($scope, $user, $password, $params, $caching)
+ {
+ require_once 'Horde/Kolab.php';
+ $params = array(
+ 'hostspec' => Kolab::getServer('ldap'),
+ 'port' => $GLOBALS['conf']['kolab']['ldap']['port'],
+ 'version' => '3',
+ 'basedn' => $GLOBALS['conf']['kolab']['ldap']['basedn'],
+ 'writedn' => 'user',
+ 'searchdn' => $GLOBALS['conf']['kolab']['ldap']['phpdn'],
+ 'searchpw' => $GLOBALS['conf']['kolab']['ldap']['phppw'],
+ 'uid' => 'mail'
+ );
+
+ parent::__construct($scope, $user, $password, $params, $caching);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Preferences storage implementation for a Kolab IMAP server.
+ *
+ * Copyright 2007-2009 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 Gunnar Wrobel <p@rdus.de>
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_KolabImap extends Horde_Prefs
+{
+ /**
+ * ID of the config default share
+ *
+ * @var string
+ */
+ protected $_share;
+
+ /**
+ * Handle for the current Kolab connection.
+ *
+ * @var Kolab
+ */
+ protected $_connection;
+
+ /**
+ * Opens a connection to the Kolab server.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _connect()
+ {
+ if (isset($this->_connection)) {
+ return;
+ }
+
+ $shares = Horde_Share::singleton('h-prefs');
+ $default = $shares->getDefaultShare();
+ if ($default instanceof PEAR_Error) {
+ Horde::logMessage($default, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception($default);
+ }
+ $this->_share = $default->getName();
+
+ require_once 'Horde/Kolab.php';
+ $connection = new Kolab('h-prefs');
+ if ($connection instanceof PEAR_Error) {
+ Horde::logMessage($connection, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception($connection);
+ }
+
+ $result = $this->_connection->open($this->_share, 1);
+ if ($result instanceof PEAR_Error) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception($result);
+ }
+
+ $this->_connection = $connection;
+ }
+
+ /**
+ * Retrieves the requested set of preferences from the user's config folder.
+ *
+ * @param string $scope Scope specifier.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _retrieve($scope)
+ {
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ if (empty($_SESSION['prefs_cache']['unavailable'])) {
+ $_SESSION['prefs_cache']['unavailable'] = true;
+ if (isset($GLOBALS['notification'])) {
+ $GLOBALS['notification']->push(_("The preferences backend is currently unavailable and your preferences have not been loaded. You may continue to use the system with default settings."));
+ }
+ }
+ return;
+ }
+
+ try {
+ $pref = $this->_getPref($scope);
+ } catch (Horde_Exception $e) {
+ return;
+ }
+
+ if (is_null($pref)) {
+ /* No preferences saved yet */
+ return;
+ }
+
+ foreach ($pref['pref'] as $prefstr) {
+ // If the string doesn't contain a colon delimiter, skip it.
+ if (strpos($prefstr, ':') === false) {
+ continue;
+ }
+
+ // Split the string into its name:value components.
+ list($name, $val) = explode(':', $prefstr, 2);
+ if (isset($this->_scopes[$scope][$name])) {
+ $this->_scopes[$scope][$name]['v'] = base64_decode($val);
+ $this->_scopes[$scope][$name]['m'] &= ~self::PREFS_DEFAULT;
+ } else {
+ // This is a shared preference.
+ $this->_scopes[$scope][$name] = array('v' => base64_decode($val),
+ 'm' => 0,
+ 'd' => null);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the requested preference from the user's config folder.
+ *
+ * @param string $scope Scope specifier.
+ *
+ * @return array The preference value.
+ * @throws Horde_Exception
+ */
+ protected function _getPref($scope)
+ {
+ $this->_connect();
+
+ $prefs = $this->_connection->getObjects();
+ if ($prefs instanceof PEAR_Error) {
+ Horde::logMessage($prefs, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception($prefs);
+ }
+
+ foreach ($prefs as $pref) {
+ if ($pref['application'] == $scope) {
+ return $pref;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Stores preferences to the Kolab server.
+ *
+ * @throws Horde_Exception
+ */
+ public function store()
+ {
+ // Get the list of preferences that have changed. If there are
+ // none, no need to hit the backend.
+ $dirty_prefs = $this->_dirtyPrefs();
+ if (!$dirty_prefs) {
+ return;
+ }
+ $dirty_scopes = array_keys($dirty_prefs);
+
+ $this->_connect();
+
+ // Build a hash of the preferences and their values that need
+ // 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 ($dirty_scopes as $scope) {
+ $new_values = array();
+ foreach ($this->_scopes[$scope] as $name => $pref) {
+ // Don't store locked preferences.
+ if (!($pref['m'] & self::LOCKED)) {
+ $new_values[] = $name . ':' . base64_encode($pref['v']);
+ }
+ }
+
+ try {
+ $pref = $this->_getPref($scope);
+ } catch (Horde_Exception $e) {
+ return;
+ }
+
+ 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
+ );
+
+ $result = $this->_connection->_storage->save($object, $old_uid);
+ if ($result instanceof PEAR_Error) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+ }
+
+ // Clean the preferences since they were just saved.
+ foreach ($dirty_prefs as $scope => $prefs) {
+ foreach ($prefs as $name => $pref) {
+ $this->_scopes[$scope][$name]['m'] &= ~_PREF_DIRTY;
+ }
+
+ // Update the cache for this scope.
+ $this->_cacheUpdate($scope, array_keys($prefs));
+ }
+ }
+
+ /**
+ * Clears all preferences from the kolab_imap backend.
+ */
+ public function clear()
+ {
+ return $this->_connection->deleteAll();
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Preferences storage implementation for PHP's LDAP extension.
+ *
+ * Required parameters:
+ * 'basedn' - The base DN for the LDAP server.
+ * 'hostspec' - The hostname of the LDAP server.
+ * 'uid' - The username search key.
+ * 'writedn' - One of "user", "admin", or "searchdn"
+ *
+ * Optional parameters:
+ * 'admindn' - The DN of the administrative account to bind for
+ * write operations.
+ * 'adminpw' - 'admindn's password for bind authentication.
+ * 'port' - The port of the LDAP server.
+ * DEFAULT: 389
+ * 'searchdn' - The DN of a user with search permissions on the directory
+ * 'searchpw' - 'searchdn's password for binding
+ * 'tls' - Whether to use TLS connections.
+ * DEFAULT: false
+ * 'version' - The version of the LDAP protocol to use.
+ * DEFAULT: NONE (system default will be used)
+ *
+ * If setting up as the Horde preference handler in conf.php, the following
+ * is an example configuration.
+ * The schemas needed for ldap are in horde/scripts/ldap.
+ *
+ * <code>
+ * $conf['prefs']['driver'] = 'ldap';
+ * $conf['prefs']['params']['hostspec'] = 'localhost';
+ * $conf['prefs']['params']['port'] = '389';
+ * $conf['prefs']['params']['basedn'] = 'dc=example,dc=org';
+ * $conf['prefs']['params']['uid'] = 'mail';
+ * </code>
+ *
+ * The following is valid but would only be necessary if users do NOT have
+ * permission to modify their own LDAP accounts.
+ *
+ * <code>
+ * $conf['prefs']['params']['admindn'] = 'cn=Manager,dc=example,dc=org';
+ * $conf['prefs']['params']['adminpw'] = 'password';
+ * </code>
+ *
+ * Copyright 1999-2009 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 Jon Parise <jon@horde.org>
+ * @author Ben Klang <ben@alkaloid.net>
+ * @category Horde
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Ldap extends Horde_Prefs
+{
+ /**
+ * Hash containing connection parameters.
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Handle for the current LDAP connection.
+ *
+ * @var resource
+ */
+ protected $_connection;
+
+ /**
+ * Boolean indicating whether or not we're connected to the LDAP server.
+ *
+ * @var boolean
+ */
+ protected $_connected = false;
+
+ /**
+ * String holding the user's DN.
+ *
+ * @var string
+ */
+ protected $_dn = '';
+
+ /**
+ * String holding the user's password.
+ *
+ * @var string
+ */
+ protected $_password = '';
+
+ /**
+ * Constructor.
+ *
+ * @param string $scope The current application scope.
+ * @param string $user The user who owns these preferences.
+ * @param string $password The password associated with $user.
+ * @param array $params A hash containing connection parameters.
+ * @param boolean $caching Should caching be used?
+ */
+ public function __construct($scope, $user, $password, $params, $caching)
+ {
+ /* If a valid server port has not been specified, set the default. */
+ if (!isset($params['port']) || !is_int($params['port'])) {
+ $params['port'] = 389;
+ }
+
+ parent::__construct($scope, $user, $password, $params, $caching);
+ }
+
+ /**
+ * Opens a connection to the LDAP server.
+ *
+ * @throws Horde_Exception
+ */
+ function _connect()
+ {
+ if ($this->_connected) {
+ return;
+ }
+
+ if (!Horde_Util::extensionExists('ldap')) {
+ throw new Horde_Exception('Required LDAP extension not found.');
+ }
+
+ Horde::assertDriverConfig($this->_params, 'prefs',
+ array('hostspec', 'basedn', 'uid', 'writedn'),
+ 'preferences LDAP');
+
+ /* Connect to the LDAP server anonymously. */
+ $conn = ldap_connect($this->_params['hostspec'], $this->_params['port']);
+ if (!$conn) {
+ Horde::logMessage(
+ sprintf('Failed to open an LDAP connection to %s.',
+ $this->_params['hostspec']),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+ }
+
+ /* Set the LDAP protocol version. */
+ if (isset($this->_params['version'])) {
+ $result = @ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION,
+ $this->_params['version']);
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Set LDAP protocol version to %d failed: [%d] %s',
+ $this->_params['version'],
+ @ldap_errno($conn),
+ @ldap_error($conn)),
+ __FILE__, __LINE__, PEAR_LOG_WARNING);
+ throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+ }
+ }
+
+ /* Start TLS if we're using it. */
+ if (!empty($this->_params['tls'])) {
+ if (!@ldap_start_tls($conn)) {
+ Horde::logMessage(
+ sprintf('STARTTLS failed: [%d] %s',
+ @ldap_errno($this->_ds),
+ @ldap_error($this->_ds)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ }
+
+ /* If necessary, bind to the LDAP server as the user with search
+ * permissions. */
+ if (!empty($this->_params['searchdn'])) {
+ $bind = @ldap_bind($conn, $this->_params['searchdn'],
+ $this->_params['searchpw']);
+ if ($bind === false) {
+ Horde::logMessage(
+ sprintf('Bind to server %s:%d with DN %s failed: [%d] %s',
+ $this->_params['hostspec'],
+ $this->_params['port'],
+ $this->_params['searchdn'],
+ @ldap_errno($conn),
+ @ldap_error($conn)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+ }
+ }
+
+ /* Register our callback function to handle referrals. */
+ if (function_exists('ldap_set_rebind_proc')) {
+ $result = @ldap_set_rebind_proc($conn, array($this, 'rebindProc'));
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Setting referral callback failed: [%d] %s',
+ @ldap_errno($conn),
+ @ldap_error($conn)),
+ __FILE__, __LINE__, PEAR_LOG_WARNING);
+ return PEAR::raiseError(_("Internal LDAP error. Details have been logged for the administrator."));
+ }
+ }
+
+ /* Store the connection handle at the instance level. */
+ $this->_connection = $conn;
+
+ /* Search for the user's full DN. */
+ $search = @ldap_search($this->_connection, $this->_params['basedn'],
+ $this->_params['uid'] . '=' . $this->_user, array('dn'));
+ if ($search === false) {
+ Horde::logMessage(
+ sprintf('Error while searching the directory for the user\'s DN: [%d]: %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+ }
+
+ $result = @ldap_get_entries($this->_connection, $search);
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Error while retrieving LDAP search results for the user\'s DN: [%d]: %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+ }
+
+ if ($result['count'] != 1) {
+ Horde::logMessage(
+ 'Zero or more than one DN returned from search; unable to determine user\'s correct DN.',
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+ }
+ $this->_dn = $result[0]['dn'];
+
+ // Now we should have the user's DN. Re-bind as appropriate with write
+ // permissions to be able to store preferences.
+ switch($this->_params['writedn']) {
+ case 'user':
+ $result = @ldap_bind($this->_connection,
+ $this->_dn, $this->_password);
+ break;
+
+ case 'admin':
+ $result = @ldap_bind($this->_connection,
+ $this->_params['admindn'],
+ $this->_params['adminpw']);
+ break;
+
+ case 'searchdn':
+ // Since we've already bound as the search DN above, no rebinding
+ // is necessary.
+ $result = true;
+ break;
+ }
+
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Error rebinding for prefs writing: [%d]: %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+ }
+
+ // We now have a ready-to-use connection.
+ $this->_connected = true;
+ }
+
+ /**
+ * Callback function for LDAP referrals. This function is called when an
+ * LDAP operation returns a referral to an alternate server.
+ *
+ * @return integer 1 on error, 0 on success.
+ */
+ public function rebindProc($conn, $who)
+ {
+ /* Strip out the hostname we're being redirected to. */
+ $who = preg_replace(array('|^.*://|', '|:\d*$|'), '', $who);
+
+ /* Make sure the server we're being redirected to is in our list of
+ valid servers. */
+ if (strpos($this->_params['hostspec'], $who) === false) {
+ Horde::logMessage(
+ sprintf('Referral target %s for DN %s is not in the authorized server list.',
+ $who, $bind_dn),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return 1;
+ }
+
+ /* Figure out the DN of the authenticating user. */
+ switch($this->_params['writedn']) {
+ case 'user':
+ $bind_dn = $this->_dn;
+ $bind_pw = $this->_password;
+ break;
+
+ case 'admin':
+ $bind_dn = $this->_params['admindn'];
+ $bind_pw = $this->_params['adminpw'];
+ break;
+
+ case 'searchdn':
+ $bind_dn = $this->_params['searchdn'];
+ $bind_dn = $this->_params['searchpw'];
+ break;
+ }
+
+ /* Bind to the new server. */
+ $bind = @ldap_bind($conn, $bind_dn, $bind_pw);
+ if ($bind === false) {
+ Horde::logMessage(
+ sprintf('Rebind to server %s:%d with DN %s failed: [%d] %s',
+ $this->_params['hostspec'],
+ $this->_params['port'],
+ $bind_dn,
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Retrieves the requested set of preferences from the user's LDAP entry.
+ *
+ * @param string $scope Scope specifier.
+ */
+ function _retrieve($scope)
+ {
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ if (empty($_SESSION['prefs_cache']['unavailable'])) {
+ $_SESSION['prefs_cache']['unavailable'] = true;
+ $GLOBALS['notification']->push(_("The preferences backend is currently unavailable and your preferences have not been loaded. You may continue to use the system with default settings."));
+ }
+ return;
+ }
+
+ // Search for the multi-valued field containing the array of
+ // preferences.
+ $search = @ldap_search($this->_connection, $this->_params['basedn'],
+ $this->_params['uid'] . '=' . $this->_user,
+ array($scope . 'Prefs'));
+ if ($search === false) {
+ Horde::logMessage(
+ sprintf('Error while searching for the user\'s prefs: [%d]: %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ $result = @ldap_get_entries($this->_connection, $search);
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Error while retrieving LDAP search results for the user\'s prefs: [%d]: %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ // 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'
+
+ // ldap_get_entries() converts attribute indexes to lowercase.
+ $field = Horde_String::lower($scope . 'prefs');
+ $prefs = isset($result[0][$field])
+ ? $result[0][$field]
+ : array();
+
+ foreach ($prefs as $prefstr) {
+ // If the string doesn't contain a colon delimiter, skip it.
+ if (strpos($prefstr, ':') === false) {
+ continue;
+ }
+
+ // Split the string into its name:value components.
+ list($name, $val) = explode(':', $prefstr, 2);
+ if (isset($this->_scopes[$scope][$name])) {
+ $this->_scopes[$scope][$name]['v'] = base64_decode($val);
+ $this->_scopes[$scope][$name]['m'] &= ~self::PREFS_DEFAULT;
+ } else {
+ // This is a shared preference.
+ $this->_scopes[$scope][$name] = array('v' => base64_decode($val),
+ 'm' => 0,
+ 'd' => null);
+ }
+ }
+ }
+
+ /**
+ * Stores preferences to the LDAP server.
+ *
+ * @throws Horde_Exception
+ */
+ public function store()
+ {
+ // Get the list of preferences that have changed. If there are
+ // none, no need to hit the backend.
+ $dirty_prefs = $this->_dirtyPrefs();
+ if (!$dirty_prefs) {
+ return;
+ }
+ $dirty_scopes = array_keys($dirty_prefs);
+
+ $this->_connect();
+
+ // Build a hash of the preferences and their values that need
+ // 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 ($dirty_scopes as $scope) {
+ foreach ($this->_scopes[$scope] as $name => $pref) {
+ // Don't store locked preferences.
+ if (!($pref['m'] & self::LOCKED)) {
+ $new_values[$scope . 'Prefs'][] =
+ $name . ':' . base64_encode($pref['v']);
+ }
+ }
+ }
+
+ // 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->_user,
+ array('objectclass'));
+ if ($search === false) {
+ Horde::logMessage(
+ sprintf('Error searching the directory for required objectClasses: [%d] %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ $result = @ldap_get_entries($this->_connection, $search);
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Error retrieving results while checking for required objectClasses: [%d] %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ if ($result['count'] > 0) {
+ $top = false;
+ $hordeperson = false;
+
+ for ($i = 0; $i < $result[0]['objectclass']['count']; $i++) {
+ if ($result[0]['objectclass'][$i] == 'top') {
+ $top = true;
+ } elseif ($result[0]['objectclass'][$i] == 'hordePerson') {
+ $hordeperson = true;
+ }
+ }
+
+ // Add any missing objectclasses.
+ if (!$top) {
+ @ldap_mod_add($this->_connection, $this->_dn, array('objectclass' => 'top'));
+ }
+
+ if (!$hordeperson) {
+ @ldap_mod_add($this->_connection, $this->_dn, array('objectclass' => 'hordePerson'));
+ }
+ }
+
+ // Send the hash to the LDAP server.
+ $result = @ldap_mod_replace($this->_connection, $this->_dn,
+ $new_values);
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Unable to modify user\'s objectClass for preferences: [%d] %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ // Clean the preferences since they were just saved.
+ foreach ($dirty_prefs as $scope => $prefs) {
+ foreach ($prefs as $name => $pref) {
+ $this->_scopes[$scope][$name]['m'] &= ~_PREF_DIRTY;
+ }
+
+ // Update the cache for this scope.
+ $this->_cacheUpdate($scope, array_keys($prefs));
+ }
+ }
+
+ /**
+ * Clears all preferences from the LDAP backend.
+ *
+ * @throws Horde_Exception
+ */
+ public function clear()
+ {
+ $this->_connect();
+
+ $attrs = $GLOBALS['registry']->listApps(array('inactive', 'active', 'hidden', 'notoolbar', 'admin'));
+ foreach ($attrs as $key => $val) {
+ $attrs[$key] = $val . 'Prefs';
+ }
+
+ $search = @ldap_read($this->_connection, $this->_dn,
+ 'objectClass=hordePerson', $attrs, 1);
+ if ($search === false) {
+ Horde::logMessage(
+ sprintf('Error while getting preferenes from LDAP: [%d] %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ $result = @ldap_get_entries($this->_connection, $search);
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Error while retrieving results from LDAP: [%d] %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ $attrs = array();
+ for ($i = 0; $i < $result[0]['count']; $i++) {
+ $attrs[$result[0][$i]] = array();
+ }
+ $result = @ldap_mod_del($this->_connection, $this->_dn, $attrs);
+ if ($result === false) {
+ Horde::logMessage(
+ sprintf('Unable to clear user\'s preferences: [%d] %s',
+ @ldap_errno($this->_connection),
+ @ldap_error($this->_connection)),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ $this->cleanup(true);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Preferences storage implementation for PHP's session implementation.
+ *
+ * Copyright 1999-2009 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 Jon Parise <jon@horde.org>
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Session extends Horde_Prefs
+{
+ /**
+ * Retrieves the requested set of preferences from the current session.
+ *
+ * @param string $scope Scope specifier.
+ */
+ protected function _retrieve($scope)
+ {
+ if (isset($_SESSION['horde_prefs'][$scope])) {
+ $this->_scopes[$scope] = $_SESSION['horde_prefs'][$scope];
+ }
+ }
+
+ /**
+ * Stores preferences in the current session.
+ */
+ public function store()
+ {
+ // Create and register the preferences array, if necessary.
+ if (!isset($_SESSION['horde_prefs'])) {
+ $_SESSION['horde_prefs'] = array();
+ }
+
+ // Copy the current preferences into the session variable.
+ foreach ($this->_scopes as $scope => $prefs) {
+ $pref_keys = array_keys($prefs);
+ foreach ($pref_keys as $pref_name) {
+ // Clean the pref since it was just saved.
+ $prefs[$pref_name]['m'] &= ~Horde_Prefs::DIRTY;
+ }
+
+ $_SESSION['horde_prefs'][$scope] = $prefs;
+ }
+ }
+
+ /**
+ * Perform cleanup operations.
+ *
+ * @param boolean $all Cleanup all Horde preferences.
+ */
+ public function cleanup($all = false)
+ {
+ // Perform a Horde-wide cleanup?
+ if ($all) {
+ unset($_SESSION['horde_prefs']);
+ } else {
+ unset($_SESSION['horde_prefs'][$this->_scope]);
+ $_SESSION['horde_prefs']['_filled'] = false;
+ }
+
+ parent::cleanup($all);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Preferences storage implementation for PHP's PEAR database
+ * abstraction layer.
+ *
+ * Required parameters:
+ * <pre>
+ * 'charset' - The database's internal charset.
+ * 'phptype' - The database type (ie. 'pgsql', 'mysql', etc.).
+ * </pre>
+ *
+ * Optional parameters:
+ * <pre>
+ * 'table' - The name of the preferences table in 'database'.
+ * DEFAULT: 'horde_prefs'
+ * </pre>
+ *
+ * Required by some database implementations:
+ * <pre>
+ * 'database' - The name of the database.
+ * 'hostspec' - The hostname of the database server.
+ * 'options' - Additional options to pass to the database.
+ * 'password' - The password associated with 'username'.
+ * 'port' - The port on which to connect to the database.
+ * 'protocol' - The communication protocol ('tcp', 'unix', etc.).
+ * 'tty' - The TTY on which to connect to the database.
+ * 'username' - The username with which to connect to the database.
+ * </pre>
+ *
+ * Optional values when using separate reading and writing servers, for
+ * example in replication settings:
+ * <pre>
+ * 'read' - Array containing the parameters which are different for the read
+ * database connection, currently supported only 'hostspec' and
+ * 'port' parameters.
+ * 'splitread' - (boolean) Whether to implement the separation or not.
+ * </pre>
+ *
+ * The table structure for the Prefs system is in
+ * scripts/sql/horde_prefs.sql.
+ *
+ * Copyright 1999-2009 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 Jon Parise <jon@horde.org>
+ * @category Horde
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Sql extends Horde_Prefs
+{
+ /**
+ * Hash containing connection parameters.
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Handle for the current database connection.
+ *
+ * @var DB
+ */
+ protected $_db;
+
+ /**
+ * Handle for the current database connection, used for
+ * writing. Defaults to the same handle as $_db if a separate
+ * write database is not configured.
+ *
+ * @var DB
+ */
+ protected $_write_db;
+
+ /**
+ * Boolean indicating whether or not we're connected to the SQL server.
+ *
+ * @var boolean
+ */
+ protected $_connected = false;
+
+ /**
+ * Returns the charset used by the concrete preference backend.
+ *
+ * @return string The preference backend's charset.
+ */
+ public function getCharset()
+ {
+ return $this->_params['charset'];
+ }
+
+ /**
+ * Retrieves the requested set of preferences from the user's database
+ * entry.
+ *
+ * @param string $scope Scope specifier.
+ */
+ protected function _retrieve($scope)
+ {
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ if (empty($_SESSION['prefs_cache']['unavailable'])) {
+ $_SESSION['prefs_cache']['unavailable'] = true;
+ if (isset($GLOBALS['notification'])) {
+ $GLOBALS['notification']->push(_("The preferences backend is currently unavailable and your preferences have not been loaded. You may continue to use the system with default settings."));
+ }
+ }
+ return;
+ }
+
+ $query = 'SELECT pref_scope, pref_name, pref_value FROM ' .
+ $this->_params['table'] . ' ' .
+ 'WHERE pref_uid = ? AND pref_scope = ?';
+
+ $values = array($this->_user, $scope);
+
+ Horde::logMessage('SQL Query by Horde_Prefs_Sql::retrieve(): ' . $query . ', values: ' . implode(', ', $values), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ $result = $this->_db->query($query, $values);
+ if ($result instanceof PEAR_Error) {
+ Horde::logMessage('No preferences were retrieved.', __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ return;
+ }
+
+ $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+ if ($row instanceof PEAR_Error) {
+ Horde::logMessage($row, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ while ($row && !($row instanceof PEAR_Error)) {
+ $name = trim($row['pref_name']);
+
+ switch ($this->_db->phptype) {
+ case 'pgsql':
+ $row['pref_value'] = pg_unescape_bytea(stripslashes($row['pref_value']));
+ break;
+ }
+
+ if (isset($this->_scopes[$scope][$name])) {
+ $this->_scopes[$scope][$name]['v'] = $row['pref_value'];
+ $this->_scopes[$scope][$name]['m'] &= ~self::PREFS_DEFAULT;
+ } else {
+ // This is a shared preference.
+ $this->_scopes[$scope][$name] = array('v' => $row['pref_value'],
+ 'm' => 0,
+ 'd' => null);
+ }
+
+ $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+ }
+ }
+
+ /**
+ * Stores preferences to the SQL server.
+ *
+ * @throws Horde_Exception
+ */
+ public function store()
+ {
+ // Get the list of preferences that have changed. If there are
+ // none, no need to hit the backend.
+ $dirty_prefs = $this->_dirtyPrefs();
+ if (!$dirty_prefs) {
+ return;
+ }
+
+ $this->_connect();
+
+ // 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 ($dirty_prefs as $scope => $prefs) {
+ foreach ($prefs as $name => $pref) {
+ // Don't store locked preferences.
+ if ($this->_scopes[$scope][$name]['m'] & self::LOCKED) {
+ continue;
+ }
+
+ $values = array($this->_user, $name, $scope);
+
+ // Does a row already exist for this preference?
+ $query = 'SELECT 1 FROM ' . $this->_params['table'] .
+ ' WHERE pref_uid = ? AND pref_name = ?' .
+ ' AND pref_scope = ?';
+ Horde::logMessage('SQL Query by Horde_Prefs_Sql::store(): ' . $query . ', values: ' . implode(', ', $values), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ $check = $this->_write_db->getOne($query, $values);
+ if ($check instanceof PEAR_Error) {
+ Horde::logMessage('Failed checking prefs for ' . $this->_user . ': ' . $check->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ $value = (string) (isset($pref['v']) ? $pref['v'] : null);
+
+ switch ($this->_db->phptype) {
+ case 'pgsql':
+ $value = pg_escape_bytea($value);
+ break;
+ }
+
+ if (!empty($check)) {
+ // Update the existing row.
+ $query = 'UPDATE ' . $this->_params['table'] .
+ ' SET pref_value = ?' .
+ ' WHERE pref_uid = ?' .
+ ' AND pref_name = ?' .
+ ' AND pref_scope = ?';
+
+ $values = array($value,
+ $this->_user,
+ $name,
+ $scope);
+ } else {
+ // Insert a new row.
+ $query = 'INSERT INTO ' . $this->_params['table'] . ' ' .
+ '(pref_uid, pref_scope, pref_name, pref_value) VALUES' .
+ '(?, ?, ?, ?)';
+
+ $values = array($this->_user,
+ $scope,
+ $name,
+ $value);
+ }
+
+ Horde::logMessage('SQL Query by Horde_Prefs_Sql::store(): ' . $query . ', values: ' . implode(', ', $values), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ $result = $this->_write_db->query($query, $values);
+ if ($result instanceof PEAR_Error) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ // Clean the pref since it was just saved.
+ $this->_scopes[$scope][$name]['m'] &= ~self::DIRTY;
+ }
+
+ // Update the cache for this scope.
+ $this->_cacheUpdate($scope, array_keys($prefs));
+ }
+ }
+
+ /**
+ * Clears all preferences from the backend.
+ *
+ * @throws Horde_Exception
+ */
+ public function clear()
+ {
+ $this->_connect();
+
+ // Build the SQL query.
+ $query = 'DELETE FROM ' . $this->_params['table'] .
+ ' WHERE pref_uid = ?';
+
+ $values = array($this->_user);
+
+ Horde::logMessage('SQL Query by Horde_Prefs_Sql::clear():' . $query . ', values: ' . implode(', ', $values), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ // Execute the query.
+ $this->_write_db->query($query, $values);
+
+ // Cleanup.
+ parent::clear();
+ }
+
+ /**
+ * Converts a value from the driver's charset to the specified charset.
+ *
+ * @param mixed $value A value to convert.
+ * @param string $charset The charset to convert to.
+ *
+ * @return mixed The converted value.
+ */
+ public function convertFromDriver($value, $charset)
+ {
+ static $converted = array();
+
+ if (is_array($value)) {
+ return Horde_String::convertCharset($value, $this->_params['charset'], $charset);
+ }
+
+ if (is_bool($value)) {
+ return $value;
+ }
+
+ if (!isset($converted[$charset][$value])) {
+ $converted[$charset][$value] = Horde_String::convertCharset($value, $this->_params['charset'], $charset);
+ }
+
+ return $converted[$charset][$value];
+ }
+
+ /**
+ * Converts a value from the specified charset to the driver's charset.
+ *
+ * @param mixed $value A value to convert.
+ * @param string $charset The charset to convert from.
+ *
+ * @return mixed The converted value.
+ */
+ public function convertToDriver($value, $charset)
+ {
+ return Horde_String::convertCharset($value, $charset, $this->_params['charset']);
+ }
+
+ /**
+ * Attempts to open a persistent connection to the SQL server.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connected) {
+ return;
+ }
+
+ Horde::assertDriverConfig($this->_params, 'prefs',
+ array('phptype', 'charset'),
+ 'preferences SQL');
+
+ if (!isset($this->_params['database'])) {
+ $this->_params['database'] = '';
+ }
+ if (!isset($this->_params['username'])) {
+ $this->_params['username'] = '';
+ }
+ if (!isset($this->_params['password'])) {
+ $this->_params['password'] = '';
+ }
+ if (!isset($this->_params['hostspec'])) {
+ $this->_params['hostspec'] = '';
+ }
+ if (!isset($this->_params['table'])) {
+ $this->_params['table'] = 'horde_prefs';
+ }
+
+ // Connect to the SQL server using the supplied parameters.
+ $this->_write_db = DB::connect($this->_params,
+ array('persistent' => !empty($this->_params['persistent']),
+ 'ssl' => !empty($this->_params['ssl'])));
+ if ($this->_write_db instanceof PEAR_Error) {
+ Horde::logMessage($this->_write_db, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception($this->_write_db);
+ }
+
+ // Set DB portability options.
+ switch ($this->_write_db->phptype) {
+ case 'mssql':
+ $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+ break;
+
+ default:
+ $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+ break;
+ }
+
+ // Check if we need to set up the read DB connection
+ // seperately.
+ if (!empty($this->_params['splitread'])) {
+ $params = array_merge($this->_params, $this->_params['read']);
+ $this->_db = DB::connect($params,
+ array('persistent' => !empty($params['persistent']),
+ 'ssl' => !empty($params['ssl'])));
+ if ($this->_db instanceof PEAR_Error) {
+ Horde::logMessage($this->_db, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Horde_Exception($this->_db);
+ }
+
+ // Set DB portability options.
+ switch ($this->_db->phptype) {
+ case 'mssql':
+ $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+ break;
+
+ default:
+ $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+ break;
+ }
+
+ } else {
+ // Default to the same DB handle for reads.
+ $this->_db = $this->_write_db;
+ }
+
+ $this->_connected = true;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Class for auto-generating the preferences user interface and
+ * processing the forms.
+ *
+ * Set $_SESSION['horde_prefs']['nomenu'] to true to suppress output of the
+ * Horde_Menu on the options pages.
+ *
+ * For 'special' group types, set 'prefsui_no_save' to suppress printing of
+ * the "Save Changes" and "Undo Changes" buttons.
+ *
+ * Copyright 2001-2009 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 Chuck Hagenbuch <chuck@horde.org>
+ * @category Horde
+ * @package Horde_Prefs
+ */
+class Horde_Prefs_Ui
+{
+ /**
+ * Cache for groupIsEditable().
+ *
+ * @var array
+ */
+ static protected $_results = array();
+
+ /**
+ * Determine whether or not a preferences group is editable.
+ *
+ * @param string $group The preferences group to check.
+ *
+ * @return boolean Whether or not the group is editable.
+ */
+ static public function groupIsEditable($group)
+ {
+ global $prefs, $prefGroups;
+
+ if (!isset(self::$_results[$group])) {
+ if (!empty($prefGroups[$group]['url'])) {
+ self::$_results[$group] = true;
+ } else {
+ self::$_results[$group] = false;
+ if (isset($prefGroups[$group]['members'])) {
+ foreach ($prefGroups[$group]['members'] as $pref) {
+ if (!$prefs->isLocked($pref)) {
+ self::$_results[$group] = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return self::$_results[$group];
+ }
+
+ /**
+ * Handle a preferences form submission if there is one, updating
+ * any preferences which have been changed.
+ *
+ * @param string $group The preferences group that was edited.
+ * @param object $save The object where the changed values are
+ * saved. Must implement setValue(string, string).
+ *
+ * @return boolean Whether preferences have been updated.
+ */
+ static public function handleForm(&$group, &$save)
+ {
+ global $app, $prefs, $prefGroups, $_prefs, $registry;
+
+ $updated = false;
+
+ $notification = Horde_Notification::singleton();
+
+ /* Run through the action handlers */
+ if (Horde_Util::getPost('actionID') == 'update_prefs') {
+ if (isset($group) && self::groupIsEditable($group)) {
+ $updated = false;
+
+ foreach ($prefGroups[$group]['members'] as $pref) {
+ if (!$prefs->isLocked($pref) ||
+ ($_prefs[$pref]['type'] == 'special')) {
+ switch ($_prefs[$pref]['type']) {
+
+ /* These either aren't set or are set in other
+ * parts of the UI. */
+ case 'implicit':
+ case 'link':
+ break;
+
+ case 'select':
+ case 'text':
+ case 'textarea':
+ case 'password':
+ $updated = $updated | $save->setValue($pref, Horde_Util::getPost($pref));
+ break;
+
+ case 'enum':
+ $val = Horde_Util::getPost($pref);
+ if (isset($_prefs[$pref]['enum'][$val])) {
+ $updated = $updated | $save->setValue($pref, $val);
+ } else {
+ $notification->push(_("An illegal value was specified."), 'horde.error');
+ }
+ break;
+
+ case 'multienum':
+ $vals = Horde_Util::getPost($pref);
+ $set = array();
+ $invalid = false;
+ if (is_array($vals)) {
+ foreach ($vals as $val) {
+ if (isset($_prefs[$pref]['enum'][$val])) {
+ $set[] = $val;
+ } else {
+ $invalid = true;
+ continue;
+ }
+ }
+ }
+
+ if ($invalid) {
+ $notification->push(_("An illegal value was specified."), 'horde.error');
+ } else {
+ $updated = $updated | $save->setValue($pref, @serialize($set));
+ }
+ break;
+
+ case 'number':
+ $num = Horde_Util::getPost($pref);
+ if ((string)(double)$num !== $num) {
+ $notification->push(_("This value must be a number."), 'horde.error');
+ } elseif (empty($num)) {
+ $notification->push(_("This number must be at least one."), 'horde.error');
+ } else {
+ $updated = $updated | $save->setValue($pref, $num);
+ }
+ break;
+
+ case 'checkbox':
+ $val = Horde_Util::getPost($pref);
+ $updated = $updated | $save->setValue($pref, isset($val) ? 1 : 0);
+ break;
+
+ case 'alarm':
+ $methods = Horde_Alarm::notificationMethods();
+ $value = array();
+ foreach (Horde_Util::getPost($pref, array()) as $method) {
+ $value[$method] = array();
+ if (!empty($methods[$method])) {
+ foreach (array_keys($methods[$method]) as $param) {
+ $value[$method][$param] = Horde_Util::getPost($pref . '_' . $param, '');
+ if (is_array($methods[$method][$param]) &&
+ $methods[$method][$param]['required'] &&
+ $value[$method][$param] === '') {
+ $notification->push(sprintf(_("You must provide a setting for \"%s\"."), $methods[$method][$param]['desc']), 'horde.error');
+ $updated = false;
+ break 3;
+ }
+ }
+ }
+ }
+ $updated = $updated | $save->setValue($pref, serialize($value));
+ break;
+
+ case 'special':
+ /* Code for special elements written specifically
+ * for each application. */
+ if ($registry->hasAppMethod($app, 'prefsHandle')) {
+ $updated = $updated | $registry->callAppMethod($app, 'prefsHandle', array('args' => array($pref, $updated)));
+ }
+ break;
+ }
+ }
+ }
+
+ if (is_callable(array($save, 'verify'))) {
+ $result = $save->verify();
+ if ($result instanceof PEAR_Error) {
+ $notification->push($result, 'horde.error');
+ $updated = false;
+ }
+ }
+
+ if ($updated) {
+ if ($registry->hasAppMethod($app, 'prefsCallback')) {
+ $registry->callAppMethod($app, 'prefsCallback');
+ }
+ if ($prefs instanceof Horde_Prefs_Session) {
+ $notification->push(_("Your options have been updated for the duration of this session."), 'horde.success');
+ } else {
+ $notification->push(_("Your options have been updated."), 'horde.success');
+ }
+ $group = null;
+ }
+ }
+ }
+
+ return $updated;
+ }
+
+ /**
+ * Generate the UI for the preferences interface, either for a
+ * specific group, or the group selection interface.
+ *
+ * @param string $group The group to generate the UI for.
+ * @param boolean $chunk Whether to only return the body part.
+ */
+ static public function generateUI($group = null, $chunk = false)
+ {
+ global $browser, $conf, $prefs, $prefGroups, $_prefs, $registry, $app;
+
+ $notification = Horde_Notification::singleton();
+
+ /* Check if any options are actually available. */
+ if (is_null($prefGroups)) {
+ $notification->push(_("There are no options available."), 'horde.message');
+ }
+
+ /* Assign variables to hold select lists. */
+ if (!$prefs->isLocked('language')) {
+ $GLOBALS['language_options'] = Horde_Nls::$config['languages'];
+ array_unshift($GLOBALS['language_options'], _("Default"));
+ }
+
+ $columns = array();
+ $in_group = (!empty($group) && self::groupIsEditable($group) && !empty($prefGroups[$group]['members']));
+
+ /* We need to do this check up here because it is possible that
+ * we will generate a notification object, which is handled by
+ * generateHeader. */
+ if (!$in_group && is_array($prefGroups)) {
+ foreach ($prefGroups as $key => $val) {
+ if (self::groupIsEditable($key)) {
+ $col = $val['column'];
+ unset($val['column']);
+ $columns[$col][$key] = $val;
+ }
+ }
+ if (!count($columns)) {
+ $notification->push(_("There are no options available."), 'horde.message');
+ }
+ }
+
+ self::generateHeader($group, $chunk);
+
+ if ($in_group) {
+ foreach ($prefGroups[$group]['members'] as $pref) {
+ if (!$prefs->isLocked($pref)) {
+ /* Get the help link. */
+ $helplink = empty($_prefs[$pref]['help'])
+ ? null
+ : Horde_Help::link(!empty($_prefs[$pref]['shared']) ? 'horde' : $registry->getApp(), $_prefs[$pref]['help']);
+
+ switch ($_prefs[$pref]['type']) {
+ case 'implicit':
+ break;
+
+ case 'special':
+ require $registry->get('templates', empty($_prefs[$pref]['shared']) ? $registry->getApp() : 'horde') . '/prefs/' . $pref . '.inc';
+ break;
+
+ default:
+ require $registry->get('templates', 'horde') . '/prefs/' . $_prefs[$pref]['type'] . '.inc';
+ break;
+ }
+ }
+ }
+ require $registry->get('templates', 'horde') . '/prefs/end.inc';
+ } elseif (count($columns)) {
+ $span = round(100 / count($columns));
+ require $registry->get('templates', 'horde') . '/prefs/overview.inc';
+ }
+ }
+
+ /**
+ * Generates the the full header of a preference screen including
+ * menu and navigation bars.
+ *
+ * @param string $group The group to generate the header for.
+ * @param boolean $chunk Whether to only return the body part.
+ */
+ static public function generateHeader($group = null, $chunk = false)
+ {
+ global $registry, $prefGroups, $app, $perms, $prefs;
+
+ $notification = Horde_Notification::singleton();
+
+ $title = _("User Options");
+ if ($group == 'identities' && !$prefs->isLocked('default_identity')) {
+ $notification->push('newChoice()', 'javascript');
+ }
+ $GLOBALS['bodyId'] = 'services_prefs';
+ if (!$chunk) {
+ require $registry->get('templates', $app) . '/common-header.inc';
+
+ if (empty($_SESSION['horde_prefs']['nomenu'])) {
+ if ($registry->hasAppMethod($app, 'prefsMenu')) {
+ $menu = $registry->callAppMethod($app, 'prefsMenu');
+ }
+ require $registry->get('templates', 'horde') . '/menu/menu.inc';
+ }
+
+ $notification->notify(array('listeners' => 'status'));
+ }
+
+ /* Get list of accessible applications. */
+ $apps = array();
+ foreach ($registry->applications as $application => $params) {
+ // Make sure the app is installed and has a prefs file.
+ if (!file_exists($registry->get('fileroot', $application) . '/config/prefs.php')) {
+ continue;
+ }
+
+ if ($params['status'] == 'heading' ||
+ $params['status'] == 'block') {
+ continue;
+ }
+
+ /* Check if the current user has permisson to see this
+ * application, and if the application is active.
+ * Administrators always see all applications. */
+ if ((Horde_Auth::isAdmin() && $params['status'] != 'inactive') ||
+ ($registry->hasPermission($application) &&
+ ($params['status'] == 'active' || $params['status'] == 'notoolbar'))) {
+ $apps[$application] = _($params['name']);
+ }
+ }
+ asort($apps);
+
+ /* Show the current application and a form for switching
+ * applications. */
+ require $registry->get('templates', 'horde') . '/prefs/app.inc';
+
+ /* If there's only one prefGroup, just show it. */
+ if (empty($group) && count($prefGroups) == 1) {
+ $group = array_keys($prefGroups);
+ $group = array_pop($group);
+ }
+
+ if (!empty($group) && self::groupIsEditable($group)) {
+ require $registry->get('templates', 'horde') . '/prefs/begin.inc';
+ }
+ }
+
+ /**
+ * Generate the content of the title bar navigation cell (previous | next
+ * option group).
+ *
+ * @param string $group Current option group.
+ */
+ static public function generateNavigationCell($group)
+ {
+ global $prefGroups, $registry, $app;
+
+ // Search for previous and next groups.
+ $first = $last = $next = $previous = null;
+ $finish = $found = false;
+
+ foreach ($prefGroups as $pgroup => $gval) {
+ if (self::groupIsEditable($pgroup)) {
+ if (!$first) {
+ $first = $pgroup;
+ }
+ if (!$found) {
+ if ($pgroup == $group) {
+ $previous = $last;
+ $found = true;
+ }
+ } elseif (!$finish) {
+ $finish = true;
+ $next = $pgroup;
+ }
+ $last = $pgroup;
+ }
+ }
+
+ if (!$previous) {
+ $previous = $last;
+ }
+
+ if (!$next) {
+ $next = $first;
+ }
+
+ /* Don't loop if there's only one group. */
+ if ($next == $previous) {
+ return;
+ }
+
+ echo '<ul><li>' .
+ Horde::link(Horde_Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/prefs.php'), array('app' => $app, 'group' => $previous), _("Previous options"))) .
+ '<< ' . $prefGroups[$previous]['label'] .
+ '</a> | ' .
+ Horde::link(Horde_Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/prefs.php'), array('app' => $app, 'group' => $next), _("Next options"))) .
+ $prefGroups[$next]['label'] . ' >>' .
+ '</a></li></ul>';
+ }
+
+ /**
+ * Get the default application to show preferences for. Defaults
+ * to 'horde'.
+ */
+ static public function getDefaultApp()
+ {
+ $applications = $GLOBALS['registry']->listApps(null, true, Horde_Perms::READ);
+ return isset($applications['horde'])
+ ? 'horde'
+ : array_shift($applications);
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Prefs</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Preferances API</summary>
+ <description>The Horde_Prefs:: package provides a common abstracted interface into the various preferences storage mediums. It also includes all of the functions for retrieving, storing, and checking preference values.
+ </description>
+ <lead>
+ <name>Chuck Hagenbuch</name>
+ <user>chuck</user>
+ <email>chuck@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2009-11-21</date>
+ <version>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.
+ </notes>
+ <contents>
+ <dir name="/">
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="Prefs">
+ <file name="CategoryManager.php" role="php" />
+ <file name="Credentials.php" role="php" />
+ <file name="File.php" role="php" />
+ <file name="Identity.php" role="php" />
+ <file name="Imsp.php" role="php" />
+ <file name="Kolab.php" role="php" />
+ <file name="KolabImap.php" role="php" />
+ <file name="Ldap.php" role="php" />
+ <file name="Session.php" role="php" />
+ <file name="Sql.php" role="php" />
+ <file name="Ui.php" role="php" />
+ </dir> <!-- /lib/Horde/Prefs -->
+ <file name="Prefs.php" role="php" />
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib -->
+ <dir name="test">
+ <dir name="Horde">
+ <dir name="Prefs">
+ <file name="bug_2838.phpt" role="test" />
+ </dir> <!-- /test/Horde/Prefs -->
+ </dir> <!-- /test/Horde -->
+ </dir> <!-- /test -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.7.0</min>
+ </pearinstaller>
+ <package>
+ <name>Core</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ <package>
+ <name>Util</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ </required>
+ <optional>
+ <extension>
+ <name>gettext</name>
+ </extension>
+ <package>
+ <name>Mime</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ </optional>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/Prefs/CategoryManager.php" as="Horde/Prefs/CategoryManager.php" />
+ <install name="lib/Horde/Prefs/Credentials.php" as="Horde/Prefs/Credentials.php" />
+ <install name="lib/Horde/Prefs/File.php" as="Horde/Prefs/File.php" />
+ <install name="lib/Horde/Prefs/Identity.php" as="Horde/Prefs/Identity.php" />
+ <install name="lib/Horde/Prefs/Imsp.php" as="Horde/Prefs/Imsp.php" />
+ <install name="lib/Horde/Prefs/Kolab.php" as="Horde/Prefs/Kolab.php" />
+ <install name="lib/Horde/Prefs/KolabImap.php" as="Horde/Prefs/KolabImap.php" />
+ <install name="lib/Horde/Prefs/Ldap.php" as="Horde/Prefs/Ldap.php" />
+ <install name="lib/Horde/Prefs/Session.php" as="Horde/Prefs/Session.php" />
+ <install name="lib/Horde/Prefs/Sql.php" as="Horde/Prefs/Sql.php" />
+ <install name="lib/Horde/Prefs/Ui.php" as="Horde/Prefs/Ui.php" />
+ <install name="lib/Horde/Prefs.php" as="Horde/Prefs.php" />
+ </filelist>
+ </phprelease>
+ <changelog>
+ <release>
+ <date>2006-05-08</date>
+ <time>22:57:02</time>
+ <version>
+ <release>0.0.3</release>
+ <api>0.0.3</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Add 'nomenu' option to hide menu generation in Prefs_UI::.
+ * Data in postgres must be stored in a BYTEA field, not a TEXT field (Bug #8130).
+ * Converted to package.xml 2.0 for pear.horde.org
+ * Added files-based preferences backend (thomas.jarosch@intra2net.com, Request #6653)
+ </notes>
+ </release>
+ <release>
+ <version>
+ <release>0.0.2</release>
+ <api>0.0.2</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <date>2004-01-01</date>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Add failover functionality, if one of the drivers is not available will fall back to session-based preferences.
+* Add support for separate read and write DB servers for the sql driver.
+ </notes>
+ </release>
+ <release>
+ <version>
+ <release>0.0.1</release>
+ <api>0.0.1</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <date>2003-07-05</date>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>Initial release as a PEAR package
+ </notes>
+ </release>
+ </changelog>
+</package>
--- /dev/null
+--TEST--
+Test for Bug #2838, overwriting of preferences when multiple scopes are retrieved.
+--FILE--
+<?php
+
+define('HORDE_BASE', dirname(dirname(dirname(dirname(__FILE__)))));
+require_once HORDE_BASE . '/lib/core.php';
+
+$registry = Horde_Registry::singleton();
+
+$prefs = Horde_Prefs::factory('session', 'horde', 'testuser', 'testpw');
+$prefs->retrieve('imp');
+$prefs->setValue('last_login', 'test');
+echo $prefs->getValue('last_login') . "\n";
+
+$prefs->retrieve('ingo');
+echo $prefs->getValue('last_login') . "\n";
+
+?>
+--EXPECT--
+test
+test