1<?php
2/**
3 * Copyright 2004-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 2004-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package   Imap_Client
12 */
13
14/**
15 * Container of IMAP mailboxes.
16 *
17 * @author    Michael Slusarz <slusarz@horde.org>
18 * @category  Horde
19 * @copyright 2004-2017 Horde LLC
20 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
21 * @package   Imap_Client
22 */
23class Horde_Imap_Client_Mailbox_List implements Countable, IteratorAggregate
24{
25    /**
26     * The delimiter character to use.
27     *
28     * @var string
29     */
30    protected $_delimiter;
31
32    /**
33     * Mailbox list.
34     *
35     * @var array
36     */
37    protected $_mboxes = array();
38
39    /**
40     * Should we sort with INBOX at the front of the list?
41     *
42     * @var boolean
43     */
44    protected $_sortinbox;
45
46    /**
47     * Constructor.
48     *
49     * @param mixed $mboxes  A mailbox or list of mailboxes.
50     */
51    public function __construct($mboxes)
52    {
53        $this->_mboxes = is_array($mboxes)
54            ? $mboxes
55            : array($mboxes);
56    }
57
58    /**
59     * Sort the list of mailboxes.
60     *
61     * @param array $opts  Options:
62     *   - delimiter: (string) The delimiter to use.
63     *                DEFAULT: '.'
64     *   - inbox: (boolean) Always put INBOX at the head of the list?
65     *            DEFAULT: Yes
66     *   - noupdate: (boolean) Do not update the object's mailbox list?
67     *               DEFAULT: true
68     *
69     * @return array  List of sorted mailboxes (index association is kept).
70     */
71    public function sort(array $opts = array())
72    {
73        $this->_delimiter = isset($opts['delimiter'])
74            ? $opts['delimiter']
75            : '.';
76        $this->_sortinbox = (!isset($opts['inbox']) || !empty($opts['inbox']));
77
78        if (empty($opts['noupdate'])) {
79            $mboxes = &$this->_mboxes;
80        } else {
81            $mboxes = $this->_mboxes;
82        }
83
84        uasort($mboxes, array($this, '_mboxCompare'));
85
86        return $mboxes;
87    }
88
89    /**
90     * Hierarchical folder sorting function (used with usort()).
91     *
92     * @param string $a  Comparison item 1.
93     * @param string $b  Comparison item 2.
94     *
95     * @return integer  See usort().
96     */
97    final protected function _mboxCompare($a, $b)
98    {
99        /* Always return INBOX as "smaller". */
100        if ($this->_sortinbox) {
101            if (strcasecmp($a, 'INBOX') === 0) {
102                return -1;
103            } elseif (strcasecmp($b, 'INBOX') === 0) {
104                return 1;
105            }
106        }
107
108        $a_parts = explode($this->_delimiter, $a);
109        $b_parts = explode($this->_delimiter, $b);
110
111        $a_count = count($a_parts);
112        $b_count = count($b_parts);
113
114        for ($i = 0, $iMax = min($a_count, $b_count); $i < $iMax; ++$i) {
115            if ($a_parts[$i] != $b_parts[$i]) {
116                /* If only one of the folders is under INBOX, return it as
117                 * "smaller". */
118                if ($this->_sortinbox && ($i === 0)) {
119                    $a_base = (strcasecmp($a_parts[0], 'INBOX') === 0);
120                    $b_base = (strcasecmp($b_parts[0], 'INBOX') === 0);
121                    if ($a_base && !$b_base) {
122                        return -1;
123                    } elseif (!$a_base && $b_base) {
124                        return 1;
125                    }
126                }
127
128                $cmp = strnatcasecmp($a_parts[$i], $b_parts[$i]);
129                return ($cmp === 0)
130                    ? strcmp($a_parts[$i], $b_parts[$i])
131                    : $cmp;
132            } elseif ($a_parts[$i] !== $b_parts[$i]) {
133                return strlen($a_parts[$i]) - strlen($b_parts[$i]);
134            }
135        }
136
137        return ($a_count - $b_count);
138    }
139
140    /* Countable methods. */
141
142    /**
143     */
144    public function count()
145    {
146        return count($this->_mboxes);
147    }
148
149    /* IteratorAggregate methods. */
150
151    /**
152     */
153    public function getIterator()
154    {
155        return new ArrayIterator($this->_mboxes);
156    }
157
158}
159