MessagePart::getResourceHandle()   A
last analyzed

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 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 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\Message;
9
10
use GuzzleHttp\Psr7\StreamWrapper;
11
use GuzzleHttp\Psr7\Utils;
12
use Psr\Http\Message\StreamInterface;
13
use Psr\Log\LoggerInterface;
14
use SplObjectStorage;
15
use SplObserver;
16
use ZBateson\MailMimeParser\ErrorBag;
17
use ZBateson\MailMimeParser\MailMimeParser;
18
use ZBateson\MailMimeParser\Stream\MessagePartStreamDecorator;
19
20
/**
21
 * Most basic representation of a single part of an email.
22
 *
23
 * @author Zaahid Bateson
24
 */
25
abstract class MessagePart extends ErrorBag implements IMessagePart
26
{
27
    /**
28
     * @var ?IMimePart parent part
29
     */
30
    protected ?IMimePart $parent;
31
32
    /**
33
     * @var PartStreamContainer holds 'stream' and 'content stream'.
34
     */
35
    protected PartStreamContainer $partStreamContainer;
36
37
    /**
38
     * @var ?string can be used to set an override for content's charset in cases
39
     *      where a user knows the charset on the content is not what it claims
40
     *      to be.
41
     */
42
    protected ?string $charsetOverride = null;
43
44
    /**
45
     * @var bool set to true when a user attaches a stream manually, it's
46
     *      assumed to already be decoded or to have relevant transfer encoding
47
     *      decorators attached already.
48
     */
49
    protected bool $ignoreTransferEncoding = false;
50
51
    /**
52
     * @var SplObjectStorage attached observers that need to be notified of
53
     *      modifications to this part.
54
     */
55
    protected SplObjectStorage $observers;
56
57 155
    public function __construct(
58
        LoggerInterface $logger,
59
        PartStreamContainer $streamContainer,
60
        ?IMimePart $parent = null
61
    ) {
62 155
        parent::__construct($logger);
63 155
        $this->partStreamContainer = $streamContainer;
64 155
        $this->parent = $parent;
65 155
        $this->observers = new SplObjectStorage();
66
    }
67
68 120
    public function attach(SplObserver $observer) : void
69
    {
70 120
        $this->observers->attach($observer);
71
    }
72
73 6
    public function detach(SplObserver $observer) : void
74
    {
75 6
        $this->observers->detach($observer);
76
    }
77
78 111
    public function notify() : void
79
    {
80 111
        foreach ($this->observers as $observer) {
81 109
            $observer->update($this);
82
        }
83 111
        if ($this->parent !== null) {
84 70
            $this->parent->notify();
85
        }
86
    }
87
88 25
    public function getParent() : ?IMimePart
89
    {
90 25
        return $this->parent;
91
    }
92
93 113
    public function hasContent() : bool
94
    {
95 113
        return $this->partStreamContainer->hasContent();
96
    }
97
98 1
    public function getFilename() : ?string
99
    {
100 1
        return null;
101
    }
102
103 2
    public function setCharsetOverride(string $charsetOverride, bool $onlyIfNoCharset = false) : static
104
    {
105 2
        if (!$onlyIfNoCharset || $this->getCharset() === null) {
106 2
            $this->charsetOverride = $charsetOverride;
107
        }
108 2
        return $this;
109
    }
110
111 108
    public function getContentStream(string $charset = MailMimeParser::DEFAULT_CHARSET) : ?MessagePartStreamDecorator
112
    {
113 108
        if ($this->hasContent()) {
114 107
            $tr = ($this->ignoreTransferEncoding) ? '' : $this->getContentTransferEncoding();
115 107
            $ch = $this->charsetOverride ?? $this->getCharset();
116 107
            return $this->partStreamContainer->getContentStream(
117 107
                $this,
118 107
                $tr,
119 107
                $ch,
120 107
                $charset
121 107
            );
122
        }
123 50
        return null;
124
    }
125
126 49
    public function getBinaryContentStream() : ?MessagePartStreamDecorator
127
    {
128 49
        if ($this->hasContent()) {
129 49
            $tr = ($this->ignoreTransferEncoding) ? '' : $this->getContentTransferEncoding();
130 49
            return $this->partStreamContainer->getBinaryContentStream($this, $tr);
131
        }
132
        return null;
133
    }
134
135 46
    public function getBinaryContentResourceHandle() : mixed
136
    {
137 46
        $stream = $this->getBinaryContentStream();
138 46
        if ($stream !== null) {
139 46
            return StreamWrapper::getResource($stream);
140
        }
141
        return null;
142
    }
143
144 3
    public function saveContent($filenameResourceOrStream) : static
145
    {
146 3
        $resourceOrStream = $filenameResourceOrStream;
147 3
        if (\is_string($filenameResourceOrStream)) {
148 1
            $resourceOrStream = \fopen($filenameResourceOrStream, 'w+');
149
        }
150
151 3
        $stream = Utils::streamFor($resourceOrStream);
152 3
        Utils::copyToStream($this->getBinaryContentStream(), $stream);
0 ignored issues
show
Bug introduced by
It seems like $this->getBinaryContentStream() can also be of type null; however, parameter $source of GuzzleHttp\Psr7\Utils::copyToStream() does only seem to accept Psr\Http\Message\StreamInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

152
        Utils::copyToStream(/** @scrutinizer ignore-type */ $this->getBinaryContentStream(), $stream);
Loading history...
153
154 3
        if (!\is_string($filenameResourceOrStream)
155 3
            && !($filenameResourceOrStream instanceof StreamInterface)) {
156
            // only detach if it wasn't a string or StreamInterface, so the
157
            // fopen call can be properly closed if it was
158 1
            $stream->detach();
159
        }
160 3
        return $this;
161
    }
162
163 25
    public function getContent(string $charset = MailMimeParser::DEFAULT_CHARSET) : ?string
164
    {
165 25
        $stream = $this->getContentStream($charset);
166 25
        if ($stream !== null) {
167 24
            return $stream->getContents();
168
        }
169 1
        return null;
170
    }
171
172 22
    public function attachContentStream(StreamInterface $stream, string $streamCharset = MailMimeParser::DEFAULT_CHARSET) : static
173
    {
174 22
        $ch = $this->charsetOverride ?? $this->getCharset();
175 22
        if ($ch !== null && $streamCharset !== $ch) {
176 9
            $this->charsetOverride = $streamCharset;
177
        }
178 22
        $this->ignoreTransferEncoding = true;
179 22
        $this->partStreamContainer->setContentStream($stream);
180 22
        $this->notify();
181 22
        return $this;
182
    }
183
184 20
    public function detachContentStream() : static
185
    {
186 20
        $this->partStreamContainer->setContentStream(null);
187 20
        $this->notify();
188 20
        return $this;
189
    }
190
191 18
    public function setContent($resource, string $charset = MailMimeParser::DEFAULT_CHARSET) : static
192
    {
193 18
        $stream = Utils::streamFor($resource);
194 18
        $this->attachContentStream($stream, $charset);
195
        // this->notify() called in attachContentStream
196 18
        return $this;
197
    }
198
199 1
    public function getResourceHandle() : mixed
200
    {
201 1
        return StreamWrapper::getResource($this->getStream());
202
    }
203
204 104
    public function getStream() : StreamInterface
205
    {
206 104
        return $this->partStreamContainer->getStream();
207
    }
208
209 97
    public function save($filenameResourceOrStream, string $filemode = 'w+') : static
210
    {
211 97
        $resourceOrStream = $filenameResourceOrStream;
212 97
        if (\is_string($filenameResourceOrStream)) {
213 1
            $resourceOrStream = \fopen($filenameResourceOrStream, $filemode);
214
        }
215
216 97
        $partStream = $this->getStream();
217 97
        $partStream->rewind();
218 97
        $stream = Utils::streamFor($resourceOrStream);
219 97
        Utils::copyToStream($partStream, $stream);
220
221 97
        if (!\is_string($filenameResourceOrStream)
222 97
            && !($filenameResourceOrStream instanceof StreamInterface)) {
223
            // only detach if it wasn't a string or StreamInterface, so the
224
            // fopen call can be properly closed if it was
225 96
            $stream->detach();
226
        }
227 97
        return $this;
228
    }
229
230 1
    public function __toString() : string
231
    {
232 1
        return $this->getStream()->getContents();
233
    }
234
235 70
    public function getErrorLoggingContextName() : string
236
    {
237 70
        $params = '';
238 70
        if (!empty($this->getContentId())) {
239
            $params .= ', content-id=' . $this->getContentId();
240
        }
241 70
        $params .= ', content-type=' . $this->getContentType();
242 70
        $nsClass = static::class;
243 70
        $class = \substr($nsClass, (\strrpos($nsClass, '\\') ?? -1) + 1);
244 70
        return $class . '(' . \spl_object_id($this) . $params . ')';
245
    }
246
247 1
    protected function getErrorBagChildren() : array
248
    {
249 1
        return [
250 1
            $this->partStreamContainer
251 1
        ];
252
    }
253
}
254