* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Prefs
*/
class Horde_Prefs implements ArrayAccess
{
/* The default scope name. */
const DEFAULT_SCOPE = 'horde';
/**
* Caching object.
*
* @var Horde_Prefs_Cache
*/
protected $_cache;
/**
* General library options.
*
* @var array
*/
protected $_opts = array(
'cache' => null,
'logger' => null,
'sizecallback' => null,
'storage' => null,
'user' => ''
);
/**
* String containing the name of the current scope. This is used
* to differentiate between sets of preferences. By default, preferences
* belong to this scope.
*
* @var string
*/
protected $_scope = self::DEFAULT_SCOPE;
/**
* Scope list. Keys are scope names, values are Horde_Prefs_Scope
* objects.
*
* @var array
*/
protected $_scopes = array();
/**
* The storage driver(s).
*
* @var array
*/
protected $_storage;
/**
* Constructor.
*
* @param string $scope The scope for this set of preferences.
* @param mixed $storage The storage object(s) to use. Either a single
* Horde_Prefs_Storage object, or an array of
* objects.
* @param array $opts Additional confguration options:
*
* cache - (Horde_Prefs_Cache) The cache driver to use.
* DEFAULT: No caching.
* logger - (Horde_Log_Logger) Logging object.
* DEFAULT: NONE
* sizecallback - (callback) If set, called when setting a value in the
* backend.
* DEFAULT: NONE
* user - (string) The name of the user who owns this set of preferences.
* DEFAULT: NONE
*
*/
public function __construct($scope, $storage = null, array $opts = array())
{
$this->_opts = array_merge($this->_opts, $opts);
$this->_cache = isset($this->_opts['cache'])
? $this->_opts['cache']
: new Horde_Prefs_Cache_Null($this->getUser());
$this->_scope = $scope;
if (is_null($storage)) {
$storage = array(new Horde_Prefs_Storage_Null($this->getUser()));
} elseif (!is_array($storage)) {
$storage = array($storage);
}
$this->_storage = $storage;
register_shutdown_function(array($this, 'store'), false);
}
/**
* Return the cache object.
*
* @since 2.6.0
*
* @return Horde_Prefs_Cache_Base Cache object.
*/
public function getCache()
{
return $this->_cache;
}
/**
* Return the user who owns these preferences.
*
* @return string The user these preferences are for.
*/
public function getUser()
{
return $this->_opts['user'];
}
/**
* Get the current scope.
*
* @return string The current scope (application).
*/
public function getScope()
{
return $this->_scope;
}
/**
* Returns the current scope object.
*
* @since 2.9.0
*
* @return Horde_Prefs_Scope The current scope object.
*/
public function getScopeObject($scope = null)
{
if (!is_null($scope)) {
$this->changeScope($scope);
}
return $this->_scopes[$this->_scope];
}
/**
* Returns the storage drivers.
*
* @return array The storage drivers.
*/
public function getStorage()
{
return $this->_storage;
}
/**
* Removes a preference entry from the $prefs hash.
*
* @param string $pref The name of the preference to remove. If null,
* removes all preferences from the current scope.
*/
public function remove($pref = null)
{
$to_remove = array();
if (is_null($pref)) {
$to_remove[$this->_scope] = array_keys(iterator_to_array($this->_scopes[$this->_scope]));
} elseif ($scope = $this->_getScope($pref)) {
$to_remove[$scope] = array($pref);
}
foreach ($to_remove as $key => $val) {
$scope = $this->_scopes[$key];
foreach ($val as $prefname) {
$scope->remove($prefname);
// We remove all prefs at once in the backends below.
if (is_null($pref)) {
continue;
}
foreach ($this->_storage as $storage) {
try {
$storage->remove($key, $prefname);
} catch (Exception $e) {}
}
}
if (is_null($pref)) {
foreach ($this->_storage as $storage) {
try {
$storage->remove($key);
} catch (Exception $e) {}
}
}
}
}
/**
* Removes all preference entries for the current user from the $prefs hash
* and the backends.
*
* @since Horde_Prefs 2.8.0
* @throws Horde_Prefs_Exception
*/
public function removeAll()
{
foreach ($this->_scopes as $key => $val) {
foreach (array_keys(iterator_to_array($val)) as $prefname) {
$this->_scopes[$key]->remove($prefname);
}
}
foreach ($this->_storage as $storage) {
$storage->remove();
}
}
/**
* Sets the given preference to the specified value if the preference is
* modifiable.
*
* @param string $pref The preference name to modify.
* @param string $val The preference value (UTF-8).
* @param array $opts Additional options:
*
* - force: (boolean) If true, will set the value disregarding the
* current locked status of the pref. (@since 2.5.0)
* DEFAULT: false
* - nosave: (boolean) If true, the preference will not be saved to the
* storage backend(s).
* DEFAULT: false
*
*
* @return boolean True if the value was successfully set, false on a
* failure.
* @throws Horde_Prefs_Exception
*/
public function setValue($pref, $val, array $opts = array())
{
/* Exit early if preference doesn't exist or is locked. */
if (!($scope = $this->_getScope($pref)) ||
(empty($opts['force']) &&
$this->_scopes[$scope]->isLocked($pref))) {
return false;
}
// Check to see if the value exceeds the allowable storage limit.
if ($this->_opts['sizecallback'] &&
call_user_func($this->_opts['sizecallback'], $pref, strlen($val))) {
return false;
}
$this->_scopes[$scope]->set($pref, $val);
if (!empty($opts['nosave'])) {
$this->_scopes[$scope]->setDirty($pref, false);
}
foreach ($this->_storage as $storage) {
$storage->onChange($scope, $pref);
}
if ($this->_opts['logger']) {
$this->_opts['logger']->log(__CLASS__ . ': Storing preference value (' . $pref . ')', 'DEBUG');
}
return true;
}
/**
* Shortcut to setValue().
*/
public function __set($name, $value)
{
return $this->setValue($name, $value);
}
/**
* Returns the value of the requested preference.
*
* @param string $pref The preference name.
*
* @return string The value of the preference (UTF-8), null if it doesn't
* exist.
*/
public function getValue($pref)
{
return ($scope = $this->_getScope($pref))
? $this->_scopes[$scope]->get($pref)
: null;
}
/**
* Shortcut to getValue().
*/
public function __get($name)
{
return $this->getValue($name);
}
/**
* Mark a preference as locked.
*
* @param string $pref The preference name.
* @param boolean $locked Is the preference locked?
*/
public function setLocked($pref, $bool)
{
if ($scope = $this->_getScope($pref)) {
$this->_scopes[$scope]->setLocked($pref, $bool);
}
}
/**
* Is a preference locked?
*
* @param string $pref The preference name.
*
* @return boolean Whether the preference is locked.
*/
public function isLocked($pref)
{
return ($scope = $this->_getScope($pref))
? $this->_scopes[$scope]->isLocked($pref)
: false;
}
/**
* Is a preference marked dirty?
*
* @param string $pref The preference name.
*
* @return boolean True if the preference is marked dirty.
*/
public function isDirty($pref)
{
return ($scope = $this->_getScope($pref))
? $this->_scopes[$scope]->isDirty($pref)
: false;
}
/**
* 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 ($scope = $this->_getScope($pref))
? $this->_scopes[$scope]->getDefault($pref)
: null;
}
/**
* Determines if the current preference value is the default 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 ($scope = $this->_getScope($pref))
? $this->_scopes[$scope]->isDefault($pref)
: true;
}
/**
* Returns the scope of a preference.
*
* @param string $pref The preference name.
*
* @return mixed The scope of the preference, or null if it doesn't
* exist.
*/
protected function _getScope($pref)
{
$this->_loadScope($this->_scope);
if ($this->_scopes[$this->_scope]->exists($pref)) {
return $this->_scope;
} elseif ($this->_scope != self::DEFAULT_SCOPE) {
$this->_loadScope(self::DEFAULT_SCOPE);
if ($this->_scopes[self::DEFAULT_SCOPE]->exists($pref)) {
return self::DEFAULT_SCOPE;
}
}
return null;
}
/**
* Retrieves preferences for the current 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)) {
$this->changeScope($scope);
}
$this->_loadScope(self::DEFAULT_SCOPE);
$this->_loadScope($this->getScope());
}
/**
* Changes the current preference scope.
*
* @since 2.6.0
*
* @param string $scope Scope specifier.
*/
public function changeScope($scope)
{
$this->_scope = $scope;
}
/**
* Load a specific preference scope.
*
* @param string $scope The scope to load.
*/
protected function _loadScope($scope)
{
// Return if we've already loaded these prefs.
if (!empty($this->_scopes[$scope])) {
return;
}
// Now check the prefs cache for existing values.
try {
if ((($cached = $this->_cache->get($scope)) !== false) &&
($cached instanceof Horde_Prefs_Scope)) {
$this->_scopes[$scope] = $cached;
return;
}
} catch (Horde_Prefs_Exception $e) {}
$scope_ob = new Horde_Prefs_Scope($scope);
$scope_ob->init = true;
// Need to set object in scopes array now, since the storage object
// might recursively call the prefs object.
$this->_scopes[$scope] = $scope_ob;
foreach ($this->_storage as $storage) {
$scope_ob = $storage->get($scope_ob);
}
$scope_ob->init = false;
$this->_cache->store($scope_ob);
}
/**
* Save all dirty prefs to the storage backend.
*
* @param boolean $throw Throw exception on error? If false, ignores
* errors. (Since 2.1.0)
*/
public function store($throw = true)
{
foreach ($this->_scopes as $scope) {
if ($scope->isDirty()) {
foreach ($this->_storage as $storage) {
try {
$storage->store($scope);
} catch (Exception $e) {
if ($throw) {
throw $e;
}
}
}
try {
$this->_cache->store($scope);
} catch (Exception $e) {
if ($throw) {
throw $e;
}
}
}
}
}
/**
* Cleanup (e.g. remove) scope(s).
*
* @param boolean $all Cleanup all scopes. If false, clean present scope
* only.
*/
public function cleanup($all = false)
{
if ($all) {
/* Destroy all scopes. */
$this->_scopes = array();
$scope = null;
} else {
unset($this->_scopes[$this->_scope]);
$scope = $this->_scope;
}
try {
$this->_cache->remove($scope);
} catch (Horde_Prefs_Exception $e) {}
}
/* ArrayAccess methods. */
public function offsetExists($offset)
{
return !is_null($this->getValue($offset));
}
public function offsetGet($offset)
{
return $this->getValue($offset);
}
public function offsetSet($offset, $value)
{
$this->setValue($offset, $value);
}
public function offsetUnset($offset)
{
$this->remove($offset);
}
}