Test Failed
Branch 1.0.0 (84f469)
by Zaahid
05:36
created

PartFilter::filter()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 4
nop 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
namespace ZBateson\MailMimeParser\Message;
8
9
use ZBateson\MailMimeParser\Message\Part\MessagePart;
10
use ZBateson\MailMimeParser\Message\Part\MimePart;
11
use InvalidArgumentException;
12
13
/**
14
 * Provides a way to define a filter of MimeParts for use in various calls to
15
 * add/remove MimeParts.
16
 * 
17
 * A PartFilter is defined as a set of properties in the class, set to either be
18
 * 'included' or 'excluded'.  The filter is simplistic in that a property
19
 * defined as included must be set on a part for it to be passed, and an
20
 * excluded filter must not be set for the part to be passed.  There is no
21
 * provision for creating logical conditions.
22
 * 
23
 * The only property set by default is $signedpart, which defaults to
24
 * FILTER_EXCLUDE.
25
 * 
26
 * A PartFilter can be instantiated with an array of keys matching class
27
 * properties, and values to set them for convenience.
28
 * 
29
 * ```php
30
 * $inlineParts = $message->getAllParts(new PartFilter([
31
 *     'multipart' => PartFilter::FILTER_INCLUDE,
32
 *     'headers' => [ 
33
 *         FILTER_EXCLUDE => [
34
 *             'Content-Disposition': 'attachment'
35
 *         ]
36
 *     ]
37
 * ]));
38
 * 
39
 * $inlineTextPart = $message->getAllParts(PartFilter::fromInlineContentType('text/plain'));
40
 * ```
41
 *
42
 * @author Zaahid Bateson
43
 */
44
class PartFilter
45
{
46
    /**
47
     * @var int indicates a filter is not in use
48
     */
49
    const FILTER_OFF = 0;
50
    
51
    /**
52
     * @var int an excluded filter must not be included in a part
53
     */
54
    const FILTER_EXCLUDE = 1;
55
    
56
    /**
57
     * @var int an included filter must be included in a part
58
     */
59
    const FILTER_INCLUDE = 2;
60
    
61
    /**
62
     * @var int filters based on whether MimePart::isMultiPart is set
63
     */
64
    private $multipart = PartFilter::FILTER_OFF;
65
    
66
    /**
67
     * @var int filters based on whether MimePart::isTextPart is set
68
     */
69
    private $textpart = PartFilter::FILTER_OFF;
70
    
71
    /**
72
     * @var int filters based on whether the parent of a part is a
73
     *      multipart/signed part and this part has a content-type equal to its
74
     *      parent's 'protocol' parameter in its content-type header
75
     */
76
    private $signedpart = PartFilter::FILTER_EXCLUDE;
77
    
78
    /**
79
     * @var string[][] array of header rules.  The top-level contains keys of
80
     * FILTER_INCLUDE and/or FILTER_EXCLUDE, which contain key => value mapping
81
     * of header names => values to search for.  Note that when searching
82
     * MimePart::getHeaderValue is used (so additional parameters need not be
83
     * matched) and strcasecmp is used.
84
     * 
85
     * ```php
86
     * $filter = new PartFilter();
87
     * $filter->headers = [ PartFilter::FILTER_INCLUDE => [ 'Content-Type' => 'text/plain' ] ];
88
     * ```
89
     */
90
    private $headers = [];
91
92
    /**
93
     * @var string[] map of headers and default values if the header isn't set.
94
     *      This allows text/plain to match a Content-Type header that hasn't
95
     *      been set for instance.
96
     */
97
    private $defaultHeaderValues = [
0 ignored issues
show
Unused Code introduced by
The property $defaultHeaderValues is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
98
        'Content-Type' => 'text/plain',
99
        'Content-Disposition' => 'inline',
100
    ];
101
    
102
    /**
103
     * Convenience method to filter for a specific mime type.
104
     * 
105
     * @param string $mimeType
106
     * @return PartFilter
107
     */
108
    public static function fromContentType($mimeType)
109
    {
110
        return new static([
111
            'headers' => [
112
                static::FILTER_INCLUDE => [
113
                    'Content-Type' => $mimeType
114
                ]
115
            ]
116
        ]);
117
    }
118
    
119
    /**
120
     * Convenience method to look for parts of a specific mime-type, and that
121
     * do not specifically have a Content-Disposition equal to 'attachment'.
122
     * 
123
     * @param string $mimeType
124
     * @return PartFilter
125
     */
126
    public static function fromInlineContentType($mimeType)
127
    {
128
        return new static([
129
            'headers' => [
130
                static::FILTER_INCLUDE => [
131
                    'Content-Type' => $mimeType
132
                ],
133
                static::FILTER_EXCLUDE => [
134
                    'Content-Disposition' => 'attachment'
135
                ]
136
            ]
137
        ]);
138
    }
139
    
140
    /**
141
     * Convenience method to search for parts with a specific
142
     * Content-Disposition, optionally including multipart parts.
143
     * 
144
     * @param string $disposition
145
     * @param int $multipart
146
     * @return PartFilter
147
     */
148
    public static function fromDisposition($disposition, $multipart = PartFilter::FILTER_OFF)
149
    {
150
        return new static([
151
            'multipart' => $multipart,
152
            'headers' => [
153
                static::FILTER_INCLUDE => [
154
                    'Content-Disposition' => $disposition
155
                ]
156
            ]
157
        ]);
158
    }
159
    
160
    /**
161
     * Constructs a PartFilter, optionally instantiating member variables with
162
     * values in the passed array.
163
     * 
164
     * The passed array must use keys equal to member variable names, e.g.
165
     * 'multipart', 'textpart', 'signedpart' and 'headers'.
166
     * 
167
     * @param array $filter
168
     */
169
    public function __construct(array $filter = [])
170
    {
171
        $params = [ 'multipart', 'textpart', 'signedpart', 'headers' ];
172
        foreach ($params as $param) {
173
            if (isset($filter[$param])) {
174
                $this->__set($param, $filter[$param]);
175
            }
176
        }
177
    }
178
    
179
    /**
180
     * Validates an argument passed to __set to insure it's set to a value in
181
     * $valid.
182
     * 
183
     * @param string $name Name of the member variable
184
     * @param string $value The value to test
185
     * @param array $valid an array of valid values
186
     * @throws InvalidArgumentException
187
     */
188
    private function validateArgument($name, $value, array $valid)
189
    {
190
        if (!in_array($value, $valid)) {
191
            $last = array_pop($valid);
192
            throw new InvalidArgumentException(
193
                '$value parameter for ' . $name . ' must be one of '
194
                . join(', ', $valid) . ' or ' . $last . ' - "' . $value
195
                . '" provided'
196
            );
197
        }
198
    }
199
    
200
    /**
201
     * Sets the PartFilter's headers filter to the passed array after validating
202
     * it.
203
     * 
204
     * @param array $headers
205
     * @throws InvalidArgumentException
206
     */
207
    public function setHeaders(array $headers)
208
    {
209
        array_walk($headers, function ($v, $k) {
210
            $this->validateArgument(
211
                'headers',
212
                $k,
213
                [ static::FILTER_EXCLUDE, static::FILTER_INCLUDE ]
214
            );
215
            if (!is_array($v)) {
216
                throw new InvalidArgumentException(
217
                    '$value must be an array with keys set to FILTER_EXCLUDE, '
218
                    . 'FILTER_INCLUDE and values set to an array of header '
219
                    . 'name => values'
220
                );
221
            }
222
        });
223
        $this->headers = $headers;
224
    }
225
    
226
    /**
227
     * Sets the member variable denoted by $name to the passed $value after
228
     * validating it.
229
     * 
230
     * @param string $name
231
     * @param int|array $value
232
     * @throws InvalidArgumentException
233
     */
234
    public function __set($name, $value)
235
    {
236
        if ($name === 'multipart' || $name === 'textpart' || $name === 'signedpart') {
237
            $this->validateArgument(
238
                $name,
239
                $value,
0 ignored issues
show
Documentation introduced by
$value is of type integer|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
240
                [ static::FILTER_OFF, static::FILTER_EXCLUDE, static::FILTER_INCLUDE ]
241
            );
242
            $this->$name = $value;
243
        } elseif ($name === 'headers') {
244
            if (!is_array($value)) {
245
                throw new InvalidArgumentException('$value must be an array');
246
            }
247
            $this->setHeaders($value);
248
        }
249
    }
250
    
251
    /**
252
     * Returns true if the variable denoted by $name is a member variable of
253
     * PartFilter.
254
     * 
255
     * @param string $name
256
     * @return bool
257
     */
258
    public function __isset($name)
259
    {
260
        return isset($this->$name);
261
    }
262
    
263
    /**
264
     * Returns the value of the member variable denoted by $name
265
     * 
266
     * @param string $name
267
     * @return mixed
268
     */
269
    public function __get($name)
270
    {
271
        return $this->$name;
272
    }
273
    
274
    /**
275
     * Returns true if the passed MimePart fails the filter's multipart filter
276
     * settings.
277
     * 
278
     * @param \ZBateson\MailMimeParser\Message\Part\MimePart $part
279
     * @return bool
280
     */
281
    private function failsMultiPartFilter(MessagePart $part)
282
    {
283
        if (!($part instanceof MimePart)) {
284
            return $this->multipart !== static::FILTER_EXCLUDE;
285
        }
286
        return ($this->multipart === static::FILTER_EXCLUDE && $part->isMultiPart())
287
            || ($this->multipart === static::FILTER_INCLUDE && !$part->isMultiPart());
288
    }
289
    
290
    /**
291
     * Returns true if the passed MimePart fails the filter's textpart filter
292
     * settings.
293
     * 
294
     * @param \ZBateson\MailMimeParser\Message\Part\MimePart $part
295
     * @return bool
296
     */
297
    private function failsTextPartFilter(MessagePart $part)
298
    {
299
        return ($this->textpart === static::FILTER_EXCLUDE && $part->isTextPart())
300
            || ($this->textpart === static::FILTER_INCLUDE && !$part->isTextPart());
301
    }
302
    
303
    /**
304
     * Returns true if the passed MimePart fails the filter's signedpart filter
305
     * settings.
306
     * 
307
     * @param \ZBateson\MailMimeParser\Message\Part\MimePart $part
308
     * @return boolean
309
     */
310
    private function failsSignedPartFilter(MessagePart $part)
311
    {
312
        if ($this->signedpart === static::FILTER_OFF) {
313
            return false;
314
        } elseif (!$part->isMime() || $part->getParent() === null) {
315
            return ($this->signedpart === static::FILTER_INCLUDE);
316
        }
317
        $partMimeType = $part->getContentType();
318
        $parentMimeType = $part->getParent()->getContentType();
319
        $parentProtocol = $part->getParent()->getHeaderParameter('Content-Type', 'protocol');
320
        if (strcasecmp($parentMimeType, 'multipart/signed') === 0 && strcasecmp($partMimeType, $parentProtocol) === 0) {
321
            return ($this->signedpart === static::FILTER_EXCLUDE);
322
        }
323
        return ($this->signedpart === static::FILTER_INCLUDE);
324
    }
325
    
326
    /**
327
     * Returns true if the passed MimePart fails the filter's header filter
328
     * settings.
329
     * 
330
     * @param \ZBateson\MailMimeParser\Message\Part\MimePart $part
331
     * @return boolean
332
     */
333
    public function failsHeaderPartFilter(MessagePart $part)
334
    {
335
        foreach ($this->headers as $type => $values) {
336
            foreach ($values as $name => $header) {
337
                
338
                $headerValue = null;
0 ignored issues
show
Unused Code introduced by
$headerValue is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
339
                if (strcasecmp($name, 'Content-Type') === 0) {
340
                    $headerValue = $part->getContentType();
341
                } elseif (strcasecmp($name, 'Content-Disposition') === 0) {
342
                    $headerValue = $part->getContentDisposition();
343
                } elseif (strcasecmp($name, 'Content-Transfer-Encoding')) {
344
                    $headerValue = $part->getContentTransferEncoding();
345
                } elseif (!($part instanceof MimePart)) {
346
                    return ($type === static::FILTER_INCLUDE);
347
                } else {
348
                    $headerValue = $part->getHeaderValue($name);
349
                }
350
                if (($type === static::FILTER_EXCLUDE && strcasecmp($headerValue, $header) === 0)
351
                    || ($type === static::FILTER_INCLUDE && strcasecmp($headerValue, $header) !== 0)) {
352
                    return true;
353
                }
354
            }
355
        }
356
        return false;
357
    }
358
    
359
    /**
360
     * Determines if the passed MimePart should be filtered out or not.  If the
361
     * MimePart passes all filter tests, true is returned.  Otherwise false is
362
     * returned.
363
     * 
364
     * @param \ZBateson\MailMimeParser\Message\Part\MimePart $part
365
     * @return boolean
366
     */
367
    public function filter(MessagePart $part)
368
    {
369
        return !($this->failsMultiPartFilter($part)
370
            || $this->failsTextPartFilter($part)
371
            || $this->failsSignedPartFilter($part)
372
            || $this->failsHeaderPartFilter($part));
373
    }
374
}
375