Completed
Push — master ( 120ac8...ec99aa )
by Zaahid
03:17
created

overrideAlternativeMessageContentFromContentPart()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2.0094

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
ccs 13
cts 15
cp 0.8667
rs 9.4285
cc 2
eloc 13
nc 2
nop 1
crap 2.0094
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 ArrayIterator;
11
use Iterator;
12
13
/**
14
 * A parsed mime message with optional mime parts depending on its type.
15
 * 
16
 * A mime message may have any number of mime parts, and each part may have any
17
 * number of sub-parts, etc...
18
 * 
19
 * A message is a specialized "mime part". Namely the message keeps hold of text
20
 * versus HTML parts (and associated streams for easy access), holds a stream
21
 * for the entire message and all its parts, and maintains parts and their
22
 * relationships.
23
 *
24
 * @author Zaahid Bateson
25
 */
26
class Message extends MimePart
27
{
28
    /**
29
     * @var string unique ID used to identify the object to
30
     *      $this->partStreamRegistry when registering the stream.  The ID is
31
     *      used for opening stream parts with the mmp-mime-message "protocol".
32
     * 
33
     * @see \ZBateson\MailMimeParser\SimpleDi::registerStreamExtensions
34
     * @see \ZBateson\MailMimeParser\Stream\PartStream::stream_open
35
     */
36
    protected $objectId;
37
    
38
    /**
39
     * @var \ZBateson\MailMimeParser\MimePart represents the content portion of
40
     *      the email message.  It is assigned either a text or HTML part, or a
41
     *      MultipartAlternativePart
42
     */
43
    protected $contentPart;
44
    
45
    /**
46
     * @var \ZBateson\MailMimeParser\MimePart contains the body of the signature
47
     *      for a multipart/signed message.
48
     */
49
    protected $signedSignaturePart;
50
    
51
    /**
52
     * @var \ZBateson\MailMimeParser\MimePart The mixed part for a
53
     *      multipart/signed message if the message contains attachments
54
     */
55
    protected $signedMixedPart;
56
    
57
    /**
58
     * @var \ZBateson\MailMimeParser\MimePart[] array of non-content parts in
59
     *      this message 
60
     */
61
    protected $attachmentParts = [];
62
    
63
    /**
64
     * @var \ZBateson\MailMimeParser\MimePartFactory a MimePartFactory to create
65
     *      parts for attachments/content
66
     */
67
    protected $mimePartFactory;
68
    
69
    /**
70
     * @var bool set to true if a newline should be inserted before the next
71
     *      boundary (signed messages are finicky)
72
     */
73
    private $insertNewLineBeforeBoundary = false;
74
    
75
    /**
76
     * Convenience method to parse a handle or string into a Message without
77
     * requiring including MailMimeParser, instantiating it, and calling parse.
78
     * 
79
     * @param resource|string $handleOrString the resource handle to the input
80
     *        stream of the mime message, or a string containing a mime message
81
     */
82 1
    public static function from($handleOrString)
83
    {
84 1
        $mmp = new MailMimeParser();
85 1
        return $mmp->parse($handleOrString);
86
    }
87
    
88
    /**
89
     * Constructs a Message.
90
     * 
91
     * @param HeaderFactory $headerFactory
92
     * @param MimePartFactory $mimePartFactory
93
     */
94 86
    public function __construct(HeaderFactory $headerFactory, MimePartFactory $mimePartFactory)
95
    {
96 86
        parent::__construct($headerFactory);
97 86
        $this->mimePartFactory = $mimePartFactory;
98 86
        $this->objectId = uniqid();
99 86
    }
100
    
101
    /**
102
     * Returns the unique object ID registered with the PartStreamRegistry
103
     * service object.
104
     * 
105
     * @return string
106
     */
107 81
    public function getObjectId()
108
    {
109 81
        return $this->objectId;
110
    }
111
    
112
    /**
113
     * Loops through the parts parents to find if it's an alternative part or
114
     * an attachment.
115
     * 
116
     * @param \ZBateson\MailMimeParser\MimePart $part
117
     * @return boolean true if its been added
118
     */
119 21
    private function addToAlternativeContentPartFromParsed(MimePart $part)
120
    {
121 21
        $partType = strtolower($this->contentPart->getHeaderValue('Content-Type', 'text/plain'));
122 21
        if ($partType === 'multipart/alternative') {
123 21
            if ($this->contentPart === $this) {
124
                // already added in addPart
125 6
                return true;
126
            }
127 16
            $parent = $part->getParent();
128 16
            while ($parent !== null) {
129 16
                if ($parent === $this->contentPart) {
130 16
                    $parent->addPart($part);
131 16
                    return true;
132
                }
133 5
                $parent = $parent->getParent();
134 5
            }
135 2
        }
136 2
        return false;
137
    }
138
    
139
    /**
140
     * Returns true if the $part should be assigned as this message's main
141
     * content part.
142
     * 
143
     * @param \ZBateson\MailMimeParser\MimePart $part
144
     * @return bool
145
     */
146 81
    private function addContentPartFromParsed(MimePart $part)
147
    {
148 81
        $type = strtolower($part->getHeaderValue('Content-Type', 'text/plain'));
149
        // separate if statements for clarity
150 81
        if (!empty($this->contentPart)) {
151 21
            return $this->addToAlternativeContentPartFromParsed($part);
152
        }
153
        if ($type === 'multipart/alternative'
154 81
            || $type === 'text/plain'
155 81
            || $type === 'text/html') {
156 76
            $this->contentPart = $part;
157 76
            return true;
158
        }
159 44
        return false;
160
    }
161
    
162
    /**
163
     * Either adds the passed part to $this->textPart if its content type is
164
     * text/plain, to $this->htmlPart if it's text/html, or adds the part to the
165
     * parts array otherwise.
166
     * 
167
     * @param \ZBateson\MailMimeParser\MimePart $part
168
     */
169 84
    public function addPart(MimePart $part)
170
    {
171 84
        parent::addPart($part);
172 84
        $disposition = $part->getHeaderValue('Content-Disposition');
173 84
        $mtype = $this->getHeaderValue('Content-Type');
174 84
        $protocol = $this->getHeaderParameter('Content-Type', 'protocol');
175 84
        $type = $part->getHeaderValue('Content-Type');
176 84
        if (strcasecmp($mtype, 'multipart/signed') === 0 && $protocol !== null && $part->getParent() === $this && strcasecmp($protocol, $type) === 0) {
177 11
            $this->signedSignaturePart = $part;
178 11
            $this->createMultipartMixedForSignedMessage();
179 84
        } else if ((!empty($disposition) || !$this->addContentPartFromParsed($part)) && !$part->isMultiPart()) {
180 50
            $this->attachmentParts[] = $part;
181 50
        }
182 84
    }
183
    
184
    /**
185
     * Returns the content part (or null) for the passed mime type looking at
186
     * the assigned content part, and if it's a multipart/alternative part,
187
     * looking to find an alternative part of the passed mime type.
188
     * 
189
     * @param string $mimeType
190
     * @return \ZBateson\MailMimeParser\MimePart or null if not available
191
     */
192 71
    protected function getContentPartByMimeType($mimeType)
193
    {
194 71
        if (!isset($this->contentPart)) {
195 2
            return null;
196
        }
197 70
        $type = strtolower($this->contentPart->getHeaderValue('Content-Type', 'text/plain'));
198 70
        if ($type === 'multipart/alternative') {
199 18
            return $this->contentPart->getPartByMimeType($mimeType);
200 55
        } elseif ($type === $mimeType) {
201 55
            return $this->contentPart;
202
        }
203 11
        return null;
204
    }
205
    
206
    /**
207
     * Sets the content of the message to the content of the passed part, for a
208
     * message with a multipart/alternative content type where the other part
209
     * has been removed, and this is the only remaining part.
210
     * 
211
     * @param \ZBateson\MailMimeParser\MimePart $part
212
     */
213 2
    private function overrideAlternativeMessageContentFromContentPart(MimePart $part)
214
    {
215 2
        $contentType = $part->getHeaderValue('Content-Type');
216 2
        if ($contentType === null) {
217
            $contentType = 'text/plain; charset="us-ascii"';
218
        }
219 2
        $this->setRawHeader(
220 2
            'Content-Type',
221
            $contentType
222 2
        );
223 2
        $this->setRawHeader(
224 2
            'Content-Transfer-Encoding',
225
            'quoted-printable'
226 2
        );
227 2
        $this->attachContentResourceHandle($part->getContentResourceHandle());
228 2
        $part->detachContentResourceHandle();
229 2
        $this->removePart($part);
230 2
    }
231
    
232
    /**
233
     * Removes the passed MimePart as a content part.  If there's a remaining
234
     * part, either sets the content on this message if the message itself is a
235
     * multipart/alternative message, or overrides the contentPart with the
236
     * remaining part.
237
     * 
238
     * @param \ZBateson\MailMimeParser\MimePart $part
239
     */
240 3
    private function removePartFromAlternativeContentPart(MimePart $part)
241
    {
242 3
        $this->removePart($part);
243 3
        $this->contentPart->removePart($part);
244 3
        if ($this->contentPart === $this) {
245 2
            $this->overrideAlternativeMessageContentFromContentPart($this->getPart(1));
0 ignored issues
show
Bug introduced by
It seems like $this->getPart(1) can be null; however, overrideAlternativeMessageContentFromContentPart() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
246 3
        } elseif ($this->contentPart->getPartCount() === 1) {
247 1
            $this->removePart($this->contentPart);
248 1
            $this->contentPart = $this->contentPart->getPart(0);
249 1
            $this->contentPart->setParent($this);
250 1
        }
251 3
    }
252
    
253
    /**
254
     * Loops over children of the content part looking for a part with the
255
     * passed mime type, then proceeds to remove it by calling
256
     * removePartFromAlternativeContentPart.
257
     * 
258
     * @param string $contentType
259
     * @return boolean true on success
260
     */
261 3
    private function removeContentPartFromAlternative($contentType)
262
    {
263 3
        $parts = $this->contentPart->getAllParts();
264 3
        foreach ($parts as $part) {
265 3
            $type = $part->getHeaderValue('Content-Type', 'text/plain');
266 3
            if (strcasecmp($type, $contentType) === 0) {
267 3
                $this->removePartFromAlternativeContentPart($part);
268 3
                return true;
269
            }
270 3
        }
271
        return false;
272
    }
273
    
274
    /**
275
     * Removes the content part of the message with the passed mime type.  If
276
     * there is a remaining content part and it is an alternative part of the
277
     * main message, the content part is moved to the message part.
278
     * 
279
     * If the content part is part of an alternative part beneath the message,
280
     * the alternative part is replaced by the remaining content part.
281
     * 
282
     * @param string $contentType
283
     * @return boolean true on success
284
     */
285 3
    protected function removeContentPart($contentType)
286
    {
287 3
        if (!isset($this->contentPart)) {
288
            return false;
289
        }
290 3
        $type = $this->contentPart->getHeaderValue('Content-Type', 'text/plain');
291 3
        if (strcasecmp($type, $contentType) === 0) {
292
            if ($this->contentPart === $this) {
293
                return false;
294
            }
295
            $this->removePart($this->contentPart);
296
            $this->contentPart = null;
297
            return true;
298
        }
299 3
        return $this->removeContentPartFromAlternative($contentType);
300
    }
301
    
302
    /**
303
     * Returns the text part (or null if none is set.)
304
     * 
305
     * @return \ZBateson\MailMimeParser\MimePart
306
     */
307 61
    public function getTextPart()
308
    {
309 61
        return $this->getContentPartByMimeType('text/plain');
310
    }
311
    
312
    /**
313
     * Returns the HTML part (or null if none is set.)
314
     * 
315
     * @return \ZBateson\MailMimeParser\MimePart
316
     */
317 33
    public function getHtmlPart()
318
    {
319 33
        return $this->getContentPartByMimeType('text/html');
320
    }
321
    
322
    /**
323
     * Returns an open resource handle for the passed string or resource handle.
324
     * 
325
     * For a string, creates a php://temp stream and returns it.
326
     * 
327
     * @param resource|string $stringOrHandle
328
     * @return resource
329
     */
330 5
    private function getHandleForStringOrHandle($stringOrHandle)
331
    {
332 5
        $tempHandle = fopen('php://temp', 'r+');
333 5
        if (is_string($stringOrHandle)) {
334 5
            fwrite($tempHandle, $stringOrHandle);
335 5
        } else {
336
            stream_copy_to_stream($stringOrHandle, $tempHandle);
337
        }
338 5
        rewind($tempHandle);
339 5
        return $tempHandle;
340
    }
341
    
342
    /**
343
     * Creates and returns a unique boundary.
344
     * 
345
     * @return string
346
     */
347 15
    private function getUniqueBoundary()
348
    {
349 15
        return uniqid('----=MMP-' . $this->objectId . '.', true);
350
    }
351
    
352
    /**
353
     * Creates a unique mime boundary and assigns it to the passed part's
354
     * Content-Type header with the passed mime type.
355
     * 
356
     * @param \ZBateson\MailMimeParser\MimePart $part
357
     * @param string $mimeType
358
     */
359 7
    private function setMimeHeaderBoundaryOnPart(MimePart $part, $mimeType)
360
    {
361 7
        $part->setRawHeader(
362 7
            'Content-Type',
363 7
            "$mimeType;\r\n\tboundary=\"" 
1 ignored issue
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $mimeType instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
364 7
                . $this->getUniqueBoundary() . "\""
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal \" does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
365 7
        );
366 7
    }
367
    
368
    /**
369
     * Sets this message to be a multipart/alternative message, making space for
370
     * another alternative content part.
371
     * 
372
     * Creates a content part and assigns the content stream from the message to
373
     * that newly created part.
374
     */
375 2
    private function setMessageAsAlternative()
376
    {
377 2
        $contentPart = $this->mimePartFactory->newMimePart();
378 2
        $contentPart->attachContentResourceHandle($this->handle);
379 2
        $this->detachContentResourceHandle();
380 2
        $contentType = 'text/plain; charset="us-ascii"';
381 2
        $contentHeader = $this->getHeader('Content-Type');
382 2
        if ($contentHeader !== null) {
383 2
            $contentType = $contentHeader->getRawValue();
384 2
        }
385 2
        $contentPart->setRawHeader('Content-Type', $contentType);
386 2
        $contentPart->setParent($this);
387 2
        $this->setMimeHeaderBoundaryOnPart($this, 'multipart/alternative');
388 2
        parent::addPart($contentPart);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (addPart() instead of setMessageAsAlternative()). Are you sure this is correct? If so, you might want to change this to $this->addPart().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
389 2
    }
390
    
391
    /**
392
     * Creates a new mime part as a multipart/alternative, assigning it to
393
     * $this->contentPart.  Adds the current contentPart below the newly created
394
     * alternative part.
395
     */
396 2
    private function createAlternativeContentPart()
397
    {
398 2
        $altPart = $this->mimePartFactory->newMimePart();
399 2
        $this->setMimeHeaderBoundaryOnPart($altPart, 'multipart/alternative');
400 2
        $this->contentPart->setParent($altPart);
401 2
        $altPart->addPart($this->contentPart);
402 2
        $this->contentPart = $altPart;
403 2
        $altPart->setParent($this);
404 2
        parent::addPart($altPart);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (addPart() instead of createAlternativeContentPart()). Are you sure this is correct? If so, you might want to change this to $this->addPart().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
405 2
    }
406
    
407
    /**
408
     * Copies Content-Type, Content-Disposition and Content-Transfer-Encoding
409
     * headers from the $from header into the $to header. If the Content-Type
410
     * header isn't defined in $from, defaults to text/plain and
411
     * quoted-printable.
412
     * 
413
     * @param \ZBateson\MailMimeParser\MimePart $from
414
     * @param \ZBateson\MailMimeParser\MimePart $to
415
     */
416 8
    private function copyTypeHeadersFromPartToPart(MimePart $from, MimePart $to)
417
    {
418 8
        $typeHeader = $from->getHeader('Content-Type');
419 8
        if (!empty($typeHeader)) {
420 8
            $to->setRawHeader('Content-Type', $typeHeader->getRawValue());
421 8
            $encodingHeader = $from->getHeader('Content-Transfer-Encoding');
422 8
            if (!empty($encodingHeader)) {
423 4
                $to->setRawHeader('Content-Transfer-Encoding', $encodingHeader->getRawValue());
424 4
            }
425 8
            $dispositionHeader = $from->getHeader('Content-Disposition');
426 8
            if (!empty($dispositionHeader)) {
427 1
                $to->setRawHeader('Content-Disposition', $dispositionHeader->getRawValue());
428 1
            }
429 8
        } else {
430
            $to->setRawHeader('Content-Type', 'text/plain;charset=us-ascii');
431
            $to->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
432
        }
433 8
    }
434
    
435
    /**
436
     * Creates a new content part from the passed part, allowing the part to be
437
     * used for something else (e.g. changing a non-mime message to a multipart
438
     * mime message).
439
     */
440 4
    private function createNewContentPartFromPart(MimePart $part)
441
    {
442 4
        $contPart = $this->mimePartFactory->newMimePart();
443 4
        $this->copyTypeHeadersFromPartToPart($part, $contPart);
444 4
        $contPart->attachContentResourceHandle($part->handle);
445 4
        $part->detachContentResourceHandle();
446 4
        return $contPart;
447
    }
448
    
449
    /**
450
     * Creates a new part out of the current contentPart and sets the message's
451
     * type to be multipart/mixed.
452
     */
453 4
    private function setMessageAsMixed()
454
    {
455 4
        $part = $this->createNewContentPartFromPart($this->contentPart);
456 4
        $this->removePart($this->contentPart);
457 4
        parent::addPart($part);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (addPart() instead of setMessageAsMixed()). Are you sure this is correct? If so, you might want to change this to $this->addPart().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
458 4
        $this->contentPart = $part;
459 4
        $this->setMimeHeaderBoundaryOnPart($this, 'multipart/mixed');
460 4
    }
461
    
462
    /**
463
     * Updates parents of the contentPart and any children, and sets
464
     * $this->contentPart to the passed $messagePart if the $this->contentPart
465
     * is set to $this
466
     * 
467
     * @param \ZBateson\MailMimeParser\MimePart $messagePart
468
     */
469 5
    private function updateContentPartForSignedMessage(MimePart $messagePart)
470
    {
471 5
        if ($this->contentPart === null) {
472 2
            foreach ($this->getAllParts() as $child) {
473 2
                if ($child === $this) {
474 2
                    continue;
475
                }
476
                $child->setParent($messagePart);
477 2
            }
478 5
        } elseif ($this->contentPart === $this) {
479 2
            $this->contentPart = $messagePart;
480 2
            foreach ($this->getAllParts() as $child) {
481 2
                if ($child === $this) {
482 2
                    continue;
483
                }
484 1
                $child->setParent($messagePart);
485 1
                array_unshift($this->contentPart->parts, $child);
486 2
            }
487 3
        } elseif ($this->contentPart->getParent() === $this) {
488
            $this->contentPart->setParent($messagePart);
489
        }
490 5
    }
491
    
492
    /**
493
     * This function makes space by moving the main message part down one level.
494
     * 
495
     * The content-type, content-disposition and content-transfer-encoding
496
     * headers are copied from this message to the newly created part, the 
497
     * resource handle is moved and detached, any attachments and content parts
498
     * with parents set to this message get their parents set to the newly
499
     * created part.
500
     */
501 5
    private function makeSpaceForMultipartSignedMessage()
502
    {
503 5
        $this->enforceMime();
504 5
        $messagePart = $this->mimePartFactory->newMimePart();
505 5
        $this->updateContentPartForSignedMessage($messagePart);
506 5
        $this->copyTypeHeadersFromPartToPart($this, $messagePart);
507 5
        $messagePart->attachContentResourceHandle($this->handle);
508 5
        $this->detachContentResourceHandle();
509 5
        $messagePart->setParent($this);
510 5
        foreach ($this->attachmentParts as $key => $part) {
511 3
            if ($part === $this) {
512 2
                $this->attachmentParts[$key] = $messagePart;
513 2
            }
514 3
            if ($part->getParent() === $this) {
515
                $part->setParent($messagePart);
516
            }
517 5
        }
518 5
        array_unshift($this->parts, $messagePart);
519 5
    }
520
    
521
    /**
522
     * Creates and returns a new MimePart for the signature part of a
523
     * multipart/signed message and assigns it to $this->signedSignaturePart.
524
     * 
525
     * @param string $protocol
0 ignored issues
show
Bug introduced by
There is no parameter named $protocol. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
526
     * @param string $body
527
     */
528 77
    public function createSignaturePart($body)
529
    {
530 7
        $signedPart = $this->mimePartFactory->newMimePart();
531 7
        $signedPart->setRawHeader(
532 7
            'Content-Type',
533 77
            $this->getHeaderParameter('Content-Type', 'protocol')
534 7
        );
535 7
        $signedPart->setContent($body);
536 7
        $this->parts[] = $signedPart;
537 7
        $signedPart->setParent($this);
538 7
        $this->signedSignaturePart = $signedPart;
539 7
    }
540
    
541
    /**
542
     * Creates a multipart/mixed MimePart assigns it to $this->signedMixedPart
543
     * if the message contains attachments.
544
     * 
545
     * @param array $parts
0 ignored issues
show
Bug introduced by
There is no parameter named $parts. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
546
     */
547 11
    private function createMultipartMixedForSignedMessage()
548 1
    {
549 11
        if (count($this->attachmentParts) === 0 || $this->signedMixedPart !== null) {
550 4
            return;
551
        }
552 7
        $mixed = $this->mimePartFactory->newMimePart();
553 7
        $mixed->setParent($this);
554 7
        $boundary = $this->getUniqueBoundary();
555 7
        $mixed->setRawHeader('Content-Type', "multipart/mixed;\r\n\tboundary=\"$boundary\"");
1 ignored issue
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $boundary instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
556 7
        foreach ($this->attachmentParts as $part) {
557 7
            $part->setParent($mixed);
558 7
        }
559 7
        if (!empty($this->contentPart)) {
560 5
            $this->contentPart->setParent($mixed);
561 5
        }
562 7
        $this->signedMixedPart = $mixed;
563 7
    }
564
    
565
    /**
566
     * Loops over parts of this message and sets the content-transfer-encoding
567
     * header to quoted-printable for text/* mime parts, and to base64
568
     * otherwise for parts that are '8bit' encoded.
569
     * 
570
     * Used for multipart/signed messages which doesn't support 8bit transfer
571
     * encodings.
572
     */
573 7
    private function overwrite8bitContentEncoding()
574
    {
575 7
        foreach ($this->parts as $part) {
576 7
            if ($part->getHeaderValue('Content-Transfer-Encoding') === '8bit') {
577 1
                if (preg_match('/text\/.*/', $part->getHeaderValue('Content-Type'))) {
578 1
                    $part->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
579 1
                } else {
580
                    $part->setRawHeader('Content-Transfer-Encoding', 'base64');
581
                }
582 1
            }
583 7
        }
584 7
    }
585
    
586
    /**
587
     * Ensures a non-text part comes first in a signed multipart/alternative
588
     * message as some clients seem to prefer the first content part if the
589
     * client doesn't understand multipart/signed.
590
     */
591 7
    private function ensureHtmlPartFirstForSignedMessage()
592
    {
593 7
        if (empty($this->contentPart)) {
594 2
            return;
595
        }
596 5
        $type = strtolower($this->contentPart->getHeaderValue('Content-Type', 'text/plain'));
597 5
        if ($type === 'multipart/alternative' && count($this->contentPart->parts) > 1) {
598 3
            if (strtolower($this->contentPart->parts[0]->getHeaderValue('Content-Type', 'text/plain')) === 'text/plain') {
599 2
                $tmp = $this->contentPart->parts[0];
600 2
                $this->contentPart->parts[0] = $this->contentPart->parts[1];
601 2
                $this->contentPart->parts[1] = $tmp;
602 2
            }
603 3
        }
604 5
    }
605
    
606
    /**
607
     * Turns the message into a multipart/signed message, moving the actual
608
     * message into a child part, sets the content-type of the main message to
609
     * multipart/signed and adds a signature part as well.
610
     * 
611
     * @param string $micalg The Message Integrity Check algorithm being used
612
     * @param string $protocol The mime-type of the signature body
613
     * @param string $body The signature signed according to the value of
0 ignored issues
show
Bug introduced by
There is no parameter named $body. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
614
     *        $protocol
615
     */
616 7
    public function setAsMultipartSigned($micalg, $protocol)
617
    {
618 7
        $contentType = $this->getHeaderValue('Content-Type', 'text/plain');
619 7
        if (strcasecmp($contentType, 'multipart/signed') !== 0 && strcasecmp($contentType,'multipart/mixed') !== 0) {
620 5
            $this->makeSpaceForMultipartSignedMessage();
621 5
        }
622 7
        $boundary = $this->getUniqueBoundary();
623 7
        $this->setRawHeader(
624 7
            'Content-Type',
625 7
            "multipart/signed;\r\n\tboundary=\"$boundary\";\r\n\tmicalg=\"$micalg\"; protocol=\"$protocol\""
3 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $boundary instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $micalg instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $protocol instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
626 7
        );
627 7
        $this->removeHeader('Content-Transfer-Encoding');
628 7
        $this->createMultipartMixedForSignedMessage();
629 7
        $this->overwrite8bitContentEncoding();
630 7
        $this->ensureHtmlPartFirstForSignedMessage();
631 7
        $this->createSignaturePart('Not set');
632 7
    }
633
    
634
    /**
635
     * Returns the signed part or null if not set.
636
     * 
637
     * @return \ZBateson\MailMimeParser\MimePart
638
     */
639 11
    public function getSignaturePart()
640
    {
641 11
        return $this->signedSignaturePart;
642
    }
643
    
644
    /**
645
     * Enforces the message to be a mime message for a non-mime (e.g. uuencoded
646
     * or unspecified) message.  If the message has uuencoded attachments, sets
647
     * up the message as a multipart/mixed message and creates a content part.
648
     */
649 9
    private function enforceMime()
650
    {
651 9
        if (!$this->isMime()) {
652 2
            if ($this->getAttachmentCount()) {
653 2
                $this->setMessageAsMixed();
654 2
            } else {
655
                $this->setRawHeader('Content-Type', "text/plain;\r\n\tcharset=\"us-ascii\"");
656
            }
657 2
            $this->setRawHeader('Mime-Version', '1.0');
658 2
        }
659 9
    }
660
    
661
    /**
662
     * Creates a new content part for the passed mimeType and charset, making
663
     * space by creating a multipart/alternative if needed
664
     * 
665
     * @param string $mimeType
666
     * @param string $charset
667
     * @return \ZBateson\MailMimeParser\MimePart
668
     */
669 4
    private function createContentPartForMimeType($mimeType, $charset)
670
    {
671
        // wouldn't come here unless there's only one 'content part' anyway
672
        // if this->contentPart === $this, then $this is not a multipart/alternative
673
        // message
674 4
        $mimePart = $this->mimePartFactory->newMimePart();
675 4
        $cset = ($charset === null) ? 'UTF-8' : $charset;
676 4
        $mimePart->setRawHeader('Content-Type', "$mimeType;\r\n\tcharset=\"$cset\"");
2 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $mimeType instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $cset instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
677 4
        $mimePart->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
678 4
        $this->enforceMime();
679 4
        if ($this->contentPart === $this) {
680 2
            $this->setMessageAsAlternative();
681 2
            $mimePart->setParent($this->contentPart);
682 2
            parent::addPart($mimePart);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (addPart() instead of createContentPartForMimeType()). Are you sure this is correct? If so, you might want to change this to $this->addPart().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
683 4
        } elseif ($this->contentPart !== null) {
684 2
            $this->createAlternativeContentPart();
685 2
            $mimePart->setParent($this->contentPart);
686 2
            $this->contentPart->addPart($mimePart);
687 2
        } else {
688 1
            $this->contentPart = $mimePart;
689 1
            $mimePart->setParent($this);
690 1
            parent::addPart($mimePart);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (addPart() instead of createContentPartForMimeType()). Are you sure this is correct? If so, you might want to change this to $this->addPart().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
691
        }
692 4
        return $mimePart;
693
    }
694
    
695
    /**
696
     * Either creates a mime part or sets the existing mime part with the passed
697
     * mimeType to $strongOrHandle.
698
     * 
699
     * @param string $mimeType
700
     * @param string|resource $stringOrHandle
701
     * @param string $charset
702
     */
703 4
    protected function setContentPartForMimeType($mimeType, $stringOrHandle, $charset)
704
    {
705 4
        $part = $this->getTextPart();
706 4
        if ($mimeType === 'text/html') {
707 4
            $part = $this->getHtmlPart();
708 4
        }
709 4
        $handle = $this->getHandleForStringOrHandle($stringOrHandle);
710 4
        if ($part === null) {
711 4
            $part = $this->createContentPartForMimeType($mimeType, $charset);
712 4
        } elseif ($charset !== null) {
713
            $cset = ($charset === null) ? 'UTF-8' : $charset;
714
            $contentType = $part->getHeaderValue('Content-Type', 'text/plain');
715
            $part->setRawHeader('Content-Type', "$contentType;\r\n\tcharset=\"$cset\"");
2 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $contentType instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $cset instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
716
        }
717 4
        $part->attachContentResourceHandle($handle);
718 4
    }
719
    
720
    /**
721
     * Sets the text/plain part of the message to the passed $stringOrHandle,
722
     * either creating a new part if one doesn't exist for text/plain, or
723
     * assigning the value of $stringOrHandle to an existing text/plain part.
724
     * 
725
     * The optional $charset parameter is the charset for saving to.
726
     * $stringOrHandle is expected to be in UTF-8.
727
     * 
728
     * @param string|resource $stringOrHandle
729
     * @param string $charset
730
     */
731 1
    public function setTextPart($stringOrHandle, $charset = null)
732
    {
733 1
        $this->setContentPartForMimeType('text/plain', $stringOrHandle, $charset);
734 1
    }
735
    
736
    /**
737
     * Sets the text/html part of the message to the passed $stringOrHandle,
738
     * either creating a new part if one doesn't exist for text/html, or
739
     * assigning the value of $stringOrHandle to an existing text/html part.
740
     * 
741
     * The optional $charset parameter is the charset for saving to.
742
     * $stringOrHandle is expected to be in UTF-8.
743
     * 
744
     * @param string|resource $stringOrHandle
745
     * @param string $charset
746
     */
747 4
    public function setHtmlPart($stringOrHandle, $charset = null)
748
    {
749 4
        $this->setContentPartForMimeType('text/html', $stringOrHandle, $charset);
750 4
    }
751
    
752
    /**
753
     * Removes the text part of the message if one exists.  Returns true on
754
     * success.
755
     * 
756
     * @return bool true on success
757
     */
758 2
    public function removeTextPart()
759
    {
760 2
        return $this->removeContentPart('text/plain');
761
    }
762
    
763
    /**
764
     * Removes the html part of the message if one exists.  Returns true on
765
     * success.
766
     * 
767
     * @return bool true on success
768
     */
769 1
    public function removeHtmlPart()
770
    {
771 1
        return $this->removeContentPart('text/html');
772
    }
773
    
774
    /**
775
     * Returns the non-content part at the given 0-based index, or null if none
776
     * is set.
777
     * 
778
     * @param int $index
779
     * @return \ZBateson\MailMimeParser\MimePart
780
     */
781 7
    public function getAttachmentPart($index)
782
    {
783 7
        if (!isset($this->attachmentParts[$index])) {
784 2
            return null;
785
        }
786 5
        return $this->attachmentParts[$index];
787
    }
788
    
789
    /**
790
     * Returns all attachment parts.
791
     * 
792
     * @return \ZBateson\MailMimeParser\MimePart[]
793
     */
794 44
    public function getAllAttachmentParts()
795
    {
796 44
        return $this->attachmentParts;
797
    }
798
    
799
    /**
800
     * Returns the number of attachments available.
801
     * 
802
     * @return int
803
     */
804 45
    public function getAttachmentCount()
805
    {
806 45
        return count($this->attachmentParts);
807
    }
808
    
809
    /**
810
     * Removes the attachment with the given index
811
     * 
812
     * @param int $index
813
     */
814 2
    public function removeAttachmentPart($index)
815
    {
816 2
        $part = $this->attachmentParts[$index];
817 2
        $this->removePart($part);
818 2
        array_splice($this->attachmentParts, $index, 1);
819 2
    }
820
    
821
    /**
822
     * Creates and returns a MimePart for use with a new attachment part being
823
     * created.
824
     * 
825
     * @return \ZBateson\MailMimeParser\MimePart
826
     */
827 2
    protected function createPartForAttachment()
828
    {
829 2
        $part = null;
0 ignored issues
show
Unused Code introduced by
$part is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
830 2
        if ($this->isMime()) {
831 2
            $part = $this->mimePartFactory->newMimePart();
832 2
            $part->setRawHeader('Content-Transfer-Encoding', 'base64');
833 2
            if ($this->getHeaderValue('Content-Type') !== 'multipart/mixed') {
834 2
                $this->setMessageAsMixed();
835 2
            }
836 2
        } else {
837
            $part = $this->mimePartFactory->newUUEncodedPart();
0 ignored issues
show
Bug introduced by
The call to newUUEncodedPart() misses some required arguments starting with $mode.
Loading history...
838
        }
839 2
        return $part;
840
    }
841
    
842
    /**
843
     * Adds an attachment part for the passed raw data string or handle and
844
     * given parameters.
845
     * 
846
     * @param string|handle $stringOrHandle
847
     * @param strubg $mimeType
848
     * @param string $filename
849
     * @param string $disposition
850
     */
851 1
    public function addAttachmentPart($stringOrHandle, $mimeType, $filename = null, $disposition = 'attachment')
852
    {
853 1
        if ($filename === null) {
854
            $filename = 'file' . uniqid();
855
        }
856 1
        $filename = iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
857 1
        $part = $this->createPartForAttachment();
858 1
        $part->setRawHeader('Content-Type', "$mimeType;\r\n\tname=\"$filename\"");
2 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $mimeType instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $filename instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
859 1
        $part->setRawHeader('Content-Disposition', "$disposition;\r\n\tfilename=\"$filename\"");
2 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $disposition instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $filename instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
860 1
        $part->setParent($this);
861 1
        $part->attachContentResourceHandle($this->getHandleForStringOrHandle($stringOrHandle));
862 1
        $this->parts[] = $part;
863 1
        $this->attachmentParts[] = $part;
864 1
    }
865
    
866
    /**
867
     * Adds an attachment part using the passed file.
868
     * 
869
     * Essentially creates a file stream and uses it.
870
     * 
871
     * @param string $file
872
     * @param string $mimeType
873
     * @param string $filename
874
     * @param string $disposition
875
     */
876 2
    public function addAttachmentPartFromFile($file, $mimeType, $filename = null, $disposition = 'attachment')
877
    {
878 2
        $handle = fopen($file, 'r');
879 2
        if ($filename === null) {
880 2
            $filename = basename($file);
881 2
        }
882 2
        $filename = iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
883 2
        $part = $this->createPartForAttachment();
884 2
        $part->setRawHeader('Content-Type', "$mimeType;\r\n\tname=\"$filename\"");
2 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $mimeType instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $filename instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
885 2
        $part->setRawHeader('Content-Disposition', "$disposition;\r\n\tfilename=\"$filename\"");
2 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $disposition instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $filename instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
886 2
        $part->setParent($this);
887 2
        $part->attachContentResourceHandle($handle);
888 2
        $this->parts[] = $part;
889 2
        $this->attachmentParts[] = $part;
890 2
    }
891
    
892
    /**
893
     * Returns a resource handle where the text content can be read or null if
894
     * unavailable.
895
     * 
896
     * @return resource
897
     */
898 57
    public function getTextStream()
899
    {
900 57
        $textPart = $this->getTextPart();
901 57
        if (!empty($textPart)) {
902 56
            return $textPart->getContentResourceHandle();
903
        }
904 1
        return null;
905
    }
906
    
907
    /**
908
     * Returns the text content as a string.
909
     * 
910
     * Reads the entire stream content into a string and returns it.  Returns
911
     * null if the message doesn't have a text part.
912
     * 
913
     * @return string
914
     */
915 1
    public function getTextContent()
916
    {
917 1
        $stream = $this->getTextStream();
918 1
        if ($stream === null) {
919
            return null;
920
        }
921 1
        return stream_get_contents($stream);
922
    }
923
    
924
    /**
925
     * Returns a resource handle where the HTML content can be read or null if
926
     * unavailable.
927
     * 
928
     * @return resource
929
     */
930 27
    public function getHtmlStream()
931
    {
932 27
        $htmlPart = $this->getHtmlPart();
933 27
        if (!empty($htmlPart)) {
934 26
            return $htmlPart->getContentResourceHandle();
935
        }
936 1
        return null;
937
    }
938
    
939
    /**
940
     * Returns the HTML content as a string.
941
     * 
942
     * Reads the entire stream content into a string and returns it.  Returns
943
     * null if the message doesn't have an HTML part.
944
     * 
945
     * @return string
946
     */
947
    public function getHtmlContent()
948
    {
949
        $stream = $this->getHtmlStream();
950
        if ($stream === null) {
951
            return null;
952
        }
953
        return stream_get_contents($stream);
954
    }
955
    
956
    /**
957
     * Returns true if either a Content-Type or Mime-Version header are defined
958
     * in this Message.
959
     * 
960
     * @return bool
961
     */
962 81
    public function isMime()
963
    {
964 81
        $contentType = $this->getHeaderValue('Content-Type');
965 81
        $mimeVersion = $this->getHeaderValue('Mime-Version');
966 81
        return ($contentType !== null || $mimeVersion !== null);
967
    }
968
    
969
    /**
970
     * Writes out a mime boundary to the passed $handle
971
     * 
972
     * @param resource $handle
973
     * @param string $boundary
974
     * @param bool $isEnd
975
     */
976 51
    private function writeBoundary($handle, $boundary, $isEnd = false)
977
    {
978 51
        if ($this->insertNewLineBeforeBoundary) {
979 51
            fwrite($handle, "\r\n");
980 51
        }
981 51
        fwrite($handle, '--');
982 51
        fwrite($handle, $boundary);
983 51
        if ($isEnd) {
984 51
            fwrite($handle, "--\r\n");
985 51
        } else {
986 51
            fwrite($handle, "\r\n");
987
        }
988 51
        $this->insertNewLineBeforeBoundary = $isEnd;
989 51
    }
990
    
991
    /**
992
     * Writes out any necessary boundaries for the given $part if required based
993
     * on its $parent and $boundaryParent.
994
     * 
995
     * Also writes out end boundaries for the previous part if applicable.
996
     * 
997
     * @param resource $handle
998
     * @param \ZBateson\MailMimeParser\MimePart $part
999
     * @param \ZBateson\MailMimeParser\MimePart $parent
1000
     * @param \ZBateson\MailMimeParser\MimePart $boundaryParent
1001
     * @param string $boundary
1002
     */
1003 51
    private function writePartBoundaries($handle, MimePart $part, MimePart $parent, MimePart &$boundaryParent, $boundary)
1004
    {
1005 51
        if ($boundaryParent !== $parent && $boundaryParent !== $part) {
1006 19
            if ($boundaryParent !== null && $parent->getParent() !== $boundaryParent) {
1007 19
                $this->writeBoundary($handle, $boundary, true);
1008 19
            }
1009 19
            $boundaryParent = $parent;
1010 19
            $boundary = $boundaryParent->getHeaderParameter('Content-Type', 'boundary');
1011 19
        }
1012 51
        if ($boundaryParent !== null && $boundaryParent !== $part) {
1013 51
            $this->writeBoundary($handle, $boundary);
1014 51
        }
1015 51
    }
1016
    
1017
    /**
1018
     * Writes out the passed mime part, writing out any necessary mime
1019
     * boundaries.
1020
     * 
1021
     * @param resource $handle
1022
     * @param \ZBateson\MailMimeParser\MimePart $part
1023
     * @param \ZBateson\MailMimeParser\MimePart $parent
1024
     * @param \ZBateson\MailMimeParser\MimePart $boundaryParent
1025
     */
1026 77
    private function writePartTo($handle, MimePart $part, MimePart $parent, MimePart &$boundaryParent)
1027
    {
1028 77
        $boundary = $boundaryParent->getHeaderParameter('Content-Type', 'boundary');
1029 77
        if (!empty($boundary)) {
1030 51
            $this->writePartBoundaries($handle, $part, $parent, $boundaryParent, $boundary);
1031 51
            if ($part !== $this) {
1032 51
                $part->writeTo($handle);
1033 51
            } else {
1034 4
                $part->writeContentTo($handle);
1035
            }
1036 77
        } elseif ($part instanceof NonMimePart) {
1037 2
            fwrite($handle, "\r\n\r\n");
1038 2
            $part->writeContentTo($handle);
0 ignored issues
show
Bug introduced by
The method writeContentTo() cannot be called from this context as it is declared protected in class ZBateson\MailMimeParser\MimePart.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
1039 2
        } else {
1040 24
            $part->writeContentTo($handle);
1041
        }
1042 77
        $this->insertNewLineBeforeBoundary = $part->hasContent();
1043 77
    }
1044
    
1045
    /**
1046
     * Either returns $this for a non-text, non-html part, or returns
1047
     * $this->contentPart.
1048
     * 
1049
     * Note that if Content-Disposition is set on the passed part, $this is
1050
     * always returned.
1051
     * 
1052
     * @param \ZBateson\MailMimeParser\MimePart $part
1053
     * @return \ZBateson\MailMimeParser\MimePart
1054
     */
1055 77
    private function getWriteParentForPart(MimePart $part)
1056
    {
1057 77
        $type = strtolower($part->getHeaderValue('Content-Type', 'text/plain'));
1058 77
        $disposition = $part->getHeaderValue('Content-Disposition');
1059 77
        if (empty($disposition) && $this->contentPart !== $part && ($type === 'text/html' || $type === 'text/plain')) {
1060 19
            return $this->contentPart;
1061 77
        } elseif ($this->signedSignaturePart !== null) {
1062 11
            return $part->getParent();
1063
        }
1064 66
        return $this;
1065
    }
1066
    
1067
    /**
1068
     * Loops over parts of the message and writes them as an email to the
1069
     * provided $handle.
1070
     * 
1071
     * The function rewrites mime parts in a multipart-mime message to be either
1072
     * alternatives of text/plain and text/html, or attachments because
1073
     * MailMimeParser doesn't currently maintain the structure of the original
1074
     * message.  This means other alternative parts would be dropped to
1075
     * attachments, and multipart/related parts are completely ignored.
1076
     * 
1077
     * @param resource $handle the handle to write out to
1078
     * @param Iterator $partsIter an Iterator for parts to save
1079
     * @param \ZBateson\MailMimeParser\MimePart $curParent the current parent
1080
     */
1081 77
    protected function writePartsTo($handle, Iterator $partsIter, MimePart $curParent)
1082
    {
1083 77
        $this->insertNewLineBeforeBoundary = false;
1084 77
        $boundary = $curParent->getHeaderParameter('Content-Type', 'boundary');
1085 77
        while ($partsIter->valid()) {
1086 77
            $part = $partsIter->current();
1087 77
            $parent = $this->getWriteParentForPart($part);
1088 77
            $this->writePartTo($handle, $part, $parent, $curParent);
1089 77
            $partsIter->next();
1090 77
        }
1091 77
        if (!empty($boundary)) {
1092 51
            $this->writeBoundary($handle, $boundary, true);
1093 51
        }
1094 77
    }
1095
    
1096
    /**
1097
     * Saves the message as a MIME message to the passed resource handle.
1098
     * 
1099
     * The saved message is not guaranteed to be the same as the parsed message.
1100
     * Namely, for mime messages anything that is not text/html or text/plain
1101
     * will be moved into parts under the main 'message' as attachments, other
1102
     * alternative parts are dropped, and multipart/related parts are ignored
1103
     * (their contents are either moved under a multipart/alternative part or as
1104
     * attachments below the main multipart/mixed message).
1105
     * 
1106
     * @param resource $handle
1107
     */
1108 77
    public function save($handle)
1109
    {
1110 77
        $this->writeHeadersTo($handle);
1111 77
        $parts = [];
1112 77
        if (!empty($this->signedMixedPart)) {
1113 7
            $parts[] = $this->signedMixedPart;
1114 7
        }
1115 77 View Code Duplication
        if ($this->contentPart !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1116 71
            if ($this->contentPart->isMultiPart()) {
1117 19
                $parts[] = $this->contentPart;
1118 19
                $parts = array_merge($parts, $this->contentPart->getAllParts());
1119 19
            } else {
1120 52
                $parts[] = $this->contentPart;
1121
            }
1122 71
        }
1123 77
        if (!empty($this->attachmentParts)) {
1124 47
            $parts = array_merge($parts, $this->attachmentParts);
1125 47
        }
1126 77
        if (!empty($this->signedSignaturePart)) {
1127 11
            $parts[] = $this->signedSignaturePart;
1128 11
        }
1129 77
        $this->writePartsTo(
1130 77
            $handle,
1131 77
            new ArrayIterator($parts),
1132
            $this
1133 77
        );
1134 77
    }
1135
    
1136
    /**
1137
     * Writes out the content of the message into a string and returns it.
1138
     * 
1139
     * @return string
1140
     */
1141 7
    private function getSignableBodyFromParts(array $parts)
1142
    {
1143 7
        $handle = fopen('php://temp', 'r+');
1144 7
        $firstPart = array_shift($parts);
1145 7
        $firstPart->writeHeadersTo($handle);
1146 7
        $firstPart->writeContentTo($handle);
1147 7
        if (!empty($parts)) {
1148 6
            $this->writePartsTo(
1149 6
                $handle,
1150 6
                new ArrayIterator($parts),
1151
                $firstPart
1152 6
            );
1153 6
        }
1154 7
        rewind($handle);
1155 7
        $str = stream_get_contents($handle);
1156 7
        fclose($handle);
1157 7
        return $str;
1158
    }
1159
    
1160
    /**
1161
     * Returns the content part of a signed message for a signature to be
1162
     * calculated on the message.
1163
     * 
1164
     * @return string
1165
     */
1166 7
    public function getSignableBody()
1167
    {
1168 7
        $parts = [];
1169 7
        if (!empty($this->signedMixedPart)) {
1170 5
            $parts[] = $this->signedMixedPart;
1171 5
        }
1172 7 View Code Duplication
        if ($this->contentPart !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1173 5
            if ($this->contentPart->isMultiPart()) {
1174 3
                $parts[] = $this->contentPart;
1175 3
                $parts = array_merge($parts, $this->contentPart->getAllParts());
1176 3
            } else {
1177 2
                $parts[] = $this->contentPart;
1178
            }
1179 5
        }
1180 7
        if (!empty($this->attachmentParts)) {
1181 5
            $parts = array_merge($parts, $this->attachmentParts);
1182 5
        }
1183 7
        return $this->getSignableBodyFromParts($parts);
1184
    }
1185
    
1186
    /**
1187
     * Shortcut to call Message::save with a php://temp stream and return the
1188
     * written email message as a string.
1189
     * 
1190
     * @return string
1191
     */
1192
    public function __toString()
1193
    {
1194
        $handle = fopen('php://temp', 'r+');
1195
        $this->save($handle);
1196
        rewind($handle);
1197
        $str = stream_get_contents($handle);
1198
        fclose($handle);
1199
        return $str;
1200
    }
1201
}
1202