Passed
Push — master ( 648094...0aa020 )
by Zaahid
03:20
created

MessagePart::getContentResourceHandle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3.1852

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 8
ccs 2
cts 6
cp 0.3333
crap 3.1852
rs 10
c 2
b 0
f 0
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\Message\Part;
8
9
use GuzzleHttp\Psr7;
10
use GuzzleHttp\Psr7\StreamWrapper;
11
use Psr\Http\Message\StreamInterface;
12
use ZBateson\MailMimeParser\MailMimeParser;
13
use ZBateson\MailMimeParser\Stream\StreamFactory;
14
15
/**
16
 * Represents a single part of a message.
17
 *
18
 * A MessagePart object may have any number of child parts, or may be a child
19
 * itself with its own parent or parents.
20
 *
21
 * @author Zaahid Bateson
22
 */
23
abstract class MessagePart
24
{
25
    /**
26
     * @var PartStreamFilterManager manages attached filters to $contentHandle
27
     */
28
    protected $partStreamFilterManager;
29
30
    /**
31
     * @var StreamFactory for creating MessagePartStream objects
32
     */
33
    protected $streamFactory;
34
35
    /**
36
     * @var ParentPart parent part
37
     */
38
    protected $parent;
39
40
    /**
41
     * @var StreamInterface a Psr7 stream containing this part's headers,
42
     *      content and children
43
     */
44
    protected $stream;
45
46
    /**
47
     * @var StreamInterface a Psr7 stream containing this part's content
48
     */
49
    protected $contentStream;
50
51
    /**
52
     * @var string can be used to set an override for content's charset in cases
53
     *      where a user knows the charset on the content is not what it claims
54
     *      to be.
55
     */
56
    protected $charsetOverride;
57
58
    /**
59
     * @var boolean set to true when a user attaches a stream manually, it's
60
     *      assumed to already be decoded or to have relevant transfer encoding
61
     *      decorators attached already.
62
     */
63
    protected $ignoreTransferEncoding;
64
65
    /**
66
     * Constructor
67
     *
68
     * @param PartStreamFilterManager $partStreamFilterManager
69
     * @param StreamFactory $streamFactory
70
     * @param StreamInterface $stream
71
     * @param StreamInterface $contentStream
72
     */
73 47
    public function __construct(
74
        PartStreamFilterManager $partStreamFilterManager,
75
        StreamFactory $streamFactory,
76
        StreamInterface $stream = null,
77
        StreamInterface $contentStream = null
78
    ) {
79 47
        $this->partStreamFilterManager = $partStreamFilterManager;
80 47
        $this->streamFactory = $streamFactory;
81
82 47
        $this->stream = $stream;
83 47
        $this->contentStream = $contentStream;
84 47
        if ($contentStream !== null) {
85 12
            $partStreamFilterManager->setStream(
86 12
                $contentStream
87
            );
88
        }
89 47
    }
90
91
    /**
92
     * Overridden to close streams.
93
     */
94 38
    public function __destruct()
95
    {
96 38
        if ($this->stream !== null) {
97 15
            $this->stream->close();
98
        }
99 38
        if ($this->contentStream !== null) {
100 11
            $this->contentStream->close();
101
        }
102 38
    }
103
104
    /**
105
     * Called when operations change the content of the MessagePart.
106
     *
107
     * The function causes calls to getStream() to return a dynamic
108
     * MessagePartStream instead of the read stream for this MessagePart and all
109
     * parent MessageParts.
110
     */
111 6
    protected function onChange()
112
    {
113 6
        $this->markAsChanged();
114 6
        if ($this->parent !== null) {
115 3
            $this->parent->onChange();
116
        }
117 6
    }
118
119
    /**
120
     * Marks the part as changed, forcing the part to be rewritten when saved.
121
     *
122
     * Normal operations to a MessagePart automatically mark the part as
123
     * changed and markAsChanged() doesn't need to be called in those cases.
124
     *
125
     * The function can be called to indicate an external change that requires
126
     * rewriting this part, for instance changing a message from a non-mime
127
     * message to a mime one, would require rewriting non-mime children to
128
     * insure suitable headers are written.
129
     *
130
     * Internally, the function discards the part's stream, forcing a stream to
131
     * be created when calling getStream().
132
     */
133 7
    public function markAsChanged()
134
    {
135
        // the stream is not closed because $this->contentStream may still be
136
        // attached to it.  GuzzleHttp will clean it up when destroyed.
137 7
        $this->stream = null;
138 7
    }
139
140
    /**
141
     * Returns true if there's a content stream associated with the part.
142
     *
143
     * @return boolean
144
     */
145 11
    public function hasContent()
146
    {
147 11
        return ($this->contentStream !== null);
148
    }
149
150
    /**
151
     * Returns true if this part's mime type is text/plain, text/html or has a
152
     * text/* and has a defined 'charset' attribute.
153
     *
154
     * @return bool
155
     */
156
    public abstract function isTextPart();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
157
158
    /**
159
     * Returns the mime type of the content.
160
     *
161
     * @return string
162
     */
163
    public abstract function getContentType();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
164
165
    /**
166
     * Returns the charset of the content, or null if not applicable/defined.
167
     *
168
     * @return string
169
     */
170
    public abstract function getCharset();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
171
172
    /**
173
     * Returns the content's disposition.
174
     *
175
     * @return string
176
     */
177
    public abstract function getContentDisposition();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
178
179
    /**
180
     * Returns the content-transfer-encoding used for this part.
181
     *
182
     * @return string
183
     */
184
    public abstract function getContentTransferEncoding();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
185
186
    /**
187
     * Returns a filename for the part if one is defined, or null otherwise.
188
     *
189
     * @return string
190
     */
191 1
    public function getFilename()
192
    {
193 1
        return null;
194
    }
195
196
    /**
197
     * Returns true if the current part is a mime part.
198
     *
199
     * @return bool
200
     */
201
    public abstract function isMime();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
202
203
    /**
204
     * Returns the Content ID of the part, or null if not defined.
205
     *
206
     * @return string|null
207
     */
208
    public abstract function getContentId();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
209
210
    /**
211
     * Returns a resource handle containing this part, including any headers for
212
     * a MimePart, its content, and all its children.
213
     *
214
     * @return resource the resource handle
215
     */
216 4
    public function getResourceHandle()
217
    {
218 4
        return StreamWrapper::getResource($this->getStream());
219
    }
220
221
    /**
222
     * Returns a Psr7 StreamInterface containing this part, including any
223
     * headers for a MimePart, its content, and all its children.
224
     *
225
     * @return StreamInterface the resource handle
226
     */
227 11
    public function getStream()
228
    {
229 11
        if ($this->stream === null) {
230 5
            return $this->streamFactory->newMessagePartStream($this);
231
        }
232 11
        $this->stream->rewind();
233 11
        return $this->stream;
234
    }
235
236
    /**
237
     * Overrides the default character set used for reading content from content
238
     * streams in cases where a user knows the source charset is not what is
239
     * specified.
240
     *
241
     * If set, the returned value from MessagePart::getCharset is ignored.
242
     *
243
     * Note that setting an override on a Message and calling getTextStream,
244
     * getTextContent, getHtmlStream or getHtmlContent will not be applied to
245
     * those sub-parts, unless the text/html part is the Message itself.
246
     * Instead, Message:getTextPart() should be called, and setCharsetOverride
247
     * called on the returned MessagePart.
248
     *
249
     * @param string $charsetOverride
250
     * @param boolean $onlyIfNoCharset if true, $charsetOverride is used only if
251
     *        getCharset returns null.
252
     */
253 1
    public function setCharsetOverride($charsetOverride, $onlyIfNoCharset = false)
254
    {
255 1
        if (!$onlyIfNoCharset || $this->getCharset() === null) {
256 1
            $this->charsetOverride = $charsetOverride;
257
        }
258 1
    }
259
260
    /**
261
     * Returns a resource handle for the content's stream, or null if the part
262
     * doesn't have a content stream.
263
     *
264
     * The method wraps a call to {@see MessagePart::getContentStream()} and
265
     * returns a resource handle for the returned Stream.
266
     *
267
     * Note: this method should *not* be used and has been deprecated. Instead,
268
     * use Psr7 streams with getContentStream.  Multibyte chars will not be read
269
     * correctly with fread.
270
     *
271
     * @param string $charset
272
     * @deprecated since version 1.2.1
273
     * @return resource|null
274
     */
275 1
    public function getContentResourceHandle($charset = MailMimeParser::DEFAULT_CHARSET)
276
    {
277 1
        trigger_error("getContentResourceHandle is deprecated since version 1.2.1", E_USER_DEPRECATED);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal getContentResourceHandle...ted since version 1.2.1 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...
278
        $stream = $this->getContentStream($charset);
279
        if ($stream !== null) {
280
            return StreamWrapper::getResource($stream);
281
        }
282
        return null;
283
    }
284
285
    /**
286
     * Returns the StreamInterface for the part's content or null if the part
287
     * doesn't have a content section.
288
     *
289
     * The library automatically handles decoding and charset conversion (to the
290
     * target passed $charset) based on the part's transfer encoding as returned
291
     * by {@see MessagePart::getContentTransferEncoding()} and the part's
292
     * charset as returned by {@see MessagePart::getCharset()}.  The returned
293
     * stream is ready to be read from directly.
294
     *
295
     * Note that the returned Stream is a shared object.  If called multiple
296
     * time with the same $charset, and the value of the part's
297
     * Content-Transfer-Encoding header not having changed, the stream will be
298
     * rewound.  This would affect other existing variables referencing the
299
     * stream, for example:
300
     *
301
     * ```
302
     * // assuming $part is a part containing the following
303
     * // string for its content: '12345678'
304
     * $stream = $part->getContentStream();
305
     * $someChars = $part->read(4);
306
     *
307
     * $stream2 = $part->getContentStream();
308
     * $moreChars = $part->read(4);
309
     * echo ($someChars === $moreChars);    //1
310
     * ```
311
     *
312
     * In this case the Stream was rewound, and $stream's second call to read 4
313
     * bytes reads the same first 4.
314
     *
315
     * @param string $charset
316
     * @return StreamInterface
317
     */
318 7
    public function getContentStream($charset = MailMimeParser::DEFAULT_CHARSET)
319
    {
320 7
        if ($this->hasContent()) {
321 5
            $tr = ($this->ignoreTransferEncoding) ? '' : $this->getContentTransferEncoding();
322 5
            $ch = ($this->charsetOverride !== null) ? $this->charsetOverride : $this->getCharset();
323 5
            return $this->partStreamFilterManager->getContentStream(
324 5
                $tr,
325 5
                $ch,
326 5
                $charset
327
            );
328
        }
329 2
        return null;
330
    }
331
332
    /**
333
     * Returns the raw data stream for the current part, if it exists, or null
334
     * if there's no content associated with the stream.
335
     *
336
     * This is basically the same as calling
337
     * {@see MessagePart::getContentStream()}, except no automatic charset
338
     * conversion is done.  Note that for non-text streams, this doesn't have an
339
     * effect, as charset conversion is not performed in that case, and is
340
     * useful only when:
341
     *
342
     * - The charset defined is not correct, and the conversion produces errors;
343
     *   or
344
     * - You'd like to read the raw contents without conversion, for instance to
345
     *   save it to file or allow a user to download it as-is (in a download
346
     *   link for example).
347
     *
348
     * @param string $charset
349
     * @return StreamInterface
350
     */
351 4
    public function getBinaryContentStream()
352
    {
353 4
        if ($this->hasContent()) {
354 4
            $tr = ($this->ignoreTransferEncoding) ? '' : $this->getContentTransferEncoding();
355 4
            return $this->partStreamFilterManager->getBinaryStream($tr);
356
        }
357
        return null;
358
    }
359
360
    /**
361
     * Returns a resource handle for the content's raw data stream, or null if
362
     * the part doesn't have a content stream.
363
     *
364
     * The method wraps a call to {@see MessagePart::getBinaryContentStream()}
365
     * and returns a resource handle for the returned Stream.
366
     *
367
     * @return resource|null
368
     */
369 1
    public function getBinaryContentResourceHandle()
370
    {
371 1
        $stream = $this->getBinaryContentStream();
372 1
        if ($stream !== null) {
373 1
            return StreamWrapper::getResource($stream);
374
        }
375
        return null;
376
    }
377
378
    /**
379
     * Saves the binary content of the stream to the passed file, resource or
380
     * stream.
381
     *
382
     * Note that charset conversion is not performed in this case, and the
383
     * contents of the part are saved in their binary format as transmitted (but
384
     * after any content-transfer decoding is performed).  {@see
385
     * MessagePart::getBinaryContentStream()} for a more detailed description of
386
     * the stream.
387
     *
388
     * If the passed parameter is a string, it's assumed to be a filename to
389
     * write to.  The file is opened in 'w+' mode, and closed before returning.
390
     *
391
     * When passing a resource or Psr7 Stream, the resource is not closed, nor
392
     * rewound.
393
     *
394
     * @param string|resource|Stream $filenameResourceOrStream
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\Message\Part\Stream was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
395
     */
396 3
    public function saveContent($filenameResourceOrStream)
397
    {
398 3
        $resourceOrStream = $filenameResourceOrStream;
399 3
        if (is_string($filenameResourceOrStream)) {
400 1
            $resourceOrStream = fopen($filenameResourceOrStream, 'w+');
401
        }
402
403 3
        $stream = Psr7\stream_for($resourceOrStream);
0 ignored issues
show
Bug introduced by
The function stream_for was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

403
        $stream = /** @scrutinizer ignore-call */ Psr7\stream_for($resourceOrStream);
Loading history...
404 3
        Psr7\copy_to_stream($this->getBinaryContentStream(), $stream);
0 ignored issues
show
Bug introduced by
The function copy_to_stream was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

404
        /** @scrutinizer ignore-call */ 
405
        Psr7\copy_to_stream($this->getBinaryContentStream(), $stream);
Loading history...
405
406 3
        if (!is_string($filenameResourceOrStream)
407 3
            && !($filenameResourceOrStream instanceof StreamInterface)) {
408
            // only detach if it wasn't a string or StreamInterface, so the
409
            // fopen call can be properly closed if it was
410 1
            $stream->detach();
411
        }
412 3
    }
413
414
    /**
415
     * Shortcut to reading stream content and assigning it to a string.  Returns
416
     * null if the part doesn't have a content stream.
417
     *
418
     * The returned string is encoded to the passed $charset character encoding,
419
     * defaulting to UTF-8.
420
     *
421
     * @see MessagePart::getContentStream()
422
     * @param string $charset
423
     * @return string
424
     */
425 2
    public function getContent($charset = MailMimeParser::DEFAULT_CHARSET)
426
    {
427 2
        $stream = $this->getContentStream($charset);
428 2
        if ($stream !== null) {
429 1
            return $stream->getContents();
430
        }
431 1
        return null;
432
    }
433
434
    /**
435
     * Returns this part's parent.
436
     *
437
     * @return \ZBateson\MailMimeParser\Message\Part\MimePart
438
     */
439 3
    public function getParent()
440
    {
441 3
        return $this->parent;
442
    }
443
444
    /**
445
     * Attaches the stream or resource handle for the part's content.  The
446
     * stream is closed when another stream is attached, or the MimePart is
447
     * destroyed.
448
     *
449
     * @param StreamInterface $stream
450
     * @param string $streamCharset
451
     */
452 1
    public function attachContentStream(StreamInterface $stream, $streamCharset = MailMimeParser::DEFAULT_CHARSET)
453
    {
454 1
        if ($this->contentStream !== null && $this->contentStream !== $stream) {
455 1
            $this->contentStream->close();
456
        }
457 1
        $this->contentStream = $stream;
458 1
        $ch = ($this->charsetOverride !== null) ? $this->charsetOverride : $this->getCharset();
459 1
        if ($ch !== null && $streamCharset !== $ch) {
460 1
            $this->charsetOverride = $streamCharset;
461
        }
462 1
        $this->ignoreTransferEncoding = true;
463 1
        $this->partStreamFilterManager->setStream($stream);
464 1
        $this->onChange();
465 1
    }
466
467
    /**
468
     * Detaches and closes the content stream.
469
     */
470 1
    public function detachContentStream()
471
    {
472 1
        $this->contentStream = null;
473 1
        $this->partStreamFilterManager->setStream(null);
474 1
        $this->onChange();
475 1
    }
476
477
    /**
478
     * Sets the content of the part to the passed resource.
479
     *
480
     * @param string|resource|StreamInterface $resource
481
     * @param string $charset
482
     */
483 1
    public function setContent($resource, $charset = MailMimeParser::DEFAULT_CHARSET)
484
    {
485 1
        $stream = Psr7\stream_for($resource);
0 ignored issues
show
Bug introduced by
The function stream_for was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

485
        $stream = /** @scrutinizer ignore-call */ Psr7\stream_for($resource);
Loading history...
486 1
        $this->attachContentStream($stream, $charset);
487
        // this->onChange called in attachContentStream
488 1
    }
489
490
    /**
491
     * Saves the message/part to the passed file, resource, or stream.
492
     *
493
     * If the passed parameter is a string, it's assumed to be a filename to
494
     * write to.  The file is opened in 'w+' mode, and closed before returning.
495
     *
496
     * When passing a resource or Psr7 Stream, the resource is not closed, nor
497
     * rewound.
498
     *
499
     * @param string|resource|StreamInterface $filenameResourceOrStream
500
     */
501 2
    public function save($filenameResourceOrStream)
502
    {
503 2
        $resourceOrStream = $filenameResourceOrStream;
504 2
        if (is_string($filenameResourceOrStream)) {
505 1
            $resourceOrStream = fopen($filenameResourceOrStream, 'w+');
506
        }
507
508 2
        $partStream = $this->getStream();
509 2
        $partStream->rewind();
510 2
        $stream = Psr7\stream_for($resourceOrStream);
0 ignored issues
show
Bug introduced by
The function stream_for was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

510
        $stream = /** @scrutinizer ignore-call */ Psr7\stream_for($resourceOrStream);
Loading history...
511 2
        Psr7\copy_to_stream($partStream, $stream);
0 ignored issues
show
Bug introduced by
The function copy_to_stream was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

511
        /** @scrutinizer ignore-call */ 
512
        Psr7\copy_to_stream($partStream, $stream);
Loading history...
512
513 2
        if (!is_string($filenameResourceOrStream)
514 2
            && !($filenameResourceOrStream instanceof StreamInterface)) {
515
            // only detach if it wasn't a string or StreamInterface, so the
516
            // fopen call can be properly closed if it was
517 1
            $stream->detach();
518
        }
519 2
    }
520
521
    /**
522
     * Returns the message/part as a string.
523
     *
524
     * Convenience method for calling getStream()->getContents().
525
     *
526
     * @return string
527
     */
528 1
    public function __toString()
529
    {
530 1
        return $this->getStream()->getContents();
531
    }
532
}
533