Passed
Push — 1.0.0 ( 4505d9...06b3ad )
by Zaahid
04:10
created

PartBuilder::isMultiPart()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 11
ccs 6
cts 7
cp 0.8571
crap 2.0116
rs 9.4285
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 Psr\Http\Message\StreamInterface;
10
use ZBateson\MailMimeParser\Header\HeaderFactory;
11
12
/**
13
 * Used by MessageParser to keep information about a parsed message as an
14
 * intermediary before creating a Message object and its MessagePart children.
15
 *
16
 * @author Zaahid Bateson
17
 */
18
class PartBuilder
19
{
20
    /**
21
     * @var int The offset read start position for this part (beginning of
22
     * headers) in the message's stream.
23
     */
24
    private $streamPartStartPos = 0;
25
    
26
    /**
27
     * @var int The offset read end position for this part.  If the part is a
28
     * multipart mime part, the end position is after all of this parts
29
     * children.
30
     */
31
    private $streamPartEndPos = 0;
32
    
33
    /**
34
     * @var int The offset read start position in the message's stream for the
35
     * beginning of this part's content (body).
36
     */
37
    private $streamContentStartPos = 0;
38
    
39
    /**
40
     * @var int The offset read end position in the message's stream for the
41
     * end of this part's content (body).
42
     */
43
    private $streamContentEndPos = 0;
44
45
    /**
46
     * @var \ZBateson\MailMimeParser\Header\HeaderFactory used to parse a
47
     *      Content-Type header when needed.
48
     */
49
    private $headerFactory;
50
    
51
    /**
52
     * @var \ZBateson\MailMimeParser\Message\Part\MessagePartFactory the factory
53
     *      needed for creating the Message or MessagePart for the parsed part.
54
     */
55
    private $messagePartFactory;
56
    
57
    /**
58
     * @var boolean set to true once the end boundary of the currently-parsed
59
     *      part is found.
60
     */
61
    private $endBoundaryFound = false;
62
    
63
    /**
64
     * @var boolean set to true once a boundary belonging to this parent's part
65
     *      is found.
66
     */
67
    private $parentBoundaryFound = false;
68
    
69
    /**
70
     * @var boolean|null|string false if not queried for in the content-type
71
     *      header of this part, null if the current part does not have a
72
     *      boundary, or the value of the boundary parameter of the content-type
73
     *      header if the part contains one.
74
     */
75
    private $mimeBoundary = false;
76
    
77
    /**
78
     * @var string[][] an array of headers on the current part.  The key index
79
     *      is set to the lower-cased, alphanumeric-only, name of the header
80
     *      (after stripping out non-alphanumeric characters, e.g. contenttype)
81
     *      and each element containing an array of 2 strings, the first being
82
     *      the original name of the header, and the second being the value.
83
     */
84
    private $headers = [];
85
    
86
    /**
87
     * @var PartBuilder[] an array of children found below this part for a mime
88
     *      email
89
     */
90
    private $children = [];
91
    
92
    /**
93
     * @var PartBuilder the parent part.
94
     */
95
    private $parent = null;
96
    
97
    /**
98
     * @var string[] key => value pairs of properties passed on to the 
99
     *      $messagePartFactory when constructing the Message and its children.
100
     */
101
    private $properties = [];
102
    
103
    /**
104
     * @var ZBateson\MailMimeParser\Header\ParameterHeader parsed content-type
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\...\Header\ParameterHeader was not found. Did you mean ZBateson\MailMimeParser\Header\ParameterHeader? If so, make sure to prefix the type with \.
Loading history...
105
     *      header.
106
     */
107
    private $contentType = null;
108
    
109
    /**
110
     * Sets up class dependencies.
111
     * 
112
     * @param HeaderFactory $hf
113
     * @param \ZBateson\MailMimeParser\Message\Part\MessagePartFactory $mpf
114
     */
115 16
    public function __construct(
116
        HeaderFactory $hf,
117
        MessagePartFactory $mpf
118
    ) {
119 16
        $this->headerFactory = $hf;
120 16
        $this->messagePartFactory = $mpf;
121 16
    }
122
    
123
    /**
124
     * Adds a header with the given $name and $value to the headers array.
125
     *
126
     * Removes non-alphanumeric characters from $name, and sets it to lower-case
127
     * to use as a key in the private headers array.  Sets the original $name
128
     * and $value as elements in the headers' array value for the calculated
129
     * key.
130
     *
131
     * @param string $name
132
     * @param string $value
133
     */
134 10
    public function addHeader($name, $value)
135
    {
136 10
        $nameKey = preg_replace('/[^a-z0-9]/', '', strtolower($name));
137 10
        $this->headers[$nameKey] = [$name, $value];
138 10
    }
139
    
140
    /**
141
     * Returns the raw headers added to this PartBuilder as an array consisting
142
     * of:
143
     * 
144
     * Keys set to the name of the header, in all lowercase, and with non-
145
     * alphanumeric characters removed (e.g. Content-Type becomes contenttype).
146
     * 
147
     * The value is an array of two elements.  The first is the original header
148
     * name (e.g. Content-Type) and the second is the raw string value of the
149
     * header, e.g. 'text/html; charset=utf8'.
150
     * 
151
     * @return array
152
     */
153 1
    public function getRawHeaders()
154
    {
155 1
        return $this->headers;
156
    }
157
    
158
    /**
159
     * Sets the specified property denoted by $name to $value.
160
     * 
161
     * @param string $name
162
     * @param mixed $value
163
     */
164 1
    public function setProperty($name, $value)
165
    {
166 1
        $this->properties[$name] = $value;
167 1
    }
168
    
169
    /**
170
     * Returns the value of the property with the given $name.
171
     * 
172
     * @param string $name
173
     * @return mixed
174
     */
175 1
    public function getProperty($name)
176
    {
177 1
        if (!isset($this->properties[$name])) {
178 1
            return null;
179
        }
180 1
        return $this->properties[$name];
181
    }
182
    
183
    /**
184
     * Registers the passed PartBuilder as a child of the current PartBuilder.
185
     * 
186
     * @param \ZBateson\MailMimeParser\Message\PartBuilder $partBuilder
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\Message\PartBuilder 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...
187
     */
188 6
    public function addChild(PartBuilder $partBuilder)
189
    {
190 6
        $partBuilder->parent = $this;
191
        // discard parts added after the end boundary
192 6
        if (!$this->endBoundaryFound) {
193 5
            $this->children[] = $partBuilder;
194
        }
195 6
    }
196
    
197
    /**
198
     * Returns all children PartBuilder objects.
199
     * 
200
     * @return \ZBateson\MailMimeParser\Message\PartBuilder[]
201
     */
202 2
    public function getChildren()
203
    {
204 2
        return $this->children;
205
    }
206
    
207
    /**
208
     * Returns this PartBuilder's parent.
209
     * 
210
     * @return PartBuilder
211
     */
212 4
    public function getParent()
213
    {
214 4
        return $this->parent;
215
    }
216
    
217
    /**
218
     * Returns true if either a Content-Type or Mime-Version header are defined
219
     * in this PartBuilder's headers.
220
     * 
221
     * @return boolean
222
     */
223 2
    public function isMime()
224
    {
225 2
        return (isset($this->headers['contenttype'])
226 2
            || isset($this->headers['mimeversion']));
227
    }
228
    
229
    /**
230
     * Returns a ParameterHeader representing the parsed Content-Type header for
231
     * this PartBuilder.
232
     * 
233
     * @return \ZBateson\MailMimeParser\Header\ParameterHeader
234
     */
235 7
    public function getContentType()
236
    {
237 7
        if ($this->contentType === null && isset($this->headers['contenttype'])) {
238 7
            $this->contentType = $this->headerFactory->newInstance(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->headerFactory->ne...ders['contenttype'][1]) of type ZBateson\MailMimeParser\Header\AbstractHeader is incompatible with the declared type ZBateson\MailMimeParser\...\Header\ParameterHeader of property $contentType.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
239 7
                'Content-Type',
240 7
                $this->headers['contenttype'][1]
241
            );
242
        }
243 7
        return $this->contentType;
244
    }
245
    
246
    /**
247
     * Returns the parsed boundary parameter of the Content-Type header if set
248
     * for a multipart message part.
249
     * 
250
     * @return string
251
     */
252 5
    public function getMimeBoundary()
253
    {
254 5
        if ($this->mimeBoundary === false) {
255 5
            $this->mimeBoundary = null;
256 5
            $contentType = $this->getContentType();
257 5
            if ($contentType !== null) {
258 5
                $this->mimeBoundary = $contentType->getValueFor('boundary');
259
            }
260
        }
261 5
        return $this->mimeBoundary;
262
    }
263
    
264
    /**
265
     * Returns true if this part's content-type is multipart/*
266
     *
267
     * @return boolean
268
     */
269 1
    public function isMultiPart()
270
    {
271 1
        $contentType = $this->getContentType();
272 1
        if ($contentType !== null) {
273
            // casting to bool, preg_match returns 1 for true
274 1
            return (bool) (preg_match(
275 1
                '~multipart/\w+~i',
276 1
                $contentType->getValue()
277
            ));
278
        }
279
        return false;
280
    }
281
    
282
    /**
283
     * Returns true if the passed $line of read input matches this PartBuilder's
284
     * mime boundary, or any of its parent's mime boundaries for a multipart
285
     * message.
286
     * 
287
     * If the passed $line is the ending boundary for the current PartBuilder,
288
     * $this->isEndBoundaryFound will return true after.
289
     * 
290
     * @param string $line
291
     * @return boolean
292
     */
293 4
    public function setEndBoundaryFound($line)
294
    {
295 4
        $boundary = $this->getMimeBoundary();
296 4
        if ($this->parent !== null && $this->parent->setEndBoundaryFound($line)) {
297 1
            $this->parentBoundaryFound = true;
298 1
            return true;
299 4
        } elseif ($boundary !== null) {
0 ignored issues
show
introduced by
The condition $boundary !== null is always true.
Loading history...
300 4
            if ($line === "--$boundary--") {
301 2
                $this->endBoundaryFound = true;
302 2
                return true;
303 3
            } elseif ($line === "--$boundary") {
304 2
                return true;
305
            }
306
        }
307 3
        return false;
308
    }
309
    
310
    /**
311
     * Returns true if MessageParser passed an input line to setEndBoundary that
312
     * matches a parent's mime boundary, and the following input belongs to a
313
     * new part under its parent.
314
     * 
315
     * @return boolean
316
     */
317 3
    public function isParentBoundaryFound()
318
    {
319 3
        return ($this->parentBoundaryFound);
320
    }
321
    
322
    /**
323
     * Called once EOF is reached while reading content.  The method sets the
324
     * flag used by PartBuilder::isParentBoundaryFound to true on this part and
325
     * all parent PartBuilders.
326
     */
327 1
    public function setEof()
328
    {
329 1
        $this->parentBoundaryFound = true;
330 1
        if ($this->parent !== null) {
331 1
            $this->parent->parentBoundaryFound = true;
332
        }
333 1
    }
334
    
335
    /**
336
     * Returns false if this part has a parent part in which endBoundaryFound is
337
     * set to true (i.e. this isn't a discardable part following the parent's
338
     * end boundary line).
339
     * 
340
     * @return booelan
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\Message\Part\booelan 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...
341
     */
342 2
    public function canHaveHeaders()
343
    {
344 2
        return ($this->parent === null || !$this->parent->endBoundaryFound);
345
    }
346
347 3
    public function getStreamPartStartOffset()
348
    {
349 3
        if ($this->parent) {
350 1
            return $this->streamPartStartPos - $this->parent->streamPartStartPos;
351
        }
352 3
        return $this->streamPartStartPos;
353
    }
354
    
355 3
    public function getStreamPartLength()
356
    {
357 3
        return $this->streamPartEndPos - $this->streamPartStartPos;
358
    }
359
360 2
    public function getStreamContentStartOffset()
361
    {
362 2
        if ($this->parent) {
363 1
            return $this->streamContentStartPos - $this->parent->streamPartStartPos;
364
        }
365 2
        return $this->streamContentStartPos;
366
    }
367
368 2
    public function getStreamContentLength()
369
    {
370 2
        return $this->streamContentEndPos - $this->streamContentStartPos;
371
    }
372
373
    /**
374
     * Sets the start position of the part in the input stream.
375
     * 
376
     * @param int $streamPartStartPos
377
     */
378 3
    public function setStreamPartStartPos($streamPartStartPos)
379
    {
380 3
        $this->streamPartStartPos = $streamPartStartPos;
381 3
    }
382
383
    /**
384
     * Sets the end position of the part in the input stream, and also calls
385
     * parent->setParentStreamPartEndPos to expand to parent parts.
386
     * 
387
     * @param int $streamPartEndPos
388
     */
389 3
    public function setStreamPartEndPos($streamPartEndPos)
390
    {
391 3
        $this->streamPartEndPos = $streamPartEndPos;
392 3
        if ($this->parent !== null) {
393 1
            $this->parent->setStreamPartEndPos($streamPartEndPos);
394
        }
395 3
    }
396
397
    /**
398
     * Sets the start position of the content in the input stream.
399
     * 
400
     * @param int $streamContentStartPos
401
     */
402 2
    public function setStreamContentStartPos($streamContentStartPos)
403
    {
404 2
        $this->streamContentStartPos = $streamContentStartPos;
405 2
    }
406
407
    /**
408
     * Sets the end position of the content and part in the input stream.
409
     * 
410
     * @param int $streamContentEndPos
411
     */
412 2
    public function setStreamPartAndContentEndPos($streamContentEndPos)
413
    {
414 2
        $this->streamContentEndPos = $streamContentEndPos;
415 2
        $this->setStreamPartEndPos($streamContentEndPos);
416 2
    }
417
418
    /**
419
     * Creates a MessagePart and returns it using the PartBuilder's
420
     * MessagePartFactory passed in during construction.
421
     * 
422
     * @param StreamInterface $stream
423
     * @return MessagePart
424
     */
425 1
    public function createMessagePart(StreamInterface $stream)
426
    {
427 1
        return $this->messagePartFactory->newInstance(
428 1
            $stream,
429 1
            $this
430
        );
431
    }
432
}
433