Completed
Push — master ( a370e2...7559ac )
by Matthias
01:14
created

src/CSS.php (1 issue)

Check for implicit conversion of array to boolean.

Best Practice Bug Minor

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * CSS Minifier
4
 *
5
 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
 *
7
 * @author Matthias Mullie <[email protected]>
8
 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
 * @license MIT License
10
 */
11
12
namespace MatthiasMullie\Minify;
13
14
use MatthiasMullie\Minify\Exceptions\FileImportException;
15
use MatthiasMullie\PathConverter\ConverterInterface;
16
use MatthiasMullie\PathConverter\Converter;
17
18
/**
19
 * CSS minifier
20
 *
21
 * Please report bugs on https://github.com/matthiasmullie/minify/issues
22
 *
23
 * @package Minify
24
 * @author Matthias Mullie <[email protected]>
25
 * @author Tijs Verkoyen <[email protected]>
26
 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
27
 * @license MIT License
28
 */
29
class CSS extends Minify
30
{
31
    /**
32
     * @var int maximum inport size in kB
33
     */
34
    protected $maxImportSize = 5;
35
36
    /**
37
     * @var string[] valid import extensions
38
     */
39
    protected $importExtensions = array(
40
        'gif' => 'data:image/gif',
41
        'png' => 'data:image/png',
42
        'jpe' => 'data:image/jpeg',
43
        'jpg' => 'data:image/jpeg',
44
        'jpeg' => 'data:image/jpeg',
45
        'svg' => 'data:image/svg+xml',
46
        'woff' => 'data:application/x-font-woff',
47
        'tif' => 'image/tiff',
48
        'tiff' => 'image/tiff',
49
        'xbm' => 'image/x-xbitmap',
50
    );
51
52
    /**
53
     * Set the maximum size if files to be imported.
54
     *
55
     * Files larger than this size (in kB) will not be imported into the CSS.
56
     * Importing files into the CSS as data-uri will save you some connections,
57
     * but we should only import relatively small decorative images so that our
58
     * CSS file doesn't get too bulky.
59
     *
60
     * @param int $size Size in kB
61
     */
62
    public function setMaxImportSize($size)
63
    {
64
        $this->maxImportSize = $size;
65
    }
66
67
    /**
68
     * Set the type of extensions to be imported into the CSS (to save network
69
     * connections).
70
     * Keys of the array should be the file extensions & respective values
71
     * should be the data type.
72
     *
73
     * @param string[] $extensions Array of file extensions
74
     */
75
    public function setImportExtensions(array $extensions)
76
    {
77
        $this->importExtensions = $extensions;
78
    }
79
80
    /**
81
     * Move any import statements to the top.
82
     *
83
     * @param string $content Nearly finished CSS content
84
     *
85
     * @return string
86
     */
87
    protected function moveImportsToTop($content)
88
    {
89
        if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)))/', $content, $matches)) {
90
            // remove from content
91
            foreach ($matches[0] as $import) {
92
                $content = str_replace($import, '', $content);
93
            }
94
95
            // add to top
96
            $content = implode(';', $matches[2]).';'.trim($content, ';');
97
        }
98
99
        return $content;
100
    }
101
102
    /**
103
     * Combine CSS from import statements.
104
     *
105
     * @import's will be loaded and their content merged into the original file,
106
     * to save HTTP requests.
107
     *
108
     * @param string   $source  The file to combine imports for
109
     * @param string   $content The CSS content to combine imports for
110
     * @param string[] $parents Parent paths, for circular reference checks
111
     *
112
     * @return string
113
     *
114
     * @throws FileImportException
115
     */
116
    protected function combineImports($source, $content, $parents)
117
    {
118
        $importRegexes = array(
119
            // @import url(xxx)
120
            '/
121
            # import statement
122
            @import
123
124
            # whitespace
125
            \s+
126
127
                # open url()
128
                url\(
129
130
                    # (optional) open path enclosure
131
                    (?P<quotes>["\']?)
132
133
                        # fetch path
134
                        (?P<path>.+?)
135
136
                    # (optional) close path enclosure
137
                    (?P=quotes)
138
139
                # close url()
140
                \)
141
142
                # (optional) trailing whitespace
143
                \s*
144
145
                # (optional) media statement(s)
146
                (?P<media>[^;]*)
147
148
                # (optional) trailing whitespace
149
                \s*
150
151
            # (optional) closing semi-colon
152
            ;?
153
154
            /ix',
155
156
            // @import 'xxx'
157
            '/
158
159
            # import statement
160
            @import
161
162
            # whitespace
163
            \s+
164
165
                # open path enclosure
166
                (?P<quotes>["\'])
167
168
                    # fetch path
169
                    (?P<path>.+?)
170
171
                # close path enclosure
172
                (?P=quotes)
173
174
                # (optional) trailing whitespace
175
                \s*
176
177
                # (optional) media statement(s)
178
                (?P<media>[^;]*)
179
180
                # (optional) trailing whitespace
181
                \s*
182
183
            # (optional) closing semi-colon
184
            ;?
185
186
            /ix',
187
        );
188
189
        // find all relative imports in css
190
        $matches = array();
191
        foreach ($importRegexes as $importRegex) {
192
            if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
193
                $matches = array_merge($matches, $regexMatches);
194
            }
195
        }
196
197
        $search = array();
198
        $replace = array();
199
200
        // loop the matches
201
        foreach ($matches as $match) {
202
            // get the path for the file that will be imported
203
            $importPath = dirname($source).'/'.$match['path'];
204
205
            // only replace the import with the content if we can grab the
206
            // content of the file
207
            if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
208
                continue;
209
            }
210
211
            // check if current file was not imported previously in the same
212
            // import chain.
213
            if (in_array($importPath, $parents)) {
214
                throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
215
            }
216
217
            // grab referenced file & minify it (which may include importing
218
            // yet other @import statements recursively)
219
            $minifier = new static($importPath);
220
            $importContent = $minifier->execute($source, $parents);
221
222
            // check if this is only valid for certain media
223
            if (!empty($match['media'])) {
224
                $importContent = '@media '.$match['media'].'{'.$importContent.'}';
225
            }
226
227
            // add to replacement array
228
            $search[] = $match[0];
229
            $replace[] = $importContent;
230
        }
231
232
        // replace the import statements
233
        return str_replace($search, $replace, $content);
234
    }
235
236
    /**
237
     * Import files into the CSS, base64-ized.
238
     *
239
     * @url(image.jpg) images will be loaded and their content merged into the
240
     * original file, to save HTTP requests.
241
     *
242
     * @param string $source  The file to import files for
243
     * @param string $content The CSS content to import files for
244
     *
245
     * @return string
246
     */
247
    protected function importFiles($source, $content)
248
    {
249
        $regex = '/url\((["\']?)(.+?)\\1\)/i';
250
        if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->importExtensions of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
251
            $search = array();
252
            $replace = array();
253
254
            // loop the matches
255
            foreach ($matches as $match) {
256
                $extension = substr(strrchr($match[2], '.'), 1);
257
                if ($extension && !array_key_exists($extension, $this->importExtensions)) {
258
                    continue;
259
                }
260
261
                // get the path for the file that will be imported
262
                $path = $match[2];
263
                $path = dirname($source).'/'.$path;
264
265
                // only replace the import with the content if we're able to get
266
                // the content of the file, and it's relatively small
267
                if ($this->canImportFile($path) && $this->canImportBySize($path)) {
268
                    // grab content && base64-ize
269
                    $importContent = $this->load($path);
270
                    $importContent = base64_encode($importContent);
271
272
                    // build replacement
273
                    $search[] = $match[0];
274
                    $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
275
                }
276
            }
277
278
            // replace the import statements
279
            $content = str_replace($search, $replace, $content);
280
        }
281
282
        return $content;
283
    }
284
285
    /**
286
     * Minify the data.
287
     * Perform CSS optimizations.
288
     *
289
     * @param string[optional] $path    Path to write the data to
290
     * @param string[]         $parents Parent paths, for circular reference checks
291
     *
292
     * @return string The minified data
293
     */
294
    public function execute($path = null, $parents = array())
295
    {
296
        $content = '';
297
298
        // loop CSS data (raw data and files)
299
        foreach ($this->data as $source => $css) {
300
            /*
301
             * Let's first take out strings & comments, since we can't just
302
             * remove whitespace anywhere. If whitespace occurs inside a string,
303
             * we should leave it alone. E.g.:
304
             * p { content: "a   test" }
305
             */
306
            $this->extractStrings();
307
            $this->stripComments();
308
            $css = $this->replace($css);
309
310
            $css = $this->stripWhitespace($css);
311
            $css = $this->shortenHex($css);
312
            $css = $this->shortenZeroes($css);
313
            $css = $this->shortenFontWeights($css);
314
            $css = $this->stripEmptyTags($css);
315
316
            // restore the string we've extracted earlier
317
            $css = $this->restoreExtractedData($css);
318
319
            $source = is_int($source) ? '' : $source;
320
            $parents = $source ? array_merge($parents, array($source)) : $parents;
321
            $css = $this->combineImports($source, $css, $parents);
322
            $css = $this->importFiles($source, $css);
323
324
            /*
325
             * If we'll save to a new path, we'll have to fix the relative paths
326
             * to be relative no longer to the source file, but to the new path.
327
             * If we don't write to a file, fall back to same path so no
328
             * conversion happens (because we still want it to go through most
329
             * of the move code, which also addresses url() & @import syntax...)
330
             */
331
            $converter = $this->getPathConverter($source, $path ?: $source);
332
            $css = $this->move($converter, $css);
333
334
            // combine css
335
            $content .= $css;
336
        }
337
338
        $content = $this->moveImportsToTop($content);
339
340
        return $content;
341
    }
342
343
    /**
344
     * Moving a css file should update all relative urls.
345
     * Relative references (e.g. ../images/image.gif) in a certain css file,
346
     * will have to be updated when a file is being saved at another location
347
     * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
348
     *
349
     * @param ConverterInterface $converter Relative path converter
350
     * @param string             $content   The CSS content to update relative urls for
351
     *
352
     * @return string
353
     */
354
    protected function move(ConverterInterface $converter, $content)
355
    {
356
        /*
357
         * Relative path references will usually be enclosed by url(). @import
358
         * is an exception, where url() is not necessary around the path (but is
359
         * allowed).
360
         * This *could* be 1 regular expression, where both regular expressions
361
         * in this array are on different sides of a |. But we're using named
362
         * patterns in both regexes, the same name on both regexes. This is only
363
         * possible with a (?J) modifier, but that only works after a fairly
364
         * recent PCRE version. That's why I'm doing 2 separate regular
365
         * expressions & combining the matches after executing of both.
366
         */
367
        $relativeRegexes = array(
368
            // url(xxx)
369
            '/
370
            # open url()
371
            url\(
372
373
                \s*
374
375
                # open path enclosure
376
                (?P<quotes>["\'])?
377
378
                    # fetch path
379
                    (?P<path>.+?)
380
381
                # close path enclosure
382
                (?(quotes)(?P=quotes))
383
384
                \s*
385
386
            # close url()
387
            \)
388
389
            /ix',
390
391
            // @import "xxx"
392
            '/
393
            # import statement
394
            @import
395
396
            # whitespace
397
            \s+
398
399
                # we don\'t have to check for @import url(), because the
400
                # condition above will already catch these
401
402
                # open path enclosure
403
                (?P<quotes>["\'])
404
405
                    # fetch path
406
                    (?P<path>.+?)
407
408
                # close path enclosure
409
                (?P=quotes)
410
411
            /ix',
412
        );
413
414
        // find all relative urls in css
415
        $matches = array();
416
        foreach ($relativeRegexes as $relativeRegex) {
417
            if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
418
                $matches = array_merge($matches, $regexMatches);
419
            }
420
        }
421
422
        $search = array();
423
        $replace = array();
424
425
        // loop all urls
426
        foreach ($matches as $match) {
427
            // determine if it's a url() or an @import match
428
            $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
429
430
            $url = $match['path'];
431
            if ($this->canImportByPath($url)) {
432
                // attempting to interpret GET-params makes no sense, so let's discard them for awhile
433
                $params = strrchr($url, '?');
434
                $url = $params ? substr($url, 0, -strlen($params)) : $url;
435
436
                // fix relative url
437
                $url = $converter->convert($url);
438
439
                // now that the path has been converted, re-apply GET-params
440
                $url .= $params;
441
            }
442
443
            /*
444
             * Urls with control characters above 0x7e should be quoted.
445
             * According to Mozilla's parser, whitespace is only allowed at the
446
             * end of unquoted urls.
447
             * Urls with `)` (as could happen with data: uris) should also be
448
             * quoted to avoid being confused for the url() closing parentheses.
449
             * And urls with a # have also been reported to cause issues.
450
             * Urls with quotes inside should also remain escaped.
451
             *
452
             * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
453
             * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
454
             * @see https://github.com/matthiasmullie/minify/issues/193
455
             */
456
            $url = trim($url);
457
            if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
458
                $url = $match['quotes'] . $url . $match['quotes'];
459
            }
460
461
            // build replacement
462
            $search[] = $match[0];
463
            if ($type === 'url') {
464
                $replace[] = 'url('.$url.')';
465
            } elseif ($type === 'import') {
466
                $replace[] = '@import "'.$url.'"';
467
            }
468
        }
469
470
        // replace urls
471
        return str_replace($search, $replace, $content);
472
    }
473
474
    /**
475
     * Shorthand hex color codes.
476
     * #FF0000 -> #F00.
477
     *
478
     * @param string $content The CSS content to shorten the hex color codes for
479
     *
480
     * @return string
481
     */
482
    protected function shortenHex($content)
483
    {
484
        $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?=[; }])/i', '#$1$2$3', $content);
485
486
        // we can shorten some even more by replacing them with their color name
487
        $colors = array(
488
            '#F0FFFF' => 'azure',
489
            '#F5F5DC' => 'beige',
490
            '#A52A2A' => 'brown',
491
            '#FF7F50' => 'coral',
492
            '#FFD700' => 'gold',
493
            '#808080' => 'gray',
494
            '#008000' => 'green',
495
            '#4B0082' => 'indigo',
496
            '#FFFFF0' => 'ivory',
497
            '#F0E68C' => 'khaki',
498
            '#FAF0E6' => 'linen',
499
            '#800000' => 'maroon',
500
            '#000080' => 'navy',
501
            '#808000' => 'olive',
502
            '#CD853F' => 'peru',
503
            '#FFC0CB' => 'pink',
504
            '#DDA0DD' => 'plum',
505
            '#800080' => 'purple',
506
            '#F00' => 'red',
507
            '#FA8072' => 'salmon',
508
            '#A0522D' => 'sienna',
509
            '#C0C0C0' => 'silver',
510
            '#FFFAFA' => 'snow',
511
            '#D2B48C' => 'tan',
512
            '#FF6347' => 'tomato',
513
            '#EE82EE' => 'violet',
514
            '#F5DEB3' => 'wheat',
515
        );
516
517
        return preg_replace_callback(
518
            '/(?<=[: ])('.implode(array_keys($colors), '|').')(?=[; }])/i',
519
            function ($match) use ($colors) {
520
                return $colors[strtoupper($match[0])];
521
            },
522
            $content
523
        );
524
    }
525
526
    /**
527
     * Shorten CSS font weights.
528
     *
529
     * @param string $content The CSS content to shorten the font weights for
530
     *
531
     * @return string
532
     */
533
    protected function shortenFontWeights($content)
534
    {
535
        $weights = array(
536
            'normal' => 400,
537
            'bold' => 700,
538
        );
539
540
        $callback = function ($match) use ($weights) {
541
            return $match[1].$weights[$match[2]];
542
        };
543
544
        return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
545
    }
546
547
    /**
548
     * Shorthand 0 values to plain 0, instead of e.g. -0em.
549
     *
550
     * @param string $content The CSS content to shorten the zero values for
551
     *
552
     * @return string
553
     */
554
    protected function shortenZeroes($content)
555
    {
556
        // we don't want to strip units in `calc()` expressions:
557
        // `5px - 0px` is valid, but `5px - 0` is not
558
        // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
559
        // `10 * 0` is invalid
560
        // best to just leave `calc()`s alone, even if they could be optimized
561
        // (which is a whole other undertaking, where units & order of
562
        // operations all need to be considered...)
563
        $calcs = $this->findCalcs($content);
564
        $content = str_replace($calcs, array_keys($calcs), $content);
565
566
        // reusable bits of code throughout these regexes:
567
        // before & after are used to make sure we don't match lose unintended
568
        // 0-like values (e.g. in #000, or in http://url/1.0)
569
        // units can be stripped from 0 values, or used to recognize non 0
570
        // values (where wa may be able to strip a .0 suffix)
571
        $before = '(?<=[:(, ])';
572
        $after = '(?=[ ,);}])';
573
        $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
574
575
        // strip units after zeroes (0px -> 0)
576
        // NOTE: it should be safe to remove all units for a 0 value, but in
577
        // practice, Webkit (especially Safari) seems to stumble over at least
578
        // 0%, potentially other units as well. Only stripping 'px' for now.
579
        // @see https://github.com/matthiasmullie/minify/issues/60
580
        $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
581
582
        // strip 0-digits (.0 -> 0)
583
        $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
584
        // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
585
        $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
586
        // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
587
        $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
588
        // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
589
        $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
590
591
        // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
592
        $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
593
594
        // IE doesn't seem to understand a unitless flex-basis value, so let's
595
        // add it in again (make it `%`, which is only 1 char: 0%, 0px, 0
596
        // anything, it's all just the same)
597
        $content = preg_replace('/flex:([^ ]+ [^ ]+ )0([;\}])/', 'flex:${1}0%${2}', $content);
598
        $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
599
600
        // restore `calc()` expressions
601
        $content = str_replace(array_keys($calcs), $calcs, $content);
602
603
        return $content;
604
    }
605
606
    /**
607
     * Strip empty tags from source code.
608
     *
609
     * @param string $content
610
     *
611
     * @return string
612
     */
613
    protected function stripEmptyTags($content)
614
    {
615
        $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content);
616
        $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content);
617
618
        return $content;
619
    }
620
621
    /**
622
     * Strip comments from source code.
623
     */
624
    protected function stripComments()
625
    {
626
        $this->registerPattern('/\/\*.*?\*\//s', '');
627
    }
628
629
    /**
630
     * Strip whitespace.
631
     *
632
     * @param string $content The CSS content to strip the whitespace for
633
     *
634
     * @return string
635
     */
636
    protected function stripWhitespace($content)
637
    {
638
        // remove leading & trailing whitespace
639
        $content = preg_replace('/^\s*/m', '', $content);
640
        $content = preg_replace('/\s*$/m', '', $content);
641
642
        // replace newlines with a single space
643
        $content = preg_replace('/\s+/', ' ', $content);
644
645
        // remove whitespace around meta characters
646
        // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
647
        $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
648
        $content = preg_replace('/([\[(:])\s+/', '$1', $content);
649
        $content = preg_replace('/\s+([\]\)])/', '$1', $content);
650
        $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
651
652
        // whitespace around + and - can only be stripped inside some pseudo-
653
        // classes, like `:nth-child(3+2n)`
654
        // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
655
        // selectors like `div.weird- p`
656
        $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
657
        $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
658
659
        // remove semicolon/whitespace followed by closing bracket
660
        $content = str_replace(';}', '}', $content);
661
662
        return trim($content);
663
    }
664
665
    /**
666
     * Find all `calc()` occurrences.
667
     *
668
     * @param string $content The CSS content to find `calc()`s in.
669
     *
670
     * @return string[]
671
     */
672
    protected function findCalcs($content)
673
    {
674
        $results = array();
675
        preg_match_all('/calc(\(.+?)(?=$|;|calc\()/', $content, $matches, PREG_SET_ORDER);
676
677
        foreach ($matches as $match) {
678
            $length = strlen($match[1]);
679
            $expr = '';
680
            $opened = 0;
681
682
            for ($i = 0; $i < $length; $i++) {
683
                $char = $match[1][$i];
684
                $expr .= $char;
685
                if ($char === '(') {
686
                    $opened++;
687
                } elseif ($char === ')' && --$opened === 0) {
688
                    break;
689
                }
690
            }
691
692
            $results['calc('.count($results).')'] = 'calc'.$expr;
693
        }
694
695
        return $results;
696
    }
697
698
    /**
699
     * Check if file is small enough to be imported.
700
     *
701
     * @param string $path The path to the file
702
     *
703
     * @return bool
704
     */
705
    protected function canImportBySize($path)
706
    {
707
        return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
708
    }
709
710
    /**
711
     * Check if file a file can be imported, going by the path.
712
     *
713
     * @param string $path
714
     *
715
     * @return bool
716
     */
717
    protected function canImportByPath($path)
718
    {
719
        return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
720
    }
721
722
    /**
723
     * Return a converter to update relative paths to be relative to the new
724
     * destination.
725
     *
726
     * @param string $source
727
     * @param string $target
728
     *
729
     * @return ConverterInterface
730
     */
731
    protected function getPathConverter($source, $target)
732
    {
733
        return new Converter($source, $target);
734
    }
735
}
736