Passed
Push — master ( 499ec4...24bb0d )
by Zaahid
03:12
created

MessagePart::getBinaryContentResourceHandle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

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

396
        $stream = /** @scrutinizer ignore-call */ Psr7\stream_for($resourceOrStream);
Loading history...
397 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

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

478
        $stream = /** @scrutinizer ignore-call */ Psr7\stream_for($resource);
Loading history...
479 1
        $this->attachContentStream($stream, $charset);
480
        // this->onChange called in attachContentStream
481 1
    }
482
483
    /**
484
     * Saves the message/part to the passed file, resource, or stream.
485
     *
486
     * If the passed parameter is a string, it's assumed to be a filename to
487
     * write to.  The file is opened in 'w+' mode, and closed before returning.
488
     *
489
     * When passing a resource or Psr7 Stream, the resource is not closed, nor
490
     * rewound.
491
     *
492
     * @param string|resource|StreamInterface $filenameResourceOrStream
493
     */
494 2
    public function save($filenameResourceOrStream)
495
    {
496 2
        $resourceOrStream = $filenameResourceOrStream;
497 2
        if (is_string($filenameResourceOrStream)) {
498 1
            $resourceOrStream = fopen($filenameResourceOrStream, 'w+');
499
        }
500
501 2
        $partStream = $this->getStream();
502 2
        $partStream->rewind();
503 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

503
        $stream = /** @scrutinizer ignore-call */ Psr7\stream_for($resourceOrStream);
Loading history...
504 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

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