source: t29-www/lib/CSSMin.php @ 782

Last change on this file since 782 was 269, checked in by sven, 12 years ago

CSS modular sauber verteilt und JS-Loading-Framework generalisiert zu einem OOP RessourceLoader, der sowohl JS als auch CSS vom Verzeichnis einlesen kann, Cachefile erzeugt und mit Hooks sowie Postprozessor verarbeiten kann (etwa fuer Minify, LESS, usw.). Noch nicht ausgiebig getestet.

File size: 8.1 KB
Line 
1<?php
2/**
3 * Minification of CSS stylesheets.
4 *
5 * Copyright 2010 Wikimedia Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License"); you may
8 * not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *              http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software distributed
14 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
15 * OF ANY KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations under the License.
17 *
18 * @file
19 * @version 0.1.1 -- 2010-09-11
20 * @author Trevor Parscal <tparscal@wikimedia.org>
21 * @copyright Copyright 2010 Wikimedia Foundation
22 * @license http://www.apache.org/licenses/LICENSE-2.0
23 */
24
25/**
26 * Transforms CSS data
27 *
28 * This class provides minification, URL remapping, URL extracting, and data-URL embedding.
29 */
30class CSSMin {
31
32        /* Constants */
33
34        /**
35         * Maximum file size to still qualify for in-line embedding as a data-URI
36         *
37         * 24,576 is used because Internet Explorer has a 32,768 byte limit for data URIs,
38         * which when base64 encoded will result in a 1/3 increase in size.
39         */
40        const EMBED_SIZE_LIMIT = 24576;
41        const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*)(?P<query>\??[^\)\'"]*)[\'"]?\s*\)';
42
43        /* Protected Static Members */
44
45        /** @var array List of common image files extensions and mime-types */
46        protected static $mimeTypes = array(
47                'gif' => 'image/gif',
48                'jpe' => 'image/jpeg',
49                'jpeg' => 'image/jpeg',
50                'jpg' => 'image/jpeg',
51                'png' => 'image/png',
52                'tif' => 'image/tiff',
53                'tiff' => 'image/tiff',
54                'xbm' => 'image/x-xbitmap',
55        );
56
57        /* Static Methods */
58
59        /**
60         * Gets a list of local file paths which are referenced in a CSS style sheet
61         *
62         * @param $source string CSS data to remap
63         * @param $path string File path where the source was read from (optional)
64         * @return array List of local file references
65         */
66        public static function getLocalFileReferences( $source, $path = null ) {
67                $files = array();
68                $rFlags = PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
69                if ( preg_match_all( '/' . self::URL_REGEX . '/', $source, $matches, $rFlags ) ) {
70                        foreach ( $matches as $match ) {
71                                $file = ( isset( $path )
72                                        ? rtrim( $path, '/' ) . '/'
73                                        : '' ) . "{$match['file'][0]}";
74
75                                // Only proceed if we can access the file
76                                if ( !is_null( $path ) && file_exists( $file ) ) {
77                                        $files[] = $file;
78                                }
79                        }
80                }
81                return $files;
82        }
83
84        /**
85         * @param $file string
86         * @return bool|string
87         */
88        protected static function getMimeType( $file ) {
89                $realpath = realpath( $file );
90                // Try a couple of different ways to get the mime-type of a file, in order of
91                // preference
92                if (
93                        $realpath
94                        && function_exists( 'finfo_file' )
95                        && function_exists( 'finfo_open' )
96                        && defined( 'FILEINFO_MIME_TYPE' )
97                ) {
98                        // As of PHP 5.3, this is how you get the mime-type of a file; it uses the Fileinfo
99                        // PECL extension
100                        return finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $realpath );
101                } elseif ( function_exists( 'mime_content_type' ) ) {
102                        // Before this was deprecated in PHP 5.3, this was how you got the mime-type of a file
103                        return mime_content_type( $file );
104                } else {
105                        // Worst-case scenario has happened, use the file extension to infer the mime-type
106                        $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
107                        if ( isset( self::$mimeTypes[$ext] ) ) {
108                                return self::$mimeTypes[$ext];
109                        }
110                }
111                return false;
112        }
113
114        /**
115         * Remaps CSS URL paths and automatically embeds data URIs for URL rules
116         * preceded by an /* @embed * / comment
117         *
118         * @param $source string CSS data to remap
119         * @param $local string File path where the source was read from
120         * @param $remote string URL path to the file
121         * @param $embedData bool If false, never do any data URI embedding, even if / * @embed * / is found
122         * @return string Remapped CSS data
123         */
124        public static function remap( $source, $local, $remote, $embedData = true ) {
125                $pattern = '/((?P<embed>\s*\/\*\s*\@embed\s*\*\/)(?P<pre>[^\;\}]*))?' .
126                        self::URL_REGEX . '(?P<post>[^;]*)[\;]?/';
127                $offset = 0;
128                while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) {
129                        // Skip fully-qualified URLs and data URIs
130                        $urlScheme = parse_url( $match['file'][0], PHP_URL_SCHEME );
131                        if ( $urlScheme ) {
132                                // Move the offset to the end of the match, leaving it alone
133                                $offset = $match[0][1] + strlen( $match[0][0] );
134                                continue;
135                        }
136                        // URLs with absolute paths like /w/index.php need to be expanded
137                        // to absolute URLs but otherwise left alone
138                        if ( $match['file'][0] !== '' && $match['file'][0][0] === '/' ) {
139                                // Replace the file path with an expanded (possibly protocol-relative) URL
140                                // ...but only if wfExpandUrl() is even available.
141                                // This will not be the case if we're running outside of MW
142                                $lengthIncrease = 0;
143                                if ( function_exists( 'wfExpandUrl' ) ) {
144                                        $expanded = wfExpandUrl( $match['file'][0], PROTO_RELATIVE );
145                                        $origLength = strlen( $match['file'][0] );
146                                        $lengthIncrease = strlen( $expanded ) - $origLength;
147                                        $source = substr_replace( $source, $expanded,
148                                                $match['file'][1], $origLength
149                                        );
150                                }
151                                // Move the offset to the end of the match, leaving it alone
152                                $offset = $match[0][1] + strlen( $match[0][0] ) + $lengthIncrease;
153                                continue;
154                        }
155                        // Shortcuts
156                        $embed = $match['embed'][0];
157                        $pre = $match['pre'][0];
158                        $post = $match['post'][0];
159                        $query = $match['query'][0];
160                        $url = "{$remote}/{$match['file'][0]}";
161                        $file = "{$local}/{$match['file'][0]}";
162                        // bug 27052 - Guard against double slashes, because foo//../bar
163                        // apparently resolves to foo/bar on (some?) clients
164                        $url = preg_replace( '#([^:])//+#', '\1/', $url );
165                        $replacement = false;
166                        if ( $local !== false && file_exists( $file ) ) {
167                                // Add version parameter as a time-stamp in ISO 8601 format,
168                                // using Z for the timezone, meaning GMT
169                                $url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) );
170                                // Embedding requires a bit of extra processing, so let's skip that if we can
171                                if ( $embedData && $embed ) {
172                                        $type = self::getMimeType( $file );
173                                        // Detect when URLs were preceeded with embed tags, and also verify file size is
174                                        // below the limit
175                                        if (
176                                                $type
177                                                && $match['embed'][1] > 0
178                                                && filesize( $file ) < self::EMBED_SIZE_LIMIT
179                                        ) {
180                                                // Strip off any trailing = symbols (makes browsers freak out)
181                                                $data = base64_encode( file_get_contents( $file ) );
182                                                // Build 2 CSS properties; one which uses a base64 encoded data URI in place
183                                                // of the @embed comment to try and retain line-number integrity, and the
184                                                // other with a remapped an versioned URL and an Internet Explorer hack
185                                                // making it ignored in all browsers that support data URIs
186                                                $replacement = "{$pre}url(data:{$type};base64,{$data}){$post};";
187                                                $replacement .= "{$pre}url({$url}){$post}!ie;";
188                                        }
189                                }
190                                if ( $replacement === false ) {
191                                        // Assume that all paths are relative to $remote, and make them absolute
192                                        $replacement = "{$embed}{$pre}url({$url}){$post};";
193                                }
194                        } elseif ( $local === false ) {
195                                // Assume that all paths are relative to $remote, and make them absolute
196                                $replacement = "{$embed}{$pre}url({$url}{$query}){$post};";
197                        }
198                        if ( $replacement !== false ) {
199                                // Perform replacement on the source
200                                $source = substr_replace(
201                                        $source, $replacement, $match[0][1], strlen( $match[0][0] )
202                                );
203                                // Move the offset to the end of the replacement in the source
204                                $offset = $match[0][1] + strlen( $replacement );
205                                continue;
206                        }
207                        // Move the offset to the end of the match, leaving it alone
208                        $offset = $match[0][1] + strlen( $match[0][0] );
209                }
210                return $source;
211        }
212
213        /**
214         * Removes whitespace from CSS data
215         *
216         * @param $css string CSS data to minify
217         * @return string Minified CSS data
218         */
219        public static function minify( $css ) {
220                return trim(
221                        str_replace(
222                                array( '; ', ': ', ' {', '{ ', ', ', '} ', ';}' ),
223                                array( ';', ':', '{', '{', ',', '}', '}' ),
224                                preg_replace( array( '/\s+/', '/\/\*.*?\*\//s' ), array( ' ', '' ), $css )
225                        )
226                );
227        }
228}
Note: See TracBrowser for help on using the repository browser.
© 2008 - 2013 technikum29 • Sven Köppel • Some rights reserved
Powered by Trac
Expect where otherwise noted, content on this site is licensed under a Creative Commons 3.0 License