1<?php
2/**
3 * Copyright 2011-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 2011-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package   Imap_Client
12 */
13
14/**
15 * Fetch query object for use with Horde_Imap_Client_Base#fetch().
16 *
17 * @author    Michael Slusarz <slusarz@horde.org>
18 * @category  Horde
19 * @copyright 2011-2017 Horde LLC
20 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
21 * @package   Imap_Client
22 */
23class Horde_Imap_Client_Fetch_Query implements ArrayAccess, Countable, Iterator
24{
25    /**
26     * Internal data array.
27     *
28     * @var array
29     */
30    protected $_data = array();
31
32    /**
33     * Get the full text of the message.
34     *
35     * @param array $opts  The following options are available:
36     *   - length: (integer) The length of the substring to return.
37     *             DEFAULT: The entire text is returned.
38     *   - peek: (boolean) If set, does not set the '\Seen' flag on the
39     *            message.
40     *            DEFAULT: The seen flag is set.
41     *   - start: (integer) If a portion of the full text is desired to be
42     *            returned, the starting position is identified here.
43     *            DEFAULT: The entire text is returned.
44     */
45    public function fullText(array $opts = array())
46    {
47        $this->_data[Horde_Imap_Client::FETCH_FULLMSG] = $opts;
48    }
49
50    /**
51     * Return header text.
52     *
53     * Header text is defined only for the base RFC 2822 message or
54     * message/rfc822 parts.
55     *
56     * @param array $opts  The following options are available:
57     *   - id: (string) The MIME ID to obtain the header text for.
58     *         DEFAULT: The header text for the base message will be
59     *         returned.
60     *   - length: (integer) The length of the substring to return.
61     *             DEFAULT: The entire text is returned.
62     *   - peek: (boolean) If set, does not set the '\Seen' flag on the
63     *           message.
64     *           DEFAULT: The seen flag is set.
65     *   - start: (integer) If a portion of the full text is desired to be
66     *            returned, the starting position is identified here.
67     *            DEFAULT: The entire text is returned.
68     */
69    public function headerText(array $opts = array())
70    {
71        $id = isset($opts['id'])
72            ? $opts['id']
73            : 0;
74        $this->_data[Horde_Imap_Client::FETCH_HEADERTEXT][$id] = $opts;
75    }
76
77    /**
78     * Return body text.
79     *
80     * Body text is defined only for the base RFC 2822 message or
81     * message/rfc822 parts.
82     *
83     * @param array $opts  The following options are available:
84     *   - id: (string) The MIME ID to obtain the body text for.
85     *         DEFAULT: The body text for the entire message will be
86     *         returned.
87     *   - length: (integer) The length of the substring to return.
88     *             DEFAULT: The entire text is returned.
89     *   - peek: (boolean) If set, does not set the '\Seen' flag on the
90     *           message.
91     *           DEFAULT: The seen flag is set.
92     *   - start: (integer) If a portion of the full text is desired to be
93     *            returned, the starting position is identified here.
94     *            DEFAULT: The entire text is returned.
95     */
96    public function bodyText(array $opts = array())
97    {
98        $id = isset($opts['id'])
99            ? $opts['id']
100            : 0;
101        $this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id] = $opts;
102    }
103
104    /**
105     * Return MIME header text.
106     *
107     * MIME header text is defined only for non-RFC 2822 messages and
108     * non-message/rfc822 parts.
109     *
110     * @param string $id   The MIME ID to obtain the MIME header text for.
111     * @param array $opts  The following options are available:
112     *   - length: (integer) The length of the substring to return.
113     *             DEFAULT: The entire text is returned.
114     *   - peek: (boolean) If set, does not set the '\Seen' flag on the
115     *           message.
116     *           DEFAULT: The seen flag is set.
117     *   - start: (integer) If a portion of the full text is desired to be
118     *            returned, the starting position is identified here.
119     *            DEFAULT: The entire text is returned.
120     */
121    public function mimeHeader($id, array $opts = array())
122    {
123        $this->_data[Horde_Imap_Client::FETCH_MIMEHEADER][$id] = $opts;
124    }
125
126    /**
127     * Return the body part data for a MIME ID.
128     *
129     * @param string $id   The MIME ID to obtain the body part text for.
130     * @param array $opts  The following options are available:
131     *   - decode: (boolean) Attempt to server-side decode the bodypart data
132     *             if it is MIME transfer encoded.
133     *             DEFAULT: false
134     *   - length: (integer) The length of the substring to return.
135     *             DEFAULT: The entire text is returned.
136     *   - peek: (boolean) If set, does not set the '\Seen' flag on the
137     *           message.
138     *           DEFAULT: The seen flag is set.
139     *   - start: (integer) If a portion of the full text is desired to be
140     *            returned, the starting position is identified here.
141     *            DEFAULT: The entire text is returned.
142     */
143    public function bodyPart($id, array $opts = array())
144    {
145        $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id] = $opts;
146    }
147
148    /**
149     * Returns the decoded body part size for a MIME ID.
150     *
151     * @param string $id  The MIME ID to obtain the decoded body part size
152     *                    for.
153     */
154    public function bodyPartSize($id)
155    {
156        $this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id] = true;
157    }
158
159    /**
160     * Returns RFC 2822 header text that matches a search string.
161     *
162     * This header search work only with the base RFC 2822 message or
163     * message/rfc822 parts.
164     *
165     * @param string $label  A unique label associated with this particular
166     *                       search. This is how the results are stored.
167     * @param array $search  The search string(s) (case-insensitive).
168     * @param array $opts    The following options are available:
169     *   - cache: (boolean) If true, and 'peek' is also true, will cache
170     *            the result of this call.
171     *            DEFAULT: false
172     *   - id: (string) The MIME ID to search.
173     *         DEFAULT: The base message part
174     *   - length: (integer) The length of the substring to return.
175     *             DEFAULT: The entire text is returned.
176     *   - notsearch: (boolean) Do a 'NOT' search on the headers.
177     *                DEFAULT: false
178     *   - peek: (boolean) If set, does not set the '\Seen' flag on the
179     *           message.
180     *           DEFAULT: The seen flag is set.
181     *   - start: (integer) If a portion of the full text is desired to be
182     *            returned, the starting position is identified here.
183     *            DEFAULT: The entire text is returned.
184     */
185    public function headers($label, $search, array $opts = array())
186    {
187        $this->_data[Horde_Imap_Client::FETCH_HEADERS][$label] = array_merge(
188            $opts,
189            array(
190                'headers' => array_map('strval', $search)
191            )
192        );
193    }
194
195    /**
196     * Return MIME structure information.
197     */
198    public function structure()
199    {
200        $this->_data[Horde_Imap_Client::FETCH_STRUCTURE] = true;
201    }
202
203    /**
204     * Return envelope header data.
205     */
206    public function envelope()
207    {
208        $this->_data[Horde_Imap_Client::FETCH_ENVELOPE] = true;
209    }
210
211    /**
212     * Return flags set for the message.
213     */
214    public function flags()
215    {
216        $this->_data[Horde_Imap_Client::FETCH_FLAGS] = true;
217    }
218
219    /**
220     * Return the internal (IMAP) date of the message.
221     */
222    public function imapDate()
223    {
224        $this->_data[Horde_Imap_Client::FETCH_IMAPDATE] = true;
225    }
226
227    /**
228     * Return the size (in bytes) of the message.
229     */
230    public function size()
231    {
232        $this->_data[Horde_Imap_Client::FETCH_SIZE] = true;
233    }
234
235    /**
236     * Return the unique ID of the message.
237     */
238    public function uid()
239    {
240        $this->_data[Horde_Imap_Client::FETCH_UID] = true;
241    }
242
243    /**
244     * Return the sequence number of the message.
245     */
246    public function seq()
247    {
248        $this->_data[Horde_Imap_Client::FETCH_SEQ] = true;
249    }
250
251    /**
252     * Return the mod-sequence value for the message.
253     *
254     * The server must support the CONDSTORE IMAP extension, and the mailbox
255     * must support mod-sequences.
256     */
257    public function modseq()
258    {
259        $this->_data[Horde_Imap_Client::FETCH_MODSEQ] = true;
260    }
261
262    /**
263     * Does the query contain the given criteria?
264     *
265     * @param integer $criteria  The criteria to remove.
266     *
267     * @return boolean  True if the query contains the given criteria.
268     */
269    public function contains($criteria)
270    {
271        return isset($this->_data[$criteria]);
272    }
273
274    /**
275     * Remove an entry under a given criteria.
276     *
277     * @param integer $criteria  Criteria ID.
278     * @param string $key        The key to remove.
279     */
280    public function remove($criteria, $key)
281    {
282        if (isset($this->_data[$criteria]) &&
283            is_array($this->_data[$criteria])) {
284            unset($this->_data[$criteria][$key]);
285            if (empty($this->_data[$criteria])) {
286                unset($this->_data[$criteria]);
287            }
288        }
289    }
290
291    /**
292     * Returns a hash of the current query object.
293     *
294     * @return string  Hash.
295     */
296    public function hash()
297    {
298        return hash('md5', serialize($this));
299    }
300
301    /* ArrayAccess methods. */
302
303    /**
304     */
305    public function offsetExists($offset)
306    {
307        return isset($this->_data[$offset]);
308    }
309
310    /**
311     */
312    public function offsetGet($offset)
313    {
314        return isset($this->_data[$offset])
315            ? $this->_data[$offset]
316            : null;
317    }
318
319    /**
320     */
321    public function offsetSet($offset, $value)
322    {
323        $this->_data[$offset] = $value;
324    }
325
326    /**
327     */
328    public function offsetUnset($offset)
329    {
330        unset($this->_data[$offset]);
331    }
332
333    /* Countable methods. */
334
335    /**
336     */
337    public function count()
338    {
339        return count($this->_data);
340    }
341
342    /* Iterator methods. */
343
344    /**
345     */
346    public function current()
347    {
348        $opts = current($this->_data);
349
350        return (!empty($opts) && ($this->key() == Horde_Imap_Client::FETCH_BODYPARTSIZE))
351            ? array_keys($opts)
352            : $opts;
353    }
354
355    /**
356     */
357    public function key()
358    {
359        return key($this->_data);
360    }
361
362    /**
363     */
364    public function next()
365    {
366        next($this->_data);
367    }
368
369    /**
370     */
371    public function rewind()
372    {
373        reset($this->_data);
374    }
375
376    /**
377     */
378    public function valid()
379    {
380        return !is_null($this->key());
381    }
382
383}
384