1<?php
2/**
3 * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (GPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/gpl.
7 *
8 * @category  Horde
9 * @copyright 2011-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/gpl GPL
11 * @package   IMP
12 */
13
14/**
15 * This object is a clearinghouse for actions related to an IMP mailbox.
16 *
17 * @author    Michael Slusarz <slusarz@horde.org>
18 * @category  Horde
19 * @copyright 2011-2017 Horde LLC
20 * @license   http://www.horde.org/licenses/gpl GPL
21 * @package   IMP
22 *
23 * @property-read string $abbrev_label  Abbreviated version of $label -
24 *                                      displays only the bare mailbox name
25 *                                      (no parents).
26 * @property-read boolean $access_creatembox  Can sub mailboxes be created?
27 * @property-read boolean $access_deletembox  Can this mailbox be deleted?
28 * @property-read boolean $access_deletembox_acl  Can this mailbox be deleted
29 *                                                according to ACL rules?
30 * @property-read boolean $access_deletemsgs  Can messages be deleted in this
31 *                                            mailbox?
32 * @property-read boolean $access_empty  Can this mailbox be emptied?
33 * @property-read boolean $access_expunge  Can messages be expunged in this
34 *                                    mailbox?
35 * @property-read boolean $access_filters  Is filtering available?
36 * @property-read boolean $access_flags  Are flags available?
37 * @property-read boolean $access_search  Is searching available?
38 * @property-read boolean $access_sort  Is sorting available?
39 * @property-read boolean $access_sortthread  Is thread sort available?
40 * @property-read mixed $acl  Either an ACL object for the mailbox, or null if
41 *                            no ACL found for the mailbox.
42 * @property-read string $basename  The basename of the mailbox (UTF-8).
43 * @property-read string $cacheid  Cache ID for the mailbox.
44 * @property-read string $cacheid_date  Cache ID for the mailbox, with added
45 *                                      data information.
46 * @property-read boolean $children  Does the element have children?
47 * @property-read boolean $container  Is this a container element?
48 * @property string $display  Display version of mailbox. Special mailboxes
49 *                            are replaced with localized strings and
50 *                            namespace information is removed.
51 * @property-read string $display_html  $display that has been HTML encoded.
52 * @property-read boolean $drafts  Is this a Drafts mailbox?
53 * @property-read boolean $editquery  Can this search query be edited?
54 * @property-read boolean $editvfolder  Can this virtual folder be edited?
55 * @property-read boolean $exists  Does this mailbox exist on the IMAP server?
56 * @property-read string $form_to  Converts this mailbox to a form
57 *                                 representation.
58 * @property-read object $icon  Icon information for the mailbox. Properties:
59 *   - alt: (string) The alt text for the icon.
60 *   - class: (string) The CSS class name.
61 *   - icon: (Horde_Themes_Image) The icon graphic to use.
62 *   - iconopen: (Horde_Themes_Image) The openicon to use.
63 *   - user_icon: (boolean) Use a user defined icon?
64 * @property-read IMP_Imap $imp_imap  The IMP_Imap object for this mailbox.
65 * @property-read string $imap_mbox  The actual name of the underlying IMAP
66 *                                   mailbox.
67 * @property-read Horde_Imap_Client_Mailbox $imap_mbox_ob  Convert this object
68 *                                                         tp an
69 *                                                         Imap_Client mailbox
70 *                                                         object.
71 * @property-read boolean $inbox  Is this the INBOX?
72 * @property-read boolean $innocent_show  Show the innocent action in this
73 *                                        mailbox?
74 * @property-read boolean $invisible  Is this mailbox invisible?
75 * @property-read boolean $is_imap  Is this an IMAP mailbox?
76 * @property-read boolean $is_open  Is this level expanded?
77 * @property-read string $label  The mailbox label. Essentially is $display
78 *                               that can be modified by user hook.
79 * @property-read integer $level  The child level of this element.
80 * @property-read IMP_Mailbox_List $list_ob  Returns the List object for the
81 *                                           mailbox.
82 * @property-read string $namespace  Is this a namespace element?
83 * @property-read IMP_Mailbox $namespace_append  The mailbox with necessary
84 *                                               namespace information appended.
85 * @property-read string $namespace_delimiter  The delimiter for this
86 *                                             namespace.
87 * @property-read Horde_Imap_Client_Data_Namespace $namespace_info  Namespace
88 *                                                                  info.
89 * @property-read boolean $nonimap  Is this a non-IMAP element?
90 * @property-read IMP_Mailbox $parent  The parent element. Returns null if no
91 *                                     parent. (Base of tree is returned as
92 *                                     a special element).
93 * @property-read string $parent_imap  The IMAP parent name.
94 * @property-read IMP_Imap_PermanentFlags $permflags  Return the list of
95 *                                                    permanent flags
96 *                                                    available to set in the
97 *                                                    mailbox.
98 * @property-read boolean $polled  Show polled information?
99 * @property-read object $poll_info  Poll information for the mailbox.
100 *                                   Properties:
101 *   - msgs: (integer) The number of total messages in the element, if polled.
102 *   - recent: (integer) The number of new messages in the element, if polled.
103 *   - unseen: (integer) The number of unseen messages in the element, if
104 *             polled.
105 * @property-read string $pref_from  Convert mailbox name from preference
106 *                                   storage.
107 * @property-read string $pref_to  Convert mailbox name to preference storage.
108 * @property-read boolean $query  Is this a search query?
109 * @property-read boolean $readonly  Is this mailbox read-only?
110 * @property-read boolean $remote  Is this a remote element?
111 * @property-read IMP_Remote_Account $remote_account  Return the account
112 *                                                    object for this element
113 *                                                    (null if not a remote
114 *                                                    element).
115 * @property-read boolean $remote_container  Is this mailbox a remote special
116 *                                           element?
117 * @property-read boolean $remote_mbox  Is this mailbox on a remote server?
118 * @property-read boolean $search  Is this a search mailbox?
119 * @property-read string $size  Human readable size of the mailbox.
120 * @property-read integer $size_raw  Size of mailbox in bytes. @since 6.2.14
121 * @property-read IMP_Prefs_Sort $sortob  Sort ob for use with this mailbox.
122 * @property-read boolean $spam  Is this a Spam mailbox?
123 * @property-read boolean $spam_show  Show the spam action in this mailbox?
124 * @property-read boolean $special  Is this is a "special" element?
125 * @property-read boolean $special_outgoing  Is this a "special" element
126 *                                           dealing with outgoing messages?
127 * @property-read boolean $specialvfolder  Is this a "special" virtual folder?
128 * @property-read boolean $sub  Is this mailbox subscribed to?
129 * @property-read array $subfolders  Returns the list of subfolders as mailbox
130 *                                   objects (including the current mailbox).
131 * @property-read array $subfolders_only  Returns the list of subfolders as
132 *                                        mailbox objects (NOT including the
133 *                                        current mailbox).
134 * @property-read boolean $systemquery  Is this a system (built-in) search
135 *                                      query?
136 * @property-read boolean $templates  Is this a Templates mailbox?
137 * @property-read boolean $trash  Is this a Trash mailbox?
138 * @property-read IMP_Ftree_Element $tree_elt  The tree element (null if it
139 *                                             doesn't exist in the tree).
140 * @property-read string $uidvalid  Returns the UIDVALIDITY string. Throws an
141 *                                  IMP_Exception on error.
142 * @property-read string $utf7imap  The UTF7-IMAP representation of this
143 *                                  object.
144 * @property-read string $value  The value of this element (IMAP mailbox name;
145 *                               UTF-8).
146 * @property-read boolean $vfolder  Is this a virtual folder?
147 * @property-read boolean $vfolder_container  Is this the virtual folder
148 *                                            container?
149 * @property-read boolean $vinbox  Is this the virtual inbox?
150 * @property-read boolean $vtrash  Is this the virtual trash?
151 */
152class IMP_Mailbox
153{
154    /* Special mailbox prefs. */
155    const MBOX_DRAFTS = 'drafts_folder';
156    const MBOX_SENT = 'sent_mail_folder';
157    const MBOX_SPAM = 'spam_folder';
158    const MBOX_TEMPLATES = 'composetemplates_mbox';
159    const MBOX_TRASH = 'trash_folder';
160    // This is just a placeholder - this pref doesn't exist.
161    const MBOX_USERSPECIAL = 'user_special';
162
163    /* Special mailbox identifiers. */
164    const SPECIAL_COMPOSETEMPLATES = 'composetemplates';
165    const SPECIAL_DRAFTS = 'drafts';
166    const SPECIAL_SENT = 'sent';
167    const SPECIAL_SPAM = 'spam';
168    const SPECIAL_TRASH = 'trash';
169    const SPECIAL_USER = 'userspecial';
170
171    /**
172     * The IMAP mailbox name (UTF-8).
173     *
174     * @var string
175     */
176    protected $_mbox;
177
178    /**
179     * Shortcut to obtaining mailbox object(s).
180     *
181     * @param mixed $mbox  The full IMAP mailbox name(s).
182     *
183     * @return mixed  The IMP_Mailbox object(s).
184     */
185    static public function get($mbox)
186    {
187        if (is_array($mbox)) {
188            return array_filter(array_map(array(__CLASS__, 'get'), $mbox));
189        }
190
191        if ($mbox instanceof IMP_Mailbox) {
192            return $mbox;
193        }
194
195        try {
196            return $GLOBALS['injector']
197                ->getInstance('IMP_Factory_Mailbox')
198                ->create(strval($mbox));
199        } catch (IMP_Exception $e) {
200            return null;
201        }
202    }
203
204    /**
205     * Shortcut to obtaining Horde_Imap_Client_Mailbox object(s).
206     *
207     * @param mixed $mbox  The full IMAP mailbox name(s).
208     *
209     * @return mixed  The Horde_Imap_Client_Mailbox object(s).
210     */
211    static public function getImapMboxOb($mbox)
212    {
213        if (is_array($mbox)) {
214            return array_filter(array_map(array(__CLASS__, 'getImapMboxOb'), $mbox));
215        }
216
217        if ($mbox instanceof Horde_Imap_Client_Mailbox) {
218            return $mbox;
219        }
220
221        // Mailbox names are always UTF-8 within IMP.
222        $mbox_ob = new self($mbox);
223        return Horde_Imap_Client_Mailbox::get($mbox_ob->imap_mbox);
224    }
225
226    /**
227     * Shortcut to obtaining a mailbox object from a preference name.
228     *
229     * @var string $pref  The preference name.
230     *
231     * @return IMP_Mailbox  The IMP_Mailbox object.
232     */
233    static public function getPref($pref)
234    {
235        return self::get(self::prefFrom($GLOBALS['prefs']->getValue($pref)));
236    }
237
238    /**
239     * Constructor.
240     *
241     * @var string $mbox  The full IMAP mailbox name.
242     *
243     * @throws IMP_Exception
244     */
245    public function __construct($mbox)
246    {
247        if (strlen($mbox) === 0) {
248            throw new IMP_Exception('Mailbox name must not be empty.');
249        }
250
251        $this->_mbox = $mbox;
252    }
253
254    /**
255     */
256    public function __toString()
257    {
258        return strval(
259            ($this->_mbox == IMP_Ftree::BASE_ELT) ? '' : $this->_mbox
260        );
261    }
262
263    /**
264     */
265    public function __get($key)
266    {
267        global $injector;
268
269        switch ($key) {
270        case 'abbrev_label':
271            $label = $this->label;
272            return ($this->nonimap || ($pos = strrpos($label, $this->namespace_delimiter)) === false)
273                ? $label
274                : substr($label, $pos + 1);
275
276        case 'access_creatembox':
277            return (!($acl = $this->acl) ||
278                    ($acl[Horde_Imap_Client::ACL_CREATEMBOX]));
279
280        case 'access_deletembox':
281            return ($this->access_deletembox_acl);
282
283        case 'access_deletembox_acl':
284            return (!($acl = $this->acl) ||
285                    ($acl[Horde_Imap_Client::ACL_DELETEMBOX]));
286
287        case 'access_deletemsgs':
288            return (!($acl = $this->acl) ||
289                    ($acl[Horde_Imap_Client::ACL_DELETEMSGS]));
290
291        case 'access_empty':
292            if ($this->access_deletemsgs && $this->access_expunge) {
293                $special = $this->getSpecialMailboxes();
294                return empty($special[self::SPECIAL_TRASH]) ||
295                    !$special[self::SPECIAL_TRASH]->vtrash ||
296                    ($special[self::SPECIAL_TRASH] == $this);
297            }
298            return false;
299
300        case 'access_expunge':
301            return (!($acl = $this->acl) ||
302                    ($acl[Horde_Imap_Client::ACL_EXPUNGE]));
303
304        case 'access_filters':
305            return !$this->search && $this->is_imap;
306
307        case 'access_flags':
308            return $this->is_imap;
309
310        case 'access_search':
311            return $this->is_imap;
312
313        case 'access_sort':
314            /* Although possible to abstract other sorting methods, all other
315             * non-sequence methods require a download of ALL messages, which
316             * is too much overhead.*/
317            return $this->is_imap;
318
319        case 'access_sortthread':
320            /* Thread sort is always available for IMAP servers, since
321             * Horde_Imap_Client_Socket has a built-in ORDEREDSUBJECT
322             * implementation. We will always prefer REFERENCES, but will
323             * fallback to ORDEREDSUBJECT if the server doesn't support THREAD
324             * sorting. */
325            return $this->is_imap;
326
327        case 'acl':
328            $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
329            if (($acl = $cache->getAcl($this->_mbox)) !== false) {
330                return $acl;
331            }
332
333            if ($this->nonimap) {
334                $acl = null;
335            } else {
336                $acl = $injector->getInstance('IMP_Imap_Acl')->getACL($this, true);
337                $hooks = $injector->getInstance('Horde_Core_Hooks');
338
339                if ($hooks->hookExists('mbox_acl', 'imp')) {
340                    $hooks->callHook('mbox_acl', 'imp', array($this, $acl));
341                }
342            }
343
344            $cache->setAcl($this->_mbox, $acl);
345
346            return $acl;
347
348        case 'basename':
349            if ($this->nonimap) {
350                return $this->label;
351            }
352
353            $mbox = $this->remote_mbox
354                ? $this->label
355                : $this->_mbox;
356
357            return (($pos = strrpos($mbox, $this->namespace_delimiter)) === false)
358                ? strval($mbox)
359                : substr($mbox, $pos + 1);
360
361        case 'cacheid':
362        case 'cacheid_date':
363            return $this->_getCacheID($key == 'cacheid_date');
364
365        case 'children':
366            return (($elt = $this->tree_elt) && $elt->children);
367
368        case 'container':
369            return (($elt = $this->tree_elt) && $elt->container);
370
371        case 'display':
372            return $this->nonimap
373                ? $this->label
374                : $this->_getDisplay();
375
376        case 'display_html':
377            return htmlspecialchars($this->display);
378
379        case 'display_notranslate':
380            return $this->nonimap
381                ? $this->label
382                : $this->_getDisplay(true);
383
384        case 'drafts':
385            $special = $this->getSpecialMailboxes();
386            return ($this->_mbox == $special[self::SPECIAL_DRAFTS]);
387
388        case 'editquery':
389            return $injector->getInstance('IMP_Search')->isQuery($this->_mbox, true);
390
391        case 'editvfolder':
392            return $injector->getInstance('IMP_Search')->isVFolder($this->_mbox, true);
393
394        case 'exists':
395            return $injector->getInstance('IMP_Mailbox_SessionCache')->exists($this);
396
397        case 'form_to':
398            return $this->formTo($this->_mbox);
399
400        case 'icon':
401            return $this->_getIcon();
402
403        case 'imp_imap':
404            return $injector->getInstance('IMP_Factory_Imap')->create(strval($this));
405
406        case 'imap_mbox':
407            return strval(
408                $injector->getInstance('IMP_Remote')->getMailboxById($this->_mbox) ?: $this->_mbox
409            );
410
411        case 'imap_mbox_ob':
412            return self::getImapMboxOb($this->_mbox);
413
414        case 'inbox':
415            return (strcasecmp($this->_mbox, 'INBOX') === 0);
416
417        case 'innocent_show':
418            $p = $this->imp_imap->config->innocent_params;
419            return (!empty($p) &&
420                    ((isset($p['display']) && empty($p['display'])) || $this->spam));
421
422        case 'invisible':
423            return (($elt = $this->tree_elt) && $elt->invisible);
424
425        case 'is_imap':
426            return $this->imp_imap->isImap();
427
428        case 'is_open':
429            return (($elt = $this->tree_elt) && $elt->open);
430
431        case 'label':
432            $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
433            if (($label = $cache->getLabel($this->_mbox)) !== false) {
434                return $label;
435            }
436
437            /* Returns the plain text label that is displayed for the
438             * current mailbox, replacing virtual search mailboxes with an
439             * appropriate description, removing namespace and mailbox
440             * prefix information from what is shown to the user, and
441             * passing the label through a user-defined hook. */
442            $imp_search = $injector->getInstance('IMP_Search');
443            $label = ($ob = $imp_search[$this->_mbox])
444                ? $ob->label
445                : $this->_getDisplay();
446
447            $hooks = $injector->getInstance('Horde_Core_Hooks');
448            if ($hooks->hookExists('mbox_label' ,'imp')) {
449                $label = $hooks->callHook(
450                    'mbox_label',
451                    'imp',
452                    array($this->_mbox, $label)
453                );
454            }
455
456            $cache->setLabel($this->_mbox, $label);
457
458            return $label;
459
460        case 'level':
461            return ($elt = $this->tree_elt) ? $elt->level : 0;
462
463        case 'list_ob':
464            return $injector->getInstance('IMP_Factory_MailboxList')->create($this);
465
466        case 'namespace':
467            return (($elt = $this->tree_elt) && $elt->namespace);
468
469        case 'namespace_append':
470            $imp_imap = $this->imp_imap;
471            $def_ns = $imp_imap->getNamespace($imp_imap::NS_DEFAULT);
472            if (is_null($def_ns)) {
473                return $this;
474            }
475            $empty_ns = $imp_imap->getNamespace('');
476
477            /* If default namespace is empty, or there is no empty namespace,
478             * then we can auto-detect namespace from input.
479             * If a non-default namespace is empty, then we must always use
480             * default namespace. */
481            if (!is_null($empty_ns) &&
482                ($def_ns->name == $empty_ns->name)) {
483                return $this;
484            }
485
486            $ns_info = $this->namespace_info;
487
488            if (is_null($ns_info) || !is_null($empty_ns)) {
489                return self::get($def_ns->name . $this->_mbox);
490            }
491
492            return $this;
493
494        case 'namespace_delimiter':
495            $ns_info = $this->namespace_info;
496            return is_null($ns_info)
497                ? ''
498                : $ns_info->delimiter;
499
500        case 'namespace_info':
501            return $this->imp_imap->getNamespace(strlen($this) ? $this->_mbox : IMP_Imap::NS_DEFAULT);
502
503        case 'nonimap':
504            return ($this->search ||
505                    (($elt = $this->tree_elt) && $elt->nonimap));
506
507        case 'parent':
508            return ($elt = $this->tree_elt) ? $elt->parent->mbox_ob : null;
509
510        case 'parent_imap':
511            return (is_null($p = $this->parent) || !strlen($p))
512                ? null
513                : $p;
514
515        case 'permflags':
516            if ($this->access_flags) {
517                $imp_imap = $this->imp_imap;
518                try {
519                    /* Make sure we are in R/W mailbox mode (SELECT). No flags
520                     * are allowed in EXAMINE mode. */
521                    $imp_imap->openMailbox($this, Horde_Imap_Client::OPEN_READWRITE);
522                    $status = $imp_imap->status($this->_mbox, Horde_Imap_Client::STATUS_FLAGS | Horde_Imap_Client::STATUS_PERMFLAGS);
523                    return new IMP_Imap_PermanentFlags($status['permflags'], $status['flags']);
524                } catch (Exception $e) {}
525            }
526
527            return new IMP_Imap_PermanentFlags();
528
529        case 'poll_info':
530            $info = new stdClass;
531            $info->msgs = 0;
532            $info->recent = 0;
533            $info->unseen = 0;
534
535            try {
536                if ($msgs_info = $this->imp_imap->status($this->_mbox, Horde_Imap_Client::STATUS_RECENT_TOTAL | Horde_Imap_Client::STATUS_UNSEEN | Horde_Imap_Client::STATUS_MESSAGES)) {
537                    if (!empty($msgs_info['recent_total'])) {
538                        $info->recent = intval($msgs_info['recent_total']);
539                    }
540                    $info->msgs = intval($msgs_info['messages']);
541                    $info->unseen = intval($msgs_info['unseen']);
542                }
543            } catch (IMP_Imap_Exception $e) {}
544
545            return $info;
546
547        case 'polled':
548            return (!$this->search &&
549                    (($elt = $this->tree_elt) && $elt->polled));
550
551        case 'pref_from':
552            return $this->prefFrom($this->_mbox);
553
554        case 'pref_to':
555            return $this->prefTo($this->_mbox);
556
557        case 'query':
558            return $injector->getInstance('IMP_Search')->isQuery($this->_mbox);
559
560        case 'readonly':
561            return (($acl = $this->acl) &&
562                    !$acl[Horde_Imap_Client::ACL_DELETEMBOX] &&
563                    !$acl[Horde_Imap_Client::ACL_DELETEMSGS] &&
564                    !$acl[Horde_Imap_Client::ACL_EXPUNGE] &&
565                    !$acl[Horde_Imap_Client::ACL_INSERT] &&
566                    !$acl[Horde_Imap_Client::ACL_SEEN] &&
567                    !$acl[Horde_Imap_Client::ACL_WRITE]);
568
569        case 'remote':
570            return $injector->getInstance('IMP_Remote')->isRemoteMbox($this->_mbox);
571
572        case 'remote_account':
573            $remote = $injector->getInstance('IMP_Remote');
574            $account = ($this->remote_container)
575                ? $remote[$this->_mbox]
576                : $remote->getRemoteById($this->_mbox);
577            return $account ?: null;
578
579        case 'remote_container':
580            return (($elt = $this->tree_elt) && $elt->remote);
581
582        case 'remote_mbox':
583            return (($elt = $this->tree_elt) && $elt->remote_mbox);
584
585        case 'search':
586            return $injector->getInstance('IMP_Search')->isSearchMbox($this->_mbox);
587
588        case 'size':
589            return $injector->getInstance('IMP_Mbox_Size')->getSize($this);
590
591        case 'size_raw':
592            return $injector->getInstance('IMP_Mbox_Size')->getSize($this, false);
593
594        case 'sortob':
595            return $this->imp_imap->access(IMP_Imap::ACCESS_SORT)
596                ? $injector->getInstance('IMP_Prefs_Sort')
597                : $injector->getInstance('IMP_Prefs_Sort_None');
598
599        case 'spam':
600            $special = $this->getSpecialMailboxes();
601            return ($this->_mbox == $special[self::SPECIAL_SPAM]);
602
603        case 'spam_show':
604            $p = $this->imp_imap->config->spam_params;
605            return (!empty($p) && (!empty($p['display']) || !$this->spam));
606
607        case 'special':
608            $special = $this->getSpecialMailboxes();
609
610            switch ($this->_mbox) {
611            case $special[self::SPECIAL_COMPOSETEMPLATES]:
612            case $special[self::SPECIAL_DRAFTS]:
613            case $special[self::SPECIAL_SPAM]:
614            case $special[self::SPECIAL_TRASH]:
615                return true;
616            }
617
618            return in_array($this->_mbox, array_merge(
619                $special[self::SPECIAL_SENT],
620                $special[self::SPECIAL_USER]
621            ));
622
623        case 'special_outgoing':
624            $special = $this->getSpecialMailboxes();
625
626            return in_array($this->_mbox, array_merge(
627                array(
628                    $special[self::SPECIAL_COMPOSETEMPLATES],
629                    $special[self::SPECIAL_DRAFTS]
630                ),
631                $special[self::SPECIAL_SENT]
632            ));
633
634        case 'specialvfolder':
635            return !$this->editvfolder;
636
637        case 'sub':
638            return (($elt = $this->tree_elt) && $elt->subscribed);
639
640        case 'subfolders':
641            return $this->get(array_merge(array($this->_mbox), $this->subfolders_only));
642
643        case 'subfolders_only':
644            return $this->get($this->imp_imap->listMailboxes($this->imap_mbox_ob->list_escape . $this->namespace_delimiter . '*', null, array('flat' => true)));
645
646        case 'systemquery':
647            return $injector->getInstance('IMP_Search')->isSystemQuery($this->_mbox);
648
649        case 'templates':
650            $special = $this->getSpecialMailboxes();
651            return ($this->_mbox == $special[self::SPECIAL_COMPOSETEMPLATES]);
652
653        case 'trash':
654            $special = $this->getSpecialMailboxes();
655            return ($this->_mbox == $special[self::SPECIAL_TRASH]);
656
657        case 'tree_elt':
658            $ftree = $injector->getInstance('IMP_Ftree');
659            return $ftree[$this->_mbox];
660
661        case 'uidvalid':
662            $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
663            $uidvalid = $cache->getUidvalidity($this->_mbox);
664            if ($uidvalid === 0) {
665                return;
666            }
667
668            // POP3 and non-IMAP mailboxes do not support UIDVALIDITY.
669            if (!$this->is_imap || $this->nonimap) {
670                $cache->setUidvalidity($this->_mbox, 0);
671                return false;
672            }
673
674            $status = $this->imp_imap->status($this->_mbox, Horde_Imap_Client::STATUS_UIDVALIDITY);
675
676            if (($first = ($uidvalid === false)) ||
677                ($status['uidvalidity'] != $uidvalid)) {
678                $uidvalid = $status['uidvalidity'];
679                $cache->setUidvalidity($this->_mbox, $uidvalid);
680
681                if (!$first) {
682                    throw new IMP_Exception(_("Mailbox structure on server has changed."));
683                }
684            }
685
686            return $uidvalid;
687
688        case 'utf7imap':
689            return Horde_String::convertCharset($this->_mbox, 'UTF-8', 'UTF7-IMAP');
690
691        case 'value':
692            return $this->_mbox;
693
694        case 'vfolder':
695            return $injector->getInstance('IMP_Search')->isVFolder($this->_mbox);
696
697        case 'vfolder_container':
698            return ($this->_mbox == IMP_Ftree_Account_Vfolder::VFOLDER_KEY);
699
700        case 'vinbox':
701            return $injector->getInstance('IMP_Search')->isVinbox($this->_mbox);
702
703        case 'vtrash':
704            return $injector->getInstance('IMP_Search')->isVTrash($this->_mbox);
705        }
706
707        return false;
708    }
709
710    /**
711     */
712    public function __set($key, $value)
713    {
714        global $injector;
715
716        switch ($key) {
717        case 'display':
718            $injector->getInstance('IMP_Mailbox_SessionCache')->setDisplay($this->_mbox, $value);
719            break;
720        }
721    }
722
723    /**
724     * Create this mailbox on the server.
725     *
726     * @param array $opts  Additional options:
727     *   - special_use: (array) An array of special-use attributes to attempt
728     *                  to add to the mailbox.
729     *                  DEFAULT: NONE
730     *   - subscribe: (boolean) Override preference value of subscribe.
731     *
732     * @return boolean  True on success.
733     * @throws Horde_Exception
734     */
735    public function create(array $opts = array())
736    {
737        global $injector, $notification, $prefs;
738
739        if ($this->exists) {
740            return true;
741        }
742
743        $imp_imap = $this->imp_imap;
744
745        /* Check permissions. */
746        if (!$imp_imap->access(IMP_Imap::ACCESS_CREATEMBOX)) {
747            Horde::permissionDeniedError(
748                'imp',
749                'create_mboxes',
750                _("You are not allowed to create mailboxes.")
751            );
752            return false;
753        }
754        if (!$imp_imap->access(IMP_Imap::ACCESS_CREATEMBOX_MAX)) {
755            Horde::permissionDeniedError(
756                'imp',
757                'max_create_mboxes',
758                sprintf(_("You are not allowed to create more than %d mailboxes."), $imp_imap->max_create_mboxes)
759            );
760            return false;
761        }
762
763        /* Special use flags. */
764        $special_use = isset($opts['special_use'])
765            ? $opts['special_use']
766            : array();
767
768        /* Attempt to create the mailbox. */
769        try {
770            $imp_imap->createMailbox($this->_mbox, array('special_use' => $special_use));
771        } catch (IMP_Imap_Exception $e) {
772            if ($e->getCode() == $e::USEATTR) {
773                unset($opts['special_use']);
774                return $this->create($opts);
775            }
776
777            $e->notify(sprintf(_("The mailbox \"%s\" was not created. This is what the server said"), $this->display) . ': ' . $e->getMessage());
778            return false;
779        }
780
781        $notification->push(sprintf(_("The mailbox \"%s\" was successfully created."), $this->display), 'horde.success');
782
783        /* Subscribe, if requested. */
784        if ((!isset($opts['subscribe']) && $prefs->getValue('subscribe')) ||
785            !empty($opts['subscribe'])) {
786            try {
787                $imp_imap->subscribeMailbox($this->_mbox, true);
788            } catch (IMP_Imap_Exception $e) {}
789        }
790
791        /* Update the mailbox tree. */
792        $injector->getInstance('IMP_Ftree')->insert($this->_mbox);
793
794        return true;
795    }
796
797    /**
798     * Deletes mailbox.
799     *
800     * @param array $opts  Addtional options:
801     *   - subfolders: (boolean) Delete all subfolders?
802     *     DEFAULT: false
803     *   - subfolders_only: (boolean) If deleting subfolders, delete only
804     *                      subfolders (not current mailbox)?
805     *     DEFAULT: false
806     *
807     * @return boolean  True on success.
808     */
809    public function delete(array $opts = array())
810    {
811        global $injector, $notification;
812
813        if ($this->vfolder) {
814            if ($this->editvfolder) {
815                $imp_search = $injector->getInstance('IMP_Search');
816                $label = $imp_search[$this->_mbox]->label;
817                unset($imp_search[$this->_mbox]);
818                $notification->push(sprintf(_("Deleted Virtual Folder \"%s\"."), $label), 'horde.success');
819                return true;
820            }
821
822            $notification->push(sprintf(_("Could not delete Virtual Folder \"%s\"."), $this->label), 'horde.error');
823            return false;
824        }
825
826        $deleted = array();
827        $imp_imap = $this->imp_imap;
828        if (empty($opts['subfolders'])) {
829            $to_delete = array($this);
830        } else {
831            $to_delete = empty($opts['subfolders_only'])
832                ? $this->subfolders
833                : $this->subfolders_only;
834        }
835
836        foreach ($to_delete as $val) {
837            if (!$val->access_deletembox_acl) {
838                $notification->push(sprintf(_("The mailbox \"%s\" may not be deleted."), $val->display), 'horde.error');
839                continue;
840            }
841
842            try {
843                $imp_imap->deleteMailbox($val->value);
844                $notification->push(sprintf(_("The mailbox \"%s\" was successfully deleted."), $val->display), 'horde.success');
845                $deleted[] = $val;
846            } catch (IMP_Imap_Exception $e) {
847                $e->notify(sprintf(_("The mailbox \"%s\" was not deleted. This is what the server said"), $val->display) . ': ' . $e->getMessage());
848            }
849        }
850
851        if (!empty($deleted)) {
852            $injector->getInstance('IMP_Ftree')->delete($deleted);
853            $this->_onDelete($deleted);
854        }
855
856        return (count($deleted) == count($to_delete));
857    }
858
859    /**
860     * Rename this mailbox on the server. The subscription status remains the
861     * same.  All subfolders will also be renamed.
862     *
863     * @param string $new_name  The new mailbox name (UTF-8).
864     *
865     * @return boolean  True on success
866     */
867    public function rename($new_name)
868    {
869        global $injector, $notification;
870
871        /* Don't try to rename to an empty string. */
872        if (!strlen($new_name)) {
873            return false;
874        }
875
876        if (!$this->access_deletembox_acl) {
877            $notification->push(sprintf(_("The mailbox \"%s\" may not be renamed."), $this->display), 'horde.error');
878            return false;
879        }
880
881        $new_mbox = $this->get($new_name);
882        $old_list = $this->subfolders;
883
884        try {
885            $this->imp_imap->renameMailbox($this->_mbox, $new_mbox);
886        } catch (IMP_Imap_Exception $e) {
887            $e->notify(sprintf(_("Renaming \"%s\" to \"%s\" failed. This is what the server said"), $this->display, $new_mbox->display) . ': ' . $e->getMessage());
888            return false;
889        }
890
891        $notification->push(sprintf(_("The mailbox \"%s\" was successfully renamed to \"%s\"."), $this->display, $new_mbox->display), 'horde.success');
892
893        $injector->getInstance('IMP_Ftree')->rename($this->_mbox, $new_mbox);
894        $this->_onDelete($old_list);
895
896        return true;
897    }
898
899    /**
900     * Subscribe/unsubscribe to an IMAP mailbox.
901     *
902     * @param boolean $sub  True to subscribe, false to unsubscribe.
903     * @param array $opts   Additional options:
904     * <pre>
905     *   - subfolders: (boolean) If true, applies actions to all subfolders.
906     * </pre>
907     *
908     * @return boolean  True on success.
909     */
910    public function subscribe($sub, array $opts = array())
911    {
912        global $injector, $notification, $prefs;
913
914        /* Skip non-IMAP/container mailboxes. */
915        if (!$prefs->getValue('subscribe') ||
916            $this->nonimap ||
917            $this->container) {
918            return false;
919        }
920
921        if (!$sub && $this->inbox) {
922            $notification->push(sprintf(_("You cannot unsubscribe from \"%s\"."), $this->display), 'horde.error');
923            return false;
924        }
925
926        $imp_imap = $this->imp_imap;
927
928        try {
929            $imp_imap->subscribeMailbox($this->_mbox, $sub);
930        } catch (IMP_Imap_Exception $e) {
931            if ($sub) {
932                $e->notify(sprintf(_("You were not subscribed to \"%s\". Here is what the server said"), $this->display) . ': ' . $e->getMessage());
933            } else {
934                $e->notify(sprintf(_("You were not unsubscribed from \"%s\". Here is what the server said"), $this->display) . ': ' . $e->getMessage());
935            }
936            return false;
937        }
938
939        $imap_tree = $injector->getInstance('IMP_Ftree');
940        if ($sub) {
941            $imap_tree->subscribe($this->_mbox);
942        } else {
943            $imap_tree->unsubscribe($this->_mbox);
944        }
945
946        if (empty($opts['subfolders'])) {
947            $notify = $sub
948                ? sprintf(_("You were successfully subscribed to \"%s\"."), $this->display)
949                : sprintf(_("You were successfully unsubscribed from \"%s\"."), $this->display);
950        } else {
951            $action = false;
952
953            foreach ($this->subfolders_only as $val) {
954                try {
955                    $imp_imap->subscribeMailbox($val, $sub);
956                    if ($sub) {
957                        $imap_tree->subscribe($val);
958                    } else {
959                        $imap_tree->unsubscribe($val);
960                    }
961
962                    $action = true;
963                } catch (IMP_Imap_Exception $e) {
964                    // Ignore errors for sub-mailboxes.
965                }
966            }
967
968            if ($action) {
969                $notify = $sub
970                    ? sprintf(_("You were successfully subscribed to \"%s\" and all subfolders."), $this->display)
971                    : sprintf(_("You were successfully unsubscribed from \"%s\" and all subfolders."), $this->display);
972            }
973        }
974
975        $notification->push($notify, 'horde.success');
976
977        return true;
978    }
979
980    /**
981     * Runs filters on this mailbox.
982     */
983    public function filter()
984    {
985        if (!$this->search) {
986            $GLOBALS['injector']->getInstance('IMP_Filter')->filter($this);
987        }
988    }
989
990    /**
991     * Filters this mailbox if it is the INBOX and the filter on display pref
992     * is active.
993     *
994     * @return boolean  True if filter() was called.
995     */
996    public function filterOnDisplay()
997    {
998        if ($this->inbox &&
999            $GLOBALS['prefs']->getValue('filter_on_display')) {
1000            $this->filter();
1001            return true;
1002        }
1003
1004        return false;
1005    }
1006
1007    /**
1008     * Return the search query object for this mailbox.
1009     *
1010     * @return IMP_Search_Query  The search query object.
1011     */
1012    public function getSearchOb()
1013    {
1014        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
1015        return $imp_search[$this->_mbox];
1016    }
1017
1018    /**
1019     * Return an indices object for this mailbox.
1020     *
1021     * @param mixed $in  Either a single UID, array of UIDs, or a
1022     *                   Horde_Imap_Client_Ids object.
1023     *
1024     * @return IMP_Indices  An indices object.
1025     */
1026    public function getIndicesOb($in)
1027    {
1028        return new IMP_Indices($this, $in);
1029    }
1030
1031    /**
1032     * Return the sorting preference for this mailbox.
1033     *
1034     * @param boolean $convert  Convert 'by' to a Horde_Imap_Client constant?
1035     *
1036     * @return IMP_Prefs_Sort_Sortpref  Sortpref object.
1037     */
1038    public function getSort($convert = false)
1039    {
1040        global $prefs;
1041
1042        $mbox = $this->search
1043            ? $this
1044            : self::get($this->pref_from);
1045
1046        $ob = $this->sortob[strval($mbox)];
1047        $ob->convertSortby();
1048
1049        if ($convert && ($ob->sortby == IMP::IMAP_SORT_DATE)) {
1050            $ob->sortby = $prefs->getValue('sortdate');
1051        }
1052
1053        return $ob;
1054    }
1055
1056    /**
1057     * Set the sorting preference for this mailbox.
1058     *
1059     * @param integer $by      The sort type.
1060     * @param integer $dir     The sort direction.
1061     * @param boolean $delete  Delete the entry?
1062     */
1063    public function setSort($by = null, $dir = null, $delete = false)
1064    {
1065        $mbox = $this->search
1066            ? $this
1067            : self::get($this->pref_from);
1068
1069        if ($delete) {
1070            unset($this->sortob[strval($mbox)]);
1071        } else {
1072            $change = array();
1073            if (!is_null($by)) {
1074                $change['by'] = $by;
1075            }
1076            if (!is_null($dir)) {
1077                $change['dir'] = $dir;
1078            }
1079            $this->sortob[strval($mbox)] = $change;
1080        }
1081    }
1082
1083    /**
1084     * Are deleted messages hidden in this mailbox?
1085     *
1086     * @param boolean $deleted  Return value is what should be done with
1087     *                          deleted messages in general, as opposed to any
1088     *                          deleted message in the mailbox.
1089     *
1090     * @return boolean  True if deleted messages should be hidden.
1091     */
1092    public function hideDeletedMsgs($deleted = false)
1093    {
1094        global $prefs;
1095
1096        if (!$this->access_flags) {
1097            return true;
1098        }
1099
1100        if ($prefs->getValue('use_trash')) {
1101            /* If using Virtual Trash, only show deleted messages in
1102             * the Virtual Trash mailbox. */
1103            return $this->get($prefs->getValue(self::MBOX_TRASH))->vtrash
1104                ? !$this->vtrash
1105                : ($prefs->getValue('delhide_trash') ? true : $deleted);
1106        }
1107
1108        return $prefs->getValue('delhide');
1109    }
1110
1111    /**
1112     * Sets the 'delhide' preference and clears necessary cached data.
1113     *
1114     * @param boolean $value  The value to set 'delhide' to.
1115     */
1116    public function setHideDeletedMsgs($value)
1117    {
1118        $GLOBALS['prefs']->setValue('delhide', $value);
1119        $GLOBALS['injector']->getInstance('IMP_Factory_MailboxList')->expireAll();
1120    }
1121
1122    /**
1123     * Run a search query on this mailbox that is not stored in the current
1124     * session. Allows custom queries with custom sorts to be used without
1125     * affecting cached mailboxes.
1126     *
1127     * @param Horde_Imap_Client_Search_Query $query  The search query object.
1128     * @param integer $sortby                        The sort criteria.
1129     * @param integer $sortdir                       The sort directory.
1130     *
1131     * @return IMP_Indices  An indices object.
1132     */
1133    public function runSearchQuery(Horde_Imap_Client_Search_Query $query,
1134                                   $sortby = null, $sortdir = null)
1135    {
1136        try {
1137            $results = $this->imp_imap->search($this, $query, array(
1138                'sort' => is_null($sortby) ? null : array($sortby)
1139            ));
1140            if ($sortdir) {
1141                $results['match']->reverse();
1142            }
1143            return $this->getIndicesOb($results['match']);
1144        } catch (IMP_Imap_Exception $e) {
1145            return new IMP_Indices();
1146        }
1147    }
1148
1149    /**
1150     * Generate a URL using the current mailbox.
1151     *
1152     * @param string|Horde_Url $page  Page name to link to.
1153     * @param string $buid            The BUID to use on the linked page.
1154     * @param boolean $encode         Encode the argument separator?
1155     *
1156     * @return Horde_Url  URL to $page with any necessary mailbox information
1157     *                    added to the parameter list of the URL.
1158     */
1159    public function url($page, $buid = null, $encode = true)
1160    {
1161        if ($page instanceof Horde_Url) {
1162            return $page->add($this->urlParams($buid))->setRaw(!$encode);
1163        }
1164
1165        switch ($GLOBALS['registry']->getView()) {
1166        case Horde_Registry::VIEW_BASIC:
1167            switch ($page) {
1168            case 'message':
1169                return IMP_Basic_Message::url(array(
1170                    'buid' => $buid,
1171                    'mailbox' => $this->_mbox
1172                ))->setRaw(!$encode);
1173
1174            case 'mailbox':
1175                return IMP_Basic_Mailbox::url(array(
1176                    'mailbox' => $this->_mbox
1177                ))->setRaw(!$encode);
1178            }
1179            break;
1180
1181        case Horde_Registry::VIEW_DYNAMIC:
1182            $anchor = is_null($buid)
1183                ? ('mbox:' . $this->form_to)
1184                : ('msg:' . $this->form_to . ';' . $buid);
1185            return Horde::url('index.php')->setAnchor($anchor);
1186
1187        case Horde_Registry::VIEW_MINIMAL:
1188            switch ($page) {
1189            case 'message':
1190                return IMP_Minimal_Message::url(array(
1191                    'buid' => $buid,
1192                    'mailbox' => $this->_mbox
1193                ))->setRaw(!$encode);
1194
1195            case 'mailbox':
1196                return IMP_Minimal_Mailbox::url(array(
1197                    'mailbox' => $this->_mbox
1198                ))->setRaw(!$encode);
1199            }
1200            break;
1201
1202        case Horde_Registry::VIEW_SMARTMOBILE:
1203            $url = Horde::url('smartmobile.php');
1204            $anchor = is_null($buid)
1205                ? ('mbox=' . $this->form_to)
1206                : ('msg=' . $this->form_to . ';' . $buid);
1207            $url->setAnchor('mailbox?' . $anchor);
1208            return $url;
1209        }
1210
1211        return Horde::url($page . '.php')->add($this->urlParams($buid))->setRaw(!$encode);
1212    }
1213
1214    /**
1215     * Returns list of URL parameters necessary to indicate current mailbox
1216     * status.
1217     *
1218     * @param string $buid  The BUID to use on the linked page.
1219     *
1220     * @return array  The list of parameters needed to indicate the current
1221     *                mailbox status.
1222     */
1223    public function urlParams($buid = null)
1224    {
1225        $params = array('mailbox' => $this->form_to);
1226        if (!is_null($buid)) {
1227            $params['buid'] = $buid;
1228        }
1229        return $params;
1230    }
1231
1232    /**
1233     * Determines if this mailbox is equal to the given mailbox.
1234     * Needed because directly comparing two mailbox objects may fail (the
1235     * member variables may be different).
1236     *
1237     * @param mixed $mbox  The mailbox to compare to.
1238     *
1239     * @return boolean  True if the mailboxes are the same.
1240     */
1241    public function equals($mbox)
1242    {
1243        return ($mbox == $this->_mbox);
1244    }
1245
1246    /**
1247     * Create an indices object from a list of browser-UIDs.
1248     *
1249     * @param IMP_Indices|array $buids  Browser-UIDs.
1250     *
1251     * @return IMP_Indices  An indices object.
1252     */
1253    public function fromBuids($buids)
1254    {
1255        if (is_array($buids)) {
1256            $buids = new IMP_Indices($this->_mbox, $buids);
1257        }
1258        $buid_list = $buids->getSingle(true);
1259
1260        $out = new IMP_Indices();
1261
1262        if ($buid_list[1]) {
1263            $list_ob = $this->list_ob;
1264            foreach ($buid_list[1] as $buid) {
1265                if ($resolve = $list_ob->resolveBuid($buid)) {
1266                    $out->add($resolve['m'], $resolve['u']);
1267                }
1268            }
1269        }
1270
1271        return $out;
1272    }
1273
1274    /**
1275     * Create a BUID indices object from a list of UIDs.
1276     *
1277     * @param IMP_Indices $uids  UIDs.
1278     *
1279     * @return IMP_Indices  An indices object.
1280     */
1281    public function toBuids(IMP_Indices $uids)
1282    {
1283        $list_ob = $this->list_ob;
1284        $out = new IMP_Indices();
1285
1286        foreach ($uids as $val) {
1287            foreach ($val->uids as $val2) {
1288                $out->add($this->_mbox, $list_ob->getBuid($val->mbox, $val2));
1289            }
1290        }
1291
1292        return $out;
1293    }
1294
1295    /**
1296     * Return the mailbox name to create given a submailbox name.
1297     *
1298     * @param string $new  The submailbox name (UTF-8).
1299     *
1300     * @return IMP_Mailbox  The mailbox to create.
1301     */
1302    public function createMailboxName($new)
1303    {
1304        if ($this->remote_container) {
1305            $new = $this->remote_account->mailbox($new);
1306        } else {
1307            $ns_info = $this->namespace_info;
1308            $new = strlen($this)
1309                ? ($this->_mbox . $ns_info->delimiter . $new)
1310                : $ns_info->name . $new;
1311        }
1312
1313        return self::get($new);
1314    }
1315
1316    /* Static methods. */
1317
1318    /**
1319     * Converts a mailbox string from a form representation.
1320     * Needed because null characters (used for various internal non-IMAP
1321     * mailbox representations) will not work in form elements.
1322     *
1323     * @param mixed $mbox  The mailbox name(s).
1324     *
1325     * @return mixed  The mailbox object(s).
1326     */
1327    static public function formFrom($mbox)
1328    {
1329        return is_array($mbox)
1330            ? array_filter(array_map(array(__CLASS__, 'formFrom'), $mbox))
1331              // Base64url (RFC 4648 [5]) encoding
1332            : self::get(base64_decode(strtr($mbox, '-_', '+/')));
1333    }
1334
1335    /**
1336     * Converts a mailbox string to a form representation.
1337     * Needed because null characters (used for various internal non-IMAP
1338     * mailbox representations) will not work in form elements.
1339     *
1340     * @param mixed $mbox  The mailbox name(s).
1341     *
1342     * @return mixed  The converted mailbox string(s).
1343     */
1344    static public function formTo($mbox)
1345    {
1346        return is_array($mbox)
1347            ? array_filter(array_map(array(__CLASS__, 'formTo'), $mbox))
1348              // Base64url (RFC 4648 [5]) encoding
1349            : strtr(rtrim(base64_encode($mbox), '='), '+/', '-_');
1350    }
1351
1352    /**
1353     * Return the list of special mailboxes.
1354     *
1355     * @return array  A list of mailboxes, with the self::SPECIAL_* constants
1356     *                as keys and values containing the IMP_Mailbox objects or
1357     *                null if the mailbox doesn't exist (self::SPECIAL_SENT
1358     *                contains an array of objects).
1359     */
1360    static public function getSpecialMailboxes()
1361    {
1362        global $injector;
1363
1364        return $injector->getInstance('IMP_Mailbox_SessionCache')->getSpecialMailboxes();
1365    }
1366
1367    /**
1368     * Return the list of sorted special mailboxes.
1369     *
1370     * @return array  The list of sorted special mailboxes (IMP_Mailbox
1371     *                objects).
1372     */
1373    static public function getSpecialMailboxesSort()
1374    {
1375        $out = array();
1376
1377        foreach (array_filter(self::getSpecialMailboxes()) as $val) {
1378            if (is_array($val)) {
1379                $out = array_merge($out, $val);
1380            } else {
1381                $out[] = $val;
1382            }
1383        }
1384
1385        $tmp = array();
1386        foreach ($out as $val) {
1387            $tmp[strval($val)] = $val->abbrev_label;
1388        }
1389        asort($tmp, SORT_LOCALE_STRING);
1390
1391        return self::get(array_keys($tmp));
1392    }
1393
1394    /**
1395     * Converts a mailbox name from a value stored in the preferences.
1396     *
1397     * @param string $mbox  The mailbox name as stored in a preference.
1398     *
1399     * @return string  The full IMAP mailbox name (UTF-8).
1400     */
1401    static public function prefFrom($mbox)
1402    {
1403        $imp_imap = $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create();
1404        if ($imp_imap->isImap()) {
1405            $empty_ns = $imp_imap->getNamespace('');
1406
1407            if (!is_null($empty_ns) &&
1408                (strpos($mbox, $empty_ns->delimiter) === 0)) {
1409                /* Prefixed with delimiter => from empty namespace. */
1410                return substr($mbox, strlen($empty_ns->delimiter));
1411            } elseif ($imp_imap->getNamespace($mbox, true) === null) {
1412                /* No namespace prefix => from personal namespace. */
1413                $def_ns = $imp_imap->getNamespace($imp_imap::NS_DEFAULT);
1414                return $def_ns->name . $mbox;
1415            }
1416        }
1417
1418        return $mbox;
1419    }
1420
1421    /**
1422     * Converts a mailbox name to a value to be stored in a preference.
1423     *
1424     * @param string $mbox  The full IMAP mailbox name (UTF-8).
1425     *
1426     * @return string  The value to store in a preference.
1427     */
1428    static public function prefTo($mbox)
1429    {
1430        global $injector;
1431
1432        $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
1433        $mbox_str = $ret = strval($mbox);
1434
1435        if (($pref_to = $cache->getPrefTo($mbox_str)) !== false) {
1436            return $pref_to;
1437        }
1438
1439        if (($ns = self::get($mbox)->namespace_info) !== null) {
1440            $imp_imap = $injector->getInstance('IMP_Factory_Imap')->create();
1441            $def_ns = $imp_imap->getNamespace($imp_imap::NS_DEFAULT);
1442
1443            if ($ns->name == $def_ns->name) {
1444                /* From personal namespace => strip namespace. */
1445                $ret = substr($mbox_str, strlen($def_ns->name));
1446            } else {
1447                $empty_ns = $imp_imap->getNamespace('');
1448                if ($ns->name == $empty_ns->name) {
1449                    /* From empty namespace => prefix with delimiter. */
1450                    $ret = $empty_ns->delimiter . $mbox_str;
1451                }
1452            }
1453        }
1454
1455        $cache->setPrefTo($mbox_str, $ret);
1456
1457        return $ret;
1458    }
1459
1460    /* Internal methods. */
1461
1462    /**
1463     * Returns a unique identifier for this mailbox's status.
1464     *
1465     * This cache ID is guaranteed to change if messages are added/deleted
1466     * from the mailbox. Additionally, if CONDSTORE is available on the remote
1467     * IMAP server, this ID will change if flag information changes.
1468     *
1469     * For search mailboxes, this value never changes (search mailboxes must
1470     * be forcibly refreshed).
1471     *
1472     * @param boolean $date  If true, adds date information to ID.
1473     *
1474     * @return string  The cache ID string, which will change when the
1475     *                 composition of this mailbox changes.
1476     */
1477    protected function _getCacheID($date = false)
1478    {
1479        global $prefs;
1480
1481        $date = $date
1482            ? 'D' . date('z')
1483            : '';
1484
1485        if ($this->search) {
1486            return '1' . ($date ? '|' . $date : '');
1487        }
1488
1489        $sortpref = $this->getSort(true);
1490        $addl = array(
1491            $sortpref->sortby,
1492            $sortpref->sortdir,
1493            intval($prefs->getValue('delhide'))
1494        );
1495        if ($date) {
1496            $addl[] = $date;
1497        }
1498
1499        try {
1500            return $this->imp_imap->getCacheId($this->_mbox, $addl);
1501        } catch (IMP_Imap_Exception $e) {
1502            /* Assume an error means that a mailbox can not be trusted. */
1503            return strval(new Horde_Support_Randomid());
1504        }
1505    }
1506
1507    /**
1508     * If there is information available to tell us about a prefix in front of
1509     * mailbox names that shouldn't be displayed to the user, then use it to
1510     * strip that prefix out. Additionally, translate prefix text if this
1511     * is a special mailbox.
1512     *
1513     * @param boolean $notranslate  Don't translate the mailbox prefix?
1514     *
1515     * @return string  The mailbox, with any prefix gone/translated.
1516     */
1517    protected function _getDisplay($notranslate = false)
1518    {
1519        global $injector;
1520
1521        $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
1522        if (!$notranslate &&
1523            (($display = $cache->getDisplay($this->_mbox)) !== false)) {
1524            return $display;
1525        }
1526
1527        /* Handle special container mailboxes. */
1528        if (($elt = $this->tree_elt) && $elt->nonimap && $elt->container) {
1529            if ($elt->remote) {
1530                return _("Remote Accounts");
1531            } elseif ($elt->vfolder) {
1532                return _("Virtual Folders");
1533            } elseif ($elt->namespace_other) {
1534                return _("Other Users");
1535            } elseif ($elt->namespace_shared) {
1536                return _("Shared");
1537            }
1538        }
1539
1540        /* Handle remote mailboxes. */
1541        if ($this->remote) {
1542            return $injector->getInstance('IMP_Remote')->label($this->_mbox);
1543        }
1544
1545        $ns_info = $this->namespace_info;
1546        $out = $this->_mbox;
1547
1548        if (!is_null($ns_info)) {
1549            /* Return translated namespace information. */
1550            if (strlen($ns_info->translation) && $this->namespace) {
1551                $cache->setDisplay($this->_mbox, $ns_info->translation);
1552                return $ns_info->translation;
1553            }
1554
1555            /* Strip personal namespace information. */
1556            if ($ns_info->type === $ns_info::NS_PERSONAL) {
1557                $out = $ns_info->stripNamespace($this->_mbox);
1558            }
1559        }
1560
1561        if ($notranslate) {
1562            return $out;
1563        }
1564
1565        /* Bug #9971: Special mailboxes can be empty IMP_Mailbox objects -
1566         * catch this with the strlen check below. */
1567        foreach ($this->getSpecialMailboxes() as $key => $val) {
1568            switch ($key) {
1569            case self::SPECIAL_COMPOSETEMPLATES:
1570                if (strval($val) == $this->_mbox) {
1571                    $out = _("Templates");
1572                }
1573                break;
1574
1575            case self::SPECIAL_DRAFTS:
1576                if (strval($val) == $this->_mbox) {
1577                    $out = _("Drafts");
1578                }
1579                break;
1580
1581            case self::SPECIAL_SENT:
1582                if (in_array($this->_mbox, $val)) {
1583                    $out = _("Sent");
1584                }
1585                break;
1586
1587            case self::SPECIAL_SPAM:
1588                if (strval($val) == $this->_mbox) {
1589                    $out = _("Spam");
1590                }
1591                break;
1592
1593            case self::SPECIAL_TRASH:
1594                if (strval($val) == $this->_mbox) {
1595                    $out = _("Trash");
1596                }
1597                break;
1598            }
1599        }
1600
1601        if ($this->inbox) {
1602            $out = _("Inbox");
1603        } elseif (($this->_mbox == $out) &&
1604            !is_null($ns_info) &&
1605            (strpos($out, 'INBOX' . $ns_info->delimiter) === 0)) {
1606            $out = substr_replace($out, _("Inbox"), 0, 5);
1607        }
1608
1609        $cache->setDisplay($this->_mbox, $out);
1610
1611        return $out;
1612    }
1613
1614    /**
1615     * Return icon information.
1616     *
1617     * @return object  Object with the following properties:
1618     *   - alt
1619     *   - class
1620     *   - icon
1621     *   - iconopen
1622     *   - user_icon
1623     */
1624    protected function _getIcon()
1625    {
1626        global $injector;
1627
1628        $info = new stdClass;
1629        $info->iconopen = null;
1630        $info->user_icon = false;
1631
1632        if ($this->container) {
1633            /* We are dealing with folders here. */
1634            if ($this->is_open) {
1635                $info->alt = _("Opened Folder");
1636                $info->class = 'folderopenImg';
1637                $info->icon = 'folders/open.png';
1638            } else {
1639                $info->alt = _("Folder");
1640                $info->class = 'folderImg';
1641                $info->icon = 'folders/folder.png';
1642                $info->iconopen = Horde_Themes::img('folders/open.png');
1643            }
1644        } elseif ($this->remote_container) {
1645            $info->alt = _("Remote Account");
1646            $info->class = 'remoteImg';
1647            $info->icon = 'shared.png';
1648        } else {
1649            $special = $this->getSpecialMailboxes();
1650
1651            switch ($this->_mbox) {
1652            case 'INBOX':
1653                $info->alt = _("Inbox");
1654                $info->class = 'inboxImg';
1655                $info->icon = 'folders/inbox.png';
1656                break;
1657
1658            case $special[self::SPECIAL_COMPOSETEMPLATES]:
1659                $info->alt = ("Templates");
1660                $info->class = 'composetemplatesImg';
1661                $info->icon = 'folders/drafts.png';
1662                break;
1663
1664            case $special[self::SPECIAL_DRAFTS]:
1665                $info->alt = _("Drafts");
1666                $info->class = 'draftsImg';
1667                $info->icon = 'folders/drafts.png';
1668                break;
1669
1670            case $special[self::SPECIAL_SPAM]:
1671                $info->alt = _("Spam");
1672                $info->class = 'spamImg';
1673                $info->icon = 'folders/spam.png';
1674                break;
1675
1676            case $special[self::SPECIAL_TRASH]:
1677                $info->alt = _("Trash");
1678                $info->class = 'trashImg';
1679                $info->icon = 'folders/trash.png';
1680                break;
1681
1682            default:
1683                if (in_array($this->_mbox, $special[self::SPECIAL_SENT])) {
1684                    $info->alt = _("Sent");
1685                    $info->class = 'sentImg';
1686                    $info->icon = 'folders/sent.png';
1687                } else {
1688                    $info->alt = in_array($this->_mbox, $special[self::SPECIAL_USER])
1689                        ? $this->display
1690                        : _("Mailbox");
1691                    if ($this->is_open) {
1692                        $info->class = 'folderopenImg';
1693                        $info->icon = 'folders/open.png';
1694                    } else {
1695                        $info->class = 'folderImg';
1696                        $info->icon = 'folders/folder.png';
1697                    }
1698                }
1699                break;
1700            }
1701
1702            /* Virtual folders. */
1703            if ($this->vfolder) {
1704                $imp_search = $injector->getInstance('IMP_Search');
1705                if ($imp_search->isVTrash($this->_mbox)) {
1706                    $info->alt = $imp_search[$this->_mbox]->label;
1707                    $info->class = 'trashImg';
1708                    $info->icon = 'folders/trash.png';
1709                } elseif ($imp_search->isVinbox($this->_mbox)) {
1710                    $info->alt = $imp_search[$this->_mbox]->label;
1711                    $info->class = 'inboxImg';
1712                    $info->icon = 'folders/inbox.png';
1713                }
1714            }
1715        }
1716
1717        /* Overwrite the icon information now. */
1718        $mi = $injector->getInstance('IMP_Mailbox_SessionCache')->getIcons($this->_mbox);
1719        if (!empty($mi)) {
1720            if (isset($mi['alt'])) {
1721                $info->alt = $mi['alt'];
1722            }
1723            $info->icon = strval($mi['icon']);
1724            $info->user_icon = true;
1725        } elseif ($info->icon) {
1726            $info->icon = Horde_Themes::img($info->icon);
1727        }
1728
1729        return $info;
1730    }
1731
1732    /**
1733     * Do the necessary cleanup/cache updates when deleting mailboxes.
1734     *
1735     * @param array $deleted  The list of deleted mailboxes.
1736     */
1737    protected function _onDelete($deleted)
1738    {
1739        /* Clear the mailboxes from the sort prefs. */
1740        foreach ($this->get($deleted) as $val) {
1741            $val->setSort(null, null, true);
1742        }
1743    }
1744
1745}
1746