MimePart   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 37
eloc 71
c 1
b 0
f 0
dl 0
loc 276
ccs 98
cts 98
cp 1
rs 9.44

24 Methods

Rating   Name   Duplication   Size   Complexity  
A isMultiPart() 0 6 1
A isMime() 0 3 1
A getRawHeaderIterator() 0 3 1
A isTextPart() 0 3 1
A getErrorBagChildren() 0 3 1
A isSignaturePart() 0 6 3
A getFilename() 0 8 1
A getHeader() 0 3 1
A removeHeader() 0 5 1
A getContentDisposition() 0 7 3
A getHeaderValue() 0 7 3
A getContentTransferEncoding() 0 12 2
A __construct() 0 15 1
A setRawHeader() 0 5 1
A getContentId() 0 3 1
A getHeaderAs() 0 3 1
A getCharset() 0 11 5
A addRawHeader() 0 5 1
A removeSingleHeader() 0 5 1
A getHeaderParameter() 0 7 3
A getContentType() 0 3 1
A getAllHeaders() 0 3 1
A getRawHeaders() 0 3 1
A getAllHeadersByName() 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\Message;
9
10
use Psr\Log\LoggerInterface;
11
use Traversable;
12
use ZBateson\MailMimeParser\Header\HeaderConsts;
13
use ZBateson\MailMimeParser\Header\IHeader;
14
use ZBateson\MailMimeParser\Header\ParameterHeader;
15
use ZBateson\MailMimeParser\IMessage;
16
use ZBateson\MailMimeParser\MailMimeParser;
17
18
/**
19
 * A mime email message part.
20
 *
21
 * A MIME part may contain any combination of headers, content and children.
22
 *
23
 * @author Zaahid Bateson
24
 */
25
class MimePart extends MultiPart implements IMimePart
26
{
27
    /**
28
     * @var PartHeaderContainer Container for this part's headers.
29
     */
30
    protected PartHeaderContainer $headerContainer;
31
32 141
    public function __construct(
33
        ?IMimePart $parent = null,
34
        ?LoggerInterface $logger = null,
35
        ?PartStreamContainer $streamContainer = null,
36
        ?PartHeaderContainer $headerContainer = null,
37
        ?PartChildrenContainer $partChildrenContainer = null
38
    ) {
39 141
        $di = MailMimeParser::getGlobalContainer();
40 141
        parent::__construct(
41 141
            $logger ?? $di->get(LoggerInterface::class),
42 141
            $streamContainer ?? $di->get(PartStreamContainer::class),
43 141
            $partChildrenContainer ?? $di->get(PartChildrenContainer::class),
44 141
            $parent
45 141
        );
46 141
        $this->headerContainer = $headerContainer ?? $di->get(PartHeaderContainer::class);
47
    }
48
49
    /**
50
     * Returns a filename for the part if one is defined, or null otherwise.
51
     *
52
     * Uses the 'filename' parameter of the Content-Disposition header if it
53
     * exists, or the 'name' parameter of the 'Content-Type' header if it
54
     * doesn't.
55
     *
56
     * @return string|null the file name of the part or null.
57
     */
58 97
    public function getFilename() : ?string
59
    {
60 97
        return $this->getHeaderParameter(
61 97
            HeaderConsts::CONTENT_DISPOSITION,
62 97
            'filename',
63 97
            $this->getHeaderParameter(
64 97
                HeaderConsts::CONTENT_TYPE,
65 97
                'name'
66 97
            )
67 97
        );
68
    }
69
70
    /**
71
     * Returns true.
72
     *
73
     */
74 2
    public function isMime() : bool
75
    {
76 2
        return true;
77
    }
78
79 57
    public function isMultiPart() : bool
80
    {
81
        // casting to bool, preg_match returns 1 for true
82 57
        return (bool) (\preg_match(
83 57
            '~multipart/.*~i',
84 57
            $this->getContentType()
85 57
        ));
86
    }
87
88
    /**
89
     * Returns true if this part has a defined 'charset' on its Content-Type
90
     * header.
91
     *
92
     * This may result in some false positives if charset is set on a part that
93
     * is not plain text which has been seen.  If a part is known to be binary,
94
     * it's better to use {@see IMessagePart::getBinaryContentStream()} to
95
     * avoid issues, or to call {@see IMessagePart::saveContent()} directly if
96
     * saving a part's content.
97
     *
98
     */
99 48
    public function isTextPart() : bool
100
    {
101 48
        return ($this->getCharset() !== null);
102
    }
103
104
    /**
105
     * Returns the mime type of the content, or $default if one is not set.
106
     *
107
     * Looks at the part's Content-Type header and returns its value if set, or
108
     * defaults to 'text/plain'.
109
     *
110
     * Note that the returned value is converted to lower case, and may not be
111
     * identical to calling {@see MimePart::getHeaderValue('Content-Type')} in
112
     * some cases.
113
     *
114
     * @param string $default Optional default value to specify a default other
115
     *        than text/plain if needed.
116
     * @return string the mime type
117
     */
118 112
    public function getContentType(string $default = 'text/plain') : ?string
119
    {
120 112
        return \strtolower($this->getHeaderValue(HeaderConsts::CONTENT_TYPE, $default));
121
    }
122
123
    /**
124
     * Returns the charset of the content, or null if not applicable/defined.
125
     *
126
     * Looks for a 'charset' parameter under the 'Content-Type' header of this
127
     * part and returns it if set, defaulting to 'ISO-8859-1' if the
128
     * Content-Type header exists and is of type text/plain or text/html.
129
     *
130
     * Note that the returned value is also converted to upper case.
131
     *
132
     * @return string|null the charset
133
     */
134 107
    public function getCharset() : ?string
135
    {
136 107
        $charset = $this->getHeaderParameter(HeaderConsts::CONTENT_TYPE, 'charset');
137 107
        if ($charset === null || \strcasecmp($charset, 'binary') === 0) {
138 77
            $contentType = $this->getContentType();
139 77
            if ($contentType === 'text/plain' || $contentType === 'text/html') {
140 11
                return 'ISO-8859-1';
141
            }
142 69
            return null;
143
        }
144 86
        return \strtoupper($charset);
145
    }
146
147
    /**
148
     * Returns the content's disposition, or returns the value of $default if
149
     * not defined.
150
     *
151
     * Looks at the 'Content-Disposition' header, which should only contain
152
     * either 'inline' or 'attachment'.  If the header is not one of those
153
     * values, $default is returned, which defaults to 'inline' unless passed
154
     * something else.
155
     *
156
     * @param string $default Optional default value if not set or does not
157
     *        match 'inline' or 'attachment'.
158
     * @return string the content disposition
159
     */
160 99
    public function getContentDisposition(?string $default = 'inline') : ?string
161
    {
162 99
        $value = $this->getHeaderValue(HeaderConsts::CONTENT_DISPOSITION);
163 99
        if ($value === null || !\in_array($value, ['inline', 'attachment'])) {
164 97
            return $default;
165
        }
166 55
        return \strtolower($value);
167
    }
168
169
    /**
170
     * Returns the content transfer encoding used to encode the content on this
171
     * part, or the value of $default if not defined.
172
     *
173
     * Looks up and returns the value of the 'Content-Transfer-Encoding' header
174
     * if set, defaulting to '7bit' if an alternate $default param is not
175
     * passed.
176
     *
177
     * The returned value is always lowercase, and header values of 'x-uue',
178
     * 'uue' and 'uuencode' will return 'x-uuencode' instead.
179
     *
180
     * @param string $default Optional default value to return if the header
181
     *        isn't set.
182
     * @return string the content transfer encoding.
183
     */
184 106
    public function getContentTransferEncoding(?string $default = '7bit') : ?string
185
    {
186 106
        static $translated = [
187 106
            'x-uue' => 'x-uuencode',
188 106
            'uue' => 'x-uuencode',
189 106
            'uuencode' => 'x-uuencode'
190 106
        ];
191 106
        $type = \strtolower($this->getHeaderValue(HeaderConsts::CONTENT_TRANSFER_ENCODING, $default));
192 106
        if (isset($translated[$type])) {
193 2
            return $translated[$type];
194
        }
195 106
        return $type;
196
    }
197
198
    /**
199
     * Returns the Content ID of the part, or null if not defined.
200
     *
201
     * Looks up and returns the value of the 'Content-ID' header.
202
     *
203
     * @return string|null the content ID or null if not defined.
204
     */
205 76
    public function getContentId() : ?string
206
    {
207 76
        return $this->getHeaderValue(HeaderConsts::CONTENT_ID);
208
    }
209
210
    /**
211
     * Returns true if this part's parent is an IMessage, and is the same part
212
     * returned by {@see IMessage::getSignaturePart()}.
213
     */
214 57
    public function isSignaturePart() : bool
215
    {
216 57
        if ($this->parent === null || !$this->parent instanceof IMessage) {
217 20
            return false;
218
        }
219 52
        return $this->parent->getSignaturePart() === $this;
0 ignored issues
show
Bug introduced by
The method getSignaturePart() does not exist on ZBateson\MailMimeParser\Message\IMimePart. It seems like you code against a sub-type of said class. However, the method does not exist in ZBateson\MailMimeParser\Message\MimePart. Are you sure you never get one of those? ( Ignorable by Annotation )

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

219
        return $this->parent->/** @scrutinizer ignore-call */ getSignaturePart() === $this;
Loading history...
220
    }
221
222 121
    public function getHeader(string $name, int $offset = 0) : ?IHeader
223
    {
224 121
        return $this->headerContainer->get($name, $offset);
225
    }
226
227 1
    public function getHeaderAs(string $name, string $iHeaderClass, int $offset = 0) : ?IHeader
228
    {
229 1
        return $this->headerContainer->getAs($name, $iHeaderClass, $offset);
230
    }
231
232 20
    public function getAllHeaders() : array
233
    {
234 20
        return $this->headerContainer->getHeaderObjects();
235
    }
236
237 1
    public function getAllHeadersByName(string $name) : array
238
    {
239 1
        return $this->headerContainer->getAll($name);
240
    }
241
242 1
    public function getRawHeaders() : array
243
    {
244 1
        return $this->headerContainer->getHeaders();
245
    }
246
247 95
    public function getRawHeaderIterator() : Traversable
248
    {
249 95
        return $this->headerContainer->getIterator();
250
    }
251
252 118
    public function getHeaderValue(string $name, ?string $defaultValue = null) : ?string
253
    {
254 118
        $header = $this->getHeader($name);
255 118
        if ($header !== null) {
256 117
            return $header->getValue() ?: $defaultValue;
257
        }
258 108
        return $defaultValue;
259
    }
260
261 110
    public function getHeaderParameter(string $header, string $param, ?string $defaultValue = null) : ?string
262
    {
263 110
        $obj = $this->getHeader($header);
264 110
        if ($obj && $obj instanceof ParameterHeader) {
265 106
            return $obj->getValueFor($param, $defaultValue);
266
        }
267 93
        return $defaultValue;
268
    }
269
270 23
    public function setRawHeader(string $name, ?string $value, int $offset = 0) : static
271
    {
272 23
        $this->headerContainer->set($name, $value, $offset);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type null; however, parameter $value of ZBateson\MailMimeParser\...tHeaderContainer::set() does only seem to accept string, 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

272
        $this->headerContainer->set($name, /** @scrutinizer ignore-type */ $value, $offset);
Loading history...
273 23
        $this->notify();
274 23
        return $this;
275
    }
276
277 1
    public function addRawHeader(string $name, string $value) : static
278
    {
279 1
        $this->headerContainer->add($name, $value);
280 1
        $this->notify();
281 1
        return $this;
282
    }
283
284 19
    public function removeHeader(string $name) : static
285
    {
286 19
        $this->headerContainer->removeAll($name);
287 19
        $this->notify();
288 19
        return $this;
289
    }
290
291 1
    public function removeSingleHeader(string $name, int $offset = 0) : static
292
    {
293 1
        $this->headerContainer->remove($name, $offset);
294 1
        $this->notify();
295 1
        return $this;
296
    }
297
298 1
    public function getErrorBagChildren() : array
299
    {
300 1
        return \array_merge(parent::getErrorBagChildren(), [$this->headerContainer]);
301
    }
302
}
303