Passed
Branch master (ba441e)
by Zaahid
03:56
created

Message   F

Complexity

Total Complexity 120

Size/Duplication

Total Lines 1083
Duplicated Lines 0 %

Test Coverage

Coverage 89.18%

Importance

Changes 0
Metric Value
wmc 120
dl 0
loc 1083
ccs 338
cts 379
cp 0.8918
rs 0.7389
c 0
b 0
f 0

56 Methods

Rating   Name   Duplication   Size   Complexity  
B findOtherContentPartFor() 0 13 6
A getContentPart() 0 8 3
A moveAllPartsAsAttachmentsExcept() 0 16 3
A removeAllTextParts() 0 3 1
A createAlternativeContentPart() 0 8 1
A createContentPartForMimeType() 0 20 3
A getAllAttachmentParts() 0 13 2
A setMimeHeaderBoundaryOnPart() 0 6 1
A removeAllHtmlParts() 0 3 1
A removeAllContentPartsByMimeType() 0 8 2
A getAttachmentPart() 0 7 2
B removePartByMimeType() 0 15 6
A isMime() 0 5 2
A addAttachmentPart() 0 11 2
A getContentPartContainerFromAlternative() 0 12 3
A setTextPart() 0 3 1
A getHtmlPartCount() 0 3 1
C removeAllContentPartsFromAlternative() 0 24 7
A createMultipartRelatedPartForInlineChildrenOf() 0 10 2
A save() 0 3 1
A addAttachmentPartFromFile() 0 12 2
A getSignableBody() 0 3 1
A __construct() 0 9 1
A getTextPartCount() 0 3 1
A createNewContentPartFromPart() 0 7 1
A movePartContentAndChildrenToPart() 0 8 2
A setContentPartForMimeType() 0 11 3
A getHtmlStream() 0 7 2
A setMessageAsAlternative() 0 13 2
A copyTypeHeadersFromPartToPart() 0 16 4
A getTextPart() 0 5 1
A ensureHtmlPartFirstForSignedMessage() 0 10 4
A enforceMime() 0 9 3
B getOriginalMessageStringForSignatureVerification() 0 16 5
A getHtmlContent() 0 7 2
A getHandleForStringOrHandle() 0 10 2
A replacePart() 0 10 2
A removeTextPart() 0 3 1
A overwrite8bitContentEncoding() 0 13 4
A getObjectId() 0 3 1
A setMessageAsMixed() 0 13 3
A setAsMultipartSigned() 0 16 2
A setHtmlPart() 0 3 1
A getSignaturePart() 0 7 2
A getHtmlPart() 0 5 1
A createSignaturePart() 0 12 2
A removeHtmlPart() 0 3 1
A from() 0 4 1
A getTextStream() 0 7 2
A getAttachmentCount() 0 3 1
A __toString() 0 8 1
A removeAttachmentPart() 0 4 1
A makeSpaceForMultipartSignedMessage() 0 14 2
A getUniqueBoundary() 0 4 1
A getTextContent() 0 7 2
A createPartForAttachment() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like Message often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Message, and based on these observations, apply Extract Interface, too.

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;
8
9
use ZBateson\MailMimeParser\Header\HeaderFactory;
10
use ZBateson\MailMimeParser\Message\MimePart;
11
use ZBateson\MailMimeParser\Message\MimePartFactory;
12
use ZBateson\MailMimeParser\Message\Writer\MessageWriter;
13
use ZBateson\MailMimeParser\Message\PartFilter;
14
15
/**
16
 * A parsed mime message with optional mime parts depending on its type.
17
 * 
18
 * A mime message may have any number of mime parts, and each part may have any
19
 * number of sub-parts, etc...
20
 *
21
 * @author Zaahid Bateson
22
 */
23
class Message extends MimePart
24
{
25
    /**
26
     * @var string unique ID used to identify the object to
27
     *      $this->partStreamRegistry when registering the stream.  The ID is
28
     *      used for opening stream parts with the mmp-mime-message "protocol".
29
     * 
30
     * @see \ZBateson\MailMimeParser\SimpleDi::registerStreamExtensions
31
     * @see \ZBateson\MailMimeParser\Stream\PartStream::stream_open
32
     */
33
    protected $objectId;
34
35
    /**
36
     * @var \ZBateson\MailMimeParser\Message\MimePartFactory a MimePartFactory to create
37
     *      parts for attachments/content
38
     */
39
    protected $mimePartFactory;
40
    
41
    /**
42
     * @var \ZBateson\MailMimeParser\Message\Writer\MessageWriter the part
43
     *      writer for this Message.  The same object is assigned to $partWriter
44
     *      but as an AbstractWriter -- not really needed in PHP but helps with
45
     *      auto-complete and code analyzers.
46
     */
47
    protected $messageWriter = null;
48
    
49
    /**
50
     * Convenience method to parse a handle or string into a Message without
51
     * requiring including MailMimeParser, instantiating it, and calling parse.
52
     * 
53
     * @param resource|string $handleOrString the resource handle to the input
54
     *        stream of the mime message, or a string containing a mime message
55
     */
56 1
    public static function from($handleOrString)
57
    {
58 1
        $mmp = new MailMimeParser();
59 1
        return $mmp->parse($handleOrString);
60
    }
61
    
62
    /**
63
     * Constructs a Message.
64
     * 
65
     * @param HeaderFactory $headerFactory
66
     * @param MessageWriter $messageWriter
67
     * @param MimePartFactory $mimePartFactory
68
     */
69 104
    public function __construct(
70
        HeaderFactory $headerFactory,   
71
        MessageWriter $messageWriter,
72
        MimePartFactory $mimePartFactory
73
    ) {
74 104
        parent::__construct($headerFactory, $messageWriter);
75 104
        $this->messageWriter = $messageWriter;
76 104
        $this->mimePartFactory = $mimePartFactory;
77 104
        $this->objectId = uniqid();
78 104
    }
79
    
80
    /**
81
     * Returns the unique object ID registered with the PartStreamRegistry
82
     * service object.
83
     * 
84
     * @return string
85
     */
86 98
    public function getObjectId()
87
    {
88 98
        return $this->objectId;
89
    }
90
91
    /**
92
     * Returns the text/plain part at the given index (or null if not found.)
93
     * 
94
     * @param int $index
95
     * @return \ZBateson\MailMimeParser\Message\MimePart
96
     */
97 74
    public function getTextPart($index = 0)
98
    {
99 74
        return $this->getPart(
100 74
            $index,
101 74
            PartFilter::fromInlineContentType('text/plain')
102
        );
103
    }
104
    
105
    /**
106
     * Returns the number of text/plain parts in this message.
107
     * 
108
     * @return int
109
     */
110
    public function getTextPartCount()
111
    {
112
        return $this->getPartCount(PartFilter::fromInlineContentType('text/plain'));
113
    }
114
    
115
    /**
116
     * Returns the text/html part at the given index (or null if not found.)
117
     * 
118
     * @param $index
119
     * @return \ZBateson\MailMimeParser\Message\MimePart
120
     */
121 40
    public function getHtmlPart($index = 0)
122
    {
123 40
        return $this->getPart(
124 40
            $index,
125 40
            PartFilter::fromInlineContentType('text/html')
126
        );
127
    }
128
    
129
    /**
130
     * Returns the number of text/html parts in this message.
131
     * 
132
     * @return int
133
     */
134
    public function getHtmlPartCount()
135
    {
136
        return $this->getPartCount(PartFilter::fromInlineContentType('text/html'));
137
    }
138
    
139
    /**
140
     * Returns the content MimePart, which could be a text/plain part,
141
     * text/html part, multipart/alternative part, or null if none is set.
142
     * 
143
     * This function is deprecated in favour of getTextPart/getHtmlPart and 
144
     * getPartByMimeType.
145
     * 
146
     * @deprecated since version 0.4.2
147
     * @return \ZBateson\MailMimeParser\Message\MimePart
148
     */
149
    public function getContentPart()
150
    {
151
        $alternative = $this->getPartByMimeType('multipart/alternative');
152
        if ($alternative !== null) {
153
            return $alternative;
154
        }
155
        $text = $this->getTextPart();
156
        return ($text !== null) ? $text : $this->getHtmlPart();
157
    }
158
    
159
    /**
160
     * Returns an open resource handle for the passed string or resource handle.
161
     * 
162
     * For a string, creates a php://temp stream and returns it.
163
     * 
164
     * @param resource|string $stringOrHandle
165
     * @return resource
166
     */
167 8
    private function getHandleForStringOrHandle($stringOrHandle)
168
    {
169 8
        $tempHandle = fopen('php://temp', 'r+');
170 8
        if (is_string($stringOrHandle)) {
171 8
            fwrite($tempHandle, $stringOrHandle);
0 ignored issues
show
Bug introduced by
It seems like $tempHandle can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

171
            fwrite(/** @scrutinizer ignore-type */ $tempHandle, $stringOrHandle);
Loading history...
172
        } else {
173
            stream_copy_to_stream($stringOrHandle, $tempHandle);
0 ignored issues
show
Bug introduced by
It seems like $tempHandle can also be of type false; however, parameter $dest of stream_copy_to_stream() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

173
            stream_copy_to_stream($stringOrHandle, /** @scrutinizer ignore-type */ $tempHandle);
Loading history...
174
        }
175 8
        rewind($tempHandle);
0 ignored issues
show
Bug introduced by
It seems like $tempHandle can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

175
        rewind(/** @scrutinizer ignore-type */ $tempHandle);
Loading history...
176 8
        return $tempHandle;
177
    }
178
    
179
    /**
180
     * Creates and returns a unique boundary.
181
     * 
182
     * @param string $mimeType first 3 characters of a multipart type are used,
183
     *      e.g. REL for relative or ALT for alternative
184
     * @return string
185
     */
186 20
    private function getUniqueBoundary($mimeType)
187
    {
188 20
        $type = ltrim(strtoupper(preg_replace('/^(multipart\/(.{3}).*|.*)$/i', '$2-', $mimeType)), '-');
189 20
        return uniqid('----=MMP-' . $type . $this->objectId . '.', true);
190
    }
191
    
192
    /**
193
     * Creates a unique mime boundary and assigns it to the passed part's
194
     * Content-Type header with the passed mime type.
195
     * 
196
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
197
     * @param string $mimeType
198
     */
199 12
    private function setMimeHeaderBoundaryOnPart(MimePart $part, $mimeType)
200
    {
201 12
        $part->setRawHeader(
202 12
            'Content-Type',
203
            "$mimeType;\r\n\tboundary=\"" 
204 12
                . $this->getUniqueBoundary($mimeType) . '"'
205
        );
206 12
    }
207
    
208
    /**
209
     * Sets this message to be a multipart/alternative message, making space for
210
     * a second content part.
211
     * 
212
     * Creates a content part and assigns the content stream from the message to
213
     * that newly created part.
214
     */
215 2
    private function setMessageAsAlternative()
216
    {
217 2
        $contentPart = $this->mimePartFactory->newMimePart();
218 2
        $contentPart->attachContentResourceHandle($this->handle);
219 2
        $this->detachContentResourceHandle();
220 2
        $contentType = 'text/plain; charset="us-ascii"';
221 2
        $contentHeader = $this->getHeader('Content-Type');
222 2
        if ($contentHeader !== null) {
223 2
            $contentType = $contentHeader->getRawValue();
224
        }
225 2
        $contentPart->setRawHeader('Content-Type', $contentType);
226 2
        $this->setMimeHeaderBoundaryOnPart($this, 'multipart/alternative');
227 2
        $this->addPart($contentPart, 0);
228 2
    }
229
230
    /**
231
     * Returns the direct child of $alternativePart containing a part of
232
     * $mimeType.
233
     * 
234
     * Used for alternative mime types that have a multipart/mixed or
235
     * multipart/related child containing a content part of $mimeType, where
236
     * the whole mixed/related part should be removed.
237
     * 
238
     * @param string $mimeType the content-type to find below $alternativePart
239
     * @param MimePart $alternativePart The multipart/alternative part to look
240
     *        under
241
     * @return boolean|MimePart false if a part is not found
242
     */
243 10
    private function getContentPartContainerFromAlternative($mimeType, MimePart $alternativePart)
244
    {
245 10
        $part = $alternativePart->getPart(0, PartFilter::fromInlineContentType($mimeType));
246 10
        $contPart = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $contPart is dead and can be removed.
Loading history...
247
        do {
248 10
            if ($part === null) {
249
                return false;
250
            }
251 10
            $contPart = $part;
252 10
            $part = $part->getParent();
253 10
        } while ($part !== $alternativePart);
254 10
        return $contPart;
255
    }
256
    
257
    /**
258
     * Moves all parts under $from into this message except those with a
259
     * content-type equal to $exceptMimeType.  If the message is not a
260
     * multipart/mixed message, it is set to multipart/mixed first.
261
     * 
262
     * @param MimePart $from
263
     * @param string $exceptMimeType
264
     */
265 6
    private function moveAllPartsAsAttachmentsExcept(MimePart $from, $exceptMimeType)
266
    {
267 6
        $parts = $from->getAllParts(new PartFilter([
268 6
            'multipart' => PartFilter::FILTER_EXCLUDE,
269
            'headers' => [
270 6
                PartFilter::FILTER_EXCLUDE => [
271 6
                    'Content-Type' => $exceptMimeType
272
                ]
273
            ]
274
        ]));
275 6
        if ($this->getHeaderValue('Content-Type') !== 'multipart/mixed') {
276 5
            $this->setMessageAsMixed();
277
        }
278 6
        foreach ($parts as $part) {
279 2
            $from->removePart($part);
280 2
            $this->addPart($part);
281
        }
282 6
    }
283
284
    /**
285
     * Removes all parts of $mimeType from $alternativePart.
286
     * 
287
     * If $alternativePart contains a multipart/mixed or multipart/relative part
288
     * with other parts of different content-types, the multipart part is
289
     * removed, and parts of different content-types can optionally be moved to
290
     * the main message part.
291
     * 
292
     * @param string $mimeType
293
     * @param MimePart $alternativePart
294
     * @param bool $keepOtherContent
295
     * @return bool
296
     */
297 6
    private function removeAllContentPartsFromAlternative($mimeType, $alternativePart, $keepOtherContent)
298
    {
299 6
        $rmPart = $this->getContentPartContainerFromAlternative($mimeType, $alternativePart);
300 6
        if ($rmPart === false) {
0 ignored issues
show
introduced by
The condition $rmPart === false can never be true.
Loading history...
301
            return false;
302
        }
303 6
        if ($keepOtherContent) {
304 6
            $this->moveAllPartsAsAttachmentsExcept($rmPart, $mimeType);
305 6
            $alternativePart = $this->getPart(0, PartFilter::fromInlineContentType('multipart/alternative'));
306
        } else {
307
            $rmPart->removeAllParts();
308
        }
309 6
        $this->removePart($rmPart);
310 6
        if ($alternativePart !== null) {
311 6
            if ($alternativePart->getChildCount() === 1) {
312 6
                $this->replacePart($alternativePart, $alternativePart->getChild(0));
313
            } elseif ($alternativePart->getChildCount() === 0) {
314
                $this->removePart($alternativePart);
315
            }
316
        }
317 6
        while ($this->getChildCount() === 1) {
318 3
            $this->replacePart($this, $this->getChild(0));
319
        }
320 6
        return true;
321
    }
322
    
323
    /**
324
     * Removes the content part of the message with the passed mime type.  If
325
     * there is a remaining content part and it is an alternative part of the
326
     * main message, the content part is moved to the message part.
327
     * 
328
     * If the content part is part of an alternative part beneath the message,
329
     * the alternative part is replaced by the remaining content part,
330
     * optionally keeping other parts if $keepOtherContent is set to true.
331
     * 
332
     * @param string $mimeType
333
     * @param bool $keepOtherContent
334
     * @return boolean true on success
335
     */
336 6
    protected function removeAllContentPartsByMimeType($mimeType, $keepOtherContent = false)
337
    {
338 6
        $alt = $this->getPart(0, PartFilter::fromInlineContentType('multipart/alternative'));
339 6
        if ($alt !== null) {
340 6
            return $this->removeAllContentPartsFromAlternative($mimeType, $alt, $keepOtherContent);
341
        }
342
        $this->removeAllParts(PartFilter::fromInlineContentType($mimeType));
343
        return true;
344
    }
345
    
346
    /**
347
     * Removes the 'inline' part with the passed contentType, at the given index
348
     * defaulting to the first 
349
     * 
350
     * @param string $contentType
351
     * @param int $index
352
     * @return boolean true on success
353
     */
354 5
    protected function removePartByMimeType($mimeType, $index = 0)
355
    {
356 5
        $parts = $this->getAllParts(PartFilter::fromInlineContentType($mimeType));
357 5
        $alt = $this->getPart(0, PartFilter::fromInlineContentType('multipart/alternative'));
358 5
        if ($parts === null || !isset($parts[$index])) {
359
            return false;
360 5
        } elseif (count($parts) === 1) {
361 5
            return $this->removeAllContentPartsByMimeType($mimeType, true);
362
        }
363 1
        $part = $parts[$index];
364 1
        $this->removePart($part);
365 1
        if ($alt !== null && $alt->getChildCount() === 1) {
366
            $this->replacePart($alt, $alt->getChild(0));
367
        }
368 1
        return true;
369
    }
370
    
371
    /**
372
     * Creates a new mime part as a multipart/alternative and assigns the passed
373
     * $contentPart as a part below it before returning it.
374
     * 
375
     * @param MimePart $contentPart
376
     * @return MimePart the alternative part
377
     */
378 2
    private function createAlternativeContentPart(MimePart $contentPart)
379
    {
380 2
        $altPart = $this->mimePartFactory->newMimePart();
381 2
        $this->setMimeHeaderBoundaryOnPart($altPart, 'multipart/alternative');
382 2
        $this->removePart($contentPart);
383 2
        $this->addPart($altPart, 0);
384 2
        $altPart->addPart($contentPart, 0);
385 2
        return $altPart;
386
    }
387
388
    /**
389
     * Copies type headers (Content-Type, Content-Disposition,
390
     * Content-Transfer-Encoding) from the $from MimePart to $to.  Attaches the
391
     * content resource handle of $from to $to, and loops over child parts,
392
     * removing them from $from and adding them to $to.
393
     * 
394
     * @param MimePart $from
395
     * @param MimePart $to
396
     */
397 6
    private function movePartContentAndChildrenToPart(MimePart $from, MimePart $to)
398
    {
399 6
        $this->copyTypeHeadersFromPartToPart($from, $to);
400 6
        $to->attachContentResourceHandle($from->getContentResourceHandle());
401 6
        $from->detachContentResourceHandle();
402 6
        foreach ($from->getChildParts() as $child) {
403 6
            $from->removePart($child);
404 6
            $to->addPart($child);
405
        }
406 6
    }
407
408
    /**
409
     * Replaces the $part MimePart with $replacement.
410
     * 
411
     * Essentially removes $part from its parent, and adds $replacement in its
412
     * same position.  If $part is this Message, its type headers are moved from
413
     * this message to $replacement, the content resource is moved, and children
414
     * are assigned to $replacement.
415
     * 
416
     * @param MimePart $part
417
     * @param MimePart $replacement
418
     */
419 6
    private function replacePart(MimePart $part, MimePart $replacement)
420
    {
421 6
        $this->removePart($replacement);
422 6
        if ($part === $this) {
0 ignored issues
show
introduced by
The condition $part === $this can never be true.
Loading history...
423 3
            $this->movePartContentAndChildrenToPart($replacement, $part);
424 3
            return;
425
        }
426 6
        $parent = $part->getParent();
427 6
        $position = $parent->removePart($part);
428 6
        $parent->addPart($replacement, $position);
429 6
    }
430
431
    /**
432
     * Copies Content-Type, Content-Disposition and Content-Transfer-Encoding
433
     * headers from the $from header into the $to header. If the Content-Type
434
     * header isn't defined in $from, defaults to text/plain and
435
     * quoted-printable.
436
     * 
437
     * @param \ZBateson\MailMimeParser\Message\MimePart $from
438
     * @param \ZBateson\MailMimeParser\Message\MimePart $to
439
     */
440 17
    private function copyTypeHeadersFromPartToPart(MimePart $from, MimePart $to)
441
    {
442 17
        $typeHeader = $from->getHeader('Content-Type');
443 17
        if ($typeHeader !== null) {
444 16
            $to->setRawHeader('Content-Type', $typeHeader->getRawValue());
445 16
            $encodingHeader = $from->getHeader('Content-Transfer-Encoding');
446 16
            if ($encodingHeader !== null) {
447 6
                $to->setRawHeader('Content-Transfer-Encoding', $encodingHeader->getRawValue());
448
            }
449 16
            $dispositionHeader = $from->getHeader('Content-Disposition');
450 16
            if ($dispositionHeader !== null) {
451 16
                $to->setRawHeader('Content-Disposition', $dispositionHeader->getRawValue());
452
            }
453
        } else {
454 2
            $to->setRawHeader('Content-Type', 'text/plain;charset=us-ascii');
455 2
            $to->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
456
        }
457 17
    }
458
    
459
    /**
460
     * Creates a new content part from the passed part, allowing the part to be
461
     * used for something else (e.g. changing a non-mime message to a multipart
462
     * mime message).
463
     * 
464
     * @param MimePart $part
465
     * @return MimePart the newly-created MimePart   
466
    */
467 4
    private function createNewContentPartFromPart(MimePart $part)
468
    {
469 4
        $contPart = $this->mimePartFactory->newMimePart();
470 4
        $this->copyTypeHeadersFromPartToPart($part, $contPart);
471 4
        $contPart->attachContentResourceHandle($part->handle);
472 4
        $part->detachContentResourceHandle();
473 4
        return $contPart;
474
    }
475
    
476
    /**
477
     * Creates a new part out of the current contentPart and sets the message's
478
     * type to be multipart/mixed.
479
     */
480 10
    private function setMessageAsMixed()
481
    {
482 10
        if ($this->isMultiPart()) {
483 6
            $part = $this->mimePartFactory->newMimePart();
484 6
            $this->movePartContentAndChildrenToPart($this, $part);
485 6
            $this->addPart($part, 0);
486 4
        } elseif ($this->handle !== null) {
0 ignored issues
show
introduced by
The condition $this->handle !== null can never be false.
Loading history...
487 4
            $part = $this->createNewContentPartFromPart($this);
488 4
            $this->addPart($part, 0);
489
        }
490 10
        $this->setMimeHeaderBoundaryOnPart($this, 'multipart/mixed');
491 10
        $this->removeHeader('Content-Transfer-Encoding');
492 10
        $this->removeHeader('Content-Disposition');
493 10
    }
494
    
495
    /**
496
     * This function makes space by moving the main message part down one level.
497
     * 
498
     * The content-type, content-disposition and content-transfer-encoding
499
     * headers are copied from this message to the newly created part, the 
500
     * resource handle is moved and detached, any attachments and content parts
501
     * with parents set to this message get their parents set to the newly
502
     * created part.
503
     */
504 8
    private function makeSpaceForMultipartSignedMessage()
505
    {
506 8
        $this->enforceMime();
507 8
        $messagePart = $this->mimePartFactory->newMimePart();
508
509 8
        $this->copyTypeHeadersFromPartToPart($this, $messagePart);
510 8
        $messagePart->attachContentResourceHandle($this->handle);
511 8
        $this->detachContentResourceHandle();
512
        
513 8
        foreach ($this->getChildParts() as $part) {
514 5
            $this->removePart($part);
515 5
            $messagePart->addPart($part);
516
        }
517 8
        $this->addPart($messagePart, 0);
518 8
    }
519
    
520
    /**
521
     * Creates and returns a new MimePart for the signature part of a
522
     * multipart/signed message
523
     * 
524
     * @param string $body
525
     */
526 9
    public function createSignaturePart($body)
527
    {
528 9
        $signedPart = $this->getSignaturePart();
529 9
        if ($signedPart === null) {
530 8
            $signedPart = $this->mimePartFactory->newMimePart();
531 8
            $this->addPart($signedPart);
532
        }
533 9
        $signedPart->setRawHeader(
534 9
            'Content-Type',
535 9
            $this->getHeaderParameter('Content-Type', 'protocol')
536
        );
537 9
        $signedPart->setContent($body);
538 9
    }
539
540
    /**
541
     * Loops over parts of this message and sets the content-transfer-encoding
542
     * header to quoted-printable for text/* mime parts, and to base64
543
     * otherwise for parts that are '8bit' encoded.
544
     * 
545
     * Used for multipart/signed messages which doesn't support 8bit transfer
546
     * encodings.
547
     */
548 9
    private function overwrite8bitContentEncoding()
549
    {
550 9
        $parts = $this->getAllParts(new PartFilter([
551 9
            'headers' => [ PartFilter::FILTER_INCLUDE => [
552
                'Content-Transfer-Encoding' => '8bit'
553
            ] ]
554
        ]));
555 9
        foreach ($parts as $part) {
556 2
            $contentType = strtolower($part->getHeaderValue('Content-Type', 'text/plain'));
557 2
            if ($contentType === 'text/plain' || $contentType === 'text/html') {
558 2
                $part->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
559
            } else {
560 2
                $part->setRawHeader('Content-Transfer-Encoding', 'base64');
561
            }
562
        }
563 9
    }
564
    
565
    /**
566
     * Ensures a non-text part comes first in a signed multipart/alternative
567
     * message as some clients seem to prefer the first content part if the
568
     * client doesn't understand multipart/signed.
569
     */
570 9
    private function ensureHtmlPartFirstForSignedMessage()
571
    {
572 9
        $alt = $this->getPartByMimeType('multipart/alternative');
573 9
        if ($alt !== null) {
574 4
            $cont = $this->getContentPartContainerFromAlternative('text/html', $alt);
575 4
            $pos = array_search($cont, $alt->parts, true);
576 4
            if ($pos !== false && $pos !== 0) {
577 4
                $tmp = $alt->parts[0];
578 4
                $alt->parts[0] = $alt->parts[$pos];
579 4
                $alt->parts[$pos] = $tmp;
580
            }
581
        }
582 9
    }
583
    
584
    /**
585
     * Turns the message into a multipart/signed message, moving the actual
586
     * message into a child part, sets the content-type of the main message to
587
     * multipart/signed and adds a signature part as well.
588
     * 
589
     * @param string $micalg The Message Integrity Check algorithm being used
590
     * @param string $protocol The mime-type of the signature body
591
     */
592 9
    public function setAsMultipartSigned($micalg, $protocol)
593
    {
594 9
        $contentType = $this->getHeaderValue('Content-Type', 'text/plain');
595 9
        if (strcasecmp($contentType, 'multipart/signed') !== 0) {
596 8
            $this->makeSpaceForMultipartSignedMessage();
597 8
            $this->removeHeader('Content-Disposition');
598 8
            $this->removeHeader('Content-Transfer-Encoding');
599
        }
600 9
        $boundary = $this->getUniqueBoundary('multipart/signed');
601 9
        $this->setRawHeader(
602 9
            'Content-Type',
603 9
            "multipart/signed;\r\n\tboundary=\"$boundary\";\r\n\tmicalg=\"$micalg\"; protocol=\"$protocol\""
604
        );
605 9
        $this->overwrite8bitContentEncoding();
606 9
        $this->ensureHtmlPartFirstForSignedMessage();
607 9
        $this->createSignaturePart('Not set');
608 9
    }
609
    
610
    /**
611
     * Returns the signed part or null if not set.
612
     * 
613
     * @return \ZBateson\MailMimeParser\Message\MimePart
614
     */
615 19
    public function getSignaturePart()
616
    {
617 19
        $contentType = $this->getHeaderValue('Content-Type', 'text/plain');
618 19
        if (strcasecmp($contentType, 'multipart/signed') === 0) {
619 19
            return $this->getChild(1);
620
        } else {
621
            return null;
622
        }
623
    }
624
    
625
    /**
626
     * Returns a string containing the original message's signed part, useful
627
     * for verifying the email.
628
     * 
629
     * If the signed part of the message ends in a final empty line, the line is
630
     * removed as it's considered part of the signature's mime boundary.  From
631
     * RFC-3156:
632
     * 
633
     * Note: The accepted OpenPGP convention is for signed data to end
634
     * with a <CR><LF> sequence.  Note that the <CR><LF> sequence
635
     * immediately preceding a MIME boundary delimiter line is considered
636
     * to be part of the delimiter in [3], 5.1.  Thus, it is not part of
637
     * the signed data preceding the delimiter line.  An implementation
638
     * which elects to adhere to the OpenPGP convention has to make sure
639
     * it inserts a <CR><LF> pair on the last line of the data to be
640
     * signed and transmitted (signed message and transmitted message
641
     * MUST be identical).
642
     * 
643
     * The additional line should be inserted by the signer -- for verification
644
     * purposes if it's missing, it would seem the content part would've been
645
     * signed without a last <CR><LF>.
646
     * 
647
     * @return string or null if the message doesn't have any children, or the
648
     *      child returns null for getOriginalStreamHandle
649
     */
650 14
    public function getOriginalMessageStringForSignatureVerification()
651
    {
652 14
        $child = $this->getChild(0);
653 14
        if ($child !== null && $child->getOriginalStreamHandle() !== null) {
654 14
            $normalized = preg_replace(
655 14
                '/\r\n|\r|\n/',
656 14
                "\r\n",
657 14
                stream_get_contents($child->getOriginalStreamHandle())
658
            );
659 14
            $len = strlen($normalized);
660 14
            if ($len > 0 && strrpos($normalized, "\r\n") == $len - 2) {
661 14
                return substr($normalized, 0, -2);
662
            }
663
            return $normalized;
664
        }
665
        return null;
666
    }
667
    
668
    /**
669
     * Enforces the message to be a mime message for a non-mime (e.g. uuencoded
670
     * or unspecified) message.  If the message has uuencoded attachments, sets
671
     * up the message as a multipart/mixed message and creates a content part.
672
     */
673 12
    private function enforceMime()
674
    {
675 12
        if (!$this->isMime()) {
676 2
            if ($this->getAttachmentCount()) {
677 2
                $this->setMessageAsMixed();
678
            } else {
679
                $this->setRawHeader('Content-Type', "text/plain;\r\n\tcharset=\"us-ascii\"");
680
            }
681 2
            $this->setRawHeader('Mime-Version', '1.0');
682
        }
683 12
    }
684
    
685
    /**
686
     * Creates a multipart/related part out of 'inline' children of $parent and
687
     * returns it.
688
     * 
689
     * @param MimePart $parent
690
     * @return MimePart
691
     */
692
    private function createMultipartRelatedPartForInlineChildrenOf(MimePart $parent)
693
    {
694
        $relatedPart = $this->mimePartFactory->newMimePart();
695
        $this->setMimeHeaderBoundaryOnPart($relatedPart, 'multipart/related');
696
        foreach ($parent->getChildParts(PartFilter::fromDisposition('inline', PartFilter::FILTER_EXCLUDE)) as $part) {
697
            $this->removePart($part);
698
            $relatedPart->addPart($part);
699
        }
700
        $parent->addPart($relatedPart, 0);
701
        return $relatedPart;
702
    }
703
704
    /**
705
     * Finds an alternative inline part in the message and returns it if one
706
     * exists.
707
     * 
708
     * If the passed $mimeType is text/plain, searches for a text/html part.
709
     * Otherwise searches for a text/plain part to return.
710
     * 
711
     * @param string $mimeType
712
     * @return MimeType or null if not found
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\MimeType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
713
     */
714 4
    private function findOtherContentPartFor($mimeType)
715
    {
716 4
        $altPart = $this->getPart(
717 4
            0,
718 4
            PartFilter::fromInlineContentType(($mimeType === 'text/plain') ? 'text/html' : 'text/plain')
719
        );
720 4
        if ($altPart !== null && $altPart->getParent() !== null && $altPart->getParent()->isMultiPart()) {
721 2
            $altPartParent = $altPart->getParent();
722 2
            if ($altPartParent->getPartCount(PartFilter::fromDisposition('inline', PartFilter::FILTER_EXCLUDE)) !== 1) {
723
                $altPart = $this->createMultipartRelatedPartForInlineChildrenOf($altPartParent);
724
            }
725
        }
726 4
        return $altPart;
727
    }
728
    
729
    /**
730
     * Creates a new content part for the passed mimeType and charset, making
731
     * space by creating a multipart/alternative if needed
732
     * 
733
     * @param string $mimeType
734
     * @param string $charset
735
     * @return \ZBateson\MailMimeParser\Message\MimePart
736
     */
737 4
    private function createContentPartForMimeType($mimeType, $charset)
738
    {
739 4
        $mimePart = $this->mimePartFactory->newMimePart();
740 4
        $mimePart->setRawHeader('Content-Type', "$mimeType;\r\n\tcharset=\"$charset\"");
741 4
        $mimePart->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
742 4
        $this->enforceMime();
743
        
744 4
        $altPart = $this->findOtherContentPartFor($mimeType);
745
        
746 4
        if ($altPart === $this) {
0 ignored issues
show
introduced by
The condition $altPart === $this can never be true.
Loading history...
747 2
            $this->setMessageAsAlternative();
748 2
            $this->addPart($mimePart);
749 2
        } elseif ($altPart !== null) {
750 2
            $mimeAltPart = $this->createAlternativeContentPart($altPart);
751 2
            $mimeAltPart->addPart($mimePart, 1);
752
        } else {
753 1
            $this->addPart($mimePart, 0);
754
        }
755
        
756 4
        return $mimePart;
757
    }
758
    
759
    /**
760
     * Either creates a mime part or sets the existing mime part with the passed
761
     * mimeType to $strongOrHandle.
762
     * 
763
     * @param string $mimeType
764
     * @param string|resource $stringOrHandle
765
     * @param string $charset
766
     */
767 5
    protected function setContentPartForMimeType($mimeType, $stringOrHandle, $charset)
768
    {
769 5
        $part = ($mimeType === 'text/html') ? $this->getHtmlPart() : $this->getTextPart();
770 5
        $handle = $this->getHandleForStringOrHandle($stringOrHandle);
771 5
        if ($part === null) {
772 4
            $part = $this->createContentPartForMimeType($mimeType, $charset);
773
        } else {
774 1
            $contentType = $part->getHeaderValue('Content-Type', 'text/plain');
775 1
            $part->setRawHeader('Content-Type', "$contentType;\r\n\tcharset=\"$charset\"");
776
        }
777 5
        $part->attachContentResourceHandle($handle);
778 5
    }
779
    
780
    /**
781
     * Sets the text/plain part of the message to the passed $stringOrHandle,
782
     * either creating a new part if one doesn't exist for text/plain, or
783
     * assigning the value of $stringOrHandle to an existing text/plain part.
784
     * 
785
     * The optional $charset parameter is the charset for saving to.
786
     * $stringOrHandle is expected to be in UTF-8 regardless of the target
787
     * charset.
788
     * 
789
     * @param string|resource $stringOrHandle
790
     * @param string $charset
791
     */
792 2
    public function setTextPart($stringOrHandle, $charset = 'UTF-8')
793
    {
794 2
        $this->setContentPartForMimeType('text/plain', $stringOrHandle, $charset);
795 2
    }
796
    
797
    /**
798
     * Sets the text/html part of the message to the passed $stringOrHandle,
799
     * either creating a new part if one doesn't exist for text/html, or
800
     * assigning the value of $stringOrHandle to an existing text/html part.
801
     * 
802
     * The optional $charset parameter is the charset for saving to.
803
     * $stringOrHandle is expected to be in UTF-8 regardless of the target
804
     * charset.
805
     * 
806
     * @param string|resource $stringOrHandle
807
     * @param string $charset
808
     */
809 4
    public function setHtmlPart($stringOrHandle, $charset = 'UTF-8')
810
    {
811 4
        $this->setContentPartForMimeType('text/html', $stringOrHandle, $charset);
812 4
    }
813
    
814
    /**
815
     * Removes the text/plain part of the message at the passed index if one
816
     * exists.  Returns true on success.
817
     * 
818
     * @return bool true on success
819
     */
820 3
    public function removeTextPart($index = 0)
821
    {
822 3
        return $this->removePartByMimeType('text/plain', $index);
823
    }
824
825
    /**
826
     * Removes all text/plain inline parts in this message, optionally keeping
827
     * other inline parts as attachments on the main message (defaults to
828
     * keeping them).
829
     * 
830
     * @param bool $keepOtherPartsAsAttachments
831
     * @return bool true on success
832
     */
833
    public function removeAllTextParts($keepOtherPartsAsAttachments = true)
834
    {
835
        return $this->removeAllContentPartsByMimeType('text/plain', $keepOtherPartsAsAttachments);
836
    }
837
    
838
    /**
839
     * Removes the html part of the message if one exists.  Returns true on
840
     * success.
841
     * 
842
     * @return bool true on success
843
     */
844 2
    public function removeHtmlPart($index = 0)
845
    {
846 2
        return $this->removePartByMimeType('text/html', $index);
847
    }
848
    
849
    /**
850
     * Removes all text/html inline parts in this message, optionally keeping
851
     * other inline parts as attachments on the main message (defaults to
852
     * keeping them).
853
     * 
854
     * @param bool $keepOtherPartsAsAttachments
855
     * @return bool true on success
856
     */
857 1
    public function removeAllHtmlParts($keepOtherPartsAsAttachments = true)
858
    {
859 1
        return $this->removeAllContentPartsByMimeType('text/html', $keepOtherPartsAsAttachments);
860
    }
861
    
862
    /**
863
     * Returns the attachment part at the given 0-based index, or null if none
864
     * is set.
865
     * 
866
     * @param int $index
867
     * @return \ZBateson\MailMimeParser\Message\MimePart
868
     */
869 7
    public function getAttachmentPart($index)
870
    {
871 7
        $attachments = $this->getAllAttachmentParts();
872 7
        if (!isset($attachments[$index])) {
873 2
            return null;
874
        }
875 5
        return $attachments[$index];
876
    }
877
    
878
    /**
879
     * Returns all attachment parts.
880
     * 
881
     * Attachments are any non-multipart, non-signature and non inline text or
882
     * html part (a text or html part with a Content-Disposition set to 
883
     * 'attachment' is considered an attachment).
884
     * 
885
     * @return \ZBateson\MailMimeParser\Message\MimePart[]
886
     */
887 55
    public function getAllAttachmentParts()
888
    {
889 55
        $parts = $this->getAllParts(
890 55
            new PartFilter([
891 55
                'multipart' => PartFilter::FILTER_EXCLUDE
892
            ])
893
        );
894 55
        return array_values(array_filter(
895 55
            $parts,
896 55
            function ($part) {
897
                return !(
898 55
                    $part->isTextPart()
899 55
                    && $part->getHeaderValue('Content-Disposition', 'inline') === 'inline'
900
                );
901 55
            }
902
        ));
903
    }
904
    
905
    /**
906
     * Returns the number of attachments available.
907
     * 
908
     * @return int
909
     */
910 52
    public function getAttachmentCount()
911
    {
912 52
        return count($this->getAllAttachmentParts());
913
    }
914
    
915
    /**
916
     * Removes the attachment with the given index
917
     * 
918
     * @param int $index
919
     */
920 2
    public function removeAttachmentPart($index)
921
    {
922 2
        $part = $this->getAttachmentPart($index);
923 2
        $this->removePart($part);
924 2
    }
925
    
926
    /**
927
     * Creates and returns a MimePart for use with a new attachment part being
928
     * created.
929
     * 
930
     * @return \ZBateson\MailMimeParser\Message\MimePart
931
     */
932 4
    protected function createPartForAttachment()
933
    {
934 4
        if ($this->isMime()) {
935 4
            $part = $this->mimePartFactory->newMimePart();
936 4
            $part->setRawHeader('Content-Transfer-Encoding', 'base64');
937 4
            if ($this->getHeaderValue('Content-Type') !== 'multipart/mixed') {
938 3
                $this->setMessageAsMixed();
939
            }
940 4
            return $part;
941
        }
942
        return $this->mimePartFactory->newUUEncodedPart();
943
    }
944
    
945
    /**
946
     * Adds an attachment part for the passed raw data string or handle and
947
     * given parameters.
948
     * 
949
     * @param string|handle $stringOrHandle
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\handle was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
950
     * @param strubg $mimeType
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\strubg was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
951
     * @param string $filename
952
     * @param string $disposition
953
     */
954 3
    public function addAttachmentPart($stringOrHandle, $mimeType, $filename = null, $disposition = 'attachment')
955
    {
956 3
        if ($filename === null) {
957
            $filename = 'file' . uniqid();
958
        }
959 3
        $filename = iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
960 3
        $part = $this->createPartForAttachment();
961 3
        $part->setRawHeader('Content-Type', "$mimeType;\r\n\tname=\"$filename\"");
962 3
        $part->setRawHeader('Content-Disposition', "$disposition;\r\n\tfilename=\"$filename\"");
963 3
        $part->attachContentResourceHandle($this->getHandleForStringOrHandle($stringOrHandle));
964 3
        $this->addPart($part);
965 3
    }
966
    
967
    /**
968
     * Adds an attachment part using the passed file.
969
     * 
970
     * Essentially creates a file stream and uses it.
971
     * 
972
     * @param string $file
973
     * @param string $mimeType
974
     * @param string $filename
975
     * @param string $disposition
976
     */
977 4
    public function addAttachmentPartFromFile($file, $mimeType, $filename = null, $disposition = 'attachment')
978
    {
979 4
        $handle = fopen($file, 'r');
980 4
        if ($filename === null) {
981 2
            $filename = basename($file);
982
        }
983 4
        $filename = iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
984 4
        $part = $this->createPartForAttachment();
985 4
        $part->setRawHeader('Content-Type', "$mimeType;\r\n\tname=\"$filename\"");
986 4
        $part->setRawHeader('Content-Disposition', "$disposition;\r\n\tfilename=\"$filename\"");
987 4
        $part->attachContentResourceHandle($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $contentHandle of ZBateson\MailMimeParser\...ContentResourceHandle() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

987
        $part->attachContentResourceHandle(/** @scrutinizer ignore-type */ $handle);
Loading history...
988 4
        $this->addPart($part);
989 4
    }
990
    
991
    /**
992
     * Returns a resource handle where the 'inline' text/plain content at the
993
     * passed $index can be read or null if unavailable.
994
     * 
995
     * @param int $index
996
     * @return resource
997
     */
998 66
    public function getTextStream($index = 0)
999
    {
1000 66
        $textPart = $this->getTextPart($index);
1001 66
        if ($textPart !== null) {
1002 65
            return $textPart->getContentResourceHandle();
1003
        }
1004 1
        return null;
1005
    }
1006
    
1007
    /**
1008
     * Returns the content of the inline text/plain part at the given index.
1009
     * 
1010
     * Reads the entire stream content into a string and returns it.  Returns
1011
     * null if the message doesn't have an inline text part.
1012
     * 
1013
     * @param int $index
1014
     * @return string
1015
     */
1016 2
    public function getTextContent($index = 0)
1017
    {
1018 2
        $part = $this->getTextPart($index);
1019 2
        if ($part !== null) {
1020 2
            return $part->getContent();
1021
        }
1022
        return null;
1023
    }
1024
    
1025
    /**
1026
     * Returns a resource handle where the 'inline' text/html content at the
1027
     * passed $index can be read or null if unavailable.
1028
     * 
1029
     * @return resource
1030
     */
1031 32
    public function getHtmlStream($index = 0)
1032
    {
1033 32
        $htmlPart = $this->getHtmlPart($index);
1034 32
        if ($htmlPart !== null) {
1035 31
            return $htmlPart->getContentResourceHandle();
1036
        }
1037 1
        return null;
1038
    }
1039
    
1040
    /**
1041
     * Returns the content of the inline text/html part at the given index.
1042
     * 
1043
     * Reads the entire stream content into a string and returns it.  Returns
1044
     * null if the message doesn't have an inline html part.
1045
     * 
1046
     * @param int $index
1047
     * @return string
1048
     */
1049 1
    public function getHtmlContent($index = 0)
1050
    {
1051 1
        $part = $this->getHtmlPart($index);
1052 1
        if ($part !== null) {
1053 1
            return $part->getContent();
1054
        }
1055
        return null;
1056
    }
1057
    
1058
    /**
1059
     * Returns true if either a Content-Type or Mime-Version header are defined
1060
     * in this Message.
1061
     * 
1062
     * @return bool
1063
     */
1064 98
    public function isMime()
1065
    {
1066 98
        $contentType = $this->getHeaderValue('Content-Type');
1067 98
        $mimeVersion = $this->getHeaderValue('Mime-Version');
1068 98
        return ($contentType !== null || $mimeVersion !== null);
1069
    }
1070
    
1071
    /**
1072
     * Saves the message as a MIME message to the passed resource handle.
1073
     * 
1074
     * @param resource $handle
1075
     */
1076 89
    public function save($handle)
1077
    {
1078 89
        $this->messageWriter->writeMessageTo($this, $handle);
1079 89
    }
1080
    
1081
    /**
1082
     * Returns the content part of a signed message for a signature to be
1083
     * calculated on the message.
1084
     * 
1085
     * @return string
1086
     */
1087 9
    public function getSignableBody()
1088
    {
1089 9
        return $this->messageWriter->getSignableBody($this);
1090
    }
1091
    
1092
    /**
1093
     * Shortcut to call Message::save with a php://temp stream and return the
1094
     * written email message as a string.
1095
     * 
1096
     * @return string
1097
     */
1098
    public function __toString()
1099
    {
1100
        $handle = fopen('php://temp', 'r+');
1101
        $this->save($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of ZBateson\MailMimeParser\Message::save() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1101
        $this->save(/** @scrutinizer ignore-type */ $handle);
Loading history...
1102
        rewind($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1102
        rewind(/** @scrutinizer ignore-type */ $handle);
Loading history...
1103
        $str = stream_get_contents($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of stream_get_contents() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1103
        $str = stream_get_contents(/** @scrutinizer ignore-type */ $handle);
Loading history...
1104
        fclose($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1104
        fclose(/** @scrutinizer ignore-type */ $handle);
Loading history...
1105
        return $str;
1106
    }
1107
}
1108