Passed
Push — 1.0.0 ( d49389...cbb558 )
by Zaahid
03:21
created

MessagePart::getParent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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
     * @param PartStreamFilterManager $partStreamFilterManager
66
     * @param StreamFactory $streamFactory
67
     * @param StreamInterface $stream
68
     * @param StreamInterface $contentStream
69
     */
70 5
    public function __construct(
71
        PartStreamFilterManager $partStreamFilterManager,
72
        StreamFactory $streamFactory,
73
        StreamInterface $stream = null,
74
        StreamInterface $contentStream = null
75
    ) {
76 5
        $this->partStreamFilterManager = $partStreamFilterManager;
77 5
        $this->streamFactory = $streamFactory;
78
79 5
        $this->stream = $stream;
80 5
        $this->contentStream = $contentStream;
81 5
        if ($contentStream !== null) {
82 3
            $partStreamFilterManager->setStream(
83 3
                $contentStream
84
            );
85
        }
86 5
    }
87
88
    /**
89
     * Overridden to close streams.
90
     */
91 5
    public function __destruct()
92
    {
93 5
        if ($this->stream !== null) {
94 5
            $this->stream->close();
95
        }
96 5
        if ($this->contentStream !== null) {
97 3
            $this->contentStream->close();
98
        }
99 5
    }
100
101
    /**
102
     * Called when operations change the content of the MessagePart.
103
     *
104
     * The function causes calls to getStream() to return a dynamic
105
     * MessagePartStream instead of the read stream for this MessagePart and all
106
     * parent MessageParts.
107
     */
108
    protected function onChange()
109
    {
110
        $this->markAsChanged();
111
        if ($this->parent !== null) {
112
            $this->parent->onChange();
113
        }
114
    }
115
116
    /**
117
     * Marks the part as changed, forcing the part to be rewritten when saved.
118
     *
119
     * Normal operations to a MessagePart automatically mark the part as
120
     * changed and markAsChanged() doesn't need to be called in those cases.
121
     *
122
     * The function can be called to indicate an external change that requires
123
     * rewriting this part, for instance changing a message from a non-mime
124
     * message to a mime one, would require rewriting non-mime children to
125
     * insure suitable headers are written.
126
     *
127
     * Internally, the function discards the part's stream, forcing a stream to
128
     * be created when calling getStream().
129
     */
130
    public function markAsChanged()
131
    {
132
        // the stream is not closed because $this->contentStream may still be
133
        // attached to it.  GuzzleHttp will clean it up when destroyed.
134
        $this->stream = null;
135
    }
136
137
    /**
138
     * Returns true if there's a content stream associated with the part.
139
     *
140
     * @return boolean
141
     */
142 5
    public function hasContent()
143
    {
144 5
        return ($this->contentStream !== null);
145
    }
146
147
    /**
148
     * Returns true if this part's mime type is text/plain, text/html or has a
149
     * text/* and has a defined 'charset' attribute.
150
     * 
151
     * @return bool
152
     */
153
    public abstract function isTextPart();
154
    
155
    /**
156
     * Returns the mime type of the content.
157
     * 
158
     * @return string
159
     */
160
    public abstract function getContentType();
161
    
162
    /**
163
     * Returns the charset of the content, or null if not applicable/defined.
164
     * 
165
     * @return string
166
     */
167
    public abstract function getCharset();
168
    
169
    /**
170
     * Returns the content's disposition.
171
     * 
172
     * @return string
173
     */
174
    public abstract function getContentDisposition();
175
    
176
    /**
177
     * Returns the content-transfer-encoding used for this part.
178
     * 
179
     * @return string
180
     */
181
    public abstract function getContentTransferEncoding();
182
    
183
    /**
184
     * Returns a filename for the part if one is defined, or null otherwise.
185
     * 
186
     * @return string
187
     */
188
    public function getFilename()
189
    {
190
        return null;
191
    }
192
    
193
    /**
194
     * Returns true if the current part is a mime part.
195
     * 
196
     * @return bool
197
     */
198
    public abstract function isMime();
199
    
200
    /**
201
     * Rewrite me
202
     * 
203
     * @return resource the resource handle
204
     */
205 2
    public function getHandle()
206
    {
207 2
        return StreamWrapper::getResource($this->getStream());
208
    }
209
210
    /**
211
     * Write me
212
     *
213
     * @return StreamInterface the resource handle
214
     */
215 2
    public function getStream()
216
    {
217 2
        if ($this->stream === null) {
218
            return $this->streamFactory->newMessagePartStream($this);
219
        }
220 2
        $this->stream->rewind();
221 2
        return $this->stream;
222
    }
223
224
    /**
225
     * Overrides the default character set used for reading content from content
226
     * streams in cases where a user knows the source charset is not what is
227
     * specified.
228
     * 
229
     * If set, the returned value from MessagePart::getCharset is ignored.
230
     * 
231
     * Note that setting an override on a Message and calling getTextStream,
232
     * getTextContent, getHtmlStream or getHtmlContent will not be applied to
233
     * those sub-parts, unless the text/html part is the Message itself.
234
     * Instead, Message:getTextPart() should be called, and setCharsetOverride
235
     * called on the returned MessagePart.
236
     * 
237
     * @param string $charsetOverride
238
     * @param boolean $onlyIfNoCharset if true, $charsetOverride is used only if
239
     *        getCharset returns null.
240
     */
241 1
    public function setCharsetOverride($charsetOverride, $onlyIfNoCharset = false)
242
    {
243 1
        if (!$onlyIfNoCharset || $this->getCharset() === null) {
244
            $this->charsetOverride = $charsetOverride;
245
        }
246 1
    }
247
248
    /**
249
     * Returns a new resource stream handle for the part's content or null if
250
     * the part doesn't have a content section.
251
     *
252
     * The returned resource handle is a resource stream with decoding filters
253
     * appended to it.  The attached filters are determined by looking at the
254
     * part's Content-Transfer-Encoding and Content-Type headers unless a
255
     * charset override is set.  The following transfer encodings are supported:
256
     *
257
     * - quoted-printable
258
     * - base64
259
     * - x-uuencode
260
     *
261
     * In addition, the charset of the underlying stream is converted to the
262
     * passed $charset if the content is known to be text.
263
     *
264
     * @param string $charset
265
     * @return resource
266
     */
267 4
    public function getContentResourceHandle($charset = MailMimeParser::DEFAULT_CHARSET)
268
    {
269 4
        $stream = $this->getContentStream($charset);
270 4
        if ($stream !== null) {
271 2
            return StreamWrapper::getResource($stream);
272
        }
273 2
        return null;
274
    }
275
276
    /**
277
     * Returns the StreamInterface for the part's content or null if the part
278
     * doesn't have a content section.
279
     *
280
     * Because the returned stream may be a shared object if called multiple
281
     * times, the function isn't exposed publicly.  If called multiple times
282
     * with the same $charset, and the value of the part's
283
     * Content-Transfer-Encoding header not having changed, the returned stream
284
     * is the same instance and may need to be rewound.
285
     *
286
     * Note that PartStreamFilterManager rewinds the stream before returning it.
287
     *
288
     * @param string $charset
289
     * @return StreamInterface
290
     */
291 5
    public function getContentStream($charset = MailMimeParser::DEFAULT_CHARSET)
292
    {
293 5
        if ($this->hasContent()) {
294 3
            $tr = ($this->ignoreTransferEncoding) ? '' : $this->getContentTransferEncoding();
295 3
            $ch = ($this->charsetOverride !== null) ? $this->charsetOverride : $this->getCharset();
296 3
            return $this->partStreamFilterManager->getContentStream(
297 3
                $tr,
298 3
                $ch,
299 3
                $charset
300
            );
301
        }
302 2
        return null;
303
    }
304
305
    /**
306
     * Shortcut to reading stream content and assigning it to a string.  Returns
307
     * null if the part doesn't have a content stream.
308
     * 
309
     * The returned string is encoded to the passed $charset character encoding,
310
     * defaulting to UTF-8.
311
     *
312
     * @return string
313
     */
314 2
    public function getContent($charset = MailMimeParser::DEFAULT_CHARSET)
315
    {
316 2
        $stream = $this->getContentStream($charset);
317 2
        if ($stream !== null) {
318 1
            return $stream->getContents();
319
        }
320 1
        return null;
321
    }
322
323
    /**
324
     * Returns this part's parent.
325
     *
326
     * @return \ZBateson\MailMimeParser\Message\Part\MimePart
327
     */
328 1
    public function getParent()
329
    {
330 1
        return $this->parent;
331
    }
332
333
    /**
334
     * Attaches the stream or resource handle for the part's content.  The
335
     * stream is closed when another stream is attached, or the MimePart is
336
     * destroyed.
337
     *
338
     * @param StreamInterface $stream
339
     * @param string $streamCharset
340
     */
341
    public function attachContentStream(StreamInterface $stream, $streamCharset = MailMimeParser::DEFAULT_CHARSET)
342
    {
343
        if ($this->contentStream !== null && $this->contentStream !== $stream) {
344
            $this->contentStream->close();
345
        }
346
        $this->contentStream = $stream;
347
        $ch = ($this->charsetOverride !== null) ? $this->charsetOverride : $this->getCharset();
348
        if ($ch !== null && $streamCharset !== $ch) {
349
            $this->charsetOverride = $streamCharset;
350
        }
351
        $this->ignoreTransferEncoding = true;
352
        $this->partStreamFilterManager->setStream($stream);
353
        $this->onChange();
354
    }
355
356
    /**
357
     * Detaches and closes the content stream.
358
     */
359
    public function detachContentStream()
360
    {
361
        $this->contentStream = null;
362
        $this->partStreamFilterManager->setStream(null);
363
        $this->onChange();
364
    }
365
366
    /**
367
     * Sets the content of the part to the passed string.
368
     *
369
     * @param string|resource $stringOrHandle
370
     * @param string $charset
371
     */
372
    public function setContent($stringOrHandle, $charset = MailMimeParser::DEFAULT_CHARSET)
373
    {
374
        $stream = Psr7\stream_for($stringOrHandle);
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

374
        $stream = /** @scrutinizer ignore-call */ Psr7\stream_for($stringOrHandle);
Loading history...
375
        $this->attachContentStream($stream, $charset);
376
        // this->onChange called in attachContentStream
377
    }
378
379
    /**
380
     * Saves the message/part as to the passed resource handle.
381
     *
382
     * @param resource|StreamInterface $streamOrHandle
383
     */
384
    public function save($streamOrHandle)
385
    {
386
        $message = $this->getStream();
387
        $message->rewind();
388
        if (!($streamOrHandle instanceof StreamInterface)) {
389
            $streamOrHandle = Psr7\stream_for($streamOrHandle);
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

389
            $streamOrHandle = /** @scrutinizer ignore-call */ Psr7\stream_for($streamOrHandle);
Loading history...
390
        }
391
        Psr7\copy_to_stream($message, $streamOrHandle);
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

391
        /** @scrutinizer ignore-call */ 
392
        Psr7\copy_to_stream($message, $streamOrHandle);
Loading history...
392
        // don't close when out of scope
393
        $streamOrHandle->detach();
394
    }
395
396
    /**
397
     * Returns the message/part as a string.
398
     *
399
     * Convenience method for calling getStream()->getContents().
400
     *
401
     * @return string
402
     */
403
    public function __toString()
404
    {
405
        return $this->getStream()->getContents();
406
    }
407
}
408