Completed
Push — master ( 5d4e1b...373de6 )
by Zaahid
11:37
created

Message::getHtmlContent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
crap 6
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 80
    public function __construct(HeaderFactory $headerFactory, MimePartFactory $mimePartFactory)
95
    {
96 80
        parent::__construct($headerFactory);
97 80
        $this->mimePartFactory = $mimePartFactory;
98 80
        $this->objectId = uniqid();
99 80
    }
100
    
101
    /**
102
     * Returns the unique object ID registered with the PartStreamRegistry
103
     * service object.
104
     * 
105
     * @return string
106
     */
107 75
    public function getObjectId()
108
    {
109 75
        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 = $this->contentPart->getHeaderValue('Content-Type');
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 3
        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 76
    private function addContentPartFromParsed(MimePart $part)
147
    {
148 76
        $type = strtolower($part->getHeaderValue('Content-Type', 'text/plain'));
149
        // separate if statements for clarity
150 76
        if (!empty($this->contentPart)) {
151 21
            return $this->addToAlternativeContentPartFromParsed($part);
152
        }
153
        if ($type === 'multipart/alternative'
154 76
            || $type === 'text/plain'
155 76
            || $type === 'text/html') {
156 74
            $this->contentPart = $part;
157 74
            return true;
158
        }
159 40
        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 78
    public function addPart(MimePart $part)
170
    {
171 78
        parent::addPart($part);
172 78
        $disposition = $part->getHeaderValue('Content-Disposition');
173 78
        $mtype = $this->getHeaderValue('Content-Type');
174 78
        $protocol = $this->getHeaderParameter('Content-Type', 'protocol');
175 78
        $type = $part->getHeaderValue('Content-Type');
176 78
        if ($mtype === 'multipart/signed' && $protocol !== null && $part->getParent() === $this && strcasecmp($protocol, $type) === 0) {
177 9
            $this->signedSignaturePart = $part;
178 9
            $this->createMultipartMixedForSignedMessage();
179 78
        } else if ((!empty($disposition) || !$this->addContentPartFromParsed($part)) && !$part->isMultiPart()) {
180 46
            $this->attachmentParts[] = $part;
181 46
        }
182 78
    }
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 69
    protected function getContentPartByMimeType($mimeType)
193
    {
194 69
        if (!isset($this->contentPart)) {
195 2
            return null;
196
        }
197 68
        $type = strtolower($this->contentPart->getHeaderValue('Content-Type', 'text/plain'));
198 68
        if ($type === 'multipart/alternative') {
199 18
            return $this->contentPart->getPartByMimeType($mimeType);
200 53
        } elseif ($type === $mimeType) {
201 53
            return $this->contentPart;
202
        }
203 10
        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 1
    private function overrideAlternativeMessageContentFromContentPart(MimePart $part)
214
    {
215 1
        $contentType = $part->getHeaderValue('Content-Type');
216 1
        if ($contentType === null) {
217
            $contentType = 'text/plain; charset="us-ascii"';
218
        }
219 1
        $this->setRawHeader(
220 1
            'Content-Type',
221
            $contentType
222 1
        );
223 1
        $this->setRawHeader(
224 1
            'Content-Transfer-Encoding',
225
            'quoted-printable'
226 1
        );
227 1
        $this->attachContentResourceHandle($part->getContentResourceHandle());
228 1
        $part->detachContentResourceHandle();
229 1
        $this->removePart($part);
230 1
        $this->removePart($this);
231 1
        $this->addPart($this);
232 1
    }
233
    
234
    /**
235
     * Removes the passed MimePart as a content part.  If there's a remaining
236
     * part, either sets the content on this message if the message itself is a
237
     * multipart/alternative message, or overrides the contentPart with the
238
     * remaining part.
239
     * 
240
     * @param \ZBateson\MailMimeParser\MimePart $part
241
     */
242 2
    private function removePartFromAlternativeContentPart(MimePart $part)
243
    {
244 2
        $this->removePart($part);
245 2
        $this->contentPart->removePart($part);
246 2
        if ($this->contentPart === $this) {
247 1
            $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...
248 2
        } elseif ($this->contentPart->getPartCount() === 1) {
249 1
            $this->removePart($this->contentPart);
250 1
            $this->contentPart = $this->contentPart->getPart(0);
251 1
            $this->contentPart->setParent($this);
252 1
        }
253 2
    }
254
    
255
    /**
256
     * Loops over children of the content part looking for a part with the
257
     * passed mime type, then proceeds to remove it by calling
258
     * removePartFromAlternativeContentPart.
259
     * 
260
     * @param string $contentType
261
     * @return boolean true on success
262
     */
263 2
    private function removeContentPartFromAlternative($contentType)
264
    {
265 2
        $parts = $this->contentPart->getAllParts();
266 2
        foreach ($parts as $part) {
267 2
            $type = strtolower($part->getHeaderValue('Content-Type', 'text/plain'));
268 2
            if ($type === $contentType) {
269 2
                $this->removePartFromAlternativeContentPart($part);
270 2
                return true;
271
            }
272 2
        }
273
        return false;
274
    }
275
    
276
    /**
277
     * Removes the content part of the message with the passed mime type.  If
278
     * there is a remaining content part and it is an alternative part of the
279
     * main message, the content part is moved to the message part.
280
     * 
281
     * If the content part is part of an alternative part beneath the message,
282
     * the alternative part is replaced by the remaining content part.
283
     * 
284
     * @param string $contentType
285
     * @return boolean true on success
286
     */
287 2
    protected function removeContentPart($contentType)
288
    {
289 2
        if (!isset($this->contentPart)) {
290
            return false;
291
        }
292 2
        $type = strtolower($this->contentPart->getHeaderValue('Content-Type', 'text/plain'));
293 2
        if ($type === $contentType) {
294
            if ($this->contentPart === $this) {
295
                return false;
296
            }
297
            $this->removePart($this->contentPart);
298
            $this->contentPart = null;
299
            return true;
300
        }
301 2
        return $this->removeContentPartFromAlternative($contentType);
302
    }
303
    
304
    /**
305
     * Returns the text part (or null if none is set.)
306
     * 
307
     * @return \ZBateson\MailMimeParser\MimePart
308
     */
309 59
    public function getTextPart()
310
    {
311 59
        return $this->getContentPartByMimeType('text/plain');
312
    }
313
    
314
    /**
315
     * Returns the HTML part (or null if none is set.)
316
     * 
317
     * @return \ZBateson\MailMimeParser\MimePart
318
     */
319 32
    public function getHtmlPart()
320
    {
321 32
        return $this->getContentPartByMimeType('text/html');
322
    }
323
    
324
    /**
325
     * Returns an open resource handle for the passed string or resource handle.
326
     * 
327
     * For a string, creates a php://temp stream and returns it.
328
     * 
329
     * @param resource|string $stringOrHandle
330
     * @return resource
331
     */
332 4
    private function getHandleForStringOrHandle($stringOrHandle)
333
    {
334 4
        $tempHandle = fopen('php://temp', 'r+');
335 4
        if (is_string($stringOrHandle)) {
336 4
            fwrite($tempHandle, $stringOrHandle);
337 4
        } else {
338
            stream_copy_to_stream($stringOrHandle, $tempHandle);
339
        }
340 4
        rewind($tempHandle);
341 4
        return $tempHandle;
342
    }
343
    
344
    /**
345
     * Creates and returns a unique boundary.
346
     * 
347
     * @return string
348
     */
349 12
    private function getUniqueBoundary()
350
    {
351 12
        return uniqid('----=MMP-' . $this->objectId . '.', true);
352
    }
353
    
354
    /**
355
     * Creates a unique mime boundary and assigns it to the passed part's
356
     * Content-Type header with the passed mime type.
357
     * 
358
     * @param \ZBateson\MailMimeParser\MimePart $part
359
     * @param string $mimeType
360
     */
361 6
    private function setMimeHeaderBoundaryOnPart(MimePart $part, $mimeType)
362
    {
363 6
        $part->setRawHeader(
364 6
            'Content-Type',
365 6
            "$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...
366 6
                . $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...
367 6
        );
368 6
    }
369
    
370
    /**
371
     * Sets this message to be a multipart/alternative message, making space for
372
     * another alternative content part.
373
     * 
374
     * Creates a content part and assigns the content stream from the message to
375
     * that newly created part.
376
     */
377 1
    private function setMessageAsAlternative()
378
    {
379 1
        $contentPart = $this->mimePartFactory->newMimePart();
380 1
        $contentPart->attachContentResourceHandle($this->handle);
381 1
        $this->detachContentResourceHandle();
382 1
        $contentType = 'text/plain; charset="us-ascii"';
383 1
        $contentHeader = $this->getHeader('Content-Type');
384 1
        if ($contentHeader !== null) {
385 1
            $contentType = $contentHeader->getRawValue();
386 1
        }
387 1
        $contentPart->setRawHeader('Content-Type', $contentType);
388 1
        $contentPart->setParent($this);
389 1
        $this->setMimeHeaderBoundaryOnPart($this, 'multipart/alternative');
390 1
        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...
391 1
    }
392
    
393
    /**
394
     * Creates a new mime part as a multipart/alternative, assigning it to
395
     * $this->contentPart.  Adds the current contentPart below the newly created
396
     * alternative part.
397
     */
398 2
    private function createAlternativeContentPart()
399
    {
400 2
        $altPart = $this->mimePartFactory->newMimePart();
401 2
        $this->setMimeHeaderBoundaryOnPart($altPart, 'multipart/alternative');
402 2
        $this->contentPart->setParent($altPart);
403 2
        $altPart->addPart($this->contentPart);
404 2
        $this->contentPart = $altPart;
405 2
        $altPart->setParent($this);
406 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...
407 2
    }
408
    
409
    /**
410
     * Copies Content-Type and Content-Transfer-Encoding headers from the $from
411
     * header into the $to header. If the Content-Type header isn't defined in
412
     * $from, defaults to text/plain and quoted-printable.
413
     * 
414
     * @param \ZBateson\MailMimeParser\MimePart $from
415
     * @param \ZBateson\MailMimeParser\MimePart $to
416
     */
417 6
    private function copyTypeHeadersFromPartToPart(MimePart $from, MimePart $to)
418
    {
419 6
        $typeHeader = $from->getHeader('Content-Type');
420 6
        if (!empty($typeHeader)) {
421 6
            $to->setRawHeader('Content-Type', $typeHeader->getRawValue());
422 6
            $encodingHeader = $from->getHeader('Content-Transfer-Encoding');
423 6
            if (!empty($encodingHeader)) {
424 3
                $to->setRawHeader('Content-Transfer-Encoding', $encodingHeader->getRawValue());
425 3
            }
426 6
        } else {
427
            $to->setRawHeader('Content-Type', 'text/plain;charset=us-ascii');
428
            $to->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
429
        }
430 6
    }
431
    
432
    /**
433
     * Creates a new content part from the passed part, allowing the part to be
434
     * used for something else (e.g. changing a non-mime message to a multipart
435
     * mime message).
436
     */
437 4
    private function createNewContentPartFromPart(MimePart $part)
438
    {
439 4
        $contPart = $this->mimePartFactory->newMimePart();
440 4
        $this->copyTypeHeadersFromPartToPart($part, $contPart);
441 4
        $contPart->attachContentResourceHandle($part->handle);
442 4
        $part->detachContentResourceHandle();
443 4
        return $contPart;
444
    }
445
    
446
    /**
447
     * Creates a new part out of the current contentPart and sets the message's
448
     * type to be multipart/mixed.
449
     */
450 4
    private function setMessageAsMixed()
451
    {
452 4
        $part = $this->createNewContentPartFromPart($this->contentPart);
453 4
        $this->removePart($this->contentPart);
454 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...
455 4
        $this->contentPart = $part;
456 4
        $this->setMimeHeaderBoundaryOnPart($this, 'multipart/mixed');
457 4
    }
458
    
459
    /**
460
     * Updates parents of the contentPart and any children, and sets
461
     * $this->contentPart to the passed $messagePart if the $this->contentPart
462
     * is set to $this
463
     * 
464
     * @param \ZBateson\MailMimeParser\MimePart $messagePart
465
     */
466 3
    private function updateContentPartForSignedMessage(MimePart $messagePart)
467
    {
468 3
        if ($this->contentPart === $this) {
469 2
            $this->contentPart = $messagePart;
470 2
            foreach ($this->getAllParts() as $child) {
471 2
                if ($child === $this) {
472 2
                    continue;
473
                }
474 1
                $child->setParent($messagePart);
475 1
                array_unshift($this->contentPart->parts, $child);
476 2
            }
477 3
        } elseif ($this->contentPart->getParent() === $this) {
478
            $this->contentPart->setParent($messagePart);
479
        }
480 3
    }
481
    
482
    /**
483
     * This function makes space by moving the main message part down one level.
484
     * 
485
     * The content-type and content-transfer-encoding headers are copied from
486
     * this message to the newly created part, the resource handle is moved and
487
     * detached, any attachments and content parts with parents set to this
488
     * message get their parents set to the newly created part.
489
     */
490 3
    private function makeSpaceForMultipartSignedMessage()
491
    {
492 3
        $this->enforceMime();
493 3
        $messagePart = $this->mimePartFactory->newMimePart();
494 3
        $this->updateContentPartForSignedMessage($messagePart);
495 3
        $this->copyTypeHeadersFromPartToPart($this, $messagePart);
496 3
        $messagePart->attachContentResourceHandle($this->handle);
497 3
        $this->detachContentResourceHandle();
498 3
        $messagePart->setParent($this);
499 3
        foreach ($this->attachmentParts as $part) {
500 1
            if ($part->getParent() === $this) {
501
                $part->setParent($messagePart);
502
            }
503 3
        }
504 3
        array_unshift($this->parts, $messagePart);
505 3
    }
506
    
507
    /**
508
     * Creates and returns a new MimePart for the signature part of a
509
     * multipart/signed message and assigns it to $this->signedSignaturePart.
510
     * 
511
     * @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...
512
     * @param string $body
513
     */
514 5
    public function createSignaturePart($body)
515
    {
516 5
        $signedPart = $this->mimePartFactory->newMimePart();
517 5
        $signedPart->setRawHeader(
518 5
            'Content-Type',
519 5
            $this->getHeaderParameter('Content-Type', 'protocol')
520 5
        );
521 5
        $signedPart->setContent($body);
522 5
        $this->parts[] = $signedPart;
523 5
        $signedPart->setParent($this);
524 5
        $this->signedSignaturePart = $signedPart;
525 5
    }
526
    
527
    /**
528
     * Creates a multipart/mixed MimePart assigns it to $this->signedMixedPart
529
     * if the message contains attachments.
530
     * 
531
     * @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...
532
     */
533 9
    private function createMultipartMixedForSignedMessage()
534
    {
535 9
        if (count($this->attachmentParts) === 0 || $this->signedMixedPart !== null) {
536 4
            return;
537
        }
538 5
        $mixed = $this->mimePartFactory->newMimePart();
539 5
        $mixed->setParent($this);
540 5
        $boundary = $this->getUniqueBoundary();
541 5
        $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...
542 6
        foreach ($this->attachmentParts as $part) {
543 5
            $part->setParent($mixed);
544 5
        }
545 5
        if (!empty($this->contentPart)) {
546 5
            $this->contentPart->setParent($mixed);
547 5
        }
548 5
        $this->signedMixedPart = $mixed;
549 5
    }
550
    
551
    /**
552
     * Loops over parts of this message and sets the content-transfer-encoding
553
     * header to quoted-printable for text/* mime parts, and to base64
554
     * otherwise for parts that are '8bit' encoded.
555
     * 
556
     * Used for multipart/signed messages which doesn't support 8bit transfer
557
     * encodings.
558
     */
559 5
    private function overwrite8bitContentEncoding()
560
    {
561 5
        foreach ($this->parts as $part) {
562 5
            if ($part->getHeaderValue('Content-Transfer-Encoding') === '8bit') {
563 1
                if (preg_match('/text\/.*/', $part->getHeaderValue('Content-Type'))) {
564 1
                    $part->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
565 1
                } else {
566
                    $part->setRawHeader('Content-Transfer-Encoding', 'base64');
567
                }
568 1
            }
569 5
        }
570 5
    }
571
    
572
    /**
573
     * Ensures a non-text part comes first in a signed multipart/alternative
574
     * message as some clients seem to prefer the first content part if the
575
     * client doesn't understand multipart/signed.
576
     */
577 5
    private function ensureHtmlPartFirstForSignedMessage()
578
    {
579 5
        if (empty($this->contentPart)) {
580
            return;
581
        }
582 5
        $type = strtolower($this->contentPart->getHeaderValue('Content-Type', 'text/plain'));
583 5
        if ($type === 'multipart/alternative' && count($this->contentPart->parts) > 1) {
584 3
            if (strtolower($this->contentPart->parts[0]->getHeaderValue('Content-Type', 'text/plain')) === 'text/plain') {
585 2
                $tmp = $this->contentPart->parts[0];
586 2
                $this->contentPart->parts[0] = $this->contentPart->parts[1];
587 2
                $this->contentPart->parts[1] = $tmp;
588 2
            }
589 3
        }
590 5
    }
591
    
592
    /**
593
     * Turns the message into a multipart/signed message, moving the actual
594
     * message into a child part, sets the content-type of the main message to
595
     * multipart/signed and adds a signature part as well.
596
     * 
597
     * @param string $micalg The Message Integrity Check algorithm being used
598
     * @param string $protocol The mime-type of the signature body
599
     * @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...
600
     *        $protocol
601
     */
602 5
    public function setAsMultipartSigned($micalg, $protocol)
603
    {
604 5
        $contentType = $this->getHeaderValue('Content-Type', 'text/plain');
605 5
        if (strtolower($contentType) !== 'multipart/signed' && $contentType !== 'multipart/mixed') {
606 3
            $this->makeSpaceForMultipartSignedMessage();
607 3
        }
608 5
        $boundary = $this->getUniqueBoundary();
609 5
        $this->setRawHeader(
610 5
            'Content-Type',
611 5
            "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...
612 5
        );
613 5
        $this->removeHeader('Content-Transfer-Encoding');
614 5
        $this->createMultipartMixedForSignedMessage();
615 5
        $this->overwrite8bitContentEncoding();
616 5
        $this->ensureHtmlPartFirstForSignedMessage();
617 5
        $this->createSignaturePart('Not set');
618 5
    }
619
    
620
    /**
621
     * Returns the signed part or null if not set.
622
     * 
623
     * @return \ZBateson\MailMimeParser\MimePart
624
     */
625 9
    public function getSignaturePart()
626
    {
627 9
        return $this->signedSignaturePart;
628
    }
629
    
630
    /**
631
     * Enforces the message to be a mime message for a non-mime (e.g. uuencoded
632
     * or unspecified) message.  If the message has uuencoded attachments, sets
633
     * up the message as a multipart/mixed message and creates a content part.
634
     */
635 6
    private function enforceMime()
636
    {
637 6
        if (!$this->isMime()) {
638 2
            if ($this->getAttachmentCount()) {
639 2
                $this->setMessageAsMixed();
640 2
            } else {
641
                $this->setRawHeader('Content-Type', "text/plain;\r\n\tcharset=\"us-ascii\"");
642
            }
643 2
            $this->setRawHeader('Mime-Version', '1.0');
644 2
        }
645 6
    }
646
    
647
    /**
648
     * Creates a new content part for the passed mimeType and charset, making
649
     * space by creating a multipart/alternative if needed
650
     * 
651
     * @param string $mimeType
652
     * @param string $charset
653
     * @return \ZBateson\MailMimeParser\MimePart
654
     */
655 3
    private function createContentPartForMimeType($mimeType, $charset)
656
    {
657
        // wouldn't come here unless there's only one 'content part' anyway
658
        // if this->contentPart === $this, then $this is not a multipart/alternative
659
        // message
660 3
        $mimePart = $this->mimePartFactory->newMimePart();
661 3
        $cset = ($charset === null) ? 'UTF-8' : $charset;
662 3
        $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...
663 3
        $mimePart->setRawHeader('Content-Transfer-Encoding', 'quoted-printable');
664 3
        $this->enforceMime();
665 3
        if ($this->contentPart === $this) {
666 1
            $this->setMessageAsAlternative();
667 1
            $mimePart->setParent($this->contentPart);
668 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...
669 3
        } elseif ($this->contentPart !== null) {
670 2
            $this->createAlternativeContentPart();
671 2
            $mimePart->setParent($this->contentPart);
672 2
            $this->contentPart->addPart($mimePart);
673 2
        } else {
674 1
            $this->contentPart = $mimePart;
675 1
            $mimePart->setParent($this);
676 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...
677
        }
678 3
        return $mimePart;
679
    }
680
    
681
    /**
682
     * Either creates a mime part or sets the existing mime part with the passed
683
     * mimeType to $strongOrHandle.
684
     * 
685
     * @param string $mimeType
686
     * @param string|resource $stringOrHandle
687
     * @param string $charset
688
     */
689 3
    protected function setContentPartForMimeType($mimeType, $stringOrHandle, $charset)
690
    {
691 3
        $part = $this->getTextPart();
692 3
        if ($mimeType === 'text/html') {
693 3
            $part = $this->getHtmlPart();
694 3
        }
695 3
        $handle = $this->getHandleForStringOrHandle($stringOrHandle);
696 3
        if ($part === null) {
697 3
            $part = $this->createContentPartForMimeType($mimeType, $charset);
698 3
        } elseif ($charset !== null) {
699
            $cset = ($charset === null) ? 'UTF-8' : $charset;
700
            $contentType = $part->getHeaderValue('Content-Type', 'text/plain');
701
            $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...
702
        }
703 3
        $part->attachContentResourceHandle($handle);
704 3
    }
705
    
706
    /**
707
     * Sets the text/plain part of the message to the passed $stringOrHandle,
708
     * either creating a new part if one doesn't exist for text/plain, or
709
     * assigning the value of $stringOrHandle to an existing text/plain part.
710
     * 
711
     * The optional $charset parameter is the charset for saving to.
712
     * $stringOrHandle is expected to be in UTF-8.
713
     * 
714
     * @param string|resource $stringOrHandle
715
     * @param string $charset
716
     */
717 1
    public function setTextPart($stringOrHandle, $charset = null)
718
    {
719 1
        $this->setContentPartForMimeType('text/plain', $stringOrHandle, $charset);
720 1
    }
721
    
722
    /**
723
     * Sets the text/html part of the message to the passed $stringOrHandle,
724
     * either creating a new part if one doesn't exist for text/html, or
725
     * assigning the value of $stringOrHandle to an existing text/html part.
726
     * 
727
     * The optional $charset parameter is the charset for saving to.
728
     * $stringOrHandle is expected to be in UTF-8.
729
     * 
730
     * @param string|resource $stringOrHandle
731
     * @param string $charset
732
     */
733 3
    public function setHtmlPart($stringOrHandle, $charset = null)
734
    {
735 3
        $this->setContentPartForMimeType('text/html', $stringOrHandle, $charset);
736 3
    }
737
    
738
    /**
739
     * Removes the text part of the message if one exists.  Returns true on
740
     * success.
741
     * 
742
     * @return bool true on success
743
     */
744 1
    public function removeTextPart()
745
    {
746 1
        return $this->removeContentPart('text/plain');
747
    }
748
    
749
    /**
750
     * Removes the html part of the message if one exists.  Returns true on
751
     * success.
752
     * 
753
     * @return bool true on success
754
     */
755 1
    public function removeHtmlPart()
756
    {
757 1
        return $this->removeContentPart('text/html');
758
    }
759
    
760
    /**
761
     * Returns the non-content part at the given 0-based index, or null if none
762
     * is set.
763
     * 
764
     * @param int $index
765
     * @return \ZBateson\MailMimeParser\MimePart
766
     */
767 6
    public function getAttachmentPart($index)
768
    {
769 6
        if (!isset($this->attachmentParts[$index])) {
770 2
            return null;
771
        }
772 4
        return $this->attachmentParts[$index];
773
    }
774
    
775
    /**
776
     * Returns all attachment parts.
777
     * 
778
     * @return \ZBateson\MailMimeParser\MimePart[]
779
     */
780 40
    public function getAllAttachmentParts()
781
    {
782 40
        return $this->attachmentParts;
783
    }
784
    
785
    /**
786
     * Returns the number of attachments available.
787
     * 
788
     * @return int
789
     */
790 40
    public function getAttachmentCount()
791
    {
792 40
        return count($this->attachmentParts);
793
    }
794
    
795
    /**
796
     * Removes the attachment with the given index
797
     * 
798
     * @param int $index
799
     */
800 2
    public function removeAttachmentPart($index)
801
    {
802 2
        $part = $this->attachmentParts[$index];
803 2
        $this->removePart($part);
804 2
        array_splice($this->attachmentParts, $index, 1);
805 2
    }
806
    
807
    /**
808
     * Creates and returns a MimePart for use with a new attachment part being
809
     * created.
810
     * 
811
     * @return \ZBateson\MailMimeParser\MimePart
812
     */
813 2
    protected function createPartForAttachment()
814
    {
815 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...
816 2
        if ($this->isMime()) {
817 2
            $part = $this->mimePartFactory->newMimePart();
818 2
            $part->setRawHeader('Content-Transfer-Encoding', 'base64');
819 2
            if ($this->getHeaderValue('Content-Type') !== 'multipart/mixed') {
820 2
                $this->setMessageAsMixed();
821 2
            }
822 2
        } else {
823
            $part = $this->mimePartFactory->newUUEncodedPart();
0 ignored issues
show
Bug introduced by
The call to newUUEncodedPart() misses some required arguments starting with $mode.
Loading history...
824
        }
825 2
        return $part;
826
    }
827
    
828
    /**
829
     * Adds an attachment part for the passed raw data string or handle and
830
     * given parameters.
831
     * 
832
     * @param string|handle $stringOrHandle
833
     * @param strubg $mimeType
834
     * @param string $filename
835
     * @param string $disposition
836
     */
837 1
    public function addAttachmentPart($stringOrHandle, $mimeType, $filename = null, $disposition = 'attachment')
838
    {
839 1
        if ($filename === null) {
840
            $filename = 'file' . uniqid();
841
        }
842 1
        $filename = iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
843 1
        $part = $this->createPartForAttachment();
844 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...
845 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...
846 1
        $part->setParent($this);
847 1
        $part->attachContentResourceHandle($this->getHandleForStringOrHandle($stringOrHandle));
848 1
        $this->parts[] = $part;
849 1
        $this->attachmentParts[] = $part;
850 1
    }
851
    
852
    /**
853
     * Adds an attachment part using the passed file.
854
     * 
855
     * Essentially creates a file stream and uses it.
856
     * 
857
     * @param string $file
858
     * @param string $mimeType
859
     * @param string $filename
860
     * @param string $disposition
861
     */
862 2
    public function addAttachmentPartFromFile($file, $mimeType, $filename = null, $disposition = 'attachment')
863
    {
864 2
        $handle = fopen($file, 'r');
865 2
        if ($filename === null) {
866 2
            $filename = basename($file);
867 2
        }
868 2
        $filename = iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
869 2
        $part = $this->createPartForAttachment();
870 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...
871 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...
872 2
        $part->setParent($this);
873 2
        $part->attachContentResourceHandle($handle);
874 2
        $this->parts[] = $part;
875 2
        $this->attachmentParts[] = $part;
876 2
    }
877
    
878
    /**
879
     * Returns a resource handle where the text content can be read or null if
880
     * unavailable.
881
     * 
882
     * @return resource
883
     */
884 55
    public function getTextStream()
885
    {
886 55
        $textPart = $this->getTextPart();
887 55
        if (!empty($textPart)) {
888 54
            return $textPart->getContentResourceHandle();
889
        }
890 1
        return null;
891
    }
892
    
893
    /**
894
     * Returns the text content as a string.
895
     * 
896
     * Reads the entire stream content into a string and returns it.  Returns
897
     * null if the message doesn't have a text part.
898
     * 
899
     * @return string
900
     */
901
    public function getTextContent()
902
    {
903
        $stream = $this->getTextStream();
904
        if ($stream === null) {
905
            return null;
906
        }
907
        return stream_get_contents($stream);
908
    }
909
    
910
    /**
911
     * Returns a resource handle where the HTML content can be read or null if
912
     * unavailable.
913
     * 
914
     * @return resource
915
     */
916 26
    public function getHtmlStream()
917
    {
918 26
        $htmlPart = $this->getHtmlPart();
919 26
        if (!empty($htmlPart)) {
920 25
            return $htmlPart->getContentResourceHandle();
921
        }
922 1
        return null;
923
    }
924
    
925
    /**
926
     * Returns the HTML content as a string.
927
     * 
928
     * Reads the entire stream content into a string and returns it.  Returns
929
     * null if the message doesn't have an HTML part.
930
     * 
931
     * @return string
932
     */
933
    public function getHtmlContent()
934
    {
935
        $stream = $this->getHtmlStream();
936
        if ($stream === null) {
937
            return null;
938
        }
939
        return stream_get_contents($stream);
940
    }
941
    
942
    /**
943
     * Returns true if either a Content-Type or Mime-Version header are defined
944
     * in this Message.
945
     * 
946
     * @return bool
947
     */
948 75
    public function isMime()
949
    {
950 75
        $contentType = $this->getHeaderValue('Content-Type');
951 75
        $mimeVersion = $this->getHeaderValue('Mime-Version');
952 75
        return ($contentType !== null || $mimeVersion !== null);
953
    }
954
    
955
    /**
956
     * Writes out a mime boundary to the passed $handle
957
     * 
958
     * @param resource $handle
959
     * @param string $boundary
960
     * @param bool $isEnd
961
     */
962 48
    private function writeBoundary($handle, $boundary, $isEnd = false)
963
    {
964 48
        if ($this->insertNewLineBeforeBoundary) {
965 48
            fwrite($handle, "\r\n");
966 48
        }
967 48
        fwrite($handle, '--');
968 48
        fwrite($handle, $boundary);
969 48
        if ($isEnd) {
970 48
            fwrite($handle, "--\r\n");
971 48
        } else {
972 48
            fwrite($handle, "\r\n");
973
        }
974 48
        $this->insertNewLineBeforeBoundary = $isEnd;
975 48
    }
976
    
977
    /**
978
     * Writes out any necessary boundaries for the given $part if required based
979
     * on its $parent and $boundaryParent.
980
     * 
981
     * Also writes out end boundaries for the previous part if applicable.
982
     * 
983
     * @param resource $handle
984
     * @param \ZBateson\MailMimeParser\MimePart $part
985
     * @param \ZBateson\MailMimeParser\MimePart $parent
986
     * @param \ZBateson\MailMimeParser\MimePart $boundaryParent
987
     * @param string $boundary
988
     */
989 48
    private function writePartBoundaries($handle, MimePart $part, MimePart $parent, MimePart &$boundaryParent, $boundary)
990
    {
991 48
        if ($boundaryParent !== $parent && $boundaryParent !== $part) {
992 17
            if ($boundaryParent !== null && $parent->getParent() !== $boundaryParent) {
993 17
                $this->writeBoundary($handle, $boundary, true);
994 17
            }
995 17
            $boundaryParent = $parent;
996 17
            $boundary = $boundaryParent->getHeaderParameter('Content-Type', 'boundary');
997 17
        }
998 48
        if ($boundaryParent !== null && $boundaryParent !== $part) {
999 48
            $this->writeBoundary($handle, $boundary);
1000 48
        }
1001 48
    }
1002
    
1003
    /**
1004
     * Writes out the passed mime part, writing out any necessary mime
1005
     * boundaries.
1006
     * 
1007
     * @param resource $handle
1008
     * @param \ZBateson\MailMimeParser\MimePart $part
1009
     * @param \ZBateson\MailMimeParser\MimePart $parent
1010
     * @param \ZBateson\MailMimeParser\MimePart $boundaryParent
1011
     */
1012 71
    private function writePartTo($handle, MimePart $part, MimePart $parent, MimePart &$boundaryParent)
1013
    {
1014 71
        $boundary = $boundaryParent->getHeaderParameter('Content-Type', 'boundary');
1015 71
        if (!empty($boundary)) {
1016 48
            $this->writePartBoundaries($handle, $part, $parent, $boundaryParent, $boundary);
1017 48
            if ($part !== $this) {
1018 48
                $part->writeTo($handle);
1019 48
            } else {
1020 4
                $part->writeContentTo($handle);
1021
            }
1022 71
        } elseif ($part instanceof NonMimePart) {
1023 2
            fwrite($handle, "\r\n\r\n");
1024 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...
1025 2
        } else {
1026 21
            $part->writeContentTo($handle);
1027
        }
1028 71
        $this->insertNewLineBeforeBoundary = $part->hasContent();
1029 71
    }
1030
    
1031
    /**
1032
     * Either returns $this for a non-text, non-html part, or returns
1033
     * $this->contentPart.
1034
     * 
1035
     * Note that if Content-Disposition is set on the passed part, $this is
1036
     * always returned.
1037
     * 
1038
     * @param \ZBateson\MailMimeParser\MimePart $part
1039
     * @return \ZBateson\MailMimeParser\MimePart
1040
     */
1041 71
    private function getWriteParentForPart(MimePart $part)
1042
    {
1043 71
        $type = $part->getHeaderValue('Content-Type');
1044 71
        $disposition = $part->getHeaderValue('Content-Disposition');
1045 71
        if (empty($disposition) && $this->contentPart !== $part && ($type === 'text/html' || $type === 'text/plain')) {
1046 19
            return $this->contentPart;
1047 71
        } elseif ($this->signedSignaturePart !== null) {
1048 9
            return $part->getParent();
1049
        }
1050 62
        return $this;
1051
    }
1052
    
1053
    /**
1054
     * Loops over parts of the message and writes them as an email to the
1055
     * provided $handle.
1056
     * 
1057
     * The function rewrites mime parts in a multipart-mime message to be either
1058
     * alternatives of text/plain and text/html, or attachments because
1059
     * MailMimeParser doesn't currently maintain the structure of the original
1060
     * message.  This means other alternative parts would be dropped to
1061
     * attachments, and multipart/related parts are completely ignored.
1062
     * 
1063
     * @param resource $handle the handle to write out to
1064
     * @param Iterator $partsIter an Iterator for parts to save
1065
     * @param \ZBateson\MailMimeParser\MimePart $curParent the current parent
1066
     */
1067 71
    protected function writePartsTo($handle, Iterator $partsIter, MimePart $curParent)
1068
    {
1069 71
        $this->insertNewLineBeforeBoundary = false;
1070 71
        $boundary = $curParent->getHeaderParameter('Content-Type', 'boundary');
1071 71
        while ($partsIter->valid()) {
1072 71
            $part = $partsIter->current();
1073 71
            $parent = $this->getWriteParentForPart($part);
1074 71
            $this->writePartTo($handle, $part, $parent, $curParent);
1075 71
            $partsIter->next();
1076 71
        }
1077 71
        if (!empty($boundary)) {
1078 48
            $this->writeBoundary($handle, $boundary, true);
1079 48
        }
1080 71
    }
1081
    
1082
    /**
1083
     * Saves the message as a MIME message to the passed resource handle.
1084
     * 
1085
     * The saved message is not guaranteed to be the same as the parsed message.
1086
     * Namely, for mime messages anything that is not text/html or text/plain
1087
     * will be moved into parts under the main 'message' as attachments, other
1088
     * alternative parts are dropped, and multipart/related parts are ignored
1089
     * (their contents are either moved under a multipart/alternative part or as
1090
     * attachments below the main multipart/mixed message).
1091
     * 
1092
     * @param resource $handle
1093
     */
1094 71
    public function save($handle)
1095
    {
1096 71
        $this->writeHeadersTo($handle);
1097 71
        $parts = [];
1098 71
        if (!empty($this->signedMixedPart)) {
1099 5
            $parts[] = $this->signedMixedPart;
1100 5
        }
1101 71 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...
1102 69
            if ($this->contentPart->isMultiPart()) {
1103 19
                $parts[] = $this->contentPart;
1104 19
                $parts = array_merge($parts, $this->contentPart->getAllParts());
1105 19
            } else {
1106 50
                $parts[] = $this->contentPart;
1107
            }
1108 69
        }
1109 71
        if (!empty($this->attachmentParts)) {
1110 42
            $parts = array_merge($parts, $this->attachmentParts);
1111 42
        }
1112 71
        if (!empty($this->signedSignaturePart)) {
1113 9
            $parts[] = $this->signedSignaturePart;
1114 9
        }
1115 71
        $this->writePartsTo(
1116 71
            $handle,
1117 71
            new ArrayIterator($parts),
1118
            $this
1119 71
        );
1120 71
    }
1121
    
1122
    /**
1123
     * Writes out the content of the message into a string and returns it.
1124
     * 
1125
     * @return string
1126
     */
1127 5
    private function getSignableBodyFromParts(array $parts)
1128
    {
1129 5
        $handle = fopen('php://temp', 'r+');
1130 5
        $firstPart = array_shift($parts);
1131 5
        $firstPart->writeHeadersTo($handle);
1132 5
        $firstPart->writeContentTo($handle);
1133 5
        if (!empty($parts)) {
1134 4
            $this->writePartsTo(
1135 4
                $handle,
1136 4
                new ArrayIterator($parts),
1137
                $firstPart
1138 4
            );
1139 4
        }
1140 5
        rewind($handle);
1141 5
        $str = stream_get_contents($handle);
1142 5
        fclose($handle);
1143 5
        return $str;
1144
    }
1145
    
1146
    /**
1147
     * Returns the content part of a signed message for a signature to be
1148
     * calculated on the message.
1149
     * 
1150
     * @return string
1151
     */
1152 5
    public function getSignableBody()
1153
    {
1154 5
        $parts = [];
1155 5
        if (!empty($this->signedMixedPart)) {
1156 3
            $parts[] = $this->signedMixedPart;
1157 3
        }
1158 5 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...
1159 5
            if ($this->contentPart->isMultiPart()) {
1160 3
                $parts[] = $this->contentPart;
1161 3
                $parts = array_merge($parts, $this->contentPart->getAllParts());
1162 3
            } else {
1163 2
                $parts[] = $this->contentPart;
1164
            }
1165 5
        }
1166 5
        if (!empty($this->attachmentParts)) {
1167 3
            $parts = array_merge($parts, $this->attachmentParts);
1168 3
        }
1169 5
        return $this->getSignableBodyFromParts($parts);
1170
    }
1171
    
1172
    /**
1173
     * Shortcut to call Message::save with a php://temp stream and return the
1174
     * written email message as a string.
1175
     * 
1176
     * @return string
1177
     */
1178
    public function __toString()
1179
    {
1180
        $handle = fopen('php://temp', 'r+');
1181
        $this->save($handle);
1182
        rewind($handle);
1183
        $str = stream_get_contents($handle);
1184
        fclose($handle);
1185
        return $str;
1186
    }
1187
}
1188