1: <?php
2: /**
3: * The Horde_Prefs:: class provides a common abstracted interface into the
4: * various preferences storage mediums. It also includes all of the
5: * functions for retrieving, storing, and checking preference values.
6: *
7: * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
8: *
9: * See the enclosed file COPYING for license information (LGPL). If you
10: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
11: *
12: * @author Jon Parise <jon@horde.org>
13: * @category Horde
14: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
15: * @package Prefs
16: */
17: class Horde_Prefs implements ArrayAccess
18: {
19: /* The default scope name. */
20: const DEFAULT_SCOPE = 'horde';
21:
22: /**
23: * Caching object.
24: *
25: * @var Horde_Prefs_Cache
26: */
27: protected $_cache;
28:
29: /**
30: * General library options.
31: *
32: * @var array
33: */
34: protected $_opts = array(
35: 'cache' => null,
36: 'logger' => null,
37: 'password' => '',
38: 'sizecallback' => null,
39: 'storage' => null,
40: 'user' => ''
41: );
42:
43: /**
44: * String containing the name of the current scope. This is used
45: * to differentiate between sets of preferences. By default, preferences
46: * belong to this scope.
47: *
48: * @var string
49: */
50: protected $_scope = self::DEFAULT_SCOPE;
51:
52: /**
53: * Scope list. Keys are scope names, values are Horde_Prefs_Scope
54: * objects.
55: *
56: * @var array
57: */
58: protected $_scopes = array();
59:
60: /**
61: * The storage driver(s).
62: *
63: * @var array
64: */
65: protected $_storage;
66:
67: /**
68: * Constructor.
69: *
70: * @param string $scope The scope for this set of preferences.
71: * @param mixed $storage The storage object(s) to use. Either a single
72: * Horde_Prefs_Storage object, or an array of
73: * objects.
74: * @param array $opts Additional confguration options:
75: * <pre>
76: * cache - (Horde_Prefs_Cache) The cache driver to use.
77: * DEFAULT: No caching.
78: * logger - (Horde_Log_Logger) Logging object.
79: * DEFAULT: NONE
80: * password - (string) The password associated with 'user'.
81: * DEFAULT: NONE
82: * sizecallback - (callback) If set, called when setting a value in the
83: * backend.
84: * DEFAULT: NONE
85: * user - (string) The name of the user who owns this set of preferences.
86: * DEFAULT: NONE
87: * </pre>
88: */
89: public function __construct($scope, $storage = null, array $opts = array())
90: {
91: $this->_opts = array_merge($this->_opts, $opts);
92:
93: $this->_cache = isset($this->_opts['cache'])
94: ? $this->_opts['cache']
95: : new Horde_Prefs_Cache_Null($this->getUser());
96:
97: $this->_scope = $scope;
98:
99: if (is_null($storage)) {
100: $storage = array(new Horde_Prefs_Storage_Null($this->getUser()));
101: } elseif (!is_array($storage)) {
102: $storage = array($storage);
103: }
104: $this->_storage = $storage;
105:
106: register_shutdown_function(array($this, 'store'));
107:
108: $this->retrieve($scope);
109: }
110:
111: /**
112: * Return the user who owns these preferences.
113: *
114: * @return string The user these preferences are for.
115: */
116: public function getUser()
117: {
118: return $this->_opts['user'];
119: }
120:
121: /**
122: * Get the current scope.
123: *
124: * @return string The current scope (application).
125: */
126: public function getScope()
127: {
128: return $this->_scope;
129: }
130:
131: /**
132: * Returns the storage drivers.
133: *
134: * @return array The storage drivers.
135: */
136: public function getStorage()
137: {
138: return $this->_storage;
139: }
140:
141: /**
142: * Removes a preference entry from the $prefs hash.
143: *
144: * @param string $pref The name of the preference to remove. If null,
145: * removes all prefs.
146: */
147: public function remove($pref = null)
148: {
149: if (is_null($pref)) {
150: foreach ($this->_scopes as $val) {
151: foreach (array_keys(iterator_to_array($val)) as $prefname) {
152: $val->remove($prefname);
153: }
154: }
155: } elseif ($scope = $this->_getScope($pref)) {
156: $this->_scopes[$scope]->remove($pref);
157: }
158: }
159:
160: /**
161: * Sets the given preference to the specified value if the preference is
162: * modifiable.
163: *
164: * @param string $pref The preference name to modify.
165: * @param string $val The preference value (UTF-8).
166: * @param array $opts Additional options:
167: * <pre>
168: * nosave - (boolean) If true, the preference will not be saved to the
169: * storage backend(s).
170: * </pre>
171: *
172: * @return boolean True if the value was successfully set, false on a
173: * failure.
174: * @throws Horde_Prefs_Exception
175: */
176: public function setValue($pref, $val, array $opts = array())
177: {
178: /* Exit early if preference doesn't exist or is locked. */
179: if (!($scope = $this->_getScope($pref)) ||
180: $this->_scopes[$scope]->isLocked($pref)) {
181: return false;
182: }
183:
184: // Check to see if the value exceeds the allowable storage limit.
185: if ($this->_opts['sizecallback'] &&
186: call_user_func($this->_opts['sizecallback'], $pref, strlen($val))) {
187: return false;
188: }
189:
190: $this->_scopes[$scope]->set($pref, $val);
191: if (!empty($opts['nosave'])) {
192: $this->_scopes[$scope]->setDirty($pref, false);
193: }
194:
195: foreach ($this->_storage as $storage) {
196: $storage->onChange($scope, $pref);
197: }
198:
199: if ($this->_opts['logger']) {
200: $this->_opts['logger']->log(__CLASS__ . ': Storing preference value (' . $pref . ')', 'DEBUG');
201: }
202:
203: return true;
204: }
205:
206: /**
207: * Shortcut to setValue().
208: */
209: public function __set($name, $value)
210: {
211: return $this->setValue($name, $value);
212: }
213:
214: /**
215: * Returns the value of the requested preference.
216: *
217: * @param string $pref The preference name.
218: *
219: * @return string The value of the preference (UTF-8), null if it doesn't
220: * exist.
221: */
222: public function getValue($pref)
223: {
224: return ($scope = $this->_getScope($pref))
225: ? $this->_scopes[$scope]->get($pref)
226: : null;
227: }
228:
229: /**
230: * Shortcut to getValue().
231: */
232: public function __get($name)
233: {
234: return $this->getValue($name);
235: }
236:
237: /**
238: * Mark a preference as locked.
239: *
240: * @param string $pref The preference name.
241: * @param boolean $locked Is the preference locked?
242: */
243: public function setLocked($pref, $bool)
244: {
245: if ($scope = $this->_getScope($pref)) {
246: $this->_scopes[$scope]->setLocked($pref, $bool);
247: }
248: }
249:
250: /**
251: * Is a preference locked?
252: *
253: * @param string $pref The preference name.
254: *
255: * @return boolean Whether the preference is locked.
256: */
257: public function isLocked($pref)
258: {
259: return ($scope = $this->_getScope($pref))
260: ? $this->_scopes[$scope]->isLocked($pref)
261: : false;
262: }
263:
264: /**
265: * Is a preference marked dirty?
266: *
267: * @param string $pref The preference name.
268: *
269: * @return boolean True if the preference is marked dirty.
270: */
271: public function isDirty($pref)
272: {
273: return ($scope = $this->_getScope($pref))
274: ? $this->_scopes[$scope]->isDirty($pref)
275: : false;
276: }
277:
278: /**
279: * Returns the default value of the given preference.
280: *
281: * @param string $pref The name of the preference to get the default for.
282: *
283: * @return string The preference's default value.
284: */
285: public function getDefault($pref)
286: {
287: return ($scope = $this->_getScope($pref))
288: ? $this->_scopes[$scope]->getDefault($pref)
289: : null;
290: }
291:
292: /**
293: * Determines if the current preference value is the default value.
294: *
295: * @param string $pref The name of the preference to check.
296: *
297: * @return boolean True if the preference is the application default
298: * value.
299: */
300: public function isDefault($pref)
301: {
302: return ($scope = $this->_getScope($pref))
303: ? $this->_scopes[$scope]->isDefault($pref)
304: : false;
305: }
306:
307: /**
308: * Returns the scope of a preference.
309: *
310: * @param string $pref The preference name.
311: *
312: * @return mixed The scope of the preference, or null if it doesn't
313: * exist.
314: */
315: protected function _getScope($pref)
316: {
317: if ($this->_scopes[$this->_scope]->exists($pref)) {
318: return $this->_scope;
319: } elseif (($this->_scope != self::DEFAULT_SCOPE) &&
320: ($this->_scopes[self::DEFAULT_SCOPE]->exists($pref))) {
321: return self::DEFAULT_SCOPE;
322: }
323:
324: return null;
325: }
326:
327: /**
328: * Retrieves preferences for the current scope.
329: *
330: * @param string $scope Optional scope specifier - if not present the
331: * current scope will be used.
332: */
333: public function retrieve($scope = null)
334: {
335: if (is_null($scope)) {
336: $scope = $this->getScope();
337: } else {
338: $this->_scope = $scope;
339: }
340:
341: $this->_loadScope(self::DEFAULT_SCOPE);
342: if ($scope != self::DEFAULT_SCOPE) {
343: $this->_loadScope($scope);
344: }
345: }
346:
347: /**
348: * Load a specific preference scope.
349: *
350: * @param string $scope The scope to load.
351: */
352: protected function _loadScope($scope)
353: {
354: // Return if we've already loaded these prefs.
355: if (!empty($this->_scopes[$scope])) {
356: return;
357: }
358:
359: // Now check the prefs cache for existing values.
360: try {
361: if ((($cached = $this->_cache->get($scope)) !== false) &&
362: ($cached instanceof Horde_Prefs_Scope)) {
363: $this->_scopes[$scope] = $cached;
364: return;
365: }
366: } catch (Horde_Prefs_Exception $e) {}
367:
368: $scope_ob = new Horde_Prefs_Scope($scope);
369: $scope_ob->init = true;
370:
371: // Need to set object in scopes array now, since the storage object
372: // might recursively call the prefs object.
373: $this->_scopes[$scope] = $scope_ob;
374:
375: foreach ($this->_storage as $storage) {
376: $scope_ob = $storage->get($scope_ob);
377: }
378:
379: $scope_ob->init = false;
380:
381: $this->_cache->store($scope_ob);
382: }
383:
384: /**
385: * This function will be run at the end of every request as a shutdown
386: * function (registered by the constructor). All dirty prefs will be
387: * saved to the storage backend.
388: */
389: public function store()
390: {
391: $backtrace = new Horde_Support_Backtrace();
392: $from_shutdown = $backtrace->getNestingLevel() == 1;
393: foreach ($this->_scopes as $scope) {
394: if ($scope->isDirty()) {
395: foreach ($this->_storage as $storage) {
396: try {
397: $storage->store($scope);
398: } catch (Exception $e) {
399: if (!$from_shutdown) {
400: throw $e;
401: }
402: }
403: }
404:
405: try {
406: $this->_cache->store($scope);
407: } catch (Exception $e) {
408: if (!$from_shutdown) {
409: throw $e;
410: }
411: }
412: }
413: }
414: }
415:
416: /**
417: * Cleanup (e.g. remove) scope(s).
418: *
419: * @param boolean $all Cleanup all scopes. If false, clean present scope
420: * only.
421: */
422: public function cleanup($all = false)
423: {
424: if ($all) {
425: /* Destroy all scopes. */
426: $this->_scopes = array();
427: $scope = null;
428: } else {
429: unset($this->_scopes[$this->_scope]);
430: $scope = $this->_scope;
431: }
432:
433: try {
434: $this->_cache->remove($scope);
435: } catch (Horde_Prefs_Exception $e) {}
436: }
437:
438: /* ArrayAccess methods. */
439:
440: public function offsetExists($offset)
441: {
442: return !is_null($this->getValue($offset));
443: }
444:
445: public function offsetGet($offset)
446: {
447: return $this->getValue($offset);
448: }
449:
450: public function offsetSet($offset, $value)
451: {
452: $this->setValue($offset, $value);
453: }
454:
455: public function offsetUnset($offset)
456: {
457: $this->remove($offset);
458: }
459:
460: }
461: