Passed
Branch 1.0.0 (a1adee)
by Zaahid
08:34
created

MimePart::getAllNonFilteredParts()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3.0052

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 11
cts 12
cp 0.9167
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 3
nop 0
crap 3.0052
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 ZBateson\MailMimeParser\Header\HeaderFactory;
10
use ZBateson\MailMimeParser\Header\ParameterHeader;
11
use ZBateson\MailMimeParser\Message\PartFilterFactory;
12
use ZBateson\MailMimeParser\Message\PartFilter;
13
14
/**
15
 * Represents a single part of a multi-part mime message.
16
 *
17
 * A MimePart object may have any number of child parts, or may be a child
18
 * itself with its own parent or parents.
19
 *
20
 * The content of the part can be read from its PartStream resource handle,
21
 * accessible via MimePart::getContentResourceHanlde.
22
 *
23
 * @author Zaahid Bateson
24
 */
25
class MimePart extends MessagePart
26
{
27
    /**
28
     * @var \ZBateson\MailMimeParser\Header\HeaderFactory the HeaderFactory
29
     *      object used for created headers
30
     */
31
    protected $headerFactory;
32
    
33
    /**
34
     * @var \ZBateson\MailMimeParser\Message\PartFilterFactory factory object
35
     *      responsible for create PartFilters
36
     */
37
    protected $partFilterFactory;
38
39
    /**
40
     * @var \ZBateson\MailMimeParser\Message\Part\MessagePart[] array of child
41
     *      parts
42
     */
43
    protected $children = [];
44
    
45
    /**
46
     * @var string[][] array of headers, with keys set to lower-cased,
47
     *      alphanumeric characters of the header's name, and values set to an
48
     *      array of 2 elements, the first being the header's original name with
49
     *      non-alphanumeric characters and original case, and the second set to
50
     *      the header's value.
51
     */
52
    protected $rawHeaders;
53
    
54
    /**
55
     * @var \ZBateson\MailMimeParser\Header\AbstractHeader[] array of parsed
56
     * header objects populated on-demand, the key is set to the header's name
57
     * lower-cased, and with non-alphanumeric characters removed.
58
     */
59
    protected $headers;
60
61
    /**
62
     * Sets up class dependencies.
63
     *
64
     * @param HeaderFactory $headerFactory 
65
     * @param PartFilterFactory $partFilterFactory
66
     * @param string $messageObjectId
67
     * @param PartBuilder $partBuilder
68
     * @param PartStreamFilterManager $partStreamFilterManager
69
     */
70 24
    public function __construct(
71
        HeaderFactory $headerFactory,
72
        PartFilterFactory $partFilterFactory,
73
        $messageObjectId,
74
        PartBuilder $partBuilder,
75
        PartStreamFilterManager $partStreamFilterManager
76
    ) {
77 24
        parent::__construct($messageObjectId, $partBuilder, $partStreamFilterManager);
78 24
        $this->headerFactory = $headerFactory;
79 24
        $this->partFilterFactory = $partFilterFactory;
80
81 24
        $pbChildren = $partBuilder->getChildren();
82 24
        if (!empty($pbChildren)) {
83 7
            $this->children = array_map(function ($child) use ($messageObjectId) {
84 7
                $childPart = $child->createMessagePart($messageObjectId);
85 7
                $childPart->parent = $this;
86 7
                return $childPart;
87 7
            }, $pbChildren);
88 7
        }
89 24
        $this->headers['contenttype'] = $partBuilder->getContentType();
90 24
        $this->rawHeaders = $partBuilder->getRawHeaders();
91 24
    }
92
    
93
    /**
94
     * Returns all parts, including the current object, and all children below
95
     * it (including children of children, etc...)
96
     * 
97
     * @return MessagePart[]
98
     */
99 6
    protected function getAllNonFilteredParts()
100
    {
101 6
        $parts = [ $this ];
102 6
        foreach ($this->children as $part) {
103 6
            if ($part instanceof MimePart) {
104 6
                $parts = array_merge(
105 6
                    $parts,
106 6
                    $part->getAllNonFilteredParts()
107 6
                );
108 6
            } else {
109
                array_push($parts, $part);
110
            }
111 6
        }
112 6
        return $parts;
113
    }
114
115
    /**
116
     * Returns the part at the given 0-based index, or null if none is set.
117
     * 
118
     * Note that the first part returned is the current part itself.  This is
119
     * often desirable for queries with a PartFilter, e.g. looking for a
120
     * MessagePart with a specific Content-Type that may be satisfied by the
121
     * current part.
122
     *
123
     * @param int $index
124
     * @param PartFilter $filter
125
     * @return MessagePart
126
     */
127 2 View Code Duplication
    public function getPart($index, PartFilter $filter = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
    {
129 2
        $parts = $this->getAllParts($filter);
130 2
        if (!isset($parts[$index])) {
131
            return null;
132
        }
133 2
        return $parts[$index];
134
    }
135
136
    /**
137
     * Returns the current part, all child parts, and child parts of all
138
     * children optionally filtering them with the provided PartFilter.
139
     * 
140
     * The first part returned is always the current MimePart.  This is often
141
     * desirable as it may be a valid MimePart for the provided PartFilter.
142
     * 
143
     * @param PartFilter $filter an optional filter
144
     * @return MessagePart[]
145
     */
146 6
    public function getAllParts(PartFilter $filter = null)
147
    {
148 6
        $parts = $this->getAllNonFilteredParts();
149 6
        if (!empty($filter)) {
150 4
            return array_values(array_filter(
151 4
                $parts,
152 4
                [ $filter, 'filter' ]
153 4
            ));
154
        }
155 4
        return $parts;
156
    }
157
158
    /**
159
     * Returns the total number of parts in this and all children.
160
     * 
161
     * Note that the current part is considered, so the minimum getPartCount is
162
     * 1 without a filter.
163
     *
164
     * @param PartFilter $filter
165
     * @return int
166
     */
167 2
    public function getPartCount(PartFilter $filter = null)
168
    {
169 2
        return count($this->getAllParts($filter));
170
    }
171
    
172
    /**
173
     * Returns the direct child at the given 0-based index, or null if none is
174
     * set.
175
     *
176
     * @param int $index
177
     * @param PartFilter $filter
178
     * @return MessagePart
179
     */
180 2 View Code Duplication
    public function getChild($index, PartFilter $filter = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
    {
182 2
        $parts = $this->getChildParts($filter);
183 2
        if (!isset($parts[$index])) {
184
            return null;
185
        }
186 2
        return $parts[$index];
187
    }
188
    
189
    /**
190
     * Returns all direct child parts.
191
     * 
192
     * If a PartFilter is provided, the PartFilter is applied before returning.
193
     * 
194
     * @param PartFilter $filter
195
     * @return MessagePart[]
196
     */
197 3
    public function getChildParts(PartFilter $filter = null)
198
    {
199 3
        if ($filter !== null) {
200 1
            return array_values(array_filter($this->children, [ $filter, 'filter' ]));
201
        }
202 2
        return $this->children;
203
    }
204
    
205
    /**
206
     * Returns the number of direct children under this part.
207
     * 
208
     * @param PartFilter $filter
209
     * @return int
210
     */
211 1
    public function getChildCount(PartFilter $filter = null)
212
    {
213 1
        return count($this->getChildParts($filter));
214
    }
215
216
    /**
217
     * Returns the part associated with the passed mime type if it exists.
218
     *
219
     * @param string $mimeType
220
     * @return MessagePart or null
221
     */
222 1
    public function getPartByMimeType($mimeType, $index = 0)
223
    {
224 1
        $partFilter = $this->partFilterFactory->newFilterFromContentType($mimeType);
225 1
        return $this->getPart($index, $partFilter);
226
    }
227
    
228
    /**
229
     * Returns an array of all parts associated with the passed mime type if any
230
     * exist or null otherwise.
231
     *
232
     * @param string $mimeType
233
     * @return MessagePart[] or null
234
     */
235 1
    public function getAllPartsByMimeType($mimeType)
236
    {
237 1
        $partFilter = $this->partFilterFactory->newFilterFromContentType($mimeType);
238 1
        return $this->getAllParts($partFilter);
239
    }
240
    
241
    /**
242
     * Returns the number of parts matching the passed $mimeType
243
     * 
244
     * @param string $mimeType
245
     * @return int
246
     */
247 1
    public function getCountOfPartsByMimeType($mimeType)
248
    {
249 1
        $partFilter = $this->partFilterFactory->newFilterFromContentType($mimeType);
250 1
        return $this->getPartCount($partFilter);
251
    }
252
253
    /**
254
     * Returns true if this part's mime type is multipart/*
255
     *
256
     * @return bool
257
     */
258 7
    public function isMultiPart()
259
    {
260
        // casting to bool, preg_match returns 1 for true
261 7
        return (bool) (preg_match(
262 7
            '~multipart/\w+~i',
263 7
            $this->getContentType()
264 7
        ));
265
    }
266
    
267
    /**
268
     * Returns a filename for the part if one is defined, or null otherwise.
269
     * 
270
     * @return string
271
     */
272
    public function getFilename()
273
    {
274
        return $this->getHeaderParameter(
275
            'Content-Disposition',
276
            'filename',
277
            $this->getHeaderParameter(
278
                'Content-Type',
279
                'name'
280
            )
281
        );
282
    }
283
    
284
    /**
285
     * Returns true.
286
     * 
287
     * @return bool
288
     */
289 1
    public function isMime()
290
    {
291 1
        return true;
292
    }
293
    
294
    /**
295
     * Returns true if this part's mime type is text/plain, text/html or if the
296
     * Content-Type header defines a charset.
297
     * 
298
     * @return bool
299
     */
300 7
    public function isTextPart()
301
    {
302 7
        return ($this->getCharset() !== null);
303
    }
304
    
305
    /**
306
     * Returns the lower-cased, trimmed value of the Content-Type header.
307
     * 
308
     * Parses the Content-Type header, defaults to returning text/plain if not
309
     * defined.
310
     * 
311
     * @return string
312
     */
313 10
    public function getContentType($default = 'text/plain')
314
    {
315 10
        return trim(strtolower($this->getHeaderValue('Content-Type', $default)));
316
    }
317
    
318
    /**
319
     * Returns the upper-cased charset of the Content-Type header's charset
320
     * parameter if set, US-ASCII if the Content-Type is text/plain or text/html
321
     * and the charset parameter isn't set, or null otherwise.
322
     * 
323
     * @return string
324
     */
325 12
    public function getCharset()
326
    {
327 12
        $charset = $this->getHeaderParameter('Content-Type', 'charset');
328 12
        if ($charset === null) {
329 8
            $contentType = $this->getContentType();
330 8
            if ($contentType === 'text/plain' || $contentType === 'text/html') {
331 3
                return 'US-ASCII';
332
            }
333 5
            return null;
334
        }
335 4
        return trim(strtoupper($charset));
336
    }
337
    
338
    /**
339
     * Returns the content's disposition, defaulting to 'inline' if not set.
340
     * 
341
     * @return string
342
     */
343 1
    public function getContentDisposition($default = 'inline')
344
    {
345 1
        return strtolower($this->getHeaderValue('Content-Disposition', $default));
346
    }
347
    
348
    /**
349
     * Returns the content-transfer-encoding used for this part, defaulting to
350
     * '7bit' if not set.
351
     * 
352
     * @return string
353
     */
354 2
    public function getContentTransferEncoding($default = '7bit')
355
    {
356
        static $translated = [
357
            'x-uue' => 'x-uuencode',
358
            'uue' => 'x-uuencode',
359
            'uuencode' => 'x-uuencode'
360 2
        ];
361 2
        $type = strtolower($this->getHeaderValue('Content-Transfer-Encoding', $default));
362 2
        if (isset($translated[$type])) {
363
            return $translated[$type];
364
        }
365 2
        return $type;
366
    }
367
368
    /**
369
     * Returns the AbstractHeader object for the header with the given $name
370
     *
371
     * Note that mime headers aren't case sensitive.
372
     *
373
     * @param string $name
374
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader
375
     */
376 16
    public function getHeader($name)
377
    {
378 16
        $nameKey = preg_replace('/[^a-z0-9]/', '', strtolower($name));
379 16
        if (isset($this->rawHeaders[$nameKey])) {
380 15
            if (!isset($this->headers[$nameKey])) {
381 14
                $this->headers[$nameKey] = $this->headerFactory->newInstance(
382 14
                    $this->rawHeaders[$nameKey][0],
383 14
                    $this->rawHeaders[$nameKey][1]
384 14
                );
385 14
            }
386 15
            return $this->headers[$nameKey];
387
        }
388 1
        return null;
389
    }
390
391
    /**
392
     * Returns the string value for the header with the given $name.
393
     *
394
     * Note that mime headers aren't case sensitive.
395
     *
396
     * @param string $name
397
     * @param string $defaultValue
398
     * @return string
399
     */
400 15
    public function getHeaderValue($name, $defaultValue = null)
401
    {
402 15
        $header = $this->getHeader($name);
403 15
        if ($header !== null) {
404 14
            return $header->getValue();
405
        }
406 1
        return $defaultValue;
407
    }
408
409
    /**
410
     * Returns a parameter of the header $header, given the parameter named
411
     * $param.
412
     *
413
     * Only headers of type
414
     * \ZBateson\MailMimeParser\Header\ParameterHeader have parameters.
415
     * Content-Type and Content-Disposition are examples of headers with
416
     * parameters. "Charset" is a common parameter of Content-Type.
417
     *
418
     * @param string $header
419
     * @param string $param
420
     * @param string $defaultValue
421
     * @return string
422
     */
423 14
    public function getHeaderParameter($header, $param, $defaultValue = null)
424
    {
425 14
        $obj = $this->getHeader($header);
426 14
        if ($obj && $obj instanceof ParameterHeader) {
427 13
            return $obj->getValueFor($param, $defaultValue);
428
        }
429 1
        return $defaultValue;
430
    }
431
}
432