PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.2
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.2
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / file-optimizer.php

file-optimizer.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 2.2, at includes/file-optimizer.php

388 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ported from https://github.com/Automattic/nginx-http-concat
4 *
5 * Concatenation script inspired by Nginx's ngx_http_concat and Apache's modconcat modules.
6 *
7 * It follows the same pattern for enabling the concatenation. It uses two ?, like this:
8 * http://example.com/??style1.css,style2.css,foo/style3.css
9 *
10 * If a third ? is present it's treated as version string. Like this:
11 * http://example.com/??style1.css,style2.css,foo/style3.css?v=102234
12 *
13 * It will also replace the relative paths in CSS files with absolute paths.
14 *
15 * @package PoweredCache
16 */
17
18 namespace PoweredCache\FileOptimizer;
19
20 // phpcs:disable
21
22 use PoweredCache\Dependencies\MatthiasMullie\Minify\CSS;
23 use PoweredCache\Dependencies\MatthiasMullie\Minify\JS;
24
25 /**
26 * PSR-4-ish autoloading
27 *
28 * @since 2.0
29 */
30 spl_autoload_register(
31 function ( $class ) {
32 // project-specific namespace prefix.
33 $prefix = 'PoweredCache\\';
34
35 // base directory for the namespace prefix.
36 $base_dir = __DIR__ . '/classes/';
37
38 // does the class use the namespace prefix?
39 $len = strlen( $prefix );
40
41 if ( strncmp( $prefix, $class, $len ) !== 0 ) {
42 return;
43 }
44
45 $relative_class = substr( $class, $len );
46
47 $file = $base_dir . str_replace( '\\', '/', $relative_class ) . '.php';
48
49 // if the file exists, require it.
50 if ( file_exists( $file ) ) {
51 require $file;
52 }
53 }
54 );
55
56 /* Config */
57 $concat_max_files = 150;
58 $concat_unique = true;
59 $concat_types = array(
60 'css' => 'text/css',
61 'js' => 'application/javascript',
62 );
63
64
65 $current_dir = file_optimizer_normalize_path( realpath( dirname( __DIR__ ) ) );
66
67 /* Constants */
68 // By default determine the document root from this scripts path in the plugins dir (you can hardcode this define)
69 define( 'CONCAT_FILES_ROOT', substr( $current_dir, 0, strpos( $current_dir, '/wp-content' ) ) );
70 define( 'POWERED_CACHE_FO_CACHE_DIR', CONCAT_FILES_ROOT . '/wp-content/cache/min/' );
71
72 if ( ! file_exists( POWERED_CACHE_FO_CACHE_DIR ) ) {
73 mkdir( POWERED_CACHE_FO_CACHE_DIR, 0775, true );
74 }
75
76 function concat_http_status_exit( $status ) {
77 switch ( $status ) {
78 case 200:
79 $text = 'OK';
80 break;
81 case 400:
82 $text = 'Bad Request';
83 break;
84 case 403:
85 $text = 'Forbidden';
86 break;
87 case 404:
88 $text = 'Not found';
89 break;
90 case 500:
91 $text = 'Internal Server Error';
92 break;
93 default:
94 $text = '';
95 }
96
97 $protocol = $_SERVER['SERVER_PROTOCOL'];
98 if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol ) {
99 $protocol = 'HTTP/1.0';
100 }
101
102 @header( "$protocol $status $text", true, $status );
103 exit();
104 }
105
106 function concat_get_mtype( $file ) {
107 global $concat_types;
108
109 $lastdot_pos = strrpos( $file, '.' );
110 if ( false === $lastdot_pos ) {
111 return false;
112 }
113
114 $ext = substr( $file, $lastdot_pos + 1 );
115
116 return isset( $concat_types[ $ext ] ) ? $concat_types[ $ext ] : false;
117 }
118
119 function concat_get_path( $uri ) {
120 if ( ! strlen( $uri ) ) {
121 concat_http_status_exit( 400 );
122 }
123
124 if ( false !== strpos( $uri, '..' ) || false !== strpos( $uri, "\0" ) ) {
125 concat_http_status_exit( 400 );
126 }
127
128 return CONCAT_FILES_ROOT . ( '/' != $uri[0] ? '/' : '' ) . $uri;
129 }
130
131 function relative_path_replace( $buf, $dirpath ) {
132 // url(relative/path/to/file) -> url(/absolute/and/not/relative/path/to/file)
133 $buf = preg_replace(
134 '/(:?\s*url\s*\()\s*(?:\'|")?\s*([^\/\'"\s\)](?:(?<!data:|http:|https:|[\(\'"]#|%23).)*)[\'"\s]*\)/isU',
135 '$1' . ( '/' === $dirpath ? '/' : $dirpath . '/' ) . '$2)',
136 $buf
137 );
138
139 return $buf;
140 }
141
142 /* Main() */
143 if ( ! in_array( $_SERVER['REQUEST_METHOD'], array( 'GET', 'HEAD' ) ) ) {
144 concat_http_status_exit( 400 );
145 }
146
147 // /_static/??/foo/bar.css,/foo1/bar/baz.css?m=293847g
148 // or
149 // /_static/??-eJzTT8vP109KLNJLLi7W0QdyDEE8IK4CiVjn2hpZGluYmKcDABRMDPM=
150 $args = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY );
151 if ( ! $args || false === strpos( $args, '?' ) ) {
152 concat_http_status_exit( 400 );
153 }
154
155 $args = substr( $args, strpos( $args, '?' ) + 1 );
156
157
158
159 // /foo/bar.css,/foo1/bar/baz.css?m=293847g
160 // or
161 // -eJzTT8vP109KLNJLLi7W0QdyDEE8IK4CiVjn2hpZGluYmKcDABRMDPM=
162 if ( '-' == $args[0] ) {
163 $args = @gzuncompress( base64_decode( substr( $args, 1 ) ) );
164
165 // Invalid data, abort!
166 if ( false === $args ) {
167 concat_http_status_exit( 400 );
168 }
169 }
170
171 // /foo/bar.css,/foo1/bar/baz.css?m=293847g
172 $version_string_pos = strpos( $args, '?' );
173 if ( false !== $version_string_pos ) {
174 $args = substr( $args, 0, $version_string_pos );
175 }
176
177 // /foo/bar.css,/foo1/bar/baz.css
178 $args = explode( ',', $args );
179 if ( ! $args ) {
180 concat_http_status_exit( 400 );
181 }
182
183 // array('/wp-content/foo/bar.css','//cdn.cname.com/wp-content/foo/bar.css')
184 // get real path when it masked from cdn
185 foreach ( $args as $index => $arg ) {
186 if ( 0 === stripos( $arg, '//' ) ) {
187 $args[ $index ] = parse_url( str_replace( '//', 'http://', $arg ), PHP_URL_PATH );
188 }
189 }
190
191 // array( '/foo/bar.css', '/foo1/bar/baz.css' )
192 if ( 0 == count( $args ) || count( $args ) > $concat_max_files ) {
193 concat_http_status_exit( 400 );
194 }
195
196 // If we're in a subdirectory context, use that as the root.
197 // We can't assume that the root serves the same content as the subdir.
198 $subdir_path_prefix = '';
199 $request_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
200 $_static_index = strpos( $request_path, '/_static/' );
201 if ( $_static_index > 0 ) {
202 $subdir_path_prefix = substr( $request_path, 0, $_static_index );
203 }
204 unset( $request_path, $_static_index );
205
206 $last_modified = 0;
207 $pre_output = '';
208 $output = '';
209
210 $do_minify = (bool) stripos( $_SERVER['REQUEST_URI'], 'minify=1' );
211 $hash = sha1( $_SERVER['REQUEST_URI'] );
212 $latest_file = end( $args );
213
214 $cache_file_name = POWERED_CACHE_FO_CACHE_DIR . $hash;
215 if ( 'application/javascript' == concat_get_mtype( $latest_file ) ) {
216 $cache_file_name .= '.js';
217 } elseif ( 'text/css' == concat_get_mtype( $latest_file ) ) {
218 $cache_file_name .= '.css';
219 }
220
221 if ( file_exists( $cache_file_name ) ) {
222 $buf = @file_get_contents( $cache_file_name );
223 if ( $buf ) {
224 $stat = stat( $cache_file_name );
225
226 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $stat['mtime'] ) . ' GMT' );
227 header( 'Content-Length: ' . ( strlen( $pre_output ) + strlen( $buf ) ) );
228 header( 'Content-Type: ' . concat_get_mtype( $latest_file ) );
229
230 echo $pre_output . $buf;
231 exit;
232 }
233 }
234
235 foreach ( $args as $uri ) {
236
237 $fullpath = concat_get_path( $uri );
238
239 if ( ! file_exists( $fullpath ) ) {
240 concat_http_status_exit( 404 );
241 }
242
243 $mime_type = concat_get_mtype( $fullpath );
244 if ( ! in_array( $mime_type, $concat_types ) ) {
245 concat_http_status_exit( 400 );
246 }
247
248 if ( $concat_unique ) {
249 if ( ! isset( $last_mime_type ) ) {
250 $last_mime_type = $mime_type;
251 }
252
253 if ( $last_mime_type != $mime_type ) {
254 concat_http_status_exit( 400 );
255 }
256 }
257
258 $stat = stat( $fullpath );
259 if ( false === $stat ) {
260 concat_http_status_exit( 500 );
261 }
262
263 if ( $stat['mtime'] > $last_modified ) {
264 $last_modified = $stat['mtime'];
265 }
266
267 $buf = file_get_contents( $fullpath );
268 if ( false === $buf ) {
269 concat_http_status_exit( 500 );
270 }
271
272 if ( 'text/css' == $mime_type ) {
273 $dirpath = $subdir_path_prefix . dirname( $uri );
274
275 // url(relative/path/to/file) -> url(/absolute/and/not/relative/path/to/file)
276 $buf = relative_path_replace( $buf, $dirpath );
277
278 // AlphaImageLoader(...src='relative/path/to/file'...) -> AlphaImageLoader(...src='/absolute/path/to/file'...)
279 $buf = preg_replace(
280 '/(Microsoft.AlphaImageLoader\s*\([^\)]*src=(?:\'|")?)([^\/\'"\s\)](?:(?<!http:|https:).)*)\)/isU',
281 '$1' . ( $dirpath == '/' ? '/' : $dirpath . '/' ) . '$2)',
282 $buf
283 );
284
285 // The @charset rules must be on top of the output
286 if ( 0 === strpos( $buf, '@charset' ) ) {
287 preg_replace_callback(
288 '/(?P<charset_rule>@charset\s+[\'"][^\'"]+[\'"];)/i',
289 function ( $match ) {
290 global $pre_output;
291
292 if ( 0 === strpos( $pre_output, '@charset' ) ) {
293 return '';
294 }
295
296 $pre_output = $match[0] . "\n" . $pre_output;
297
298 return '';
299 },
300 $buf
301 );
302 }
303
304 // Move the @import rules on top of the concatenated output.
305 // Only @charset rule are allowed before them.
306 if ( false !== strpos( $buf, '@import' ) ) {
307 $buf = preg_replace_callback(
308 '/(?P<pre_path>@import\s+(?:url\s*\()?[\'"\s]*)(?P<path>[^\'"\s](?:https?:\/\/.+\/?)?.+?)(?P<post_path>[\'"\s\)]*;)/i',
309 function ( $match ) use ( $dirpath ) {
310 global $pre_output;
311
312 if ( 0 !== strpos( $match['path'], 'http' ) && '/' != $match['path'][0] ) {
313 $pre_output .= $match['pre_path'] . ( $dirpath == '/' ? '/' : $dirpath . '/' ) .
314 $match['path'] . $match['post_path'] . "\n";
315 } else {
316 $pre_output .= $match[0] . "\n";
317 }
318
319 return '';
320 },
321 $buf
322 );
323 }
324 }
325
326 if ( 'application/javascript' == $mime_type ) {
327 $output .= "$buf;\n";
328 } else {
329 $output .= "$buf";
330 }
331 }
332
333 if ( $do_minify && false === stripos( $uri, '.min' ) ) {
334 if ( 'application/javascript' == $mime_type ) {
335 $js_minify = new JS( $output );
336 $output = $js_minify->minify();
337 } elseif ( 'text/css' == $mime_type ) {
338 $css_minify = new CSS( $output );
339 $output = $css_minify->minify();
340 }
341 }
342
343 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $last_modified ) . ' GMT' );
344 header( 'Content-Length: ' . ( strlen( $pre_output ) + strlen( $output ) ) );
345 header( "Content-Type: $mime_type" );
346
347 echo $pre_output . $output;
348
349 $cache_file_name = POWERED_CACHE_FO_CACHE_DIR . $hash;
350 if ( 'application/javascript' == $mime_type ) {
351 $cache_file_name .= '.js';
352 } elseif ( 'text/css' == $mime_type ) {
353 $cache_file_name .= '.css';
354 }
355
356 file_put_contents( $cache_file_name, $pre_output . $output );
357
358
359 /**
360 * Normalize a filesystem path.
361 *
362 * On windows systems, replaces backslashes with forward slashes
363 * and forces upper-case drive letters.
364 * Allows for two leading slashes for Windows network shares, but
365 * ensures that all other duplicate slashes are reduced to a single.
366 *
367 * @param string $path Path to normalize.
368 *
369 * @return string Normalized path.
370 * @see wp_normalize_path
371 *
372 */
373 function file_optimizer_normalize_path( $path ) {
374 $wrapper = '';
375
376 // Standardise all paths to use '/'.
377 $path = str_replace( '\\', '/', $path );
378
379 // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
380 $path = preg_replace( '|(?<=.)/+|', '/', $path );
381
382 // Windows paths should uppercase the drive letter.
383 if ( ':' === substr( $path, 1, 1 ) ) {
384 $path = ucfirst( $path );
385 }
386
387 return $wrapper . $path;
388 }