Passed
Push — master ( 2ea88e...404a7f )
by Zaahid
03:05
created

MimePart::getPartCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 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
namespace ZBateson\MailMimeParser\Message;
8
9
use ZBateson\MailMimeParser\Header\HeaderFactory;
10
use ZBateson\MailMimeParser\Header\ParameterHeader;
11
use ZBateson\MailMimeParser\Message\Writer\MimePartWriter;
12
13
/**
14
 * Represents a single part of a multi-part mime message.
15
 *
16
 * A MimePart object may have any number of child parts, or may be a child
17
 * itself with its own parent or parents.
18
 *
19
 * The content of the part can be read from its PartStream resource handle,
20
 * accessible via MimePart::getContentResourceHanlde.
21
 *
22
 * @author Zaahid Bateson
23
 */
24
class MimePart
25
{
26
    /**
27
     * @var \ZBateson\MailMimeParser\Header\HeaderFactory the HeaderFactory
28
     *      object used for created headers
29
     */
30
    protected $headerFactory;
31
32
    /**
33
     * @var \ZBateson\MailMimeParser\Header\AbstractHeader[] array of header
34
     * objects
35
     */
36
    protected $headers;
37
38
    /**
39
     * @var \ZBateson\MailMimeParser\Message\MimePart parent part
40
     */
41
    protected $parent;
42
43
    /**
44
     * @var resource the content's resource handle
45
     */
46
    protected $handle;
47
    
48
    /**
49
     * 
50
     */
51
    protected $originalStreamHandle;
52
53
    /**
54
     * @var \ZBateson\MailMimeParser\Message\MimePart[] array of parts in this
55
     *      message
56
     */
57
    protected $parts = [];
58
59
    /**
60
     * @var \ZBateson\MailMimeParser\Message\Writer\MimePartWriter the part
61
     *      writer for this MimePart
62
     */
63
    protected $partWriter = null;
64
65
    /**
66
     * Sets up class dependencies.
67
     *
68
     * @param HeaderFactory $headerFactory
69
     * @param MimePartWriter $partWriter
70
     */
71 106
    public function __construct(HeaderFactory $headerFactory, MimePartWriter $partWriter)
72
    {
73 106
        $this->headerFactory = $headerFactory;
74 106
        $this->partWriter = $partWriter;
75 106
    }
76
77
    /**
78
     * Closes the attached resource handle.
79
     */
80 98
    public function __destruct()
81
    {
82 98
        if (is_resource($this->handle)) {
83 36
            fclose($this->handle);
84 36
        }
85 98
        if (is_resource($this->originalStreamHandle)) {
86 31
            fclose($this->originalStreamHandle);
87 31
        }
88 98
    }
89
90
    /**
91
     * Registers the passed part as a child of the current part.
92
     * 
93
     * If the $position parameter is non-null, adds the part at the passed
94
     * position index.
95
     *
96
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
97
     * @param int $position
98
     */
99 72
    public function addPart(MimePart $part, $position = null)
100
    {
101 72
        if ($part !== $this) {
102 72
            $part->setParent($this);
103 72
            array_splice($this->parts, ($position === null) ? count($this->parts) : $position, 0, [ $part ]);
104 72
        }
105 72
    }
106
    
107
    /**
108
     * Removes the child part from this part and returns its position or
109
     * null if it wasn't found.
110
     * 
111
     * Note that if the part is not a direct child of this part, the returned
112
     * position is its index within its parent (calls removePart on its direct
113
     * parent).
114
     *
115
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
116
     * @return int or null if not found
117
     */
118 14
    public function removePart(MimePart $part)
119
    {
120 14
        $parent = $part->getParent();
121 14
        if ($this !== $parent && $parent !== null) {
122 2
            return $parent->removePart($part);
123
        } else {
124 14
            $position = array_search($part, $this->parts, true);
125 14
            if ($position !== false) {
126 14
                array_splice($this->parts, $position, 1);
127 14
                return $position;
128
            }
129
        }
130
        return null;
131
    }
132
    
133
    /**
134
     * Removes all parts that are matched by the passed PartFilter.
135
     * 
136
     * @param \ZBateson\MailMimeParser\Message\PartFilter $filter
137
     */
138
    public function removeAllParts(PartFilter $filter = null)
139
    {
140
        foreach ($this->getAllParts($filter) as $part) {
141
            $this->removePart($part);
142
        }
143
    }
144
145
    /**
146
     * Returns the part at the given 0-based index, or null if none is set.
147
     * 
148
     * Note that the first part returned is the current part itself.  This is
149
     * often desirable for queries with a PartFilter, e.g. looking for a
150
     * MimePart with a specific Content-Type that may be satisfied by the
151
     * current part.
152
     *
153
     * @param int $index
154
     * @param PartFilter $filter
155
     * @return \ZBateson\MailMimeParser\Message\MimePart
156
     */
157 78 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...
158
    {
159 78
        $parts = $this->getAllParts($filter);
160 78
        if (!isset($parts[$index])) {
161 13
            return null;
162
        }
163 76
        return $parts[$index];
164
    }
165
166
    /**
167
     * Returns the current part, all child parts, and child parts of all
168
     * children optionally filtering them with the provided PartFilter.
169
     * 
170
     * The first part returned is always the current MimePart.  This is often
171
     * desirable as it may be a valid MimePart for the provided PartFilter.
172
     * 
173
     * @param PartFilter $filter an optional filter
174
     * @return \ZBateson\MailMimeParser\Message\MimePart[]
175
     */
176 88
    public function getAllParts(PartFilter $filter = null)
177
    {
178 88
        $aParts = [ $this ];
179 88
        foreach ($this->parts as $part) {
180 63
            $aParts = array_merge($aParts, $part->getAllParts(null, true));
0 ignored issues
show
Unused Code introduced by
The call to MimePart::getAllParts() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
181 88
        }
182 88
        if (!empty($filter)) {
183 87
            return array_values(array_filter(
184 87
                $aParts,
185 87
                [ $filter, 'filter' ]
186 87
            ));
187
        }
188 63
        return $aParts;
189
    }
190
191
    /**
192
     * Returns the total number of parts in this and all children.
193
     * 
194
     * Note that the current part is considered, so the minimum getPartCount is
195
     * 1 without a filter.
196
     *
197
     * @param PartFilter $filter
198
     * @return int
199
     */
200 5
    public function getPartCount(PartFilter $filter = null)
201
    {
202 5
        return count($this->getAllParts($filter));
203
    }
204
    
205
    /**
206
     * Returns the direct child at the given 0-based index, or null if none is
207
     * set.
208
     *
209
     * @param int $index
210
     * @param PartFilter $filter
211
     * @return \ZBateson\MailMimeParser\Message\MimePart
212
     */
213 22 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...
214
    {
215 22
        $parts = $this->getChildParts($filter);
216 22
        if (!isset($parts[$index])) {
217 8
            return null;
218
        }
219 22
        return $parts[$index];
220
    }
221
    
222
    /**
223
     * Returns all direct child parts.
224
     * 
225
     * If a PartFilter is provided, the PartFilter is applied before returning.
226
     * 
227
     * @param PartFilter $filter
228
     * @return \ZBateson\MailMimeParser\Message\MimePart[]
229
     */
230 90
    public function getChildParts(PartFilter $filter = null)
231
    {
232 90
        if ($filter !== null) {
233 17
            return array_values(array_filter($this->parts, [ $filter, 'filter' ]));
234
        }
235 89
        return $this->parts;
236
    }
237
    
238
    /**
239
     * Returns the number of direct children under this part.
240
     * 
241
     * @param PartFilter $filter
242
     * @return int
243
     */
244 9
    public function getChildCount(PartFilter $filter = null)
245
    {
246 9
        return count($this->getChildParts($filter));
247
    }
248
249
    /**
250
     * Returns the part associated with the passed mime type if it exists.
251
     *
252
     * @param string $mimeType
253
     * @return \ZBateson\MailMimeParser\Message\MimePart or null
254
     */
255 10
    public function getPartByMimeType($mimeType, $index = 0)
256
    {
257 10
        return $this->getPart($index, PartFilter::fromContentType($mimeType));
258
    }
259
    
260
    /**
261
     * Returns an array of all parts associated with the passed mime type if any
262
     * exist or null otherwise.
263
     *
264
     * @param string $mimeType
265
     * @return \ZBateson\MailMimeParser\Message\MimePart[] or null
266
     */
267 1
    public function getAllPartsByMimeType($mimeType)
268
    {
269 1
        return $this->getAllParts(PartFilter::fromContentType($mimeType));
270
    }
271
    
272
    /**
273
     * Returns the number of parts matching the passed $mimeType
274
     * 
275
     * @param string $mimeType
276
     * @return int
277
     */
278 1
    public function getCountOfPartsByMimeType($mimeType)
279
    {
280 1
        return $this->getPartCount(PartFilter::fromContentType($mimeType));
281
    }
282
283
    /**
284
     * Returns true if there's a content stream associated with the part.
285
     *
286
     * @return boolean
287
     */
288 23
    public function hasContent()
289
    {
290 23
        if ($this->handle !== null) {
291 23
            return true;
292
        }
293 1
        return false;
294
    }
295
296
    /**
297
     * Returns true if this part's mime type is multipart/*
298
     *
299
     * @return bool
300
     */
301 69
    public function isMultiPart()
302
    {
303
        // casting to bool, preg_match returns 1 for true
304 69
        return (bool) (preg_match(
305 69
            '~multipart/\w+~i',
306 69
            $this->getHeaderValue('Content-Type', 'text/plain')
307 69
        ));
308
    }
309
    
310
    /**
311
     * Returns true if this part's mime type is text/plain, text/html or has a
312
     * text/* and has a defined 'charset' attribute.
313
     * 
314
     * @return bool
315
     */
316 93
    public function isTextPart()
317
    {
318 93
        $type = $this->getHeaderValue('Content-Type', 'text/plain');
319 93
        if ($type === 'text/html' || $type === 'text/plain') {
320 82
            return true;
321
        }
322 70
        $charset = $this->getHeaderParameter('Content-Type', 'charset');
323 70
        return ($charset !== null && preg_match(
324 4
            '~text/\w+~i',
325 4
            $this->getHeaderValue('Content-Type', 'text/plain')
326 70
        ));
327
    }
328
329
    /**
330
     * Attaches the resource handle for the part's content.  The attached handle
331
     * is closed when the MimePart object is destroyed.
332
     *
333
     * @param resource $contentHandle
334
     */
335 93
    public function attachContentResourceHandle($contentHandle)
336
    {
337 93
        if ($this->handle !== null && $this->handle !== $contentHandle) {
338 10
            fclose($this->handle);
339 10
        }
340 93
        $this->handle = $contentHandle;
341 93
    }
342
    
343
    /**
344
     * Attaches the resource handle representing the original stream that
345
     * created this part (including any sub-parts).  The attached handle is
346
     * closed when the MimePart object is destroyed.
347
     * 
348
     * This stream is not modified or changed as the part is changed and is only
349
     * set during parsing in MessageParser.
350
     *
351
     * @param resource $handle
352
     */
353 88
    public function attachOriginalStreamHandle($handle)
354
    {
355 88
        if ($this->originalStreamHandle !== null && $this->originalStreamHandle !== $handle) {
356 63
            fclose($this->originalStreamHandle);
357 63
        }
358 88
        $this->originalStreamHandle = $handle;
359 88
    }
360
    
361
    /**
362
     * Returns a resource stream handle allowing a user to read the original
363
     * stream (including headers and child parts) that was used to create the
364
     * current part.
365
     * 
366
     * The part contains an original stream handle only if it was explicitly set
367
     * by a call to MimePart::attachOriginalStreamHandle.  MailMimeParser only
368
     * sets this during the parsing phase in MessageParser, and is not otherwise
369
     * changed or updated.  New parts added below this part, changed headers,
370
     * etc... would not be reflected in the returned stream handle.
371
     * 
372
     * @return resource the resource handle or null if not set
373
     */
374 12
    public function getOriginalStreamHandle()
375
    {
376 12
        if (is_resource($this->originalStreamHandle)) {
377 12
            rewind($this->originalStreamHandle);
378 12
        }
379 12
        return $this->originalStreamHandle;
380
    }
381
382
    /**
383
     * Detaches the content resource handle from this part but does not close
384
     * it.
385
     */
386 17
    protected function detachContentResourceHandle()
387
    {
388 17
        $this->handle = null;
389 17
    }
390
391
    /**
392
     * Sets the content of the part to the passed string (effectively creates
393
     * a php://temp stream with the passed content and calls
394
     * attachContentResourceHandle with the opened stream).
395
     *
396
     * @param string $string
397
     */
398 10
    public function setContent($string)
399
    {
400 10
        $handle = fopen('php://temp', 'r+');
401 10
        fwrite($handle, $string);
402 10
        rewind($handle);
403 10
        $this->attachContentResourceHandle($handle);
404 10
    }
405
406
    /**
407
     * Returns the resource stream handle for the part's content or null if not
408
     * set.  rewind() is called on the stream before returning it.
409
     *
410
     * The resource is automatically closed by MimePart's destructor and should
411
     * not be closed otherwise.
412
     *
413
     * The returned resource handle is a stream with decoding filters appended
414
     * to it.  The attached filters are determined by looking at the part's
415
     * Content-Encoding header.  The following encodings are currently
416
     * supported:
417
     *
418
     * - Quoted-Printable
419
     * - Base64
420
     * - X-UUEncode
421
     *
422
     * UUEncode may be automatically attached for a message without a defined
423
     * Content-Encoding and Content-Type if it has a UUEncoded part to support
424
     * older non-mime message attachments.
425
     *
426
     * In addition, character encoding for text streams is converted to UTF-8
427
     * if {@link \ZBateson\MailMimeParser\Message\MimePart::isTextPart
428
     * MimePart::isTextPart} returns true.
429
     *
430
     * @return resource
431
     */
432 88
    public function getContentResourceHandle()
433
    {
434 88
        if (is_resource($this->handle)) {
435 88
            rewind($this->handle);
436 88
        }
437 88
        return $this->handle;
438
    }
439
440
    /**
441
     * Shortcut to reading stream content and assigning it to a string.  Returns
442
     * null if the part doesn't have a content stream.
443
     *
444
     * @return string
445
     */
446 22
    public function getContent()
447
    {
448 22
        if ($this->hasContent()) {
449 22
            $text = stream_get_contents($this->handle);
450 22
            rewind($this->handle);
451 22
            return $text;
452
        }
453
        return null;
454
    }
455
456
    /**
457
     * Adds a header with the given $name and $value.
458
     *
459
     * Creates a new \ZBateson\MailMimeParser\Header\AbstractHeader object and
460
     * registers it as a header.
461
     *
462
     * @param string $name
463
     * @param string $value
464
     */
465 98
    public function setRawHeader($name, $value)
466
    {
467 98
        $this->headers[strtolower($name)] = $this->headerFactory->newInstance($name, $value);
468 98
    }
469
470
    /**
471
     * Removes the header with the given name
472
     *
473
     * @param string $name
474
     */
475 17
    public function removeHeader($name)
476
    {
477 17
        unset($this->headers[strtolower($name)]);
478 17
    }
479
480
    /**
481
     * Returns the AbstractHeader object for the header with the given $name
482
     *
483
     * Note that mime headers aren't case sensitive.
484
     *
485
     * @param string $name
486
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader
487
     */
488 100
    public function getHeader($name)
489
    {
490 100
        if (isset($this->headers[strtolower($name)])) {
491 98
            return $this->headers[strtolower($name)];
492
        }
493 93
        return null;
494
    }
495
496
    /**
497
     * Returns the string value for the header with the given $name.
498
     *
499
     * Note that mime headers aren't case sensitive.
500
     *
501
     * @param string $name
502
     * @param string $defaultValue
503
     * @return string
504
     */
505 97
    public function getHeaderValue($name, $defaultValue = null)
506
    {
507 97
        $header = $this->getHeader($name);
508 97
        if ($header !== null) {
509 96
            return $header->getValue();
510
        }
511 92
        return $defaultValue;
512
    }
513
514
    /**
515
     * Returns the full array of headers for this part.
516
     *
517
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader[]
518
     */
519 84
    public function getHeaders()
520
    {
521 84
        return $this->headers;
522
    }
523
524
    /**
525
     * Returns a parameter of the header $header, given the parameter named
526
     * $param.
527
     *
528
     * Only headers of type
529
     * \ZBateson\MailMimeParser\Header\ParameterHeader have parameters.
530
     * Content-Type and Content-Disposition are examples of headers with
531
     * parameters. "Charset" is a common parameter of Content-Type.
532
     *
533
     * @param string $header
534
     * @param string $param
535
     * @param string $defaultValue
536
     * @return string
537
     */
538 96
    public function getHeaderParameter($header, $param, $defaultValue = null)
539
    {
540 96
        $obj = $this->getHeader($header);
541 96
        if ($obj && $obj instanceof ParameterHeader) {
542 94
            return $obj->getValueFor($param, $defaultValue);
543
        }
544 7
        return $defaultValue;
545
    }
546
547
    /**
548
     * Sets the parent part.
549
     *
550
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
551
     */
552 97
    public function setParent(MimePart $part)
553
    {
554 97
        $this->parent = $part;
555 97
    }
556
557
    /**
558
     * Returns this part's parent.
559
     *
560
     * @return \ZBateson\MailMimeParser\Message\MimePart
561
     */
562 96
    public function getParent()
563
    {
564 96
        return $this->parent;
565
    }
566
}
567