Passed
Push — master ( 170dc8...c9950a )
by mon
02:34
created

Type::removeParameter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 1
1
<?php
2
3
namespace FileEye\MimeMap;
4
5
/**
6
 * Class for working with MIME types
7
 */
8
class Type
9
{
10
    /**
11
     * The MIME media type.
12
     *
13
     * @var string
14
     */
15
    protected $media;
16
17
    /**
18
     * The MIME media type comment.
19
     *
20
     * @var string
21
     */
22
    protected $mediaComment;
23
24
    /**
25
     * The MIME media sub-type.
26
     *
27
     * @var string
28
     */
29
    protected $subType;
30
31
    /**
32
     * The MIME media sub-type comment.
33
     *
34
     * @var string
35
     */
36
    protected $subTypeComment;
37
38
    /**
39
     * Optional MIME parameters
40
     *
41
     * @var TypeParameter[]
42
     */
43
    protected $parameters = [];
44
45
    /**
46
     * Constructor.
47
     *
48
     * The type string will be parsed and the appropriate class vars set.
49
     *
50
     * @param string $type MIME type
51
     */
52 50
    public function __construct($type)
53
    {
54 50
        $this->parse($type);
55 43
    }
56
57
    /**
58
     * Parse a mime-type and set the class variables.
59
     *
60
     * @param string $type MIME type to parse
61
     *
62
     * @return void
63
     */
64 50
    protected function parse($type)
65
    {
66
        // Media and SubType are separated by a slash '/'.
67 50
        $sub = $this->parseStringPart($type, 0, '/');
68
69 49
        if (!$sub['string']) {
70 3
            throw new MalformedTypeException('Media type not found');
71
        }
72 46
        if (!$sub['delimiter_matched']) {
73 1
            throw new MalformedTypeException('Slash \'/\' to separate media type and subtype not found');
74
        }
75
76 45
        $this->media = strtolower($sub['string']);
77 45
        $this->mediaComment = $sub['comment'];
78 45
        $this->parseSubType(substr($type, $sub['end_offset'] + 1));
79 43
    }
80
81
    /**
82
     * Parse the sub-type part of the type and set the class variables.
83
     *
84
     * @param string $sub_type
85
     *
86
     * @return void
87
     */
88 45
    protected function parseSubType($sub_type)
89
    {
90
        // SubType and Parameters are separated by semicolons ';'.
91 45
        $sub = $this->parseStringPart($sub_type, 0, ';');
92
93 44
        if (!$sub['string']) {
94 1
            throw new MalformedTypeException('Media subtype not found');
95
        }
96
97 43
        $this->subType = strtolower($sub['string']);
98 43
        $this->subTypeComment = $sub['comment'];
99
100
        // Loops through the parameter.
101 43
        while ($sub['delimiter_matched']) {
102 26
            $sub = $this->parseStringPart($sub_type, $sub['end_offset'] + 1, ';');
103 26
            $p_name = static::getAttribute($sub['string']);
104 26
            $p_val = static::getValue($sub['string']);
105 26
            $this->addParameter($p_name, $p_val, $sub['comment']);
106
        }
107 43
    }
108
109
    /**
110
     * Parses a part of the content MIME type string.
111
     *
112
     * Splits string and comment until a delimiter is found.
113
     *
114
     * @param string $string     Input string.
115
     * @param int $offset        Offset to start parsing from.
116
     * @param string $delimiter  Stop parsing when delimiter found.
117
     *
118
     * @return array An array with the following keys:
119
     *   'string' - the uncommented part of $string
120
     *   'comment' - the comment part of $string
121
     *   'delimiter_matched' - true if a $delimiter stopped the parsing, false
122
     *                         otherwise
123
     *   'end_offset' - the last position parsed in $string.
124
     */
125 50
    protected function parseStringPart($string, $offset, $delimiter)
126
    {
127 50
        $inquote   = false;
128 50
        $escaped   = false;
129 50
        $incomment = 0;
130 50
        $newstring = '';
131 50
        $comment = '';
132
133 50
        for ($n = $offset; $n < strlen($string); $n++) {
134 48
            if ($string[$n] === $delimiter && !$escaped && !$inquote && $incomment === 0) {
135 46
                break;
136
            }
137 47
            if ($escaped) {
138 4
                if ($incomment == 0) {
139 2
                    $newstring .= $string[$n];
140
                } else {
141 2
                    $comment .= $string[$n];
142
                }
143 4
                $escaped = false;
144 47
            } elseif ($string[$n] == '\\') {
145 4
                if ($incomment > 0) {
146 2
                    $comment .= $string[$n];
147
                }
148 4
                $escaped = true;
149 47
            } elseif (!$inquote && $incomment > 0 && $string[$n] == ')') {
150 20
                $incomment--;
151 20
                if ($incomment == 0) {
152 20
                    $comment .= ' ';
153
                }
154 47
            } elseif (!$inquote && $string[$n] == '(') {
155 20
                $incomment++;
156 47
            } elseif ($string[$n] == '"') {
157 9
                if ($incomment > 0) {
158 1
                    $comment .= $string[$n];
159
                } else {
160 8
                    if ($inquote) {
161 8
                        $inquote = false;
162
                    } else {
163 9
                        $inquote = true;
164
                    }
165
                }
166 47
            } elseif ($incomment == 0) {
167 47
                $newstring .= $string[$n];
168
            } else {
169 20
                $comment .= $string[$n];
170
            }
171
        }
172
173 50
        if ($incomment > 0) {
174 2
            throw new MalformedTypeException('Comment closing bracket missing: ' . $comment);
175
        }
176
177
        return [
178 49
          'string' => empty($newstring) ? null : trim($newstring),
179 49
          'comment' => empty($comment) ? null : trim($comment),
180 49
          'delimiter_matched' => isset($string[$n]) ? ($string[$n] === $delimiter) : false,
181 49
          'end_offset' => $n,
182
        ];
183
    }
184
185
    /**
186
     * Get a parameter attribute (e.g. name)
187
     *
188
     * @param string $param MIME type parameter
189
     *
190
     * @return string Attribute name
191
     */
192 26
    public static function getAttribute($param)
193
    {
194 26
        $tmp = explode('=', $param);
195 26
        return trim($tmp[0]);
196
    }
197
198
    /**
199
     * Get a parameter value
200
     *
201
     * @param string $param MIME type parameter
202
     *
203
     * @return string Value
204
     */
205 26
    public static function getValue($param)
206
    {
207 26
        $tmp = explode('=', $param, 2);
208 26
        $value = $tmp[1];
209 26
        $value = trim($value);
210 26
        if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
211
            $value = substr($value, 1, -1);
212
        }
213 26
        $value = str_replace('\\"', '"', $value);
214 26
        return $value;
215
    }
216
217
    /**
218
     * Does this type have any parameters?
219
     *
220
     * @return boolean true if type has parameters, false otherwise
221
     */
222 28
    public function hasParameters()
223
    {
224 28
        return (bool) $this->parameters;
225
    }
226
227
    /**
228
     * Get a MIME type's parameters
229
     *
230
     * @return TypeParameter[] Type's parameters
231
     */
232 29
    public function getParameters()
233
    {
234 29
        return $this->parameters;
235
    }
236
237
    /**
238
     * Get a MIME type's parameter
239
     *
240
     * @param string $name Parameter name
241
     *
242
     * @return TypeParameter|null
243
     */
244 23
    public function getParameter($name)
245
    {
246 23
        return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
247
    }
248
249
    /**
250
     * Get a MIME type's media.
251
     *
252
     * Note: 'media' refers to the portion before the first slash.
253
     *
254
     * @return string Type's media.
255
     */
256 37
    public function getMedia()
257
    {
258 37
        return $this->media;
259
    }
260
261
    /**
262
     * Get a MIME type's media comment.
263
     *
264
     * @return string Type's media comment.
265
     */
266 28
    public function getMediaComment()
267
    {
268 28
        return $this->mediaComment;
269
    }
270
271
    /**
272
     * Get a MIME type's subtype.
273
     *
274
     * @return string Type's subtype, null if invalid mime type.
275
     */
276 38
    public function getSubType()
277
    {
278 38
        return $this->subType;
279
    }
280
281
    /**
282
     * Get a MIME type's subtype comment.
283
     *
284
     * @return string Type's subtype comment, null if invalid mime type.
285
     */
286 28
    public function getSubTypeComment()
287
    {
288 28
        return $this->subTypeComment;
289
    }
290
291
    /**
292
     * Create a textual MIME type from object values
293
     *
294
     * This function performs the opposite function of parse().
295
     *
296
     * @return string MIME type string
297
     */
298 30
    public function toString()
299
    {
300 30
        if (is_null($this->subType)) {
301
            return $this->media;
302
        }
303 30
        $type = strtolower($this->media . '/' . $this->subType);
304 30
        if (count($this->parameters)) {
305 23
            foreach ($this->parameters as $key => $null) {
306 23
                $type .= '; ' . $this->parameters[$key]->toString();
307
            }
308
        }
309 30
        return $type;
310
    }
311
312
    /**
313
     * Is this type experimental?
314
     *
315
     * Note: Experimental types are denoted by a leading 'x-' in the media or
316
     *       subtype, e.g. text/x-vcard or x-world/x-vrml.
317
     *
318
     * @return boolean true if type is experimental, false otherwise
319
     */
320 1
    public function isExperimental()
321
    {
322 1
        if (substr($this->getMedia(), 0, 2) == 'x-' || substr($this->getSubType(), 0, 2) == 'x-') {
323 1
            return true;
324
        }
325 1
        return false;
326
    }
327
328
    /**
329
     * Is this a vendor MIME type?
330
     *
331
     * Note: Vendor types are denoted with a leading 'vnd. in the subtype.
332
     *
333
     * @return boolean true if type is a vendor type, false otherwise
334
     */
335 1
    public function isVendor()
336
    {
337 1
        if (substr($this->getSubType(), 0, 4) == 'vnd.') {
338 1
            return true;
339
        }
340 1
        return false;
341
    }
342
343
    /**
344
     * Is this a wildcard type?
345
     *
346
     * @return boolean true if type is a wildcard, false otherwise
347
     */
348 3
    public function isWildcard()
349
    {
350
        // xxx also if a subtype can be submatched i.e. vnd.ms-excel.*
351 3
        if (($this->getMedia() === '*' && $this->getSubtype() === '*') || $this->getSubtype() === '*') {
352 2
            return true;
353
        }
354 2
        return false;
355
    }
356
357
    /**
358
     * Perform a wildcard match on a MIME type
359
     *
360
     * Example:
361
     * $type = new Type('image/png');
362
     * $type->wildcardMatch('image/*');
363
     *
364
     * @param string $card Wildcard to check against
365
     *
366
     * @return boolean true if there was a match, false otherwise
367
     */
368 2
    public function wildcardMatch($card)
369
    {
370 2
        $match_type = new static($card);
371
372 2
        if (!$match_type->isWildcard()) {
373 1
            return false;
374
        }
375
376 1
        if ($match_type->getMedia() === '*' && $match_type->getSubType() === '*') {
377 1
            return true;
378
        }
379
380 1
        if ($match_type->getMedia() === $this->getMedia()) {
381 1
            return true;
382
        }
383
384 1
        return false;
385
    }
386
387
    /**
388
     * Add a parameter to this type
389
     *
390
     * @param string $name    Parameter name
391
     * @param string $value   Parameter value
392
     * @param string $comment Comment for this parameter
393
     *
394
     * @return void
395
     */
396 26
    public function addParameter($name, $value, $comment = null)
397
    {
398 26
        $this->parameters[$name] = new TypeParameter($name, $value, $comment);
399 26
    }
400
401
    /**
402
     * Remove a parameter from this type
403
     *
404
     * @param string $name Parameter name
405
     *
406
     * @return void
407
     */
408 1
    public function removeParameter($name)
409
    {
410 1
        unset($this->parameters[$name]);
411 1
    }
412
413 4
    public function getDefaultExtension($strict = true)
414
    {
415 4
        $extensions = $this->getExtensions($strict);
416 3
        return isset($extensions[0]) ? $extensions[0] : null;
417
    }
418
419 5
    public function getExtensions($strict = true)
420
    {
421
        // xx use tostring here
422 5
        $type = $this->getMedia() . '/' . $this->getSubType();
423
424 5
        $map = new MapHandler();
425 5
        if (!isset($map->get()['types'][$type])) {
426 2
            if ($strict) {
427 1
                throw new MappingException('MIME type ' . $type . ' not found in map');
428
            } else {
429 1
                return [];
430
            }
431
        }
432 4
        return $map->get()['types'][$type];
433
    }
434
}
435