Completed
Push — master ( 1a3789...b27911 )
by Zaahid
08:22
created

MimePart::detachContentResourceHandle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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