Completed
Push — master ( 147214...53e4d6 )
by Zaahid
09:05
created

Message::ensureHtmlPartFirstForSignedMessage()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0144

Importance

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