Completed
Push — master ( b69ec3...c60636 )
by Zaahid
09:03
created

MimePart::removePart()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
ccs 8
cts 9
cp 0.8889
rs 9.2
cc 4
eloc 10
nc 3
nop 1
crap 4.0218
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
     * @var \ZBateson\MailMimeParser\Message\MimePart[] array of parts in this
50
     *      message
51
     */
52
    protected $parts = [];
53
54
    /**
55
     * @var \ZBateson\MailMimeParser\Message\Writer\MimePartWriter the part
56
     *      writer for this MimePart
57
     */
58
    protected $partWriter = null;
59
60
    /**
61
     * Sets up class dependencies.
62
     *
63
     * @param HeaderFactory $headerFactory
64
     * @param MimePartWriter $partWriter
65
     */
66 102
    public function __construct(HeaderFactory $headerFactory, MimePartWriter $partWriter)
67
    {
68 102
        $this->headerFactory = $headerFactory;
69 102
        $this->partWriter = $partWriter;
70 102
    }
71
72
    /**
73
     * Closes the attached resource handle.
74
     */
75 94
    public function __destruct()
76
    {
77 94
        if (is_resource($this->handle)) {
78 36
            fclose($this->handle);
79 36
        }
80 94
    }
81
82
    /**
83
     * Registers the passed part as a child of the current part.
84
     * 
85
     * If the $position parameter is non-null, adds the part at the passed
86
     * position index.
87
     *
88
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
89
     * @param int $position
90
     */
91 68
    public function addPart(MimePart $part, $position = null)
92
    {
93 68
        if ($part !== $this) {
94 68
            $part->setParent($this);
95 68
            array_splice($this->parts, ($position === null) ? count($this->parts) : $position, 0, [ $part ]);
96 68
        }
97 68
    }
98
    
99
    /**
100
     * Removes the child part from this part and returns its position or
101
     * null if it wasn't found.
102
     * 
103
     * Note that if the part is not a direct child of this part, the returned
104
     * position is its index within its parent (calls removePart on its direct
105
     * parent).
106
     *
107
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
108
     * @return int or null if not found
109
     */
110 14
    public function removePart(MimePart $part)
111
    {
112 14
        $parent = $part->getParent();
113 14
        if ($this !== $parent && $parent !== null) {
114 2
            return $parent->removePart($part);
115
        } else {
116 14
            $position = array_search($part, $this->parts, true);
117 14
            if ($position !== false) {
118 14
                array_splice($this->parts, $position, 1);
119 14
                return $position;
120
            }
121
        }
122
        return null;
123
    }
124
    
125
    /**
126
     * Removes all parts that are matched by the passed PartFilter.
127
     * 
128
     * @param \ZBateson\MailMimeParser\Message\PartFilter $filter
129
     */
130
    public function removeAllParts(PartFilter $filter = null)
131
    {
132
        foreach ($this->getAllParts($filter) as $part) {
133
            $this->removePart($part);
134
        }
135
    }
136
137
    /**
138
     * Returns the part at the given 0-based index, or null if none is set.
139
     * 
140
     * Note that the first part returned is the current part itself.  This is
141
     * often desirable for queries with a PartFilter, e.g. looking for a
142
     * MimePart with a specific Content-Type that may be satisfied by the
143
     * current part.
144
     *
145
     * @param int $index
146
     * @param PartFilter $filter
147
     * @return \ZBateson\MailMimeParser\Message\MimePart
148
     */
149 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...
150
    {
151 78
        $parts = $this->getAllParts($filter);
152 78
        if (!isset($parts[$index])) {
153 13
            return null;
154
        }
155 76
        return $parts[$index];
156
    }
157
158
    /**
159
     * Returns the current part, all child parts, and child parts of all
160
     * children optionally filtering them with the provided PartFilter.
161
     * 
162
     * The first part returned is always the current MimePart.  This is often
163
     * desirable as it may be a valid MimePart for the provided PartFilter.
164
     * 
165
     * @param PartFilter $filter an optional filter
166
     * @return \ZBateson\MailMimeParser\Message\MimePart[]
167
     */
168 88
    public function getAllParts(PartFilter $filter = null)
169
    {
170 88
        $aParts = [ $this ];
171 88
        foreach ($this->parts as $part) {
172 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...
173 88
        }
174 88
        if (!empty($filter)) {
175 87
            return array_values(array_filter(
176 87
                $aParts,
177 87
                [ $filter, 'filter' ]
178 87
            ));
179
        }
180 63
        return $aParts;
181
    }
182
183
    /**
184
     * Returns the total number of parts in this and all children.
185
     * 
186
     * Note that the current part is considered, so the minimum getPartCount is
187
     * 1 without a filter.
188
     *
189
     * @param PartFilter $filter
190
     * @return int
191
     */
192 5
    public function getPartCount(PartFilter $filter = null)
193
    {
194 5
        return count($this->getAllParts($filter));
195
    }
196
    
197
    /**
198
     * Returns the direct child at the given 0-based index, or null if none is
199
     * set.
200
     *
201
     * @param int $index
202
     * @param PartFilter $filter
203
     * @return \ZBateson\MailMimeParser\Message\MimePart
204
     */
205 18 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...
206
    {
207 18
        $parts = $this->getChildParts($filter);
208 18
        if (!isset($parts[$index])) {
209 8
            return null;
210
        }
211 18
        return $parts[$index];
212
    }
213
    
214
    /**
215
     * Returns all direct child parts.
216
     * 
217
     * If a PartFilter is provided, the PartFilter is applied before returning.
218
     * 
219
     * @param PartFilter $filter
220
     * @return \ZBateson\MailMimeParser\Message\MimePart[]
221
     */
222 86
    public function getChildParts(PartFilter $filter = null)
223
    {
224 86
        if ($filter !== null) {
225 13
            return array_values(array_filter($this->parts, [ $filter, 'filter' ]));
226
        }
227 85
        return $this->parts;
228
    }
229
    
230
    /**
231
     * Returns the number of direct children under this part.
232
     * 
233
     * @param PartFilter $filter
234
     * @return int
235
     */
236 9
    public function getChildCount(PartFilter $filter = null)
237
    {
238 9
        return count($this->getChildParts($filter));
239
    }
240
241
    /**
242
     * Returns the part associated with the passed mime type if it exists.
243
     *
244
     * @param string $mimeType
245
     * @return \ZBateson\MailMimeParser\Message\MimePart or null
246
     */
247 10
    public function getPartByMimeType($mimeType, $index = 0)
248
    {
249 10
        return $this->getPart($index, PartFilter::fromContentType($mimeType));
250
    }
251
    
252
    /**
253
     * Returns an array of all parts associated with the passed mime type if any
254
     * exist or null otherwise.
255
     *
256
     * @param string $mimeType
257
     * @return \ZBateson\MailMimeParser\Message\MimePart[] or null
258
     */
259 1
    public function getAllPartsByMimeType($mimeType)
260
    {
261 1
        return $this->getAllParts(PartFilter::fromContentType($mimeType));
262
    }
263
    
264
    /**
265
     * Returns the number of parts matching the passed $mimeType
266
     * 
267
     * @param string $mimeType
268
     * @return int
269
     */
270 1
    public function getCountOfPartsByMimeType($mimeType)
271
    {
272 1
        return $this->getPartCount(PartFilter::fromContentType($mimeType));
273
    }
274
275
    /**
276
     * Returns true if there's a content stream associated with the part.
277
     *
278
     * @return boolean
279
     */
280 19
    public function hasContent()
281
    {
282 19
        if ($this->handle !== null) {
283 19
            return true;
284
        }
285 1
        return false;
286
    }
287
288
    /**
289
     * Returns true if this part's mime type is multipart/*
290
     *
291
     * @return bool
292
     */
293 65
    public function isMultiPart()
294
    {
295
        // casting to bool, preg_match returns 1 for true
296 65
        return (bool) (preg_match(
297 65
            '~multipart/\w+~i',
298 65
            $this->getHeaderValue('Content-Type', 'text/plain')
299 65
        ));
300
    }
301
    
302
    /**
303
     * Returns true if this part's mime type is text/plain, text/html or has a
304
     * text/* and has a defined 'charset' attribute.
305
     * 
306
     * @return bool
307
     */
308 89
    public function isTextPart()
309
    {
310 89
        $type = $this->getHeaderValue('Content-Type', 'text/plain');
311 89
        if ($type === 'text/html' || $type === 'text/plain') {
312 78
            return true;
313
        }
314 66
        $charset = $this->getHeaderParameter('Content-Type', 'charset');
315 66
        return ($charset !== null && preg_match(
316 4
            '~text/\w+~i',
317 4
            $this->getHeaderValue('Content-Type', 'text/plain')
318 66
        ));
319
    }
320
321
    /**
322
     * Attaches the resource handle for the part's content.  The attached handle
323
     * is closed when the MimePart object is destroyed.
324
     *
325
     * @param resource $contentHandle
326
     */
327 89
    public function attachContentResourceHandle($contentHandle)
328
    {
329 89
        if ($this->handle !== null && $this->handle !== $contentHandle) {
330 10
            fclose($this->handle);
331 10
        }
332 89
        $this->handle = $contentHandle;
333 89
    }
334
335
    /**
336
     * Detaches the content resource handle from this part but does not close
337
     * it.
338
     */
339 17
    protected function detachContentResourceHandle()
340
    {
341 17
        $this->handle = null;
342 17
    }
343
344
    /**
345
     * Sets the content of the part to the passed string (effectively creates
346
     * a php://temp stream with the passed content and calls
347
     * attachContentResourceHandle with the opened stream).
348
     *
349
     * @param string $string
350
     */
351 10
    public function setContent($string)
352
    {
353 10
        $handle = fopen('php://temp', 'r+');
354 10
        fwrite($handle, $string);
355 10
        rewind($handle);
356 10
        $this->attachContentResourceHandle($handle);
357 10
    }
358
359
    /**
360
     * Returns the resource stream handle for the part's content or null if not
361
     * set.  rewind() is called on the stream before returning it.
362
     *
363
     * The resource is automatically closed by MimePart's destructor and should
364
     * not be closed otherwise.
365
     *
366
     * @return resource
367
     */
368 88
    public function getContentResourceHandle()
369
    {
370 88
        if (is_resource($this->handle)) {
371 88
            rewind($this->handle);
372 88
        }
373 88
        return $this->handle;
374
    }
375
376
    /**
377
     * Shortcut to reading stream content and assigning it to a string.  Returns
378
     * null if the part doesn't have a content stream.
379
     *
380
     * @return string
381
     */
382 18
    public function getContent()
383
    {
384 18
        if ($this->hasContent()) {
385 18
            $text = stream_get_contents($this->handle);
386 18
            rewind($this->handle);
387 18
            return $text;
388
        }
389
        return null;
390
    }
391
392
    /**
393
     * Adds a header with the given $name and $value.
394
     *
395
     * Creates a new \ZBateson\MailMimeParser\Header\AbstractHeader object and
396
     * registers it as a header.
397
     *
398
     * @param string $name
399
     * @param string $value
400
     */
401 94
    public function setRawHeader($name, $value)
402
    {
403 94
        $this->headers[strtolower($name)] = $this->headerFactory->newInstance($name, $value);
404 94
    }
405
406
    /**
407
     * Removes the header with the given name
408
     *
409
     * @param string $name
410
     */
411 17
    public function removeHeader($name)
412
    {
413 17
        unset($this->headers[strtolower($name)]);
414 17
    }
415
416
    /**
417
     * Returns the AbstractHeader object for the header with the given $name
418
     *
419
     * Note that mime headers aren't case sensitive.
420
     *
421
     * @param string $name
422
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader
423
     */
424 96
    public function getHeader($name)
425
    {
426 96
        if (isset($this->headers[strtolower($name)])) {
427 94
            return $this->headers[strtolower($name)];
428
        }
429 87
        return null;
430
    }
431
432
    /**
433
     * Returns the string value for the header with the given $name.
434
     *
435
     * Note that mime headers aren't case sensitive.
436
     *
437
     * @param string $name
438
     * @param string $defaultValue
439
     * @return string
440
     */
441 93
    public function getHeaderValue($name, $defaultValue = null)
442
    {
443 93
        $header = $this->getHeader($name);
444 93
        if ($header !== null) {
445 92
            return $header->getValue();
446
        }
447 86
        return $defaultValue;
448
    }
449
450
    /**
451
     * Returns the full array of headers for this part.
452
     *
453
     * @return \ZBateson\MailMimeParser\Header\AbstractHeader[]
454
     */
455 84
    public function getHeaders()
456
    {
457 84
        return $this->headers;
458
    }
459
460
    /**
461
     * Returns a parameter of the header $header, given the parameter named
462
     * $param.
463
     *
464
     * Only headers of type
465
     * \ZBateson\MailMimeParser\Header\ParameterHeader have parameters.
466
     * Content-Type and Content-Disposition are examples of headers with
467
     * parameters. "Charset" is a common parameter of Content-Type.
468
     *
469
     * @param string $header
470
     * @param string $param
471
     * @param string $defaultValue
472
     * @return string
473
     */
474 92
    public function getHeaderParameter($header, $param, $defaultValue = null)
475
    {
476 92
        $obj = $this->getHeader($header);
477 92
        if ($obj && $obj instanceof ParameterHeader) {
478 90
            return $obj->getValueFor($param, $defaultValue);
479
        }
480 7
        return $defaultValue;
481
    }
482
483
    /**
484
     * Sets the parent part.
485
     *
486
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
487
     */
488 93
    public function setParent(MimePart $part)
489
    {
490 93
        $this->parent = $part;
491 93
    }
492
493
    /**
494
     * Returns this part's parent.
495
     *
496
     * @return \ZBateson\MailMimeParser\Message\MimePart
497
     */
498 92
    public function getParent()
499
    {
500 92
        return $this->parent;
501
    }
502
}
503