Passed
Branch php8-testing (0e47ea)
by Zaahid
03:09
created

HeaderContainer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\Header;
8
9
use ArrayIterator;
10
use IteratorAggregate;
11
use ZBateson\MailMimeParser\Header\HeaderFactory;
12
13
/**
14
 * Maintains a collection of headers for a part.
15
 *
16
 * @author Zaahid Bateson
17
 */
18
class HeaderContainer 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\AbstractHeader[] Each element is an
33
     *      AbstractHeader representing the header at the same index in the
34
     *      $headers array.  If an AbstractHeader has not been constructed for
35
     *      the header at that index, 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
     * Constructor
56
     *
57
     * @param HeaderFactory $headerFactory
58
     */
59 109
    public function __construct(HeaderFactory $headerFactory)
60
    {
61 109
        $this->headerFactory = $headerFactory;
62 109
    }
63
64
    /**
65
     * Returns true if the passed header exists in this collection.
66
     *
67
     * @param string $name
68
     * @param int $offset
69
     * @return boolean
70
     */
71 109
    public function exists($name, $offset = 0)
72
    {
73 109
        $s = $this->headerFactory->getNormalizedHeaderName($name);
74 109
        return isset($this->headerMap[$s][$offset]);
75
    }
76
77
    /**
78
     * Returns an array of header indexes with names that more closely match
79
     * the passed $name if available: for instance if there are two headers in
80
     * an email, "Content-Type" and "ContentType", and the query is for a header
81
     * with the name "Content-Type", only headers that match exactly
82
     * "Content-Type" would be returned.
83
     *
84
     * @param string $name
85
     * @return int[]
86
     */
87 109
    private function getAllWithOriginalHeaderNameIfSet($name)
88
    {
89 109
        $s = $this->headerFactory->getNormalizedHeaderName($name);
90 109
        if (isset($this->headerMap[$s])) {
91 109
            $self = $this;
92
            $filtered = array_filter($this->headerMap[$s], function ($h) use ($name, $self) {
93 109
                return (strcasecmp($self->headers[$h][0], $name) === 0);
94 109
            });
95 109
            return (!empty($filtered)) ? $filtered : $this->headerMap[$s];
96
        }
97 103
        return null;
98
    }
99
100
    /**
101
     * Returns the AbstractHeader object for the header with the given $name and
102
     * at the optional offset (defaulting to the first header in the collection
103
     * where more than one header with the same name exists).
104
     *
105
     * Note that mime headers aren't case sensitive.
106
     *
107
     * @param string $name
108
     * @param int $offset
109
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader
110
     */
111 109
    public function get($name, $offset = 0)
112
    {
113 109
        $a = $this->getAllWithOriginalHeaderNameIfSet($name);
114 109
        if (!empty($a) && isset($a[$offset])) {
115 109
            return $this->getByIndex($a[$offset]);
116
        }
117 105
        return null;
118
    }
119
120
    /**
121
     * Returns all headers with the passed name.
122
     *
123
     * @param string $name
124
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader[]
125
     */
126 2
    public function getAll($name)
127
    {
128 2
        $a = $this->getAllWithOriginalHeaderNameIfSet($name);
129 2
        if (!empty($a)) {
130 2
            $self = $this;
131
            return array_map(function ($index) use ($self) {
132 2
                return $self->getByIndex($index);
133 2
            }, $a);
134
        }
135
        return [];
136
    }
137
138
    /**
139
     * Returns the header in the headers array at the passed 0-based integer
140
     * index.
141
     *
142
     * @param int $index
143
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader
144
     */
145 109
    private function getByIndex($index)
146
    {
147 109
        if (!isset($this->headers[$index])) {
148
            return null;
149
        }
150 109
        if ($this->headerObjects[$index] === null) {
151 109
            $this->headerObjects[$index] = $this->headerFactory->newInstance(
152 109
                $this->headers[$index][0],
153 109
                $this->headers[$index][1]
154
            );
155
        }
156 109
        return $this->headerObjects[$index];
157
    }
158
159
    /**
160
     * Removes the header from the collection with the passed name.  Defaults to
161
     * removing the first instance of the header for a collection that contains
162
     * more than one with the same passed name.
163
     *
164
     * @param string $name
165
     * @param int $offset
166
     * @return boolean
167
     */
168 2
    public function remove($name, $offset = 0)
169
    {
170 2
        $s = $this->headerFactory->getNormalizedHeaderName($name);
171 2
        if (isset($this->headerMap[$s][$offset])) {
172 2
            $index = $this->headerMap[$s][$offset];
173 2
            array_splice($this->headerMap[$s], $offset, 1);
174 2
            unset($this->headers[$index]);
175 2
            unset($this->headerObjects[$index]);
176 2
            return true;
177
        }
178
        return false;
179
    }
180
181
    /**
182
     * Removes all headers that match the passed name.
183
     *
184
     * @param string $name
185
     * @return boolean
186
     */
187 19
    public function removeAll($name)
188
    {
189 19
        $s = $this->headerFactory->getNormalizedHeaderName($name);
190 19
        if (!empty($this->headerMap[$s])) {
191 18
            foreach ($this->headerMap[$s] as $i) {
192 18
                unset($this->headers[$i]);
193 18
                unset($this->headerObjects[$i]);
194
            }
195 18
            $this->headerMap[$s] = [];
196 18
            return true;
197
        }
198 18
        return false;
199
    }
200
201
    /**
202
     * Adds the header to the collection.
203
     *
204
     * @param string $name
205
     * @param string $value
206
     */
207 109
    public function add($name, $value)
208
    {
209 109
        $s = $this->headerFactory->getNormalizedHeaderName($name);
210 109
        $this->headers[$this->nextIndex] = [ $name, $value ];
211 109
        $this->headerObjects[$this->nextIndex] = null;
212 109
        if (!isset($this->headerMap[$s])) {
213 109
            $this->headerMap[$s] = [];
214
        }
215 109
        array_push($this->headerMap[$s], $this->nextIndex);
216 109
        $this->nextIndex++;
217 109
    }
218
219
    /**
220
     * If a header exists with the passed name, and at the passed offset if more
221
     * than one exists, its value is updated.
222
     *
223
     * If a header with the passed name doesn't exist at the passed offset, it
224
     * is created at the next available offset (offset is ignored when adding).
225
     *
226
     * @param string $name
227
     * @param string $value
228
     * @param int $offset
229
     */
230 24
    public function set($name, $value, $offset = 0)
231
    {
232 24
        $s = $this->headerFactory->getNormalizedHeaderName($name);
233 24
        if (!isset($this->headerMap[$s][$offset])) {
234 22
            $this->add($name, $value);
235 22
            return;
236
        }
237 14
        $i = $this->headerMap[$s][$offset];
238 14
        $this->headers[$i] = [ $name, $value ];
239 14
        $this->headerObjects[$i] = null;
240 14
    }
241
242
    /**
243
     * Returns an array of AbstractHeader objects representing all headers in
244
     * this collection.
245
     *
246
     * @return AbstractHeader
247
     */
248
    public function getHeaderObjects()
249
    {
250
        return array_filter(array_map([ $this, 'getByIndex' ], array_keys($this->headers)));
251
    }
252
253
    /**
254
     * Returns an array of headers in this collection.  Each returned element in
255
     * the array is an array with the first element set to the name, and the
256
     * second its value:
257
     *
258
     * [
259
     *     [ 'Header-Name', 'Header Value' ],
260
     *     [ 'Second-Header-Name', 'Second-Header-Value' ],
261
     *     // etc...
262
     * ]
263
     *
264
     * @return string[][]
265
     */
266 97
    public function getHeaders()
267
    {
268 97
        return array_values(array_filter($this->headers));
269
    }
270
271
    /**
272
     * Returns an iterator to the headers in this collection.  Each returned
273
     * element is an array with its first element set to the header's name, and
274
     * the second to its value:
275
     *
276
     * [ 'Header-Name', 'Header Value' ]
277
     *
278
     * @return ArrayIterator
279
     */
280 92
    public function getIterator()
281
    {
282 92
        return new ArrayIterator($this->getHeaders());
283
    }
284
}
285