PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.1.2
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.1.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.1.2, at includes/classes/FileOptimizer.php

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