From: Michael M Slusarz Date: Mon, 23 Nov 2009 09:00:42 +0000 (-0700) Subject: Import Horde_Prefs from CVS HEAD X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=8da7292569a86ba84aed4854f0227205b51c1acb;p=horde.git Import Horde_Prefs from CVS HEAD --- diff --git a/framework/Core/lib/Horde/Registry.php b/framework/Core/lib/Horde/Registry.php index f3ae178fb..569fd370c 100644 --- a/framework/Core/lib/Horde/Registry.php +++ b/framework/Core/lib/Horde/Registry.php @@ -1022,20 +1022,18 @@ class Horde_Registry */ 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); } diff --git a/framework/Kolab_Format/test/Horde/Kolab/Format/ContactTest.php b/framework/Kolab_Format/test/Horde/Kolab/Format/ContactTest.php index c8382ce67..45ed208ac 100644 --- a/framework/Kolab_Format/test/Horde/Kolab/Format/ContactTest.php +++ b/framework/Kolab_Format/test/Horde/Kolab/Format/ContactTest.php @@ -105,14 +105,11 @@ class Horde_Kolab_Format_ContactTest extends PHPUnit_Framework_TestCase */ 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' => ''); @@ -215,4 +212,4 @@ class Horde_Kolab_Format_Xml_Contact_Dummy extends Horde_Kolab_Format_Xml_Contac 0, array('type' => self::TYPE_DATETIME)); } -} \ No newline at end of file +} diff --git a/framework/Mime/lib/Horde/Mime/Mdn.php b/framework/Mime/lib/Horde/Mime/Mdn.php index be54b59b5..bf32a7ff1 100644 --- a/framework/Mime/lib/Horde/Mime/Mdn.php +++ b/framework/Mime/lib/Horde/Mime/Mdn.php @@ -143,10 +143,8 @@ class Horde_Mime_Mdn $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(); diff --git a/framework/Prefs/lib/Horde/Prefs.php b/framework/Prefs/lib/Horde/Prefs.php new file mode 100644 index 000000000..e2c9f54d2 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs.php @@ -0,0 +1,872 @@ + *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 + * @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; + } + } + } + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/CategoryManager.php b/framework/Prefs/lib/Horde/Prefs/CategoryManager.php new file mode 100644 index 000000000..9e9e77ee3 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/CategoryManager.php @@ -0,0 +1,236 @@ + + * @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 = ''; + } + + /** + * 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; + } + + /** + * 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)); + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Credentials.php b/framework/Prefs/lib/Horde/Prefs/Credentials.php new file mode 100644 index 000000000..898e68054 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Credentials.php @@ -0,0 +1,160 @@ + + * @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 . '

'; + printf(_("%s authentication credentials"), + $GLOBALS['registry']->get('name', $app)); + echo '

'; + 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 = '
'; + } + } + + /** + * 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; + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/File.php b/framework/Prefs/lib/Horde/Prefs/File.php new file mode 100644 index 000000000..cebba2ff3 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/File.php @@ -0,0 +1,227 @@ + + * @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; + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Identity.php b/framework/Prefs/lib/Horde/Prefs/Identity.php new file mode 100644 index 000000000..8391c66d0 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Identity.php @@ -0,0 +1,579 @@ + + * @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]; + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Imsp.php b/framework/Prefs/lib/Horde/Prefs/Imsp.php new file mode 100644 index 000000000..5013111f0 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Imsp.php @@ -0,0 +1,157 @@ + + * @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; + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Kolab.php b/framework/Prefs/lib/Horde/Prefs/Kolab.php new file mode 100644 index 000000000..41796aa8a --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Kolab.php @@ -0,0 +1,44 @@ + + * + * 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 + * @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); + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/KolabImap.php b/framework/Prefs/lib/Horde/Prefs/KolabImap.php new file mode 100644 index 000000000..1cdc1b2b7 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/KolabImap.php @@ -0,0 +1,220 @@ + + * @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(); + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Ldap.php b/framework/Prefs/lib/Horde/Prefs/Ldap.php new file mode 100644 index 000000000..8a0e3d0b4 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Ldap.php @@ -0,0 +1,543 @@ + + * $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'; + * + * + * The following is valid but would only be necessary if users do NOT have + * permission to modify their own LDAP accounts. + * + * + * $conf['prefs']['params']['admindn'] = 'cn=Manager,dc=example,dc=org'; + * $conf['prefs']['params']['adminpw'] = 'password'; + * + * + * 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 + * @author Ben Klang + * @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); + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Session.php b/framework/Prefs/lib/Horde/Prefs/Session.php new file mode 100644 index 000000000..6f953e5a1 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Session.php @@ -0,0 +1,67 @@ + + * @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); + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Sql.php b/framework/Prefs/lib/Horde/Prefs/Sql.php new file mode 100644 index 000000000..a9a298f2e --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Sql.php @@ -0,0 +1,390 @@ + + * 'charset' - The database's internal charset. + * 'phptype' - The database type (ie. 'pgsql', 'mysql', etc.). + * + * + * Optional parameters: + *
+ * 'table' - The name of the preferences table in 'database'.
+ *           DEFAULT: 'horde_prefs'
+ * 
+ * + * Required by some database implementations: + *
+ * '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.
+ * 
+ * + * Optional values when using separate reading and writing servers, for + * example in replication settings: + *
+ * '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.
+ * 
+ * + * 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 + * @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; + } + +} diff --git a/framework/Prefs/lib/Horde/Prefs/Ui.php b/framework/Prefs/lib/Horde/Prefs/Ui.php new file mode 100644 index 000000000..12f7cbfe5 --- /dev/null +++ b/framework/Prefs/lib/Horde/Prefs/Ui.php @@ -0,0 +1,415 @@ + + * @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 '
  • ' . + Horde::link(Horde_Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/prefs.php'), array('app' => $app, 'group' => $previous), _("Previous options"))) . + '<< ' . $prefGroups[$previous]['label'] . + ' | ' . + Horde::link(Horde_Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/prefs.php'), array('app' => $app, 'group' => $next), _("Next options"))) . + $prefGroups[$next]['label'] . ' >>' . + '
'; + } + + /** + * 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); + } + +} diff --git a/framework/Prefs/package.xml b/framework/Prefs/package.xml new file mode 100644 index 000000000..1cd2bc5c9 --- /dev/null +++ b/framework/Prefs/package.xml @@ -0,0 +1,150 @@ + + + Prefs + pear.horde.org + Horde Preferances API + 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. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + 2009-11-21 + + 0.1.0 + 0.1.0 + + + beta + beta + + LGPL + * Initial Horde 4 package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.7.0 + + + Core + pear.horde.org + + + Util + pear.horde.org + + + + + gettext + + + Mime + pear.horde.org + + + + + + + + + + + + + + + + + + + + + + 2006-05-08 + + + 0.0.3 + 0.0.3 + + + alpha + alpha + + LGPL + * 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) + + + + + 0.0.2 + 0.0.2 + + + alpha + alpha + + 2004-01-01 + LGPL + * 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. + + + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2003-07-05 + LGPL + Initial release as a PEAR package + + + + diff --git a/framework/Prefs/test/Horde/Prefs/bug_2838.phpt b/framework/Prefs/test/Horde/Prefs/bug_2838.phpt new file mode 100644 index 000000000..b434a1638 --- /dev/null +++ b/framework/Prefs/test/Horde/Prefs/bug_2838.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for Bug #2838, overwriting of preferences when multiple scopes are retrieved. +--FILE-- +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