Completed
Push — master ( b74d12...cf431b )
by Zaahid
09:07
created

Message::removePartFromAlternativeContentPart()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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