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 / classes / FileOptimizer.php

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

560 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * File optimizer (minify, contact) related functionalities
4 *
5 * @package PoweredCache
6 */
7
8 namespace PoweredCache;
9
10 use const PoweredCache\Constants\POST_META_DISABLE_CSS_OPTIMIZATION;
11 use const PoweredCache\Constants\POST_META_DISABLE_JS_OPTIMIZATION;
12 use PoweredCache\Optimizer\CSS;
13 use PoweredCache\Optimizer\Helper;
14 use PoweredCache\Optimizer\JS;
15 use function PoweredCache\Utils\get_cache_dir;
16 use function PoweredCache\Utils\remove_dir;
17
18
19 /**
20 * Class FileOptimizer
21 */
22 class FileOptimizer {
23 /**
24 * Plugin settings
25 *
26 * @var array
27 */
28 public $settings = [];
29
30 /**
31 * Do optimizations for wp-admin?
32 *
33 * @var bool
34 */
35 public $optimize_dashboard;
36
37
38 /**
39 * Return an instance of the current class
40 *
41 * @return FileOptimizer
42 * @since 1.0
43 */
44 public static function factory() {
45 static $instance = false;
46
47 if ( ! $instance ) {
48 $instance = new self();
49 $instance->setup();
50 }
51
52 return $instance;
53 }
54
55 /**
56 * Setup routine
57 */
58 public function setup() {
59 $this->settings = \PoweredCache\Utils\get_settings();
60
61 // Check request method
62 if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || ! in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD' ], true ) ) {
63 return true;
64 }
65
66 /**
67 * Filters whether apply or not apply file optimizations on wp-admin.
68 *
69 * @hook powered_cache_fo_dashboard
70 *
71 * @param {boolean} true to enable optimizations for dashboard.
72 *
73 * @return {boolean} New value.
74 * @since 2.0
75 */
76 $this->optimize_dashboard = apply_filters( 'powered_cache_fo_dashboard', false );
77
78 add_action( 'powered_cache_purge_all_cache', [ $this, 'maybe_purge_fo_cache' ] );
79
80 /**
81 * Don't optimize wp-admin by default
82 * This might worth to add as option?
83 */
84
85 if ( is_admin() && ! $this->optimize_dashboard ) {
86 return;
87 }
88
89 /**
90 * Filters FileOptimizer integration
91 *
92 * @hook powered_cache_fo_disable
93 *
94 * @param {boolean} False by default.
95 *
96 * @return {boolean} New value.
97 * @since 2.2
98 */
99 $disable_file_optimizer = apply_filters( 'powered_cache_fo_disable', false );
100
101 if ( $disable_file_optimizer ) {
102 return;
103 }
104
105 add_action( 'init', [ $this, 'setup_css_combine' ] );
106 add_action( 'init', [ $this, 'setup_js_combine' ] );
107 add_filter( 'script_loader_tag', [ $this, 'js_minify' ], 10, 3 );
108 add_filter( 'style_loader_tag', [ $this, 'css_minify' ], 10, 4 );
109
110 if ( ! $this->settings['combine_js'] ) {
111 add_filter( 'powered_cache_fo_js_do_concat', '__return_false' );
112 }
113
114 if ( ! $this->settings['combine_css'] ) {
115 add_filter( 'powered_cache_fo_css_do_concat', '__return_false' );
116 }
117
118 add_filter( 'powered_cache_fo_script_loader_tag', [ $this, 'change_js_execute_method' ] );
119
120 if ( ! $this->settings['js_execution_optimized_only'] ) {
121 add_filter( 'script_loader_tag', [ $this, 'change_js_execute_method' ], 99 );
122 }
123
124 add_action( 'after_setup_theme', [ $this, 'html_minify' ] );
125
126 if ( $this->settings['combine_google_fonts'] ) {
127 add_action( 'wp_enqueue_scripts', [ $this, 'combine_google_fonts' ], 99 );
128 }
129
130 add_action( 'template_redirect', [ $this, 'maybe_suppress_optimizations' ] );
131 }
132
133
134 /**
135 * Maybe suppress optimizations based on the post meta options
136 *
137 * @since 2.1
138 */
139 public function maybe_suppress_optimizations() {
140 if ( is_singular() ) {
141 $disable_css_optimization = (bool) get_post_meta( get_the_ID(), POST_META_DISABLE_CSS_OPTIMIZATION, true );
142 $disable_js_optimization = (bool) get_post_meta( get_the_ID(), POST_META_DISABLE_JS_OPTIMIZATION, true );
143
144 if ( $disable_css_optimization ) {
145 add_filter( 'powered_cache_fo_css_do_concat', '__return_false' );
146 add_filter( 'powered_cache_fo_disable_css_minify', '__return_true' );
147 }
148
149 if ( $disable_js_optimization ) {
150 add_filter( 'powered_cache_fo_js_do_concat', '__return_false' );
151 add_filter( 'powered_cache_fo_disable_js_minify', '__return_true' );
152 }
153 }
154 }
155
156 /**
157 * Purge FO cache
158 *
159 * @since 2.0
160 */
161 public function maybe_purge_fo_cache() {
162 if ( file_exists( POWERED_CACHE_FO_CACHE_DIR ) ) {
163 remove_dir( POWERED_CACHE_FO_CACHE_DIR );
164 }
165 }
166
167 /**
168 * Minify HTML output
169 */
170 public function html_minify() {
171 if ( ! $this->settings['minify_html'] ) {
172 return;
173 }
174
175 /**
176 * Filters whether disable or not disable HTML minification.
177 *
178 * @hook powered_cache_fo_disable_html_minify
179 *
180 * @param {boolean} true to disable html minify
181 *
182 * @return {boolean} New value.
183 * @since 2.0
184 */
185 if ( apply_filters( 'powered_cache_fo_disable_html_minify', false ) ) {
186 return;
187 }
188
189 ob_start( [ $this, 'run_html_minify' ] );
190 }
191
192 /**
193 * Minify given HTML output
194 *
195 * @param string $buffer HTML
196 *
197 * @return string|string[]|null
198 */
199 public function run_html_minify( $buffer ) {
200 $search = array(
201 '/\>[^\S ]+/s', // strip whitespaces after tags, except space
202 '/[^\S ]+\</s', // strip whitespaces before tags, except space
203 '/(\s)+/s', // shorten multiple whitespace sequences
204 '/^\\s+|\\s+$/', // shorten multiple whitespace sequences
205 );
206
207 $replace = array( '>', '<', '\\1', ' ' );
208 $buffer = preg_replace( $search, $replace, $buffer );
209
210 return $buffer;
211 }
212
213
214 /**
215 * Add JS execution method to script tag
216 *
217 * @param string $tag <script... tag
218 *
219 * @return mixed
220 */
221 public function change_js_execute_method( $tag ) {
222 $js_execution = $this->settings['js_execution_method'];
223 if ( ! $js_execution ) {
224 return $tag;
225 }
226
227 if ( 'blocking' === $js_execution ) {
228 return $tag;
229 }
230
231 if ( 'async' === $js_execution ) {
232 $js_attr = 'async="async"';
233 } elseif ( 'defer' === $js_execution ) {
234 $js_attr = 'defer="defer"';
235 }
236
237 if ( false === stripos( $tag, $js_attr ) ) {
238 $search = '<script ';
239 $replace = sprintf( '<script %s ', $js_attr );
240 $tag = str_replace( $search, $replace, $tag );
241 }
242
243 return $tag;
244 }
245
246 /**
247 * Minify CSS item
248 *
249 * @param string $tag style tag <link...
250 * @param string $handle style handle name
251 * @param string $href Resource URL
252 * @param string $media CSS media
253 *
254 * @return mixed
255 */
256 public function css_minify( $tag, $handle, $href, $media ) {
257 if ( ! $this->settings['minify_css'] ) {
258 return $tag;
259 }
260
261 /**
262 * Filters whether disable or not disable CSS minification.
263 *
264 * @hook powered_cache_fo_disable_css_minify
265 *
266 * @param {boolean} true to disable CSS minify
267 *
268 * @return {boolean} New value.
269 * @since 2.0
270 */
271 if ( apply_filters( 'powered_cache_fo_disable_css_minify', false ) ) {
272 return $tag;
273 }
274
275 // only minify static .css
276 if ( false === strpos( $href, '.css' ) ) {
277 return $tag;
278 }
279
280 // already minified
281 if ( false !== strpos( $href, '.min' ) ) {
282 return $tag;
283 }
284
285 // only minify local hosted
286 if ( ! Helper::is_internal_url( $href, home_url() ) ) {
287 return $tag;
288 }
289
290 // excluded explicitly
291 if ( Helper::is_excluded_css( $href ) ) {
292 return $tag;
293 }
294
295 $realpath = Helper::realpath( $href, home_url() );
296 $path = substr( $realpath, strlen( ABSPATH ) - 1 );
297 $optimized_url = Helper::get_optimized_url( $path, true );
298 $new_tag = str_replace( $href, $optimized_url, $tag );
299
300 return $new_tag;
301 }
302
303 /**
304 * Minify JS file
305 *
306 * @param string $tag script tag <script...
307 * @param string $handle JS handle
308 * @param string $src Resource URL
309 *
310 * @return mixed
311 */
312 public function js_minify( $tag, $handle, $src ) {
313 if ( ! $this->settings['minify_js'] ) {
314 return $tag;
315 }
316
317 /**
318 * Filters whether disable or not disable JS minification.
319 *
320 * @hook powered_cache_fo_disable_js_minify
321 *
322 * @param {boolean} true to disable JS minify
323 *
324 * @return {boolean} New value.
325 * @since 2.0
326 */
327 if ( apply_filters( 'powered_cache_fo_disable_js_minify', false ) ) {
328 return $tag;
329 }
330
331 // only minify static .css
332 if ( false === strpos( $src, '.js' ) ) {
333 return $tag;
334 }
335
336 // already minified
337 if ( false !== strpos( $src, '.min' ) ) {
338 return $tag;
339 }
340
341 // only minify local hosted
342 if ( ! Helper::is_internal_url( $src, home_url() ) ) {
343 return $tag;
344 }
345
346 // excluded explicitly
347 if ( Helper::is_excluded_js( $src ) ) {
348 return $tag;
349 }
350
351 $realpath = Helper::realpath( $src, home_url() );
352 $path = substr( $realpath, strlen( ABSPATH ) - 1 );
353 $optimized_url = Helper::get_optimized_url( $path, true );
354 $new_tag = str_replace( $src, $optimized_url, $tag );
355
356 return $new_tag;
357 }
358
359
360 /**
361 * Setup JS concat
362 */
363 public function setup_js_combine() {
364 if ( ! $this->settings['combine_js'] ) {
365 return;
366 }
367
368 /**
369 * Filters whether disable or not disable JS combine
370 *
371 * @hook powered_cache_fo_disable_js_combine
372 *
373 * @param {boolean} true to disable JS combine
374 *
375 * @return {boolean} New value.
376 * @since 2.0
377 */
378 if ( apply_filters( 'powered_cache_fo_disable_js_combine', false ) ) {
379 return;
380 }
381
382 global $wp_scripts;
383
384 $wp_scripts = new JS( $wp_scripts );
385 /**
386 * Filters whether allow or not allow gzip compression for combined file names.
387 *
388 * @hook powered_cache_fo_allow_gzip_compression
389 *
390 * @param {boolean} true to enable gzip compression on filenames
391 *
392 * @return {boolean} New value.
393 * @since 2.0
394 */
395 $wp_scripts->allow_gzip_compression = apply_filters( 'powered_cache_fo_allow_gzip_compression', true );
396 $wp_scripts->do_minify = $this->settings['minify_js'];
397
398 }
399
400 /**
401 * Setup CSS concat
402 */
403 public function setup_css_combine() {
404 if ( ! $this->settings['combine_css'] ) {
405 return;
406 }
407
408 /**
409 * Filters whether disable or not disable CSS combine
410 *
411 * @hook powered_cache_fo_disable_css_combine
412 *
413 * @param {boolean} true to disable CSS combine
414 *
415 * @return {boolean} New value.
416 * @since 2.0
417 */
418 if ( apply_filters( 'powered_cache_fo_disable_css_combine', false ) ) {
419 return;
420 }
421
422 global $wp_styles;
423
424 $wp_styles = new CSS( $wp_styles );
425 $wp_styles->allow_gzip_compression = apply_filters( 'powered_cache_fo_allow_gzip_compression', true );
426 $wp_styles->do_minify = $this->settings['minify_css'];
427 $wp_styles->enable_cdn = $this->settings['enable_cdn'];
428
429 }
430
431 /**
432 * Combine google fonts
433 * Derived from https://gist.github.com/eugenealegiojo/dbdd620a998458aa2eb1f124b2f0b18e
434 */
435 public function combine_google_fonts() {
436 global $wp_styles;
437
438 // Check for any enqueued `fonts.googleapis.com` from themes or plugins
439 if ( isset( $wp_styles->queue ) ) {
440 $google_fonts_domain = '//fonts.googleapis.com/css';
441 $enqueued_google_fonts = array();
442 $families = array();
443 $subsets = array();
444 $font_args = array();
445 $font_display = '';
446
447 // Collect all enqueued google fonts
448 foreach ( $wp_styles->queue as $key => $handle ) {
449 if ( ! isset( $wp_styles->registered[ $handle ] ) ) {
450 continue;
451 }
452
453 $style_src = $wp_styles->registered[ $handle ]->src;
454
455 if ( false !== strpos( $style_src, 'fonts.googleapis.com/css' ) ) {
456 $url = wp_parse_url( $style_src );
457
458 if ( is_string( $url['query'] ) ) {
459 parse_str( $url['query'], $parsed_url );
460
461 if ( isset( $parsed_url['family'] ) ) {
462 // Collect all subsets
463 if ( isset( $parsed_url['subset'] ) ) {
464 $subsets[] = rawurlencode( trim( $parsed_url['subset'] ) );
465 }
466
467 $font_families = explode( '|', $parsed_url['family'] );
468 foreach ( $font_families as $parsed_font ) {
469 $get_font = explode( ':', $parsed_font );
470
471 // Extract the font data
472 if ( isset( $get_font[0] ) && ! empty( $get_font[0] ) ) {
473 $family = $get_font[0];
474 $weights = isset( $get_font[1] ) && ! empty( $get_font[1] ) ? explode( ',', $get_font[1] ) : array();
475
476 // Combine weights if family has been enqueued
477 if ( isset( $enqueued_google_fonts[ $family ] ) && $weights !== $enqueued_google_fonts[ $family ]['weights'] ) {
478 $combined_weights = array_merge( $weights, $enqueued_google_fonts[ $family ]['weights'] );
479 $enqueued_google_fonts[ $family ]['weights'] = array_unique( $combined_weights );
480 } else {
481 $enqueued_google_fonts[ $family ] = array(
482 'handle' => $handle,
483 'family' => $family,
484 'weights' => $weights,
485 );
486
487 if ( isset( $parsed_url['display'] ) ) {
488 $font_display = $parsed_url['display'];
489 }
490
491 // Remove enqueued google font style, so we would only have one HTTP request.
492 wp_dequeue_style( $handle );
493 }
494 }
495 }
496 }
497 }
498 }
499 }
500
501 // Combine all queued fonts
502 if ( count( $enqueued_google_fonts ) > 0 ) {
503 foreach ( $enqueued_google_fonts as $family => $data ) {
504 // Collect all family and weights
505 if ( ! empty( $data['weights'] ) ) {
506 $families[] = $family . ':' . implode( ',', $data['weights'] );
507 } else {
508 $families[] = $family;
509 }
510 }
511
512 if ( ! empty( $families ) ) {
513 $font_args['family'] = implode( '|', $families );
514
515 if ( ! empty( $subsets ) ) {
516 $font_args['subset'] = implode( ',', $subsets );
517 }
518
519 /**
520 * Force font display: swap
521 *
522 * @since 2.2
523 */
524 if ( $this->settings['swap_google_fonts_display'] ) {
525 $font_display = 'swap';
526 }
527
528 /**
529 * Filters font display attribute of the google fonts.
530 *
531 * @hook powered_cache_fo_google_font_display
532 *
533 * @param {string} $font_display font display attribute.
534 *
535 * @return {boolean} New value.
536 * @since 2.0
537 */
538 $font_display = apply_filters( 'powered_cache_fo_google_font_display', $font_display );
539
540 if ( ! empty( $font_display ) ) {
541 $font_args['display'] = $font_display;
542 }
543
544 $src = esc_url_raw( add_query_arg( $font_args, $google_fonts_domain ) );
545
546 // Enqueue google fonts into one URL request
547 wp_enqueue_style( // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
548 'pc-google-fonts-' . md5( $src ),
549 $src,
550 array()
551 );
552
553 unset( $enqueued_google_fonts );
554 }
555 }
556 }
557 }
558
559 }
560