Passed
Push — master ( 181d77...7fe46a )
by Zaahid
06:13 queued 12s
created

PartHeaderContainer   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Test Coverage

Coverage 95%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 26
eloc 72
c 2
b 0
f 0
dl 0
loc 277
ccs 76
cts 80
cp 0.95
rs 10

13 Methods

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