PluginProbe
Page Optimize / trunk
Page Optimize vtrunk
trunk 0.4.2 0.4.3 0.4.4 0.4.6 0.4.7 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 0.5.8 0.6.0 0.6.1 0.6.2 0.6.3
page-optimize / concat-css.php

concat-css.php in Page Optimize trunk, at concat-css.php

367 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 require_once __DIR__ . '/dependency-path-mapping.php';
4 require_once __DIR__ . '/utils.php';
5
6 if ( ! defined( 'ALLOW_GZIP_COMPRESSION' ) ) {
7 define( 'ALLOW_GZIP_COMPRESSION', true );
8 }
9
10 class Page_Optimize_CSS_Concat extends WP_Styles {
11 private $dependency_path_mapping;
12 private $old_styles;
13
14 public $allow_gzip_compression;
15
16 function __construct( $styles ) {
17 if ( empty( $styles ) || ! ( $styles instanceof WP_Styles ) ) {
18 $this->old_styles = new WP_Styles();
19 } else {
20 $this->old_styles = $styles;
21 }
22
23 // Unset all the object properties except our private copy of the styles object.
24 // We have to unset everything so that the overload methods talk to $this->old_styles->whatever
25 // instead of $this->whatever.
26 foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
27 if ( 'old_styles' === $key ) {
28 continue;
29 }
30 unset( $this->$key );
31 }
32
33 $this->dependency_path_mapping = new Page_Optimize_Dependency_Path_Mapping(
34 apply_filters( 'page_optimize_site_url', $this->base_url )
35 );
36 }
37
38 protected function has_inline_style( $handle ) {
39 $after_output = $this->get_data( $handle, 'after' );
40 if ( ! empty( $after_output ) ) {
41 return true;
42 }
43
44 return false;
45 }
46
47 protected function css_has_import( $path ) {
48 static $cache = array();
49
50 if ( empty( $path ) ) {
51 return false;
52 }
53
54 $key = $path;
55
56 if ( array_key_exists( $key, $cache ) ) {
57 return $cache[ $key ];
58 }
59
60 $fh = @fopen( $path, 'rb' );
61 if ( ! $fh ) {
62 $cache[ $key ] = false;
63 return false;
64 }
65
66 $found = false;
67 $tail = '';
68
69 // Scan in chunks to keep memory bounded for large stylesheets.
70 while ( ! feof( $fh ) ) {
71 $chunk = fread( $fh, 32768 );
72 if ( false === $chunk || '' === $chunk ) {
73 break;
74 }
75
76 $haystack = $tail . $chunk;
77 if ( false !== stripos( $haystack, '@import' ) ) {
78 $found = true;
79 break;
80 }
81
82 // Keep a short overlap so "@import" across chunk boundaries isn't missed.
83 $tail = substr( $haystack, -7 );
84 }
85
86 fclose( $fh );
87
88 $cache[ $key ] = $found;
89 return $found;
90 }
91
92 function do_items( $handles = false, $group = false ) {
93 $handles = false === $handles ? $this->queue : (array) $handles;
94 $stylesheets = array();
95 $siteurl = apply_filters( 'page_optimize_site_url', $this->base_url );
96
97 $this->all_deps( $handles );
98
99 $concat_group = null;
100
101 foreach ( $this->to_do as $key => $handle ) {
102 $css_realpath = null;
103 // 1a. Skip invalid dependencies.
104 if ( ! isset( $this->registered[ $handle ] ) ) {
105 unset( $this->to_do[ $key ] );
106 continue;
107 }
108
109 $obj = $this->registered[ $handle ];
110
111 // 1b. Skip virtual (src-less) dependencies.
112 if ( empty( $obj->src ) ) {
113 if ( null !== $concat_group ) {
114 $stylesheets[] = $concat_group;
115 $concat_group = null;
116 }
117
118 $stylesheets[] = array(
119 'type' => 'do_item',
120 'handle' => $handle,
121 );
122 unset( $this->to_do[ $key ] );
123 continue;
124 }
125
126 $obj->src = apply_filters( 'style_loader_src', $obj->src, $obj->handle );
127
128 // Core is kind of broken and returns "true" for src of "colors" handle
129 // http://core.trac.wordpress.org/attachment/ticket/16827/colors-hacked-fixed.diff
130 // http://core.trac.wordpress.org/ticket/20729
131 $css_url = $obj->src;
132 if ( 'colors' == $obj->handle && true === $css_url ) {
133 $css_url = wp_style_loader_src( $css_url, $obj->handle );
134 }
135
136 $css_url_parsed = parse_url( $css_url );
137 $css_path = ( is_array( $css_url_parsed ) && isset( $css_url_parsed['path'] ) ) ? $css_url_parsed['path'] : '';
138 $extra = $obj->extra;
139
140 // 2. Determine if this stylesheet can be concatenated.
141
142 // Don't concat by default
143 $do_concat = false;
144
145 // Only try to concat static css files
146 if ( $css_path && false !== strpos( $css_path, '.css' ) ) {
147 $do_concat = true;
148 } else {
149 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
150 echo sprintf( "\n<!-- No Concat CSS %s => Maybe Not Static File %s -->\n", esc_html( $handle ), esc_html( $obj->src ) );
151 }
152 }
153
154 // Don't try to concat styles which are loaded conditionally (like IE stuff)
155 if ( isset( $extra['conditional'] ) ) {
156 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
157 echo sprintf( "\n<!-- No Concat CSS %s => Has Conditional -->\n", esc_html( $handle ) );
158 }
159 $do_concat = false;
160 }
161
162 // Don't concat rtl stuff for now until concat supports it correctly
163 if ( $do_concat && 'rtl' === $this->text_direction && ! empty( $extra['rtl'] ) ) {
164 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
165 echo sprintf( "\n<!-- No Concat CSS %s => Is RTL -->\n", esc_html( $handle ) );
166 }
167 $do_concat = false;
168 }
169
170 // Don't try to concat externally hosted scripts
171 $is_internal_uri = $this->dependency_path_mapping->is_internal_uri( $css_url );
172 if ( $do_concat && ! $is_internal_uri ) {
173 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
174 echo sprintf( "\n<!-- No Concat CSS %s => External URL: %s -->\n", esc_html( $handle ), esc_url( $css_url ) );
175 }
176 $do_concat = false;
177 }
178
179 if ( $do_concat ) {
180 // Resolve paths and concat styles that exist in the filesystem
181 $css_realpath = $this->dependency_path_mapping->dependency_src_to_fs_path( $css_url );
182 if ( false === $css_realpath ) {
183 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
184 echo sprintf( "\n<!-- No Concat CSS %s => Invalid Path %s -->\n", esc_html( $handle ), esc_html( $css_realpath ) );
185 }
186 $do_concat = false;
187 }
188 }
189
190 // Skip concating CSS from exclusion list
191 $exclude_list = page_optimize_css_exclude_list();
192 foreach ( $exclude_list as $exclude ) {
193 if ( $do_concat && $handle === $exclude ) {
194 $do_concat = false;
195 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
196 echo sprintf( "\n<!-- No Concat CSS %s => Excluded option -->\n", esc_html( $handle ) );
197 }
198 }
199 }
200
201 // Allow plugins to disable concatenation of certain stylesheets.
202 $filtered_concat = apply_filters( 'css_do_concat', $do_concat, $handle );
203 if ( $do_concat && ! $filtered_concat ) {
204 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
205 echo sprintf( "\n<!-- No Concat CSS %s => Filtered `false` -->\n", esc_html( $handle ) );
206 }
207 }
208 $do_concat = $filtered_concat;
209
210 /**
211 * 3. Add to the stylesheets output list.
212 *
213 * We keep a running $concat_group that accumulates consecutive concat-eligible styles
214 * sharing the same media type. The group is finalized and appended to $stylesheets when:
215 * - A non-concat style appears
216 * - The media type changes
217 * - An inline style is attached to the current style
218 * This makes sure output order matches registration order.
219 **/
220 if ( true === $do_concat ) {
221 $media = $obj->args;
222 if ( empty( $media ) ) {
223 $media = 'all';
224 }
225
226 // Media type changed - finish the current group.
227 if ( null !== $concat_group && $concat_group['media'] !== $media ) {
228 $stylesheets[] = $concat_group;
229 $concat_group = null;
230 }
231
232 // If a non-first stylesheet in a group contains @import, the concat service hoists it,
233 // reordering CSS. Split groups so @import stylesheets are always first in their group.
234 if (
235 null !== $concat_group
236 && ! empty( $css_realpath )
237 && $this->css_has_import( $css_realpath )
238 ) {
239 $stylesheets[] = $concat_group;
240 $concat_group = null;
241 }
242
243 // Start a new group if needed.
244 if ( null === $concat_group ) {
245 $concat_group = array(
246 'type' => 'concat',
247 'media' => $media,
248 'paths' => array(),
249 'handles' => array(),
250 );
251 }
252
253 // Add this stylesheet to the current group.
254 $concat_group['paths'][] = $css_path;
255 $concat_group['handles'][] = $handle;
256 $this->done[] = $handle;
257
258 // Inline styles must print right after their <link>, so break the group
259 // because we can't concat directly anything after this CSS.
260 if ( $this->has_inline_style( $handle ) ) {
261 $stylesheets[] = $concat_group;
262 $concat_group = null;
263 }
264 } else {
265 // Non-concat item - finish any current open group to preserve order.
266 if ( null !== $concat_group ) {
267 $stylesheets[] = $concat_group;
268 $concat_group = null;
269 }
270 // Add the non-concat item.
271 $stylesheets[] = array(
272 'type' => 'do_item',
273 'handle' => $handle,
274 );
275 }
276 unset( $this->to_do[ $key ] );
277 }
278
279 if ( null !== $concat_group ) {
280 $stylesheets[] = $concat_group;
281 }
282
283 foreach ( $stylesheets as $css_array ) {
284 if ( 'do_item' === $css_array['type'] ) {
285 if ( $this->do_item( $css_array['handle'], $group ) ) {
286 $this->done[] = $css_array['handle'];
287 }
288 } elseif ( 'concat' === $css_array['type'] && isset( $css_array['paths'] ) ) {
289 $media = $css_array['media'];
290 $css = $css_array['paths'];
291 $handles = $css_array['handles'];
292
293 if ( count( $css ) > 1 ) {
294 $fs_paths = array();
295 foreach ( $css as $css_uri_path ) {
296 $fs_paths[] = $this->dependency_path_mapping->uri_path_to_fs_path( $css_uri_path );
297 }
298
299 $mtime = max( array_map( 'filemtime', $fs_paths ) );
300 if ( page_optimize_use_concat_base_dir() ) {
301 $path_str = implode( ',', array_map( 'page_optimize_remove_concat_base_prefix', $fs_paths ) );
302 } else {
303 $path_str = implode( ',', $css );
304 }
305 $path_str = "$path_str?m=$mtime";
306
307 if ( $this->allow_gzip_compression ) {
308 $path_64 = base64_encode( gzcompress( $path_str ) );
309 if ( strlen( $path_str ) > ( strlen( $path_64 ) + 1 ) ) {
310 $path_str = '-' . $path_64;
311 }
312 }
313
314 $href = $siteurl . "/_static/??" . $path_str;
315 } else {
316 $href = Page_Optimize_Utils::cache_bust_mtime( $css[0], $siteurl );
317 }
318
319 $css_id = "$media-css-" . md5( $href );
320 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
321 $tag = "<link data-handles='" . esc_attr( implode( ',', $handles ) ) . "' rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n";
322 } else {
323 $tag = "<link rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n";
324 }
325
326 $tag = apply_filters( 'page_optimize_style_loader_tag', $tag, $handles, $href, $media );
327
328 if ( is_array( $handles ) && count( $handles ) === 1 ) {
329 $tag = apply_filters( 'style_loader_tag', $tag, $handles[0], $href, $media );
330 }
331
332 echo $tag;
333 array_map( array( $this, 'print_inline_style' ), $handles );
334 }
335 }
336
337 return $this->done;
338 }
339
340 function __isset( $key ) {
341 return isset( $this->old_styles->$key );
342 }
343
344 function __unset( $key ) {
345 unset( $this->old_styles->$key );
346 }
347
348 function &__get( $key ) {
349 return $this->old_styles->$key;
350 }
351
352 function __set( $key, $value ) {
353 $this->old_styles->$key = $value;
354 }
355 }
356
357 function page_optimize_css_concat_init() {
358 global $wp_styles;
359
360 $wp_styles = new Page_Optimize_CSS_Concat( $wp_styles );
361 $wp_styles->allow_gzip_compression = ALLOW_GZIP_COMPRESSION;
362 }
363
364 if ( page_optimize_should_concat_css() ) {
365 add_action( 'init', 'page_optimize_css_concat_init' );
366 }
367