Completed
Push — master ( 6512b0...b69ec3 )
by Zaahid
03:33
created

PartFilter::filter()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 4
nop 1
crap 4
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 InvalidArgumentException;
10
11
/**
12
 * Provides a way to define a filter of MimeParts for use in various calls to
13
 * add/remove MimeParts.
14
 * 
15
 * A PartFilter is defined as a set of properties in the class, set to either be
16
 * 'included' or 'excluded'.  The filter is simplistic in that a property
17
 * defined as included must be set on a part for it to be passed, and an
18
 * excluded filter must not be set for the part to be passed.  There is no
19
 * provision for creating logical conditions.
20
 * 
21
 * The only property set by default is $signedpart, which defaults to
22
 * FILTER_EXCLUDE.
23
 * 
24
 * A PartFilter can be instantiated with an array of keys matching class
25
 * properties, and values to set them for convenience.
26
 * 
27
 * ```php
28
 * $inlineParts = $message->getAllParts(new PartFilter([
29
 *     'multipart' => PartFilter::FILTER_INCLUDE,
30
 *     'headers' => [ 
31
 *         FILTER_EXCLUDE => [
32
 *             'Content-Disposition': 'attachment'
33
 *         ]
34
 *     ]
35
 * ]));
36
 * 
37
 * $inlineTextPart = $message->getAllParts(PartFilter::fromInlineContentType('text/plain'));
38
 * ```
39
 *
40
 * @author Zaahid Bateson
41
 */
42
class PartFilter
43
{
44
    /**
45
     * @var int indicates a filter is not in use
46
     */
47
    const FILTER_OFF = 0;
48
    
49
    /**
50
     * @var int an excluded filter must not be included in a part
51
     */
52
    const FILTER_EXCLUDE = 1;
53
    
54
    /**
55
     * @var int an included filter must be included in a part
56
     */
57
    const FILTER_INCLUDE = 2;
58
    
59
    /**
60
     * @var int filters based on whether MimePart::isMultiPart is set
61
     */
62
    private $multipart = PartFilter::FILTER_OFF;
63
    
64
    /**
65
     * @var int filters based on whether MimePart::isTextPart is set
66
     */
67
    private $textpart = PartFilter::FILTER_OFF;
68
    
69
    /**
70
     * @var int filters based on whether the parent of a part is a
71
     *      multipart/signed part and this part has a content-type equal to its
72
     *      parent's 'protocol' parameter in its content-type header
73
     */
74
    private $signedpart = PartFilter::FILTER_EXCLUDE;
75
    
76
    /**
77
     * @var string[][] array of header rules.  The top-level contains keys of
78
     * FILTER_INCLUDE and/or FILTER_EXCLUDE, which contain key => value mapping
79
     * of header names => values to search for.  Note that when searching
80
     * MimePart::getHeaderValue is used (so additional parameters need not be
81
     * matched) and strcasecmp is used.
82
     * 
83
     * ```php
84
     * $filter = new PartFilter();
85
     * $filter->headers = [ PartFilter::FILTER_INCLUDE => [ 'Content-Type' => 'text/plain' ] ];
86
     * ```
87
     */
88
    private $headers = [];
89
    
90
    /**
91
     * Convenience method to filter for a specific mime type.
92
     * 
93
     * @param string $mimeType
94
     * @return PartFilter
95
     */
96 2
    public static function fromContentType($mimeType)
97
    {
98 2
        return new static([
99
            'headers' => [
100 2
                static::FILTER_INCLUDE => [
101
                    'Content-Type' => $mimeType
102 2
                ]
103 2
            ]
104 2
        ]);
105
    }
106
    
107
    /**
108
     * Convenience method to look for parts of a specific mime-type, and that
109
     * do not specifically have a Content-Disposition equal to 'attachment'.
110
     * 
111
     * @param string $mimeType
112
     * @return PartFilter
113
     */
114 1
    public static function fromInlineContentType($mimeType)
115
    {
116 1
        return new static([
117
            'headers' => [
118 1
                static::FILTER_INCLUDE => [
119
                    'Content-Type' => $mimeType
120 1
                ],
121 1
                static::FILTER_EXCLUDE => [
122
                    'Content-Disposition' => 'attachment'
123 1
                ]
124 1
            ]
125 1
        ]);
126
    }
127
    
128
    /**
129
     * Convenience method to search for parts with a specific
130
     * Content-Disposition, optionally including multipart parts.
131
     * 
132
     * @param string $disposition
133
     * @param int $multipart
134
     * @return PartFilter
135
     */
136 5
    public static function fromDisposition($disposition, $multipart = PartFilter::FILTER_OFF)
137
    {
138 5
        return new static([
139 5
            'multipart' => $multipart,
140
            'headers' => [
141 5
                static::FILTER_INCLUDE => [
142
                    'Content-Disposition' => $disposition
143 5
                ]
144 5
            ]
145 5
        ]);
146
    }
147
    
148
    /**
149
     * Constructs a PartFilter, optionally instantiating member variables with
150
     * values in the passed array.
151
     * 
152
     * The passed array must use keys equal to member variable names, e.g.
153
     * 'multipart', 'textpart', 'signedpart' and 'headers'.
154
     * 
155
     * @param array $filter
156
     */
157 16
    public function __construct(array $filter = [])
158
    {
159 16
        $params = [ 'multipart', 'textpart', 'signedpart', 'headers' ];
160 16
        foreach ($params as $param) {
161 16
            if (isset($filter[$param])) {
162 16
                $this->__set($param, $filter[$param]);
163 16
            }
164 16
        }
165 16
    }
166
    
167
    /**
168
     * Validates an argument passed to __set to insure it's set to a value in
169
     * $valid.
170
     * 
171
     * @param string $name Name of the member variable
172
     * @param string $value The value to test
173
     * @param array $valid an array of valid values
174
     * @throws InvalidArgumentException
175
     */
176 16
    private function validateArgument($name, $value, array $valid)
177
    {
178 16
        if (!in_array($value, $valid)) {
179
            $last = array_pop($valid);
180
            throw new InvalidArgumentException(
181
                '$value parameter for ' . $name . ' must be one of '
182
                . join(', ', $valid) . ' or ' . $last . ' - "' . $value
183
                . '" provided'
184
            );
185
        }
186 16
    }
187
    
188
    /**
189
     * Sets the PartFilter's headers filter to the passed array after validating
190
     * it.
191
     * 
192
     * @param array $headers
193
     * @throws InvalidArgumentException
194
     */
195
    public function setHeaders(array $headers)
196
    {
197 13
        array_walk($headers, function ($v, $k) {
198 13
            $this->validateArgument(
199 13
                'headers',
200 13
                $k,
201 13
                [ static::FILTER_EXCLUDE, static::FILTER_INCLUDE ]
202 13
            );
203 13
            if (!is_array($v)) {
204
                throw new InvalidArgumentException(
205
                    '$value must be an array with keys set to FILTER_EXCLUDE, '
206
                    . 'FILTER_INCLUDE and values set to an array of header '
207
                    . 'name => values'
208
                );
209
            }
210 13
        });
211 13
        $this->headers = $headers;
212 13
    }
213
    
214
    /**
215
     * Sets the member variable denoted by $name to the passed $value after
216
     * validating it.
217
     * 
218
     * @param string $name
219
     * @param int|array $value
220
     * @throws InvalidArgumentException
221
     */
222 16
    public function __set($name, $value)
223
    {
224 16
        if ($name === 'multipart' || $name === 'textpart' || $name === 'signedpart') {
225 13
            $this->validateArgument(
226 13
                $name,
227 13
                $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...
228 13
                [ static::FILTER_OFF, static::FILTER_EXCLUDE, static::FILTER_INCLUDE ]
229 13
            );
230 13
            $this->$name = $value;
231 16
        } elseif ($name === 'headers') {
232 13
            if (!is_array($value)) {
233
                throw new InvalidArgumentException('$value must be an array');
234
            }
235 13
            $this->setHeaders($value);
236 13
        }
237 16
    }
238
    
239
    /**
240
     * Returns true if the variable denoted by $name is a member variable of
241
     * PartFilter.
242
     * 
243
     * @param string $name
244
     * @return bool
245
     */
246
    public function __isset($name)
247
    {
248
        return isset($this->$name);
249
    }
250
    
251
    /**
252
     * Returns the value of the member variable denoted by $name
253
     * 
254
     * @param string $name
255
     * @return mixed
256
     */
257 16
    public function __get($name)
258
    {
259 16
        return $this->$name;
260
    }
261
    
262
    /**
263
     * Returns true if the passed MimePart fails the filter's multipart filter
264
     * settings.
265
     * 
266
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
267
     * @return bool
268
     */
269 16
    private function failsMultiPartFilter(MimePart $part)
270
    {
271 16
        return ($this->multipart === static::FILTER_EXCLUDE && $part->isMultiPart())
272 16
            || ($this->multipart === static::FILTER_INCLUDE && !$part->isMultiPart());
273
    }
274
    
275
    /**
276
     * Returns true if the passed MimePart fails the filter's textpart filter
277
     * settings.
278
     * 
279
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
280
     * @return bool
281
     */
282 16
    private function failsTextPartFilter(MimePart $part)
283
    {
284 16
        return ($this->textpart === static::FILTER_EXCLUDE && $part->isTextPart())
285 16
            || ($this->textpart === static::FILTER_INCLUDE && !$part->isTextPart());
286
    }
287
    
288
    /**
289
     * Returns true if the passed MimePart fails the filter's signedpart filter
290
     * settings.
291
     * 
292
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
293
     * @return boolean
294
     */
295 16
    private function failsSignedPartFilter(MimePart $part)
296
    {
297 16
        if ($this->signedpart === static::FILTER_OFF) {
298 1
            return false;
299 15
        } elseif ($part->getParent() === null) {
300 15
            return ($this->signedpart === static::FILTER_INCLUDE);
301
        }
302 10
        $partMimeType = $part->getHeaderValue('Content-Type');
303 10
        $parentMimeType = $part->getParent()->getHeaderValue('Content-Type');
304 10
        $parentProtocol = $part->getParent()->getHeaderParameter('Content-Type', 'protocol');
305 10
        if (strcasecmp($parentMimeType, 'multipart/signed') === 0 && strcasecmp($partMimeType, $parentProtocol) === 0) {
306 10
            return ($this->signedpart === static::FILTER_EXCLUDE);
307
        }
308
        return ($this->signedpart === static::FILTER_INCLUDE);
309
    }
310
    
311
    /**
312
     * Returns true if the passed MimePart fails the filter's header filter
313
     * settings.
314
     * 
315
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
316
     * @return boolean
317
     */
318 16
    public function failsHeaderPartFilter(MimePart $part)
319
    {
320 16
        foreach ($this->headers as $type => $values) {
321 13
            foreach ($values as $name => $header) {
322 13
                $headerValue = $part->getHeaderValue($name);
323 13
                if (($type === static::FILTER_EXCLUDE && strcasecmp($headerValue, $header) === 0)
324 13
                    || ($type === static::FILTER_INCLUDE && strcasecmp($headerValue, $header) !== 0)) {
325 13
                    return true;
326
                }
327 11
            }
328 14
        }
329 14
        return false;
330
    }
331
    
332
    /**
333
     * Determines if the passed MimePart should be filtered out or not.  If the
334
     * MimePart passes all filter tests, true is returned.  Otherwise false is
335
     * returned.
336
     * 
337
     * @param \ZBateson\MailMimeParser\Message\MimePart $part
338
     * @return boolean
339
     */
340 16
    public function filter(MimePart $part)
341
    {
342 16
        return !($this->failsMultiPartFilter($part)
343 16
            || $this->failsTextPartFilter($part)
344 16
            || $this->failsSignedPartFilter($part)
345 16
            || $this->failsHeaderPartFilter($part));
346
    }
347
}
348