aboutsummaryrefslogtreecommitdiff
path: root/vendors/min/lib/Minify/ImportProcessor.php
blob: 0d6d90a8166501834846f71ff08c3a0c1121e688 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<?php
/**
 * Class Minify_ImportProcessor  
 * @package Minify
 */

/**
 * Linearize a CSS/JS file by including content specified by CSS import
 * declarations. In CSS files, relative URIs are fixed.
 * 
 * @imports will be processed regardless of where they appear in the source 
 * files; i.e. @imports commented out or in string content will still be
 * processed!
 * 
 * This has a unit test but should be considered "experimental".
 *
 * @package Minify
 * @author Stephen Clay <steve@mrclay.org>
 */
class Minify_ImportProcessor {
    
    public static $filesIncluded = array();
    
    public static function process($file)
    {
        self::$filesIncluded = array();
        self::$_isCss = (strtolower(substr($file, -4)) === '.css');
        $obj = new Minify_ImportProcessor(dirname($file));
        return $obj->_getContent($file);
    }
    
    // allows callback funcs to know the current directory
    private $_currentDir = null;
    
    // allows _importCB to write the fetched content back to the obj
    private $_importedContent = '';
    
    private static $_isCss = null;
    
    private function __construct($currentDir)
    {
        $this->_currentDir = $currentDir;
    }
    
    private function _getContent($file)
    {
        $file = realpath($file);
        if (! $file
            || in_array($file, self::$filesIncluded)
            || false === ($content = @file_get_contents($file))
        ) {
            // file missing, already included, or failed read
            return '';
        }
        self::$filesIncluded[] = realpath($file);
        $this->_currentDir = dirname($file);
        
        // remove UTF-8 BOM if present
        if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
            $content = substr($content, 3);
        }
        // ensure uniform EOLs
        $content = str_replace("\r\n", "\n", $content);
        
        // process @imports
        $content = preg_replace_callback(
            '/
                @import\\s+
                (?:url\\(\\s*)?      # maybe url(
                [\'"]?               # maybe quote
                (.*?)                # 1 = URI
                [\'"]?               # maybe end quote
                (?:\\s*\\))?         # maybe )
                ([a-zA-Z,\\s]*)?     # 2 = media list
                ;                    # end token
            /x'
            ,array($this, '_importCB')
            ,$content
        );
        
        if (self::$_isCss) {
            // rewrite remaining relative URIs
            $content = preg_replace_callback(
                '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
                ,array($this, '_urlCB')
                ,$content
            );
        }
        
        return $this->_importedContent . $content;
    }
    
    private function _importCB($m)
    {
        $url = $m[1];
        $mediaList = preg_replace('/\\s+/', '', $m[2]);
        
        if (strpos($url, '://') > 0) {
            // protocol, leave in place for CSS, comment for JS
            return self::$_isCss
                ? $m[0]
                : "/* Minify_ImportProcessor will not include remote content */";
        }
        if ('/' === $url[0]) {
            // protocol-relative or root path
            $url = ltrim($url, '/');
            $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
                . strtr($url, '/', DIRECTORY_SEPARATOR);
        } else {
            // relative to current path
            $file = $this->_currentDir . DIRECTORY_SEPARATOR 
                . strtr($url, '/', DIRECTORY_SEPARATOR);
        }
        $obj = new Minify_ImportProcessor(dirname($file));
        $content = $obj->_getContent($file);
        if ('' === $content) {
            // failed. leave in place for CSS, comment for JS
            return self::$_isCss
                ? $m[0]
                : "/* Minify_ImportProcessor could not fetch '{$file}' */";;
        }
        return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
            ? $content
            : "@media {$mediaList} {\n{$content}\n}\n";
    }
    
    private function _urlCB($m)
    {
        // $m[1] is either quoted or not
        $quote = ($m[1][0] === "'" || $m[1][0] === '"')
            ? $m[1][0]
            : '';
        $url = ($quote === '')
            ? $m[1]
            : substr($m[1], 1, strlen($m[1]) - 2);
        if ('/' !== $url[0]) {
            if (strpos($url, '//') > 0) {
                // probably starts with protocol, do not alter
            } else {
                // prepend path with current dir separator (OS-independent)
                $path = $this->_currentDir 
                    . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
                // strip doc root
                $path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT'])));
                // fix to absolute URL
                $url = strtr($path, '/\\', '//');
                // remove /./ and /../ where possible
                $url = str_replace('/./', '/', $url);
                // inspired by patch from Oleg Cherniy
                do {
                    $url = preg_replace('@/[^/]+/\\.\\./@', '/', $url, 1, $changed);
                } while ($changed);
            }
        }
        return "url({$quote}{$url}{$quote})";
    }
}