MessagePartStream   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 8
Bugs 1 Features 0
Metric Value
wmc 20
eloc 65
c 8
b 1
f 0
dl 0
loc 161
ccs 72
cts 72
cp 1
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getCharsetDecoratorForStream() 0 25 4
A update() 0 6 2
A getBoundaryAndChildStreams() 0 22 5
A getStreamsArray() 0 27 5
A __construct() 0 11 1
A createStream() 0 6 2
A __destruct() 0 3 1
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
8
namespace ZBateson\MailMimeParser\Stream;
9
10
use GuzzleHttp\Psr7;
11
use GuzzleHttp\Psr7\AppendStream;
12
use Psr\Http\Message\StreamInterface;
13
use SplObserver;
14
use SplSubject;
15
use ZBateson\MailMimeParser\Header\HeaderConsts;
16
use ZBateson\MailMimeParser\MailMimeParser;
17
use ZBateson\MailMimeParser\Message\IMessagePart;
18
use ZBateson\MailMimeParser\Message\IMimePart;
19
use ZBateson\MbWrapper\UnsupportedCharsetException;
20
21
/**
22
 * Provides a readable stream for a MessagePart.
23
 *
24
 * @author Zaahid Bateson
25
 */
26
class MessagePartStream extends MessagePartStreamDecorator implements SplObserver, StreamInterface
27
{
28
    /**
29
     * @var StreamFactory For creating needed stream decorators.
30
     */
31
    protected StreamFactory $streamFactory;
32
33
    /**
34
     * @var IMessagePart The part to read from.
35
     */
36
    protected IMessagePart $part;
37
38
    /**
39
     * @var bool if false, saving a content stream with an unsupported charset
40
     *      will be written in the default charset, otherwise the stream will be
41
     *      created with the unsupported charset, and an exception will be
42
     *      thrown when read from.
43
     */
44
    protected bool $throwExceptionReadingPartContentFromUnsupportedCharsets;
45
46
    /**
47
     * @var ?AppendStream
48
     */
49
    protected ?AppendStream $appendStream = null;
50
51 113
    public function __construct(StreamFactory $sdf, IMessagePart $part, bool $throwExceptionReadingPartContentFromUnsupportedCharsets)
52
    {
53 113
        parent::__construct($part);
54 113
        $this->streamFactory = $sdf;
55 113
        $this->part = $part;
56 113
        $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets;
57 113
        $part->attach($this);
58
59
        // unsetting the property forces the first access to go through
60
        // __get().
61 113
        unset($this->stream);
62
    }
63
64 11
    public function __destruct()
65
    {
66 11
        $this->part->detach($this);
67
    }
68
69 96
    public function update(SplSubject $subject) : void
70
    {
71 96
        if ($this->appendStream !== null) {
72
            // unset forces recreation in StreamDecoratorTrait with a call to __get
73 3
            unset($this->stream);
74 3
            $this->appendStream = null;
75
        }
76
    }
77
78
    /**
79
     * Attaches and returns a CharsetStream decorator to the passed $stream.
80
     *
81
     * If the current attached IMessagePart doesn't specify a charset, $stream
82
     * is returned as-is.
83
     */
84 100
    private function getCharsetDecoratorForStream(StreamInterface $stream) : StreamInterface
85
    {
86 100
        $charset = $this->part->getCharset();
87 100
        if (!empty($charset)) {
88 79
            if (!$this->throwExceptionReadingPartContentFromUnsupportedCharsets) {
89 77
                $test = $this->streamFactory->newCharsetStream(
90 77
                    Psr7\Utils::streamFor(),
91 77
                    $charset,
92 77
                    MailMimeParser::DEFAULT_CHARSET
93 77
                );
94
                try {
95 77
                    $test->write('t');
96 2
                } catch (UnsupportedCharsetException $e) {
97 2
                    return $stream;
98
                } finally {
99 77
                    $test->close();
100
                }
101
            }
102 77
            $stream = $this->streamFactory->newCharsetStream(
103 77
                $stream,
104 77
                $charset,
105 77
                MailMimeParser::DEFAULT_CHARSET
106 77
            );
107
        }
108 98
        return $stream;
109
    }
110
111
    /**
112
     * Creates an array of streams based on the attached part's mime boundary
113
     * and child streams.
114
     *
115
     * @param IMimePart $part passed in because $this->part is declared
116
     *        as IMessagePart
117
     * @return StreamInterface[]
118
     */
119 67
    protected function getBoundaryAndChildStreams(IMimePart $part) : array
120
    {
121 67
        $boundary = $part->getHeaderParameter(HeaderConsts::CONTENT_TYPE, 'boundary');
122 67
        if ($boundary === null) {
123 2
            return \array_map(
124 2
                function($child) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
125 2
                    return $child->getStream();
126 2
                },
127 2
                $part->getChildParts()
128 2
            );
129
        }
130 65
        $streams = [];
131 65
        foreach ($part->getChildParts() as $i => $child) {
132 65
            if ($i !== 0 || $part->hasContent()) {
133 65
                $streams[] = Psr7\Utils::streamFor("\r\n");
134
            }
135 65
            $streams[] = Psr7\Utils::streamFor("--$boundary\r\n");
136 65
            $streams[] = $child->getStream();
137
        }
138 65
        $streams[] = Psr7\Utils::streamFor("\r\n--$boundary--\r\n");
139
140 65
        return $streams;
141
    }
142
143
    /**
144
     * Returns an array of Psr7 Streams representing the attached part and it's
145
     * direct children.
146
     *
147
     * @return StreamInterface[]
148
     */
149 100
    protected function getStreamsArray() : array
150
    {
151 100
        $contentStream = $this->part->getContentStream();
152 100
        if ($contentStream !== null) {
153
            // wrapping in a SeekingLimitStream because the underlying
154
            // ContentStream could be rewound, etc...
155 100
            $contentStream = $this->streamFactory->newDecoratedCachingStream(
156 100
                $this->streamFactory->newSeekingStream($contentStream),
157 100
                function($stream) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
158 100
                    $es = $this->streamFactory->getTransferEncodingDecoratedStream(
159 100
                        $stream,
160 100
                        $this->part->getContentTransferEncoding(),
161 100
                        $this->part->getFilename()
162 100
                    );
163 100
                    $cs = $this->getCharsetDecoratorForStream($es);
164 100
                    return $cs;
165 100
                }
166 100
            );
167
        }
168
169 100
        $streams = [$this->streamFactory->newHeaderStream($this->part), $contentStream ?: Psr7\Utils::streamFor()];
170
171 100
        if ($this->part instanceof IMimePart && $this->part->getChildCount() > 0) {
0 ignored issues
show
Bug introduced by
The method getChildCount() does not exist on ZBateson\MailMimeParser\Message\IMessagePart. It seems like you code against a sub-type of ZBateson\MailMimeParser\Message\IMessagePart such as ZBateson\MailMimeParser\Message\IMultiPart or ZBateson\MailMimeParser\Message\MultiPart. ( Ignorable by Annotation )

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

171
        if ($this->part instanceof IMimePart && $this->part->/** @scrutinizer ignore-call */ getChildCount() > 0) {
Loading history...
172 67
            $streams = \array_merge($streams, $this->getBoundaryAndChildStreams($this->part));
173
        }
174
175 100
        return $streams;
176
    }
177
178
    /**
179
     * Creates the underlying stream lazily when required.
180
     */
181 100
    protected function createStream() : StreamInterface
182
    {
183 100
        if ($this->appendStream === null) {
184 100
            $this->appendStream = new AppendStream($this->getStreamsArray());
185
        }
186 100
        return $this->appendStream;
187
    }
188
}
189