Passed
Push — 1.0.0 ( 4505d9...06b3ad )
by Zaahid
04:10
created

MimePart   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 408
Duplicated Lines 0 %

Test Coverage

Coverage 90.83%

Importance

Changes 0
Metric Value
dl 0
loc 408
ccs 99
cts 109
cp 0.9083
rs 8.3999
c 0
b 0
f 0
wmc 38

22 Methods

Rating   Name   Duplication   Size   Complexity  
A getFilename() 0 8 1
A getContentType() 0 3 1
A isMultiPart() 0 6 1
A getAllNonFilteredParts() 0 14 3
A getAllParts() 0 10 2
A getCharset() 0 11 4
A getContentDisposition() 0 3 1
A getHeaderParameter() 0 7 3
A getAllPartsByMimeType() 0 4 1
A getHeaderValue() 0 7 2
A getHeader() 0 13 3
A getPartCount() 0 3 1
A isTextPart() 0 3 1
A getChildCount() 0 3 1
A getChild() 0 7 2
A getContentTransferEncoding() 0 12 2
A getChildParts() 0 6 2
A getPartByMimeType() 0 4 1
A getCountOfPartsByMimeType() 0 4 1
A isMime() 0 3 1
A getPart() 0 7 2
A __construct() 0 23 2
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\Part;
8
9
use Psr\Http\Message\StreamInterface;
10
use ZBateson\MailMimeParser\Header\HeaderFactory;
11
use ZBateson\MailMimeParser\Header\ParameterHeader;
12
use ZBateson\MailMimeParser\Message\PartFilterFactory;
13
use ZBateson\MailMimeParser\Message\PartFilter;
14
15
/**
16
 * Represents a single part of a multi-part mime message.
17
 *
18
 * A MimePart object may have any number of child parts, or may be a child
19
 * itself with its own parent or parents.
20
 *
21
 * The content of the part can be read from its PartStream resource handle,
22
 * accessible via MessagePart::getContentResourceHandle.
23
 *
24
 * @author Zaahid Bateson
25
 */
26
class MimePart extends MessagePart
27
{
28
    /**
29
     * @var \ZBateson\MailMimeParser\Header\HeaderFactory the HeaderFactory
30
     *      object used for created headers
31
     */
32
    protected $headerFactory;
33
    
34
    /**
35
     * @var \ZBateson\MailMimeParser\Message\PartFilterFactory factory object
36
     *      responsible for create PartFilters
37
     */
38
    protected $partFilterFactory;
39
40
    /**
41
     * @var \ZBateson\MailMimeParser\Message\Part\MessagePart[] array of child
42
     *      parts
43
     */
44
    protected $children = [];
45
    
46
    /**
47
     * @var string[][] array of headers, with keys set to lower-cased,
48
     *      alphanumeric characters of the header's name, and values set to an
49
     *      array of 2 elements, the first being the header's original name with
50
     *      non-alphanumeric characters and original case, and the second set to
51
     *      the header's value.
52
     */
53
    protected $rawHeaders;
54
    
55
    /**
56
     * @var \ZBateson\MailMimeParser\Header\AbstractHeader[] array of parsed
57
     * header objects populated on-demand, the key is set to the header's name
58
     * lower-cased, and with non-alphanumeric characters removed.
59
     */
60
    protected $headers;
61
62
    /**
63
     * Sets up class dependencies.
64
     *
65
     * @param HeaderFactory $headerFactory 
66
     * @param PartFilterFactory $partFilterFactory
67
     * @param PartBuilder $partBuilder
68
     * @param PartStreamFilterManager $partStreamFilterManager
69
     * @param StreamInterface $stream
70
     * @param StreamInterface $contentStream
71
     */
72 24
    public function __construct(
73
        HeaderFactory $headerFactory,
74
        PartFilterFactory $partFilterFactory,
75
        PartBuilder $partBuilder,
76
        PartStreamFilterManager $partStreamFilterManager,
77
        StreamInterface $stream,
78
        StreamInterface $contentStream = null
79
    ) {
80 24
        parent::__construct($partStreamFilterManager, $stream, $contentStream);
81
82 24
        $this->headerFactory = $headerFactory;
83 24
        $this->partFilterFactory = $partFilterFactory;
84
85 24
        $pbChildren = $partBuilder->getChildren();
86 24
        if (!empty($pbChildren)) {
87 7
            $this->children = array_map(function ($child) use ($stream) {
88 7
                $childPart = $child->createMessagePart($stream);
89 7
                $childPart->parent = $this;
90 7
                return $childPart;
91 7
            }, $pbChildren);
92
        }
93 24
        $this->headers['contenttype'] = $partBuilder->getContentType();
94 24
        $this->rawHeaders = $partBuilder->getRawHeaders();
95 24
    }
96
    
97
    /**
98
     * Returns all parts, including the current object, and all children below
99
     * it (including children of children, etc...)
100
     * 
101
     * @return MessagePart[]
102
     */
103 6
    protected function getAllNonFilteredParts()
104
    {
105 6
        $parts = [ $this ];
106 6
        foreach ($this->children as $part) {
107 6
            if ($part instanceof MimePart) {
108 6
                $parts = array_merge(
109 6
                    $parts,
110 6
                    $part->getAllNonFilteredParts()
111
                );
112
            } else {
113 6
                array_push($parts, $part);
114
            }
115
        }
116 6
        return $parts;
117
    }
118
119
    /**
120
     * Returns the part at the given 0-based index, or null if none is set.
121
     * 
122
     * Note that the first part returned is the current part itself.  This is
123
     * often desirable for queries with a PartFilter, e.g. looking for a
124
     * MessagePart with a specific Content-Type that may be satisfied by the
125
     * current part.
126
     *
127
     * @param int $index
128
     * @param PartFilter $filter
129
     * @return MessagePart
130
     */
131 2
    public function getPart($index, PartFilter $filter = null)
132
    {
133 2
        $parts = $this->getAllParts($filter);
134 2
        if (!isset($parts[$index])) {
135
            return null;
136
        }
137 2
        return $parts[$index];
138
    }
139
140
    /**
141
     * Returns the current part, all child parts, and child parts of all
142
     * children optionally filtering them with the provided PartFilter.
143
     * 
144
     * The first part returned is always the current MimePart.  This is often
145
     * desirable as it may be a valid MimePart for the provided PartFilter.
146
     * 
147
     * @param PartFilter $filter an optional filter
148
     * @return MessagePart[]
149
     */
150 6
    public function getAllParts(PartFilter $filter = null)
151
    {
152 6
        $parts = $this->getAllNonFilteredParts();
153 6
        if (!empty($filter)) {
154 4
            return array_values(array_filter(
155 4
                $parts,
156 4
                [ $filter, 'filter' ]
157
            ));
158
        }
159 4
        return $parts;
160
    }
161
162
    /**
163
     * Returns the total number of parts in this and all children.
164
     * 
165
     * Note that the current part is considered, so the minimum getPartCount is
166
     * 1 without a filter.
167
     *
168
     * @param PartFilter $filter
169
     * @return int
170
     */
171 2
    public function getPartCount(PartFilter $filter = null)
172
    {
173 2
        return count($this->getAllParts($filter));
174
    }
175
    
176
    /**
177
     * Returns the direct child at the given 0-based index, or null if none is
178
     * set.
179
     *
180
     * @param int $index
181
     * @param PartFilter $filter
182
     * @return MessagePart
183
     */
184 2
    public function getChild($index, PartFilter $filter = null)
185
    {
186 2
        $parts = $this->getChildParts($filter);
187 2
        if (!isset($parts[$index])) {
188
            return null;
189
        }
190 2
        return $parts[$index];
191
    }
192
    
193
    /**
194
     * Returns all direct child parts.
195
     * 
196
     * If a PartFilter is provided, the PartFilter is applied before returning.
197
     * 
198
     * @param PartFilter $filter
199
     * @return MessagePart[]
200
     */
201 3
    public function getChildParts(PartFilter $filter = null)
202
    {
203 3
        if ($filter !== null) {
204 1
            return array_values(array_filter($this->children, [ $filter, 'filter' ]));
205
        }
206 2
        return $this->children;
207
    }
208
    
209
    /**
210
     * Returns the number of direct children under this part.
211
     * 
212
     * @param PartFilter $filter
213
     * @return int
214
     */
215 1
    public function getChildCount(PartFilter $filter = null)
216
    {
217 1
        return count($this->getChildParts($filter));
218
    }
219
220
    /**
221
     * Returns the part associated with the passed mime type if it exists.
222
     *
223
     * @param string $mimeType
224
     * @return MessagePart or null
225
     */
226 1
    public function getPartByMimeType($mimeType, $index = 0)
227
    {
228 1
        $partFilter = $this->partFilterFactory->newFilterFromContentType($mimeType);
229 1
        return $this->getPart($index, $partFilter);
230
    }
231
    
232
    /**
233
     * Returns an array of all parts associated with the passed mime type if any
234
     * exist or null otherwise.
235
     *
236
     * @param string $mimeType
237
     * @return MessagePart[] or null
238
     */
239 1
    public function getAllPartsByMimeType($mimeType)
240
    {
241 1
        $partFilter = $this->partFilterFactory->newFilterFromContentType($mimeType);
242 1
        return $this->getAllParts($partFilter);
243
    }
244
    
245
    /**
246
     * Returns the number of parts matching the passed $mimeType
247
     * 
248
     * @param string $mimeType
249
     * @return int
250
     */
251 1
    public function getCountOfPartsByMimeType($mimeType)
252
    {
253 1
        $partFilter = $this->partFilterFactory->newFilterFromContentType($mimeType);
254 1
        return $this->getPartCount($partFilter);
255
    }
256
257
    /**
258
     * Returns true if this part's mime type is multipart/*
259
     *
260
     * @return bool
261
     */
262 7
    public function isMultiPart()
263
    {
264
        // casting to bool, preg_match returns 1 for true
265 7
        return (bool) (preg_match(
266 7
            '~multipart/\w+~i',
267 7
            $this->getContentType()
268
        ));
269
    }
270
    
271
    /**
272
     * Returns a filename for the part if one is defined, or null otherwise.
273
     * 
274
     * @return string
275
     */
276
    public function getFilename()
277
    {
278
        return $this->getHeaderParameter(
279
            'Content-Disposition',
280
            'filename',
281
            $this->getHeaderParameter(
282
                'Content-Type',
283
                'name'
284
            )
285
        );
286
    }
287
    
288
    /**
289
     * Returns true.
290
     * 
291
     * @return bool
292
     */
293 1
    public function isMime()
294
    {
295 1
        return true;
296
    }
297
    
298
    /**
299
     * Returns true if this part's mime type is text/plain, text/html or if the
300
     * Content-Type header defines a charset.
301
     * 
302
     * @return bool
303
     */
304 7
    public function isTextPart()
305
    {
306 7
        return ($this->getCharset() !== null);
307
    }
308
    
309
    /**
310
     * Returns the lower-cased, trimmed value of the Content-Type header.
311
     * 
312
     * Parses the Content-Type header, defaults to returning text/plain if not
313
     * defined.
314
     * 
315
     * @return string
316
     */
317 10
    public function getContentType($default = 'text/plain')
318
    {
319 10
        return trim(strtolower($this->getHeaderValue('Content-Type', $default)));
320
    }
321
    
322
    /**
323
     * Returns the upper-cased charset of the Content-Type header's charset
324
     * parameter if set, US-ASCII if the Content-Type is text/plain or text/html
325
     * and the charset parameter isn't set, or null otherwise.
326
     * 
327
     * @return string
328
     */
329 12
    public function getCharset()
330
    {
331 12
        $charset = $this->getHeaderParameter('Content-Type', 'charset');
332 12
        if ($charset === null) {
0 ignored issues
show
introduced by
The condition $charset === null is always false.
Loading history...
333 8
            $contentType = $this->getContentType();
334 8
            if ($contentType === 'text/plain' || $contentType === 'text/html') {
335 3
                return 'US-ASCII';
336
            }
337 5
            return null;
338
        }
339 4
        return trim(strtoupper($charset));
340
    }
341
    
342
    /**
343
     * Returns the content's disposition, defaulting to 'inline' if not set.
344
     * 
345
     * @return string
346
     */
347 1
    public function getContentDisposition($default = 'inline')
348
    {
349 1
        return strtolower($this->getHeaderValue('Content-Disposition', $default));
350
    }
351
    
352
    /**
353
     * Returns the content-transfer-encoding used for this part, defaulting to
354
     * '7bit' if not set.
355
     * 
356
     * @return string
357
     */
358 2
    public function getContentTransferEncoding($default = '7bit')
359
    {
360 2
        static $translated = [
361
            'x-uue' => 'x-uuencode',
362
            'uue' => 'x-uuencode',
363
            'uuencode' => 'x-uuencode'
364
        ];
365 2
        $type = strtolower($this->getHeaderValue('Content-Transfer-Encoding', $default));
366 2
        if (isset($translated[$type])) {
367
            return $translated[$type];
368
        }
369 2
        return $type;
370
    }
371
372
    /**
373
     * Returns the AbstractHeader object for the header with the given $name
374
     *
375
     * Note that mime headers aren't case sensitive.
376
     *
377
     * @param string $name
378
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader
379
     */
380 16
    public function getHeader($name)
381
    {
382 16
        $nameKey = preg_replace('/[^a-z0-9]/', '', strtolower($name));
383 16
        if (isset($this->rawHeaders[$nameKey])) {
384 15
            if (!isset($this->headers[$nameKey])) {
385 14
                $this->headers[$nameKey] = $this->headerFactory->newInstance(
386 14
                    $this->rawHeaders[$nameKey][0],
387 14
                    $this->rawHeaders[$nameKey][1]
388
                );
389
            }
390 15
            return $this->headers[$nameKey];
391
        }
392 1
        return null;
393
    }
394
395
    /**
396
     * Returns the string value for the header with the given $name.
397
     *
398
     * Note that mime headers aren't case sensitive.
399
     *
400
     * @param string $name
401
     * @param string $defaultValue
402
     * @return string
403
     */
404 15
    public function getHeaderValue($name, $defaultValue = null)
405
    {
406 15
        $header = $this->getHeader($name);
407 15
        if ($header !== null) {
408 14
            return $header->getValue();
409
        }
410 1
        return $defaultValue;
411
    }
412
413
    /**
414
     * Returns a parameter of the header $header, given the parameter named
415
     * $param.
416
     *
417
     * Only headers of type
418
     * \ZBateson\MailMimeParser\Header\ParameterHeader have parameters.
419
     * Content-Type and Content-Disposition are examples of headers with
420
     * parameters. "Charset" is a common parameter of Content-Type.
421
     *
422
     * @param string $header
423
     * @param string $param
424
     * @param string $defaultValue
425
     * @return string
426
     */
427 14
    public function getHeaderParameter($header, $param, $defaultValue = null)
428
    {
429 14
        $obj = $this->getHeader($header);
430 14
        if ($obj && $obj instanceof ParameterHeader) {
431 13
            return $obj->getValueFor($param, $defaultValue);
432
        }
433 1
        return $defaultValue;
434
    }
435
}
436