Passed
Push — master ( 2b87da...5b7515 )
by mon
01:43
created

Type::getExtensions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
ccs 8
cts 8
cp 1
crap 3
1
<?php
2
3
namespace FileEye\MimeMap;
4
5
/**
6
 * Class for working with MIME types
7
 */
8
class Type
9
{
10
    /**
11
     * Short format [e.g. image/jpeg] for strings.
12
     */
13
    const SHORT_TEXT = 0;
14
15
    /**
16
     * Full format [e.g. image/jpeg; p="1"] for strings.
17
     */
18
    const FULL_TEXT = 1;
19
20
    /**
21
     * Full format with comments [e.g. image/jpeg; p="1" (comment)] for strings.
22
     */
23
    const FULL_TEXT_WITH_COMMENTS = 2;
24
25
    /**
26
     * The MIME media type.
27
     *
28
     * @var string
29
     */
30
    protected $media;
31
32
    /**
33
     * The MIME media type comment.
34
     *
35
     * @var string
36
     */
37
    protected $mediaComment;
38
39
    /**
40
     * The MIME media sub-type.
41
     *
42
     * @var string
43
     */
44
    protected $subType;
45
46
    /**
47
     * The MIME media sub-type comment.
48
     *
49
     * @var string
50
     */
51
    protected $subTypeComment;
52
53
    /**
54
     * Optional MIME parameters
55
     *
56
     * @var TypeParameter[]
57
     */
58
    protected $parameters = [];
59
60
    /**
61
     * Constructor.
62
     *
63
     * The type string will be parsed and the appropriate class vars set.
64
     *
65
     * @param string $type MIME type
66
     */
67 61
    public function __construct($type)
68
    {
69 61
        $this->parse($type);
70 54
    }
71
72
    /**
73
     * Parse a mime-type and set the class variables.
74
     *
75
     * @param string $type MIME type to parse
76
     *
77
     * @return void
78
     */
79 61
    protected function parse($type)
80
    {
81
        // Media and SubType are separated by a slash '/'.
82 61
        $media = TypeParser::parseStringPart($type, 0, '/');
83
84 60
        if (!$media['string']) {
85 3
            throw new MalformedTypeException('Media type not found');
86
        }
87 57
        if (!$media['delimiter_matched']) {
88 1
            throw new MalformedTypeException('Slash \'/\' to separate media type and subtype not found');
89
        }
90
91 56
        $this->media = strtolower($media['string']);
0 ignored issues
show
Bug introduced by
$media['string'] of type void is incompatible with the type string expected by parameter $str of strtolower(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

91
        $this->media = strtolower(/** @scrutinizer ignore-type */ $media['string']);
Loading history...
92 56
        $this->mediaComment = $media['comment'];
93
94
        // SubType and Parameters are separated by semicolons ';'.
95 56
        $sub = TypeParser::parseStringPart($type, $media['end_offset'] + 1, ';');
96
97 55
        if (!$sub['string']) {
98 1
            throw new MalformedTypeException('Media subtype not found');
99
        }
100
101 54
        $this->subType = strtolower($sub['string']);
102 54
        $this->subTypeComment = $sub['comment'];
103
104
        // Loops through the parameter.
105 54
        while ($sub['delimiter_matched']) {
106 27
            $sub = TypeParser::parseStringPart($type, $sub['end_offset'] + 1, ';');
107 27
            $tmp = explode('=', $sub['string'], 2);
108 27
            $p_name = trim($tmp[0]);
109 27
            $p_val = trim($tmp[1]);
110 27
            $p_val = str_replace('\\"', '"', $p_val);
111 27
            $this->addParameter($p_name, $p_val, $sub['comment']);
112
        }
113 54
    }
114
115
    /**
116
     * Does this type have any parameters?
117
     *
118
     * @return boolean true if type has parameters, false otherwise
119
     */
120 29
    public function hasParameters()
121
    {
122 29
        return (bool) $this->parameters;
123
    }
124
125
    /**
126
     * Get a MIME type's parameters
127
     *
128
     * @return TypeParameter[] Type's parameters
129
     */
130 30
    public function getParameters()
131
    {
132 30
        return $this->parameters;
133
    }
134
135
    /**
136
     * Get a MIME type's parameter
137
     *
138
     * @param string $name Parameter name
139
     *
140
     * @return TypeParameter|null
141
     */
142 24
    public function getParameter($name)
143
    {
144 24
        return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
145
    }
146
147
    /**
148
     * Gets a MIME type's media.
149
     *
150
     * Note: 'media' refers to the portion before the first slash.
151
     *
152
     * @return string Type's media.
153
     */
154 47
    public function getMedia()
155
    {
156 47
        return $this->media;
157
    }
158
159
    /**
160
     * Gets a MIME type's media comment.
161
     *
162
     * @return string Type's media comment.
163
     */
164 29
    public function getMediaComment()
165
    {
166 29
        return $this->mediaComment;
167
    }
168
169
    /**
170
     * Sets a MIME type's media comment.
171
     *
172
     * @param string Type's media comment.
0 ignored issues
show
Documentation Bug introduced by
The doc comment Type's at position 0 could not be parsed: Unknown type name 'Type's' at position 0 in Type's.
Loading history...
173
     *
174
     * @return $this
175
     */
176 1
    public function setMediaComment($comment)
177
    {
178 1
        $this->mediaComment = $comment;
179 1
        return $this;
180
    }
181
182
    /**
183
     * Gets a MIME type's subtype.
184
     *
185
     * @return string Type's subtype, null if invalid mime type.
186
     */
187 48
    public function getSubType()
188
    {
189 48
        return $this->subType;
190
    }
191
192
    /**
193
     * Gets a MIME type's subtype comment.
194
     *
195
     * @return string Type's subtype comment, null if invalid mime type.
196
     */
197 29
    public function getSubTypeComment()
198
    {
199 29
        return $this->subTypeComment;
200
    }
201
202
    /**
203
     * Sets a MIME type's subtype comment.
204
     *
205
     * @param string Type's subtype comment.
0 ignored issues
show
Documentation Bug introduced by
The doc comment Type's at position 0 could not be parsed: Unknown type name 'Type's' at position 0 in Type's.
Loading history...
206
     *
207
     * @return $this
208
     */
209 1
    public function setSubTypeComment($comment)
210
    {
211 1
        $this->subTypeComment = $comment;
212 1
        return $this;
213
    }
214
215
    /**
216
     * Create a textual MIME type from object values
217
     *
218
     * This function performs the opposite function of parse().
219
     *
220
     * @param int $format The format of the output string.
221
     *
222
     * @return string MIME type string
223
     */
224 48
    public function toString($format = Type::FULL_TEXT)
225
    {
226 48
        $type = strtolower($this->media);
227 48
        if ($format > Type::FULL_TEXT && isset($this->mediaComment)) {
228 3
            $type .= ' (' .  $this->mediaComment . ')';
229
        }
230 48
        $type .= '/' . strtolower($this->subType);
231 48
        if ($format > Type::FULL_TEXT && isset($this->subTypeComment)) {
232 6
            $type .= ' (' .  $this->subTypeComment . ')';
233
        }
234 48
        if ($format > Type::SHORT_TEXT && count($this->parameters)) {
235 24
            foreach ($this->parameters as $parameter) {
236 24
                $type .= '; ' . $parameter->toString($format);
237
            }
238
        }
239 48
        return $type;
240
    }
241
242
    /**
243
     * Is this type experimental?
244
     *
245
     * Note: Experimental types are denoted by a leading 'x-' in the media or
246
     *       subtype, e.g. text/x-vcard or x-world/x-vrml.
247
     *
248
     * @return boolean true if type is experimental, false otherwise
249
     */
250 1
    public function isExperimental()
251
    {
252 1
        if (substr($this->getMedia(), 0, 2) == 'x-' || substr($this->getSubType(), 0, 2) == 'x-') {
253 1
            return true;
254
        }
255 1
        return false;
256
    }
257
258
    /**
259
     * Is this a vendor MIME type?
260
     *
261
     * Note: Vendor types are denoted with a leading 'vnd. in the subtype.
262
     *
263
     * @return boolean true if type is a vendor type, false otherwise
264
     */
265 1
    public function isVendor()
266
    {
267 1
        if (substr($this->getSubType(), 0, 4) == 'vnd.') {
268 1
            return true;
269
        }
270 1
        return false;
271
    }
272
273
    /**
274
     * Is this a wildcard type?
275
     *
276
     * @return boolean true if type is a wildcard, false otherwise.
277
     */
278 17
    public function isWildcard()
279
    {
280 17
        if (($this->getMedia() === '*' && $this->getSubtype() === '*') || strpos($this->getSubtype(), '*') !== false) {
281 8
            return true;
282
        }
283 14
        return false;
284
    }
285
286
    /**
287
     * Is this an alias?
288
     *
289
     * @return boolean true if type is an alias, false otherwise.
290
     */
291 15
    public function isAlias()
292
    {
293 15
        if ($this->isWildcard()) {
294 5
            return false;
295
        }
296
297 12
        $map = MapHandler::map();
298 12
        $subject = $this->toString(static::SHORT_TEXT);
299 12
        return $map->hasAlias($subject);
300
    }
301
302
    /**
303
     * Perform a wildcard match on a MIME type
304
     *
305
     * Example:
306
     * $type = new Type('image/png');
307
     * $type->wildcardMatch('image/*');
308
     *
309
     * @param string $wildcard Wildcard to check against
310
     *
311
     * @return boolean true if there was a match, false otherwise
312
     */
313 1
    public function wildcardMatch($wildcard)
314
    {
315 1
        $wildcard_type = new static($wildcard);
316
317 1
        if (!$wildcard_type->isWildcard()) {
318 1
            return false;
319
        }
320
321 1
        $wildcard_re = strtr($wildcard_type->toString(static::SHORT_TEXT), [
322 1
            '/' => '\\/',
323
            '*' => '.*',
324
        ]);
325 1
        $subject = $this->toString(static::SHORT_TEXT);
326
327 1
        return preg_match("/$wildcard_re/", $subject) === 1;
328
    }
329
330
    /**
331
     * Add a parameter to this type
332
     *
333
     * @param string $name    Parameter name
334
     * @param string $value   Parameter value
335
     * @param string $comment Comment for this parameter
336
     *
337
     * @return void
338
     */
339 27
    public function addParameter($name, $value, $comment = null)
340
    {
341 27
        $this->parameters[$name] = new TypeParameter($name, $value, $comment);
342 27
    }
343
344
    /**
345
     * Remove a parameter from this type
346
     *
347
     * @param string $name Parameter name
348
     *
349
     * @return void
350
     */
351 1
    public function removeParameter($name)
352
    {
353 1
        unset($this->parameters[$name]);
354 1
    }
355
356
    /**
357
     * Builds a list of MIME types existing in the map.
358
     *
359
     * If the current type is a wildcard, than all the types matching the
360
     * wildcard will be returned.
361
     *
362
     * @param bool $strict
363
     *   (Optional) if true a MappingException is thrown when no type is
364
     *   found, if false it returns an empty array as a default.
365
     *   Defaults to true.
366
     *
367
     * @throws MappingException if no mapping found and $strict is true.
368
     *
369
     * @return string[]
370
     */
371 8
    protected function buildTypesList($strict = true)
372
    {
373 8
        $map = MapHandler::map();
374 8
        $subject = $this->toString(static::SHORT_TEXT);
375
376
        // Find all types.
377 8
        $types = [];
378 8
        if (!$this->isWildcard()) {
379 8
            if ($map->hasType($subject)) {
380 8
                $types[] = $subject;
381
            }
382
        } else {
383 1
            foreach ($map->listTypes($subject) as $t) {
384 1
                $types[] = $t;
385
            }
386
        }
387
388
        // No types found, throw exception or return emtpy array.
389 8
        if (empty($types)) {
390 5
            if ($strict) {
391 2
                throw new MappingException('No MIME type found for ' . $subject . ' in map');
392
            } else {
393 3
                return [];
394
            }
395
        }
396
397 6
        return $types;
398
    }
399
400
    /**
401
     * Returns the unaliased MIME type.
402
     *
403
     * @return Type
404
     *   $this if the current type is not an alias, the parent type if the
405
     *   current type is an alias.
406
     */
407 11
    protected function getUnaliasedType()
408
    {
409 11
        if (!$this->isAlias()) {
410 11
            return $this;
411
        } else {
412 3
            $map = MapHandler::map();
413 3
            $subject = $this->toString(static::SHORT_TEXT);
414 3
            $types = $map->getAliasTypes($subject);
415 3
            return new static($types[0]);
416
        }
417
    }
418
419
    /**
420
     * Returns a description for the MIME type, if existing in the map.
421
     *
422
     * @param bool $include_acronym
423
     *   (Optional) if true and an acronym description exists for the type,
424
     *   the returned description will contain the acronym and its description,
425
     *   appended with a comma. Defaults to false.
426
     *
427
     * @return string|null
428
     */
429 1
    public function getDescription($include_acronym = false)
430
    {
431 1
        if ($this->isWildcard()) {
432 1
            return null;
433
        }
434
435 1
        $map = MapHandler::map();
436 1
        $subject = $this->getUnaliasedType()->toString(static::SHORT_TEXT);
437 1
        $descriptions = $map->getTypeDescriptions($subject);
438 1
        $res = null;
439 1
        if (isset($descriptions[0])) {
440 1
            $res = $descriptions[0];
441
        }
442 1
        if ($include_acronym && isset($descriptions[1])) {
443 1
            $res .= ', ' . $descriptions[1];
444
        }
445 1
        return $res;
446
    }
447
448
    /**
449
     * Returns all the aliases related to the MIME type(s).
450
     *
451
     * If the current type is a wildcard, than all aliases of all the
452
     * types matching the wildcard will be returned.
453
     *
454
     * @param bool $strict
455
     *   (Optional) if true a MappingException is thrown when no mapping is
456
     *   found, if false it returns an empty array as a default.
457
     *   Defaults to true.
458
     *
459
     * @throws MappingException if error and $strict is true.
460
     *
461
     * @return string[]
462
     */
463 4
    public function getAliases($strict = true)
464
    {
465
        // Fail if the current type is an alias already.
466 4
        if ($this->isAlias()) {
467 2
            if ($strict) {
468 1
                $subject = $this->toString(static::SHORT_TEXT);
469 1
                throw new MappingException("Cannot get aliases for '{$subject}', it is an alias itself");
470
            } else {
471 1
                return [];
472
            }
473
        }
474
475 3
        $map = MapHandler::map();
476 3
        $types = $this->buildTypesList($strict);
477
478
        // Build the array of aliases.
479 2
        $aliases = [];
480 2
        foreach ($types as $t) {
481 2
            foreach ($map->getTypeAliases($t) as $a) {
482 2
                $aliases[$a] = $a;
483
            }
484
        }
485
486 2
        return array_keys($aliases);
487
    }
488
489
    /**
490
     * Returns the MIME type's preferred file extension.
491
     *
492
     * @param bool $strict
493
     *   (Optional) if true a MappingException is thrown when no mapping is
494
     *   found, if false it returns null as a default.
495
     *   Defaults to true.
496
     *
497
     * @throws MappingException if no mapping found and $strict is true.
498
     *
499
     * @return string
500
     */
501 7
    public function getDefaultExtension($strict = true)
502
    {
503 7
        $map = MapHandler::map();
504 7
        $unaliased_type = $this->getUnaliasedType();
505 7
        $subject = $unaliased_type->toString(static::SHORT_TEXT);
506
507 7
        if (!$unaliased_type->isWildcard()) {
508 4
            $proceed = $map->hasType($subject);
509
        } else {
510 3
            $proceed = count($map->listTypes($subject)) === 1;
511
        }
512
513 7
        if (!$proceed) {
514 5
            if ($strict) {
515 4
                throw new MappingException('Cannot determine default extension for type: ' . $unaliased_type->toString(static::SHORT_TEXT));
516
            } else {
517 1
                return null;
518
            }
519
        }
520
521 3
        return $unaliased_type->getExtensions()[0];
522
    }
523
524
    /**
525
     * Returns all the file extensions related to the MIME type(s).
526
     *
527
     * If the current type is a wildcard, than all extensions of all the
528
     * types matching the wildcard will be returned.
529
     *
530
     * @param bool $strict
531
     *   (Optional) if true a MappingException is thrown when no mapping is
532
     *   found, if false it returns an empty array as a default.
533
     *   Defaults to true.
534
     *
535
     * @throws MappingException if no mapping found and $strict is true.
536
     *
537
     * @return string[]
538
     */
539 6
    public function getExtensions($strict = true)
540
    {
541 6
        $map = MapHandler::map();
542 6
        $types = $this->getUnaliasedType()->buildTypesList($strict);
543
544
        // Build the array of extensions.
545 5
        $extensions = [];
546 5
        foreach ($types as $t) {
547 5
            foreach ($map->getTypeExtensions($t) as $e) {
548 5
                $extensions[$e] = $e;
549
            }
550
        }
551
552 5
        return array_keys($extensions);
553
    }
554
}
555