1<?php
2/**
3 * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file LICENSE 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 * Object containing data returned by the Horde_Imap_Client_Base#fetch()
16 * command.
17 *
18 * @author    Michael Slusarz <slusarz@horde.org>
19 * @category  Horde
20 * @copyright 2011-2017 Horde LLC
21 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
22 * @package   Imap_Client
23 */
24class Horde_Imap_Client_Data_Fetch
25{
26    /** Header formatting constants. */
27    const HEADER_PARSE = 1;
28    const HEADER_STREAM = 2;
29
30    /**
31     * Internal data array.
32     *
33     * @var array
34     */
35    protected $_data = array();
36
37    /**
38     */
39    public function __clone()
40    {
41        $this->_data = unserialize(serialize($this->_data));
42    }
43
44    /**
45     * Set the full message property.
46     *
47     * @param mixed $msg  The full message text, as either a string or stream
48     *                    resource.
49     */
50    public function setFullMsg($msg)
51    {
52        $this->_data[Horde_Imap_Client::FETCH_FULLMSG] = $this->_setMixed($msg);
53    }
54
55    /**
56     * Returns the full message.
57     *
58     * @param boolean $stream  Return as a stream?
59     *
60     * @return mixed  The full text of the entire message.
61     */
62    public function getFullMsg($stream = false)
63    {
64        return $this->_msgText(
65            $stream,
66            isset($this->_data[Horde_Imap_Client::FETCH_FULLMSG])
67                ? $this->_data[Horde_Imap_Client::FETCH_FULLMSG]
68                : null
69        );
70    }
71
72    /**
73     * Set the message structure.
74     *
75     * @param Horde_Mime_Part $structure  The base MIME part of the message.
76     */
77    public function setStructure(Horde_Mime_Part $structure)
78    {
79        $this->_data[Horde_Imap_Client::FETCH_STRUCTURE] = $structure;
80    }
81
82    /**
83     * Get the message structure.
84     *
85     * @return Horde_Mime_Part $structure  The base MIME part of the message.
86     */
87    public function getStructure()
88    {
89        return isset($this->_data[Horde_Imap_Client::FETCH_STRUCTURE])
90            ? clone $this->_data[Horde_Imap_Client::FETCH_STRUCTURE]
91            : new Horde_Mime_Part();
92    }
93
94    /**
95     * Set a header entry.
96     *
97     * @param string $label  The search label.
98     * @param mixed $data    Either a Horde_Mime_Headers object or the raw
99     *                       header text.
100     */
101    public function setHeaders($label, $data)
102    {
103        if ($data instanceof Horde_Stream) {
104            $data = $data->stream;
105        }
106        $this->_data[Horde_Imap_Client::FETCH_HEADERS][$label] = $data;
107    }
108
109    /**
110     * Get a header entry.
111     *
112     * @param string $label    The search label.
113     * @param integer $format  The return format. If self::HEADER_PARSE,
114     *                         returns a Horde_Mime_Headers object. If
115     *                         self::HEADER_STREAM, returns a stream.
116     *                         Otherwise, returns header text.
117     *
118     * @return mixed  See $format.
119     */
120    public function getHeaders($label, $format = 0)
121    {
122        return $this->_getHeaders(
123            $label,
124            $format,
125            Horde_Imap_Client::FETCH_HEADERS
126        );
127    }
128
129    /**
130     * Set a header text entry.
131     *
132     * @param string $id   The MIME ID.
133     * @param mixed $text  The header text, as either a string or stream
134     *                     resource.
135     */
136    public function setHeaderText($id, $text)
137    {
138        $this->_data[Horde_Imap_Client::FETCH_HEADERTEXT][$id] = $this->_setMixed($text);
139    }
140
141    /**
142     * Get a header text entry.
143     *
144     * @param string $id       The MIME ID.
145     * @param integer $format  The return format. If self::HEADER_PARSE,
146     *                         returns a Horde_Mime_Headers object. If
147     *                         self::HEADER_STREAM, returns a stream.
148     *                         Otherwise, returns header text.
149     *
150     * @return mixed  See $format.
151     */
152    public function getHeaderText($id = 0, $format = 0)
153    {
154        return $this->_getHeaders(
155            $id,
156            $format,
157            Horde_Imap_Client::FETCH_HEADERTEXT
158        );
159    }
160
161    /**
162     * Set a MIME header entry.
163     *
164     * @param string $id   The MIME ID.
165     * @param mixed $text  The header text, as either a string or stream
166     *                     resource.
167     */
168    public function setMimeHeader($id, $text)
169    {
170        $this->_data[Horde_Imap_Client::FETCH_MIMEHEADER][$id] = $this->_setMixed($text);
171    }
172
173    /**
174     * Get a MIME header entry.
175     *
176     * @param string $id       The MIME ID.
177     * @param integer $format  The return format. If self::HEADER_PARSE,
178     *                         returns a Horde_Mime_Headers object. If
179     *                         self::HEADER_STREAM, returns a stream.
180     *                         Otherwise, returns header text.
181     *
182     * @return mixed  See $format.
183     */
184    public function getMimeHeader($id, $format = 0)
185    {
186        return $this->_getHeaders(
187            $id,
188            $format,
189            Horde_Imap_Client::FETCH_MIMEHEADER
190        );
191    }
192
193    /**
194     * Set a body part entry.
195     *
196     * @param string $id      The MIME ID.
197     * @param mixed $text     The body part text, as either a string or stream
198     *                        resource.
199     * @param string $decode  Either '8bit', 'binary', or null.
200     */
201    public function setBodyPart($id, $text, $decode = null)
202    {
203        $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id] = array(
204            'd' => $decode,
205            't' => $this->_setMixed($text)
206        );
207    }
208
209    /**
210     * Get a body part entry.
211     *
212     * @param string $id       The MIME ID.
213     * @param boolean $stream  Return as a stream?
214     *
215     * @return mixed  The full text of the body part.
216     */
217    public function getBodyPart($id, $stream = false)
218    {
219        return $this->_msgText(
220            $stream,
221            isset($this->_data[Horde_Imap_Client::FETCH_BODYPART][$id])
222                ? $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id]['t']
223                : null
224        );
225    }
226
227    /**
228     * Determines if/how a body part was MIME decoded on the server.
229     *
230     * @param string $id  The MIME ID.
231     *
232     * @return string  Either '8bit', 'binary', or null.
233     */
234    public function getBodyPartDecode($id)
235    {
236        return isset($this->_data[Horde_Imap_Client::FETCH_BODYPART][$id])
237            ? $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id]['d']
238            : null;
239    }
240
241    /**
242     * Set the body part size for a body part.
243     *
244     * @param string $id     The MIME ID.
245     * @param integer $size  The size (in bytes).
246     */
247    public function setBodyPartSize($id, $size)
248    {
249        $this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id] = intval($size);
250    }
251
252    /**
253     * Returns the body part size, if returned by the server.
254     *
255     * @param string $id  The MIME ID.
256     *
257     * @return integer  The body part size, in bytes.
258     */
259    public function getBodyPartSize($id)
260    {
261        return isset($this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id])
262            ? $this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id]
263            : null;
264    }
265
266    /**
267     * Set a body text entry.
268     *
269     * @param string $id   The MIME ID.
270     * @param mixed $text  The body part text, as either a string or stream
271     *                     resource.
272     */
273    public function setBodyText($id, $text)
274    {
275        $this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id] = $this->_setMixed($text);
276    }
277
278    /**
279     * Get a body text entry.
280     *
281     * @param string $id       The MIME ID.
282     * @param boolean $stream  Return as a stream?
283     *
284     * @return mixed  The full text of the body text.
285     */
286    public function getBodyText($id = 0, $stream = false)
287    {
288        return $this->_msgText(
289            $stream,
290            isset($this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id])
291                ? $this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id]
292                : null
293        );
294    }
295
296    /**
297     * Set envelope data.
298     *
299     * @param array $data  The envelope data to pass to the Envelope object
300     *                     constructor, or an Envelope object.
301     */
302    public function setEnvelope($data)
303    {
304        $this->_data[Horde_Imap_Client::FETCH_ENVELOPE] = is_array($data)
305            ? new Horde_Imap_Client_Data_Envelope($data)
306            : $data;
307    }
308
309    /**
310     * Get envelope data.
311     *
312     * @return Horde_Imap_Client_Data_Envelope  An envelope object.
313     */
314    public function getEnvelope()
315    {
316        return isset($this->_data[Horde_Imap_Client::FETCH_ENVELOPE])
317            ? clone $this->_data[Horde_Imap_Client::FETCH_ENVELOPE]
318            : new Horde_Imap_Client_Data_Envelope();
319    }
320
321    /**
322     * Set IMAP flags.
323     *
324     * @param array $flags  An array of IMAP flags.
325     */
326    public function setFlags(array $flags)
327    {
328        $this->_data[Horde_Imap_Client::FETCH_FLAGS] = array_map(
329            'Horde_String::lower',
330            array_map('trim', $flags)
331        );
332    }
333
334    /**
335     * Get IMAP flags.
336     *
337     * @return array  An array of IMAP flags (all flags in lowercase).
338     */
339    public function getFlags()
340    {
341        return isset($this->_data[Horde_Imap_Client::FETCH_FLAGS])
342            ? $this->_data[Horde_Imap_Client::FETCH_FLAGS]
343            : array();
344    }
345
346    /**
347     * Set IMAP internal date.
348     *
349     * @param mixed $date  Either a Horde_Imap_Client_DateTime object or a
350     *                     date string.
351     */
352    public function setImapDate($date)
353    {
354        $this->_data[Horde_Imap_Client::FETCH_IMAPDATE] = is_object($date)
355            ? $date
356            : new Horde_Imap_Client_DateTime($date);
357    }
358
359    /**
360     * Get internal IMAP date.
361     *
362     * @return Horde_Imap_Client_DateTime  A date object.
363     */
364    public function getImapDate()
365    {
366        return isset($this->_data[Horde_Imap_Client::FETCH_IMAPDATE])
367            ? clone $this->_data[Horde_Imap_Client::FETCH_IMAPDATE]
368            : new Horde_Imap_Client_DateTime();
369    }
370
371    /**
372     * Set message size.
373     *
374     * @param integer $size  The size of the message, in bytes.
375     */
376    public function setSize($size)
377    {
378        $this->_data[Horde_Imap_Client::FETCH_SIZE] = intval($size);
379    }
380
381    /**
382     * Get message size.
383     *
384     * @return integer  The size of the message, in bytes.
385     */
386    public function getSize()
387    {
388        return isset($this->_data[Horde_Imap_Client::FETCH_SIZE])
389            ? $this->_data[Horde_Imap_Client::FETCH_SIZE]
390            : 0;
391    }
392
393    /**
394     * Set UID.
395     *
396     * @param integer $uid  The message UID.
397     */
398    public function setUid($uid)
399    {
400        $this->_data[Horde_Imap_Client::FETCH_UID] = intval($uid);
401    }
402
403    /**
404     * Get UID.
405     *
406     * @return integer  The message UID.
407     */
408    public function getUid()
409    {
410        return isset($this->_data[Horde_Imap_Client::FETCH_UID])
411            ? $this->_data[Horde_Imap_Client::FETCH_UID]
412            : null;
413    }
414
415    /**
416     * Set message sequence number.
417     *
418     * @param integer $seq  The message sequence number.
419     */
420    public function setSeq($seq)
421    {
422        $this->_data[Horde_Imap_Client::FETCH_SEQ] = intval($seq);
423    }
424
425    /**
426     * Get message sequence number.
427     *
428     * @return integer  The message sequence number.
429     */
430    public function getSeq()
431    {
432        return isset($this->_data[Horde_Imap_Client::FETCH_SEQ])
433            ? $this->_data[Horde_Imap_Client::FETCH_SEQ]
434            : null;
435    }
436
437    /**
438     * Set the modified sequence value for the message.
439     *
440     * @param integer $modseq  The modseq value.
441     */
442    public function setModSeq($modseq)
443    {
444        $this->_data[Horde_Imap_Client::FETCH_MODSEQ] = intval($modseq);
445    }
446
447    /**
448     * Get the modified sequence value for the message.
449     *
450     * @return integer  The modseq value.
451     */
452    public function getModSeq()
453    {
454        return isset($this->_data[Horde_Imap_Client::FETCH_MODSEQ])
455            ? $this->_data[Horde_Imap_Client::FETCH_MODSEQ]
456            : null;
457    }
458
459    /**
460     * Set the internationalized downgraded status for the message.
461     *
462     * @since 2.11.0
463     *
464     * @param boolean $downgraded  True if at least one message component has
465     *                             been downgraded.
466     */
467    public function setDowngraded($downgraded)
468    {
469        if ($downgraded) {
470            $this->_data[Horde_Imap_Client::FETCH_DOWNGRADED] = true;
471        } else {
472            unset($this->_data[Horde_Imap_Client::FETCH_DOWNGRADED]);
473        }
474    }
475
476    /**
477     * Does the message contain internationalized downgraded data (i.e. it
478     * is a "surrogate" message)?
479     *
480     * @since 2.11.0
481     *
482     * @return boolean  True if at least one message components has been
483     *                  downgraded.
484     */
485    public function isDowngraded()
486    {
487        return !empty($this->_data[Horde_Imap_Client::FETCH_DOWNGRADED]);
488    }
489
490    /**
491     * Return the internal representation of the data.
492     *
493     * @return array  The data array.
494     */
495    public function getRawData()
496    {
497        return $this->_data;
498    }
499
500    /**
501     * Merge a fetch object into this one.
502     *
503     * @param Horde_Imap_Client_Data_Fetch $data  A fetch object.
504     */
505    public function merge(Horde_Imap_Client_Data_Fetch $data)
506    {
507        $this->_data = array_replace_recursive(
508            $this->_data,
509            $data->getRawData()
510        );
511    }
512
513    /**
514     * Does this object containing cacheable data of the given type?
515     *
516     * @param integer $type  The type to query.
517     *
518     * @return boolean  True if the type is cacheable.
519     */
520    public function exists($type)
521    {
522        return isset($this->_data[$type]);
523    }
524
525    /**
526     * Does this object contain only default values for all fields?
527     *
528     * @return boolean  True if object contains default data.
529     */
530    public function isDefault()
531    {
532        return empty($this->_data);
533    }
534
535    /**
536     * Return text representation of a field.
537     *
538     * @param boolean $stream  Return as a stream?
539     * @param mixed $data      The field data (string or resource) or null if
540     *                         field does not exist.
541     *
542     * @return mixed  Requested text representation.
543     */
544    protected function _msgText($stream, $data)
545    {
546        if ($data instanceof Horde_Stream) {
547            if ($stream) {
548                $data->rewind();
549                return $data->stream;
550            }
551            return strval($data);
552        }
553
554        if (is_resource($data)) {
555            rewind($data);
556            return $stream
557                ? $data
558                : stream_get_contents($data);
559        }
560
561        if (!$stream) {
562            return strval($data);
563        }
564
565        $tmp = fopen('php://temp', 'w+');
566
567        if (!is_null($data)) {
568            fwrite($tmp, $data);
569            rewind($tmp);
570        }
571
572        return $tmp;
573    }
574
575    /**
576     * Return representation of a header field.
577     *
578     * @param string $id       The header id.
579     * @param integer $format  The return format. If self::HEADER_PARSE,
580     *                         returns a Horde_Mime_Headers object. If
581     *                         self::HEADER_STREAM, returns a stream.
582     *                         Otherwise, returns header text.
583     * @param integer $key     The array key where the data is stored in the
584     *                         internal array.
585     *
586     * @return mixed  The data in the format specified by $format.
587     */
588    protected function _getHeaders($id, $format, $key)
589    {
590        switch ($format) {
591        case self::HEADER_STREAM:
592            if (!isset($this->_data[$key][$id])) {
593                $data = null;
594            } elseif (is_object($this->_data[$key][$id])) {
595                switch ($key) {
596                case Horde_Imap_Client::FETCH_HEADERS:
597                    $data = $this->_getHeaders($id, 0, $key);
598                    break;
599
600                case Horde_Imap_Client::FETCH_HEADERTEXT:
601                case Horde_Imap_Client::FETCH_MIMEHEADER:
602                    $data = $this->_data[$key][$id];
603                    break;
604                }
605            } else {
606                $data = $this->_data[$key][$id];
607            }
608
609            return $this->_msgText(true, $data);
610
611        case self::HEADER_PARSE:
612            if (!isset($this->_data[$key][$id])) {
613                return new Horde_Mime_Headers();
614            } elseif (is_object($this->_data[$key][$id])) {
615                switch ($key) {
616                case Horde_Imap_Client::FETCH_HEADERS:
617                    return clone $this->_data[$key][$id];
618
619                case Horde_Imap_Client::FETCH_HEADERTEXT:
620                case Horde_Imap_Client::FETCH_MIMEHEADER:
621                    $hdrs = $this->_data[$key][$id];
622                    break;
623                }
624            } else {
625                $hdrs = $this->_getHeaders($id, self::HEADER_STREAM, $key);
626            }
627
628            return Horde_Mime_Headers::parseHeaders($hdrs);
629        }
630
631        if (!isset($this->_data[$key][$id])) {
632            return '';
633        }
634
635        if (is_object($this->_data[$key][$id])) {
636            switch ($key) {
637            case Horde_Imap_Client::FETCH_HEADERS:
638                return $this->_data[$key][$id]->toString(
639                    array('nowrap' => true)
640                );
641
642            case Horde_Imap_Client::FETCH_HEADERTEXT:
643            case Horde_Imap_Client::FETCH_MIMEHEADER:
644                return strval($this->_data[$key][$id]);
645            }
646        }
647
648        return $this->_msgText(false, $this->_data[$key][$id]);
649    }
650
651    /**
652     * Converts mixed input (string or resource) to the correct internal
653     * representation.
654     *
655     * @param mixed $data  Mixed data (string, resource, Horde_Stream object).
656     *
657     * @return mixed  The internal representation of that data.
658     */
659    protected function _setMixed($data)
660    {
661        return is_resource($data)
662            ? new Horde_Stream_Existing(array('stream' => $data))
663            : $data;
664    }
665
666}
667