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

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