Passed
Push — master ( 295c7f...61bbf4 )
by Zaahid
09:01
created

PartHeaderContainer::getAll()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2.0078

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 10
ccs 7
cts 8
cp 0.875
rs 10
cc 2
nc 2
nop 1
crap 2.0078
1
<?php
2
/**
3
 * This file is part of the ZBateson\MailMimeParser project.
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
namespace ZBateson\MailMimeParser\Message;
8
9
use ZBateson\MailMimeParser\Header\HeaderFactory;
10
use ArrayIterator;
11
use IteratorAggregate;
12
13
/**
14
 * Maintains a collection of headers for a part.
15
 *
16
 * @author Zaahid Bateson
17
 */
18
class PartHeaderContainer implements IteratorAggregate
19
{
20
    /**
21
     * @var HeaderFactory the HeaderFactory object used for created headers
22
     */
23
    protected $headerFactory;
24
25
    /**
26
     * @var string[][] Each element in the array is an array with its first
27
     * element set to the header's name, and the second its value.
28
     */
29
    private $headers = [];
30
31
    /**
32
     * @var \ZBateson\MailMimeParser\Header\IHeader[] Each element is an IHeader
33
     *      representing the header at the same index in the $headers array.  If
34
     *      an IHeader has not been constructed for the header at that index,
35
     *      the element would be set to null.
36
     */
37
    private $headerObjects = [];
38
39
    /**
40
     * @var array Maps header names by their "normalized" (lower-cased,
41
     *      non-alphanumeric characters stripped) name to an array of indexes in
42
     *      the $headers array.  For example:
43
     *      $headerMap['contenttype'] = [ 1, 4 ]
44
     *      would indicate that the headers in $headers[1] and $headers[4] are
45
     *      both headers with the name 'Content-Type' or 'contENTtype'.
46
     */
47
    private $headerMap = [];
48
49
    /**
50
     * @var int the next index to use for $headers and $headerObjects.
51
     */
52
    private $nextIndex = 0;
53
54
    /**
55
     * Pass a PartHeaderContainer as the second parameter.  This is useful when
56
     * creating a new MimePart with this PartHeaderContainer and the original
57
     * container is needed for parsing and changes to the header in the part
58
     * should not affect parsing.
59
     *
60
     * @param HeaderFactory $headerFactory
61
     * @param PartHeaderContainer $cloneSource the original container to clone
62
     *        from
63
     */
64 109
    public function __construct(HeaderFactory $headerFactory, PartHeaderContainer $cloneSource = null)
65
    {
66 109
        $this->headerFactory = $headerFactory;
67 109
        if ($cloneSource !== null) {
68 104
            $this->headers = $cloneSource->headers;
69 104
            $this->headerObjects = $cloneSource->headerObjects;
70 104
            $this->headerMap = $cloneSource->headerMap;
71 104
            $this->nextIndex = $cloneSource->nextIndex;
72
        }
73
    }
74
75
    /**
76
     * Returns true if the passed header exists in this collection.
77
     *
78
     * @param string $name
79
     * @param int $offset
80
     * @return boolean
81
     */
82 109
    public function exists($name, $offset = 0)
83
    {
84 109
        $s = $this->headerFactory->getNormalizedHeaderName($name);
85 109
        return isset($this->headerMap[$s][$offset]);
86
    }
87
88
    /**
89
     * Returns an array of header indexes with names that more closely match
90
     * the passed $name if available: for instance if there are two headers in
91
     * an email, "Content-Type" and "ContentType", and the query is for a header
92
     * with the name "Content-Type", only headers that match exactly
93
     * "Content-Type" would be returned.
94
     *
95
     * @param string $name
96
     * @return int[]|null
97
     */
98 109
    private function getAllWithOriginalHeaderNameIfSet($name)
99
    {
100 109
        $s = $this->headerFactory->getNormalizedHeaderName($name);
101 109
        if (isset($this->headerMap[$s])) {
102 109
            $self = $this;
103 109
            $filtered = array_filter($this->headerMap[$s], function ($h) use ($name, $self) {
104 109
                return (strcasecmp($self->headers[$h][0], $name) === 0);
105 109
            });
106 109
            return (!empty($filtered)) ? $filtered : $this->headerMap[$s];
107
        }
108 103
        return null;
109
    }
110
111
    /**
112
     * Returns the IHeader object for the header with the given $name, or null
113
     * if none exist.
114
     *
115
     * An optional offset can be provided, which defaults to the first header in
116
     * the collection when more than one header with the same name exists.
117
     *
118
     * Note that mime headers aren't case sensitive.
119
     *
120
     * @param string $name
121
     * @param int $offset
122
     * @return \ZBateson\MailMimeParser\Header\IHeader|null
123
     */
124 109
    public function get($name, $offset = 0)
125
    {
126 109
        $a = $this->getAllWithOriginalHeaderNameIfSet($name);
127 109
        if (!empty($a) && isset($a[$offset])) {
128 109
            return $this->getByIndex($a[$offset]);
129
        }
130 105
        return null;
131
    }
132
133
    /**
134
     * Returns all headers with the passed name.
135
     *
136
     * @param string $name
137
     * @return \ZBateson\MailMimeParser\Header\IHeader[]
138
     */
139 2
    public function getAll($name)
140
    {
141 2
        $a = $this->getAllWithOriginalHeaderNameIfSet($name);
142 2
        if (!empty($a)) {
143 2
            $self = $this;
144 2
            return array_map(function ($index) use ($self) {
145 2
                return $self->getByIndex($index);
146 2
            }, $a);
147
        }
148
        return [];
149
    }
150
151
    /**
152
     * Returns the header in the headers array at the passed 0-based integer
153
     * index or null if one doesn't exist.
154
     *
155
     * @param int $index
156
     * @return \ZBateson\MailMimeParser\Header\IHeader|null
157
     */
158 109
    private function getByIndex($index)
159
    {
160 109
        if (!isset($this->headers[$index])) {
161
            return null;
162
        }
163 109
        if ($this->headerObjects[$index] === null) {
164 109
            $this->headerObjects[$index] = $this->headerFactory->newInstance(
165 109
                $this->headers[$index][0],
166 109
                $this->headers[$index][1]
167 109
            );
168
        }
169 109
        return $this->headerObjects[$index];
170
    }
171
172
    /**
173
     * Removes the header from the collection with the passed name.  Defaults to
174
     * removing the first instance of the header for a collection that contains
175
     * more than one with the same passed name.
176
     *
177
     * @param string $name
178
     * @param int $offset
179
     * @return boolean if a header was removed.
180
     */
181 2
    public function remove($name, $offset = 0)
182
    {
183 2
        $s = $this->headerFactory->getNormalizedHeaderName($name);
184 2
        if (isset($this->headerMap[$s][$offset])) {
185 2
            $index = $this->headerMap[$s][$offset];
186 2
            array_splice($this->headerMap[$s], $offset, 1);
187 2
            unset($this->headers[$index]);
188 2
            unset($this->headerObjects[$index]);
189 2
            return true;
190
        }
191
        return false;
192
    }
193
194
    /**
195
     * Removes all headers that match the passed name.
196
     *
197
     * @param string $name
198
     * @return boolean true if one or more headers were removed.
199
     */
200 18
    public function removeAll($name)
201
    {
202 18
        $s = $this->headerFactory->getNormalizedHeaderName($name);
203 18
        if (!empty($this->headerMap[$s])) {
204 18
            foreach ($this->headerMap[$s] as $i) {
205 18
                unset($this->headers[$i]);
206 18
                unset($this->headerObjects[$i]);
207
            }
208 18
            $this->headerMap[$s] = [];
209 18
            return true;
210
        }
211
        return false;
212
    }
213
214
    /**
215
     * Adds the header to the collection.
216
     *
217
     * @param string $name
218
     * @param string $value
219
     */
220 109
    public function add($name, $value)
221
    {
222 109
        $s = $this->headerFactory->getNormalizedHeaderName($name);
223 109
        $this->headers[$this->nextIndex] = [ $name, $value ];
224 109
        $this->headerObjects[$this->nextIndex] = null;
225 109
        if (!isset($this->headerMap[$s])) {
226 109
            $this->headerMap[$s] = [];
227
        }
228 109
        array_push($this->headerMap[$s], $this->nextIndex);
229 109
        $this->nextIndex++;
230
    }
231
232
    /**
233
     * If a header exists with the passed name, and at the passed offset if more
234
     * than one exists, its value is updated.
235
     *
236
     * If a header with the passed name doesn't exist at the passed offset, it
237
     * is created at the next available offset (offset is ignored when adding).
238
     *
239
     * @param string $name
240
     * @param string $value
241
     * @param int $offset
242
     */
243 24
    public function set($name, $value, $offset = 0)
244
    {
245 24
        $s = $this->headerFactory->getNormalizedHeaderName($name);
246 24
        if (!isset($this->headerMap[$s][$offset])) {
247 21
            $this->add($name, $value);
248 21
            return;
249
        }
250 14
        $i = $this->headerMap[$s][$offset];
251 14
        $this->headers[$i] = [ $name, $value ];
252 14
        $this->headerObjects[$i] = null;
253
    }
254
255
    /**
256
     * Returns an array of IHeader objects representing all headers in this
257
     * collection.
258
     *
259
     * @return IHeader[]
260
     */
261 18
    public function getHeaderObjects()
262
    {
263 18
        return array_filter(array_map([ $this, 'getByIndex' ], array_keys($this->headers)));
264
    }
265
266
    /**
267
     * Returns an array of headers in this collection.  Each returned element in
268
     * the array is an array with the first element set to the name, and the
269
     * second its value:
270
     *
271
     * [
272
     *     [ 'Header-Name', 'Header Value' ],
273
     *     [ 'Second-Header-Name', 'Second-Header-Value' ],
274
     *     // etc...
275
     * ]
276
     *
277
     * @return string[][]
278
     */
279 97
    public function getHeaders()
280
    {
281 97
        return array_values(array_filter($this->headers));
282
    }
283
284
    /**
285
     * Returns an iterator to the headers in this collection.  Each returned
286
     * element is an array with its first element set to the header's name, and
287
     * the second to its value:
288
     *
289
     * [ 'Header-Name', 'Header Value' ]
290
     *
291
     * @return ArrayIterator
292
     */
293
    #[\ReturnTypeWillChange]
294 92
    public function getIterator()
295
    {
296 92
        return new ArrayIterator($this->getHeaders());
297
    }
298
}
299