1<?php
2/**
3 * Copyright 2012-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 2012-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package   Mime
12 */
13
14/**
15 * This class parses a multipart/related MIME part (RFC 2387) to provide
16 * information on the part contents.
17 *
18 * @author    Michael Slusarz <slusarz@horde.org>
19 * @category  Horde
20 * @copyright 2012-2017 Horde LLC
21 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
22 * @package   Mime
23 */
24class Horde_Mime_Related implements IteratorAggregate
25{
26    /**
27     * Content IDs.
28     *
29     * @var array
30     */
31    protected $_cids = array();
32
33    /**
34     * Start ID.
35     *
36     * @var string
37     */
38    protected $_start;
39
40    /**
41     * Constructor.
42     *
43     * @param Horde_Mime_Part $mime_part  A MIME part object. Must be of
44     *                                    type multipart/related.
45     */
46    public function __construct(Horde_Mime_Part $mime_part)
47    {
48        if ($mime_part->getType() != 'multipart/related') {
49            throw new InvalidArgumentException('MIME part must be of type multipart/related');
50        }
51
52        $id = null;
53        $ids = array();
54        $related_id = $mime_part->getMimeId();
55
56        /* Build a list of parts -> CIDs. */
57        foreach ($mime_part->partIterator() as $val) {
58            $part_id = $val->getMimeId();
59            $ids[] = $part_id;
60
61            if ((strcmp($related_id, $part_id) !== 0) &&
62                ($cid = $val->getContentId())) {
63                $this->_cids[$part_id] = $cid;
64            }
65        }
66
67        /* Look at the 'start' parameter to determine which part to start
68         * with. If no 'start' parameter, use the first part (RFC 2387
69         * [3.1]). */
70        if ($start = $mime_part->getContentTypeParameter('start')) {
71            $id = $this->cidSearch(trim($start, '<> '));
72        }
73
74        if (empty($id)) {
75            reset($ids);
76            $id = next($ids);
77        }
78
79        $this->_start = $id;
80    }
81
82    /**
83     * Return the start ID.
84     *
85     * @return string  The start ID.
86     */
87    public function startId()
88    {
89        return $this->_start;
90    }
91
92    /**
93     * Search for a CID in the related part.
94     *
95     * @param string $cid  The CID to search for.
96     *
97     * @return string  The MIME ID or false if not found.
98     */
99    public function cidSearch($cid)
100    {
101        return array_search($cid, $this->_cids);
102    }
103
104    /**
105     * Scan for CID strings in HTML data and replace with data returned from
106     * a callback method.
107     *
108     * @param mixed $text         The HTML text (can be Horde_Domhtml object).
109     * @param callback $callback  Callback method. Receives three arguments:
110     *                            MIME ID, the attribute name containing the
111     *                            content ID, and the node object. Expects
112     *                            return value of URL to display the data.
113     * @param string $charset     HTML data charset.
114     *
115     * @return Horde_Domhtml  A Horde_Domhtml object.
116     */
117    public function cidReplace($text, $callback, $charset = 'UTF-8')
118    {
119        $dom = ($text instanceof Horde_Domhtml)
120            ? $text
121            : new Horde_Domhtml($text, $charset);
122
123        foreach ($dom as $node) {
124            if ($node instanceof DOMElement) {
125                switch (Horde_String::lower($node->tagName)) {
126                case 'body':
127                case 'td':
128                    $this->_cidReplace($node, 'background', $callback);
129                    break;
130
131                case 'img':
132                    $this->_cidReplace($node, 'src', $callback);
133                    break;
134                }
135            }
136        }
137
138        return $dom;
139    }
140
141    /**
142     */
143    protected function _cidReplace($node, $attribute, $callback)
144    {
145        if ($node->hasAttribute($attribute)) {
146            $val = $node->getAttribute($attribute);
147            if ((strpos($val, 'cid:') === 0) &&
148                ($id = $this->cidSearch(substr($val, 4)))) {
149                $node->setAttribute($attribute, call_user_func($callback, $id, $attribute, $node));
150            }
151        }
152    }
153
154    /* IteratorAggregate method. */
155
156    public function getIterator()
157    {
158        return new ArrayIterator($this->_cids);
159    }
160
161}
162