1<?php
2/**
3 * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (LGPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @author   Michael Slusarz <slusarz@horde.org>
9 * @category Horde
10 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package  Prefs
12 */
13
14/**
15 * This class provides the storage for a preference scope.
16 *
17 * @author   Michael Slusarz <slusarz@horde.org>
18 * @category Horde
19 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
20 * @package  Prefs
21 */
22class Horde_Prefs_Scope implements Iterator, Serializable
23{
24    /**
25     * Is the object being initialized?
26     *
27     * @var boolean
28     */
29    public $init = false;
30
31    /**
32     * The scope name.
33     *
34     * @var string
35     */
36    public $scope;
37
38    /**
39     * List of dirty prefs.
40     *
41     * @var array
42     */
43    protected $_dirty = array();
44
45    /**
46     * Preferences list.  Each preference has the following format:
47     * <pre>
48     * [pref_name] => array(
49     *     [d] => (string) Default value
50     *            If not present, 'v' is the default value.
51     *     [l] => (boolean) Locked
52     *            If not present, pref is not locked.
53     *     [v] => (string) Current pref value
54     * )
55     *
56     * For internal storage, if 'l' and 'v' are both not available:
57     * [pref_name] => (string) Current pref value
58     * </pre>
59     *
60     * @var array
61     */
62    protected $_prefs = array();
63
64    /**
65     * Constructor.
66     *
67     * @param string $scope  The scope for this set of preferences.
68     */
69    public function __construct($scope)
70    {
71        $this->scope = $scope;
72    }
73
74    /**
75     * Removes a preference entry.
76     *
77     * @param string $pref  The name of the preference to remove.
78     *
79     * @return boolean  True if preference was removed.
80     */
81    public function remove($pref)
82    {
83        if (!($p = $this->_fromInternal($pref))) {
84            return false;
85        }
86
87        if (isset($p['d'])) {
88            $p['v'] = $p['d'];
89            unset($p['d']);
90            $this->_toInternal($pref, $p);
91            $this->setDirty($pref, false);
92        }
93
94        return true;
95    }
96
97    /**
98     * Sets the value for a preference.
99     *
100     * @param string $pref  The preference name.
101     * @param string $val   The preference value.
102     */
103    public function set($pref, $val)
104    {
105        if ($p = $this->_fromInternal($pref)) {
106            if ($val != $p['v']) {
107                if (isset($p['d']) && ($val == $p['d'])) {
108                    unset($p['d']);
109                } else {
110                    $p['d'] = $p['v'];
111                }
112                $p['v'] = $val;
113                $this->_toInternal($pref, $p);
114            }
115        } else {
116            $this->_toInternal($pref, array('v' => $val));
117        }
118    }
119
120    /**
121     * Does a preference exist in this scope?
122     *
123     * @return boolean  True if the preference exists.
124     */
125    public function exists($pref)
126    {
127        return isset($this->_prefs[$pref]);
128    }
129
130    /**
131     * Returns the value of a preference.
132     *
133     * @param string $pref  The preference name to retrieve.
134     *
135     * @return string  The value of the preference, null if it doesn't exist.
136     */
137    public function get($pref)
138    {
139        return ($p = $this->_fromInternal($pref))
140            ? $p['v']
141            : null;
142    }
143
144    /**
145     * Mark a preference as locked.
146     *
147     * @param string $pref     The preference name.
148     * @param boolean $locked  Is the preference locked?
149     */
150    public function setLocked($pref, $locked)
151    {
152        if ($p = $this->_fromInternal($pref)) {
153            if ($locked) {
154                if (!isset($p['l'])) {
155                    $p['l'] = true;
156                    $this->_toInternal($pref, $p);
157                }
158            } elseif (isset($p['l'])) {
159                unset($p['l']);
160                $this->_toInternal($pref, $p);
161            }
162        }
163    }
164
165    /**
166     * Is a preference locked?
167     *
168     * @param string $pref  The preference name.
169     *
170     * @return boolean  Whether the preference is locked.
171     */
172    public function isLocked($pref)
173    {
174        return ($p = $this->_fromInternal($pref))
175            ? !empty($p['l'])
176            : false;
177    }
178
179    /**
180     * Is a preference's value the default?
181     *
182     * @param string $pref  The preference name.
183     *
184     * @return boolean  True if the preference contains the default value.
185     */
186    public function isDefault($pref)
187    {
188        return ($p = $this->_fromInternal($pref))
189            ? !isset($p['d'])
190            : true;
191    }
192
193    /**
194     * Returns the default value of a preference.
195     *
196     * @param string $pref  The preference name.
197     *
198     * @return string  The preference's default value.
199     */
200    public function getDefault($pref)
201    {
202        return ($p = $this->_fromInternal($pref))
203            ? (isset($p['d']) ? $p['d'] : $p['v'])
204            : null;
205    }
206
207    /**
208     * Get the list of dirty preferences.
209     *
210     * @return array  The list of dirty preferences.
211     */
212    public function getDirty()
213    {
214        return array_keys($this->_dirty);
215    }
216
217    /**
218     * Is a preference marked dirty?
219     *
220     * @param mixed $pref  The preference name.  If null, will return true if
221     *                     scope contains at least one dirty pref.
222     *
223     * @return boolean  True if the preference is marked dirty.
224     */
225    public function isDirty($pref = null)
226    {
227        return is_null($pref)
228            ? !empty($this->_dirty)
229            : isset($this->_dirty[$pref]);
230    }
231
232    /**
233     * Set the dirty flag for a preference
234     *
235     * @param string $pref    The preference name.
236     * @param boolean $dirty  True to mark the pref as dirty.
237     */
238    public function setDirty($pref, $dirty)
239    {
240        if ($dirty) {
241            $this->_dirty[$pref] = true;
242        } else {
243            unset($this->_dirty[$pref]);
244        }
245    }
246
247    /**
248     */
249    protected function _fromInternal($pref)
250    {
251        if (!isset($this->_prefs[$pref])) {
252            return false;
253        }
254
255        return is_array($this->_prefs[$pref])
256            ? $this->_prefs[$pref]
257            : array('v' => $this->_prefs[$pref]);
258    }
259
260    /**
261     */
262    protected function _toInternal($pref, array $value)
263    {
264        if (!isset($value['d']) && !isset($value['l'])) {
265            $value = $value['v'];
266        }
267
268        $this->_prefs[$pref] = $value;
269
270        if (!$this->init) {
271            $this->setDirty($pref, true);
272        }
273    }
274
275    /* Iterator methods. */
276
277    /**
278     */
279    public function current()
280    {
281        return $this->_fromInternal($this->key());
282    }
283
284    /**
285     */
286    public function key()
287    {
288        return key($this->_prefs);
289    }
290
291    /**
292     */
293    public function next()
294    {
295        return next($this->_prefs);
296    }
297
298    /**
299     */
300    public function rewind()
301    {
302        return reset($this->_prefs);
303    }
304
305    /**
306     */
307    public function valid()
308    {
309        return !is_null(key($this->_prefs));
310    }
311
312    /* Serializable methods. */
313
314    /**
315     */
316    public function serialize()
317    {
318        return json_encode(array(
319            $this->scope,
320            $this->_prefs
321        ));
322    }
323
324    /**
325     */
326    public function unserialize($data)
327    {
328        list($this->scope, $this->_prefs) = json_decode($data, true);
329    }
330
331}
332