Passed
Branch 1.0.0 (a1adee)
by Zaahid
08:34
created

PartBuilder::canHaveHeaders()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 0
crap 2
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 ZBateson\MailMimeParser\Header\HeaderFactory;
10
11
/**
12
 * Used by MessageParser to keep information about a parsed message as an
13
 * intermediary before creating a Message object and its MessagePart children.
14
 *
15
 * @author Zaahid Bateson
16
 */
17
class PartBuilder
18
{
19
    /**
20
     * @var int The offset read start position for this part (beginning of
21
     * headers) in the message's stream.
22
     */
23
    private $streamPartStartPos = 0;
24
    
25
    /**
26
     * @var int The offset read end position for this part.  If the part is a
27
     * multipart mime part, the end position is after all of this parts
28
     * children.
29
     */
30
    private $streamPartEndPos = 0;
31
    
32
    /**
33
     * @var int The offset read start position in the message's stream for the
34
     * beginning of this part's content (body).
35
     */
36
    private $streamContentStartPos = 0;
37
    
38
    /**
39
     * @var int The offset read end position in the message's stream for the
40
     * end of this part's content (body).
41
     */
42
    private $streamContentEndPos = 0;
43
44
    /**
45
     * @var \ZBateson\MailMimeParser\Header\HeaderFactory used to parse a
46
     *      Content-Type header when needed.
47
     */
48
    private $headerFactory;
49
    
50
    /**
51
     * @var \ZBateson\MailMimeParser\Message\Part\MessagePartFactory the factory
52
     *      needed for creating the Message or MessagePart for the parsed part.
53
     */
54
    private $messagePartFactory;
55
    
56
    /**
57
     * @var boolean set to true once the end boundary of the currently-parsed
58
     *      part is found.
59
     */
60
    private $endBoundaryFound = false;
61
    
62
    /**
63
     * @var boolean set to true once a boundary belonging to this parent's part
64
     *      is found.
65
     */
66
    private $parentBoundaryFound = false;
67
    
68
    /**
69
     * @var boolean|null|string false if not queried for in the content-type
70
     *      header of this part, null if the current part does not have a
71
     *      boundary, or the value of the boundary parameter of the content-type
72
     *      header if the part contains one.
73
     */
74
    private $mimeBoundary = false;
75
    
76
    /**
77
     * @var string[][] an array of headers on the current part.  The key index
78
     *      is set to the lower-cased, alphanumeric-only, name of the header
79
     *      (after stripping out non-alphanumeric characters, e.g. contenttype)
80
     *      and each element containing an array of 2 strings, the first being
81
     *      the original name of the header, and the second being the value.
82
     */
83
    private $headers = [];
84
    
85
    /**
86
     * @var PartBuilder[] an array of children found below this part for a mime
87
     *      email
88
     */
89
    private $children = [];
90
    
91
    /**
92
     * @var PartBuilder the parent part.
93
     */
94
    private $parent = null;
95
    
96
    /**
97
     * @var string[] key => value pairs of properties passed on to the 
98
     *      $messagePartFactory when constructing the Message and its children.
99
     */
100
    private $properties = [];
101
    
102
    /**
103
     * @var ZBateson\MailMimeParser\Header\ParameterHeader parsed content-type
104
     *      header.
105
     */
106
    private $contentType = null;
107
    
108
    /**
109
     * @var string the PartStream protocol used to create part and content
110
     *      filenames for fopen
111
     */
112
    private $streamWrapperProtocol = null;
113
    
114
    /**
115
     * Sets up class dependencies.
116
     * 
117
     * @param HeaderFactory $hf
118
     * @param \ZBateson\MailMimeParser\Message\Part\MessagePartFactory $mpf
119
     * @param string $streamWrapperProtocol
120
     */
121 16
    public function __construct(
122
        HeaderFactory $hf,
123
        MessagePartFactory $mpf,
124
        $streamWrapperProtocol
125
    ) {
126 16
        $this->headerFactory = $hf;
127 16
        $this->messagePartFactory = $mpf;
128 16
        $this->streamWrapperProtocol = $streamWrapperProtocol;
129 16
    }
130
    
131
    /**
132
     * Adds a header with the given $name and $value to the headers array.
133
     *
134
     * Removes non-alphanumeric characters from $name, and sets it to lower-case
135
     * to use as a key in the private headers array.  Sets the original $name
136
     * and $value as elements in the headers' array value for the calculated
137
     * key.
138
     *
139
     * @param string $name
140
     * @param string $value
141
     */
142 10
    public function addHeader($name, $value)
143
    {
144 10
        $nameKey = preg_replace('/[^a-z0-9]/', '', strtolower($name));
145 10
        $this->headers[$nameKey] = [$name, $value];
146 10
    }
147
    
148
    /**
149
     * Returns the raw headers added to this PartBuilder as an array consisting
150
     * of:
151
     * 
152
     * Keys set to the name of the header, in all lowercase, and with non-
153
     * alphanumeric characters removed (e.g. Content-Type becomes contenttype).
154
     * 
155
     * The value is an array of two elements.  The first is the original header
156
     * name (e.g. Content-Type) and the second is the raw string value of the
157
     * header, e.g. 'text/html; charset=utf8'.
158
     * 
159
     * @return array
160
     */
161 1
    public function getRawHeaders()
162
    {
163 1
        return $this->headers;
164
    }
165
    
166
    /**
167
     * Sets the specified property denoted by $name to $value.
168
     * 
169
     * @param string $name
170
     * @param mixed $value
171
     */
172 1
    public function setProperty($name, $value)
173
    {
174 1
        $this->properties[$name] = $value;
175 1
    }
176
    
177
    /**
178
     * Returns the value of the property with the given $name.
179
     * 
180
     * @param string $name
181
     * @return mixed
182
     */
183 1
    public function getProperty($name)
184
    {
185 1
        if (!isset($this->properties[$name])) {
186 1
            return null;
187
        }
188 1
        return $this->properties[$name];
189
    }
190
    
191
    /**
192
     * Registers the passed PartBuilder as a child of the current PartBuilder.
193
     * 
194
     * @param \ZBateson\MailMimeParser\Message\PartBuilder $partBuilder
195
     */
196 6
    public function addChild(PartBuilder $partBuilder)
197
    {
198 6
        $partBuilder->parent = $this;
199
        // discard parts added after the end boundary
200 6
        if (!$this->endBoundaryFound) {
201 5
            $this->children[] = $partBuilder;
202 5
        }
203 6
    }
204
    
205
    /**
206
     * Returns all children PartBuilder objects.
207
     * 
208
     * @return \ZBateson\MailMimeParser\Message\PartBuilder[]
209
     */
210 2
    public function getChildren()
211
    {
212 2
        return $this->children;
213
    }
214
    
215
    /**
216
     * Returns this PartBuilder's parent.
217
     * 
218
     * @return PartBuilder
219
     */
220 4
    public function getParent()
221
    {
222 4
        return $this->parent;
223
    }
224
    
225
    /**
226
     * Returns true if either a Content-Type or Mime-Version header are defined
227
     * in this PartBuilder's headers.
228
     * 
229
     * @return boolean
230
     */
231 2
    public function isMime()
232
    {
233 2
        return (isset($this->headers['contenttype'])
234 2
            || isset($this->headers['mimeversion']));
235
    }
236
    
237
    /**
238
     * Returns a ParameterHeader representing the parsed Content-Type header for
239
     * this PartBuilder.
240
     * 
241
     * @return \ZBateson\MailMimeParser\Header\ParameterHeader
242
     */
243 7
    public function getContentType()
244
    {
245 7
        if ($this->contentType === null && isset($this->headers['contenttype'])) {
246 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 object<ZBateson\MailMime...\Header\AbstractHeader> is incompatible with the declared type object<ZBateson\MailMime...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...
247 7
                'Content-Type',
248 7
                $this->headers['contenttype'][1]
249 7
            );
250 7
        }
251 7
        return $this->contentType;
252
    }
253
    
254
    /**
255
     * Returns the parsed boundary parameter of the Content-Type header if set
256
     * for a multipart message part.
257
     * 
258
     * @return string
259
     */
260 5
    public function getMimeBoundary()
261
    {
262 5
        if ($this->mimeBoundary === false) {
263 5
            $this->mimeBoundary = null;
264 5
            $contentType = $this->getContentType();
265 5
            if ($contentType !== null) {
266 5
                $this->mimeBoundary = $contentType->getValueFor('boundary');
267 5
            }
268 5
        }
269 5
        return $this->mimeBoundary;
270
    }
271
    
272
    /**
273
     * Returns true if this part's content-type is multipart/*
274
     *
275
     * @return boolean
276
     */
277 1
    public function isMultiPart()
278
    {
279 1
        $contentType = $this->getContentType();
280 1
        if ($contentType !== null) {
281
            // casting to bool, preg_match returns 1 for true
282 1
            return (bool) (preg_match(
283 1
                '~multipart/\w+~i',
284 1
                $contentType->getValue()
285 1
            ));
286
        }
287
        return false;
288
    }
289
    
290
    /**
291
     * Returns true if the passed $line of read input matches this PartBuilder's
292
     * mime boundary, or any of its parent's mime boundaries for a multipart
293
     * message.
294
     * 
295
     * If the passed $line is the ending boundary for the current PartBuilder,
296
     * $this->isEndBoundaryFound will return true after.
297
     * 
298
     * @param string $line
299
     * @return boolean
300
     */
301 4
    public function setEndBoundaryFound($line)
302
    {
303 4
        $boundary = $this->getMimeBoundary();
304 4
        if ($this->parent !== null && $this->parent->setEndBoundaryFound($line)) {
305 1
            $this->parentBoundaryFound = true;
306 1
            return true;
307 4
        } elseif ($boundary !== null) {
308 4
            if ($line === "--$boundary--") {
309 2
                $this->endBoundaryFound = true;
310 2
                return true;
311 3
            } elseif ($line === "--$boundary") {
312 2
                return true;
313
            }
314 3
        }
315 3
        return false;
316
    }
317
    
318
    /**
319
     * Returns true if MessageParser passed an input line to setEndBoundary that
320
     * matches a parent's mime boundary, and the following input belongs to a
321
     * new part under its parent.
322
     * 
323
     * @return boolean
324
     */
325 3
    public function isParentBoundaryFound()
326
    {
327 3
        return ($this->parentBoundaryFound);
328
    }
329
    
330
    /**
331
     * Called once EOF is reached while reading content.  The method sets the
332
     * flag used by PartBuilder::isParentBoundaryFound to true on this part and
333
     * all parent PartBuilders.
334
     */
335 1
    public function setEof()
336
    {
337 1
        $this->parentBoundaryFound = true;
338 1
        if ($this->parent !== null) {
339 1
            $this->parent->parentBoundaryFound = true;
340 1
        }
341 1
    }
342
    
343
    /**
344
     * Returns false if this part has a parent part in which endBoundaryFound is
345
     * set to true (i.e. this isn't a discardable part following the parent's
346
     * end boundary line).
347
     * 
348
     * @return booelan
349
     */
350 2
    public function canHaveHeaders()
351
    {
352 2
        return ($this->parent === null || !$this->parent->endBoundaryFound);
353
    }
354
355
    /**
356
     * Constructs and returns a filename where the part can be read from the
357
     * passed $messageObjectId.
358
     * 
359
     * @param string $messageObjectId the message object id
360
     * @return string
361
     */
362 3
    public function getStreamPartUrl($messageObjectId)
363
    {
364 3
        if ($this->streamPartEndPos === 0) {
365
            return null;
366
        }
367 3
        return $this->streamWrapperProtocol . '://' . $messageObjectId
368 3
            . '?start=' . $this->streamPartStartPos . '&end='
369 3
            . $this->streamPartEndPos;
370
    }
371
    
372
    /**
373
     * Constructs and returns a filename where the part's content can be read
374
     * from the passed $messageObjectId.
375
     * 
376
     * @param string $messageObjectId the message object id
377
     * @return string
378
     */
379 2
    public function getStreamContentUrl($messageObjectId)
380
    {
381 2
        if ($this->streamContentEndPos === 0) {
382
            return null;
383
        }
384 2
        return $this->streamWrapperProtocol . '://' . $messageObjectId
385 2
            . '?start=' . $this->streamContentStartPos . '&end='
386 2
            . $this->streamContentEndPos;
387
    }
388
    
389
    /**
390
     * Sets the start position of the part in the input stream.
391
     * 
392
     * @param int $streamPartStartPos
393
     */
394 3
    public function setStreamPartStartPos($streamPartStartPos)
395
    {
396 3
        $this->streamPartStartPos = $streamPartStartPos;
397 3
    }
398
399
    /**
400
     * Sets the end position of the part in the input stream, and also calls
401
     * parent->setParentStreamPartEndPos to expand to parent parts.
402
     * 
403
     * @param int $streamPartEndPos
404
     */
405 3
    public function setStreamPartEndPos($streamPartEndPos)
406
    {
407 3
        $this->streamPartEndPos = $streamPartEndPos;
408 3
        if ($this->parent !== null) {
409 1
            $this->parent->setStreamPartEndPos($streamPartEndPos);
410 1
        }
411 3
    }
412
413
    /**
414
     * Sets the start position of the content in the input stream.
415
     * 
416
     * @param int $streamContentStartPos
417
     */
418 2
    public function setStreamContentStartPos($streamContentStartPos)
419
    {
420 2
        $this->streamContentStartPos = $streamContentStartPos;
421 2
    }
422
423
    /**
424
     * Sets the end position of the content and part in the input stream.
425
     * 
426
     * @param int $streamContentEndPos
427
     */
428 2
    public function setStreamPartAndContentEndPos($streamContentEndPos)
429
    {
430 2
        $this->streamContentEndPos = $streamContentEndPos;
431 2
        $this->setStreamPartEndPos($streamContentEndPos);
432 2
    }
433
434
    /**
435
     * Creates a MessagePart and returns it using the PartBuilder's
436
     * MessagePartFactory passed in during construction.
437
     * 
438
     * @param string $messageObjectId
439
     * @return MessagePart
440
     */
441 1
    public function createMessagePart($messageObjectId)
442
    {
443 1
        return $this->messagePartFactory->newInstance(
444 1
            $messageObjectId,
445
            $this
446 1
        );
447
    }
448
}
449