1<?php
2/**
3 * Copyright 2001-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 * @category  Horde
9 * @copyright 2001-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package   Prefs
12 */
13
14/**
15 * This class provides an interface to all identities a user might have.
16 *
17 * @author    Jan Schneider <jan@horde.org>
18 * @category  Horde
19 * @copyright 2001-2017 Horde LLC
20 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
21 * @package   Prefs
22 */
23class Horde_Prefs_Identity
24implements ArrayAccess, Countable, IteratorAggregate
25{
26    /**
27     * Array containing all the user's identities.
28     *
29     * @var array
30     */
31    protected $_identities = array();
32
33    /**
34     * A pointer to the user's standard identity.
35     * This one is used by the methods returning values if no other one is
36     * specified.
37     *
38     * @var integer
39     */
40    protected $_default = 0;
41
42    /**
43     * The user whose identities these are.
44     *
45     * @var string
46     */
47    protected $_user = null;
48
49    /**
50     * Preference names.
51     *
52     * @var array
53     */
54    protected $_prefnames = array(
55        'default_identity' => 'default_identity',
56        'from_addr' => 'from_addr',
57        'fullname' => 'fullname',
58        'id' => 'id',
59        'identities' => 'identities',
60        'properties' => array('id', 'fullname', 'from_addr')
61    );
62
63    /**
64     * The prefs object that this Identity points to.
65     *
66     * @var Horde_Prefs
67     */
68    protected $_prefs;
69
70    /**
71     * Constructor.
72     *
73     * @param array $params  Parameters:
74     *   - default_identity: (string) The preference name for the default
75     *                       identity.
76     *                       DEFAULT: 'default_identity'
77     *   - from_addr: (string) The preference name for the user's from e-mail
78     *                address.
79     *                DEFAULT: 'from_addr'
80     *   - fullname: (string) The preference name for the user's full name.
81     *               DEFAULT: 'fullname'
82     *   - id: (string) The preference name for the identity name.
83     *         DEFAULT: 'id'
84     *   - identities: (string) The preference name for the identity store.
85     *                 DEFAULT: 'identities'
86     *   - prefs: (Horde_Prefs) [REQUIRED] The prefs object to use.
87     *   - properties: (array) The list of properties for the identity.
88     *                 DEFAULT: array('from_addr', 'fullname', 'id')
89     *   - user: (string) [REQUIRED] The user whose prefs we are handling.
90     */
91    public function __construct($params = array())
92    {
93        foreach (array_keys($this->_prefnames) as $val) {
94            if (isset($params[$val])) {
95                $this->_prefnames[$val] = $params[$val];
96            }
97        }
98        $this->_prefs = $params['prefs'];
99        $this->_user = $params['user'];
100
101        if (!($this->_identities = @unserialize($this->_prefs->getValue($this->_prefnames['identities'])))) {
102            $this->_identities = $this->_prefs->getDefault($this->_prefnames['identities']);
103        }
104
105        $this->setDefault($this->_prefs->getValue($this->_prefnames['default_identity']));
106    }
107
108    /**
109     * Creates a default identity if none exists yet and sets the preferences
110     * up if the identities are locked.
111     */
112    public function init()
113    {
114        if (!is_array($this->_identities) || (count($this->_identities) <= 0)) {
115            foreach (array_keys($this->_prefnames) as $key) {
116                $identity[$key] = $this->_prefs->getValue($key);
117            }
118            if (empty($identity['id'])) {
119                $identity['id'] = Horde_Prefs_Translation::t("Default Identity");
120            }
121
122            $this->_identities = array($identity);
123            $this->verify(0);
124        }
125    }
126
127    /**
128     * Saves all identities in the prefs backend.
129     */
130    public function save()
131    {
132        $this->_prefs->setValue($this->_prefnames['identities'], serialize($this->_identities));
133        $this->_prefs->setValue($this->_prefnames['default_identity'], $this->_default);
134    }
135
136    /**
137     * Adds a new identity to the array of identities.
138     *
139     * @param array $identity  An identity hash to add.
140     *
141     * @return integer  The pointer to the created identity
142     */
143    public function add($identity = array())
144    {
145        $this->_identities[] = $identity;
146        return count($this->_identities) - 1;
147    }
148
149    /**
150     * Returns a complete identity hash.
151     *
152     * @param integer $identity  The identity to retrieve.
153     *
154     * @return array  An identity hash. Returns null if the identity does not
155     *                exist.
156     */
157    public function get($identity = null)
158    {
159        if (is_null($identity)) {
160            $identity = $this->_default;
161        }
162
163        return isset($this->_identities[$identity])
164            ? $this->_identities[$identity]
165            : null;
166    }
167
168    /**
169     * Removes an identity from the array of identities.
170     *
171     * @param integer $identity  The pointer to the identity to be removed
172     *
173     * @return array  The removed identity.
174     */
175    public function delete($identity)
176    {
177        $deleted = array_splice($this->_identities, $identity, 1);
178
179        if (!empty($deleted)) {
180            foreach (array_keys($this->_identities) as $id) {
181                if ($this->setDefault($id)) {
182                    break;
183                }
184            }
185            $this->save();
186        }
187
188        return reset($deleted);
189    }
190
191    /**
192     * Returns a pointer to the current default identity.
193     *
194     * @return integer  The pointer to the current default identity.
195     */
196    public function getDefault()
197    {
198        return $this->_default;
199    }
200
201    /**
202     * Sets the current default identity.
203     * If the identity doesn't exist, the old default identity stays the same.
204     *
205     * @param integer $identity  The pointer to the new default identity.
206     *
207     * @return boolean  True on success, false on failure.
208     */
209    public function setDefault($identity)
210    {
211        if (isset($this->_identities[$identity])) {
212            $this->_default = $identity;
213            return true;
214        }
215
216        return false;
217    }
218
219    /**
220     * Returns a property from one of the identities. If this value doesn't
221     * exist or is locked, the property is retrieved from the prefs backend.
222     *
223     * @param string $key        The property to retrieve.
224     * @param integer $identity  The identity to retrieve the property from.
225     *
226     * @return mixed  The value of the property.
227     */
228    public function getValue($key, $identity = null)
229    {
230        if (is_null($identity) || !isset($this->_identities[$identity])) {
231            $identity = $this->_default;
232        }
233
234        return (!isset($this->_identities[$identity][$key]) || $this->_prefs->isLocked($key))
235            ? $this->_prefs->getValue($key)
236            : $this->_identities[$identity][$key];
237    }
238
239    /**
240     * Returns an array with the specified property from all existing
241     * identities.
242     *
243     * @param string $key  The property to retrieve.
244     *
245     * @return array  The array with the values from all identities.
246     */
247    public function getAll($key)
248    {
249        $list = array();
250
251        foreach (array_keys($this->_identities) as $identity) {
252            $list[$identity] = $this->getValue($key, $identity);
253        }
254
255        return $list;
256    }
257
258    /**
259     * Sets a property with a specified value.
260     *
261     * @param string $key        The property to set.
262     * @param mixed $val         The value to which the property should be
263     *                           set.
264     * @param integer $identity  The identity to set the property in.
265     *
266     * @return boolean  True on success, false on failure (property was
267     *                  locked).
268     */
269    public function setValue($key, $val, $identity = null)
270    {
271        if (is_null($identity)) {
272            $identity = $this->_default;
273        }
274
275        if (!$this->_prefs->isLocked($key)) {
276            $this->_identities[$identity][$key] = $val;
277            return true;
278        }
279
280        return false;
281    }
282
283    /**
284     * Returns true if all properties are locked and therefore nothing in the
285     * identities can be changed.
286     *
287     * @return boolean  True if all properties are locked, false otherwise.
288     */
289    public function isLocked()
290    {
291        foreach ($this->_prefnames['properties'] as $key) {
292            if (!$this->_prefs->isLocked($key)) {
293                return false;
294            }
295        }
296
297        return true;
298    }
299
300    /**
301     * Returns true if the given address belongs to one of the identities.
302     *
303     * @param string $key    The identity key to search.
304     * @param string $value  The value to search for in $key.
305     *
306     * @return boolean  True if the $value was found in $key.
307     */
308    public function hasValue($key, $value)
309    {
310        $list = $this->getAll($key);
311
312        foreach ($list as $valueB) {
313            if (!empty($valueB) &&
314                strpos(Horde_String::lower($value), Horde_String::lower($valueB)) !== false) {
315                return true;
316            }
317        }
318
319        return false;
320    }
321
322    /**
323     * Verifies and sanitizes all identity properties.
324     *
325     * @param integer $identity  The identity to verify.
326     *
327     * @throws Horde_Prefs_Exception
328     */
329    public function verify($identity = null)
330    {
331        if (is_null($identity)) {
332            $identity = $this->_default;
333        }
334
335        if (!$this->getValue('id', $identity)) {
336            $this->setValue('id', Horde_Prefs_Translation::t("Unnamed"), $identity);
337        }
338
339        // To verify e-mail, first parse input, than re-parse in verify mode.
340        $ob = new Horde_Mail_Rfc822_Address($this->getValue($this->_prefnames['from_addr'], $identity));
341        try {
342            $rfc822 = new Horde_Mail_Rfc822();
343            $rfc822->parseAddressList($ob, array(
344                'validate' => true
345            ));
346        } catch (Horde_Mail_Exception $e) {
347            throw new Horde_Prefs_Exception(sprintf(Horde_Prefs_Translation::t("\"%s\" is not a valid email address."), strval($ob)));
348        }
349
350        $this->setValue('from_addr', strval($ob), $identity);
351    }
352
353    /**
354     * Returns the user's full name.
355     *
356     * @param integer $ident  The identity to retrieve the name from.
357     *
358     * @return string  The user's full name, or the user name if it doesn't
359     *                 exist.
360     */
361    public function getName($ident = null)
362    {
363        if (isset($this->_names[$ident])) {
364            return $this->_names[$ident];
365        }
366
367        $this->_names[$ident] = $this->getValue($this->_prefnames['fullname'], $ident);
368        if (!strlen($this->_names[$ident])) {
369            $this->_names[$ident] = $this->_user;
370        }
371
372        return $this->_names[$ident];
373    }
374
375    /**
376     * Returns the from address based on the chosen identity.
377     *
378     * If no address can be found it is built from the current user.
379     *
380     * @since Horde_Prefs 2.3.0
381     *
382     * @param integer $ident  The identity to retrieve the address from.
383     *
384     * @return Horde_Mail_Rfc822_Address  A valid from address.
385     */
386    public function getFromAddress($ident = null)
387    {
388        $val = $this->getValue($this->_prefnames['from_addr'], $ident);
389        if (!strlen($val)) {
390            $val = $this->_user;
391        }
392        return new Horde_Mail_Rfc822_Address($val);
393    }
394
395    /**
396     * Generates the from address to use for the default identity.
397     *
398     * @param boolean $fullname  Include the fullname information.
399     *
400     * @return Horde_Mail_Rfc822_Address  The default from address (object
401     *                                    returned since 2.2.0).
402     */
403    public function getDefaultFromAddress($fullname = false)
404    {
405        $ob = new Horde_Mail_Rfc822_Address($this->getFromAddress());
406        $ob->personal = $fullname
407            ? $this->getValue($this->_prefnames['fullname'])
408            : null;
409
410        return $ob;
411    }
412
413    /* ArrayAccess methods. */
414
415    /**
416     * @since 2.7.0
417     */
418    public function offsetExists($offset)
419    {
420        return isset($this->_identities[$offset]);
421    }
422
423    /**
424     * @since 2.7.0
425     */
426    public function offsetGet($offset)
427    {
428        return $this->get($offset);
429    }
430
431    /**
432     * @since 2.7.0
433     */
434    public function offsetSet($offset, $value)
435    {
436        // $value is ignored.
437        $this->set($offset);
438    }
439
440    /**
441     * @since 2.7.0
442     */
443    public function offsetUnset($offset)
444    {
445        $this->delete($offset);
446    }
447
448    /* Countable method. */
449
450    /**
451     * @since 2.7.0
452     */
453    public function count()
454    {
455        return count($this->_identities);
456    }
457
458    /* IteratorAggregate method. */
459
460    /**
461     * @since 2.7.0
462     */
463    public function getIterator()
464    {
465        return new ArrayIterator($this->_identities);
466    }
467
468}
469