PluginProbe
Autoptimize / 2.5.1
Autoptimize v2.5.1
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeExtra.php

autoptimizeExtra.php in Autoptimize 2.5.1, at classes/autoptimizeExtra.php

448 lines 22.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles autoptimizeExtra frontend features + admin options page
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 class autoptimizeExtra
11 {
12 /**
13 * Options
14 *
15 * @var array
16 */
17 protected $options = array();
18
19 /**
20 * Creates an instance and calls run().
21 *
22 * @param array $options Optional. Allows overriding options without having to specify them via admin options page.
23 */
24 public function __construct( $options = array() )
25 {
26 if ( empty( $options ) ) {
27 $options = self::fetch_options();
28 }
29
30 $this->options = $options;
31 }
32
33 public function run()
34 {
35 if ( is_admin() ) {
36 add_action( 'admin_menu', array( $this, 'admin_menu' ) );
37 add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_extra_tab' ) );
38 } else {
39 $this->run_on_frontend();
40 }
41 }
42
43 public static function fetch_options()
44 {
45 $value = get_option( 'autoptimize_extra_settings' );
46 if ( empty( $value ) ) {
47 // Fallback to returning defaults when no stored option exists yet.
48 $value = autoptimizeConfig::get_ao_extra_default_options();
49 }
50
51 return $value;
52 }
53
54 public function disable_emojis()
55 {
56 // Removing all actions related to emojis!
57 remove_action( 'admin_print_styles', 'print_emoji_styles' );
58 remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
59 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
60 remove_action( 'wp_print_styles', 'print_emoji_styles' );
61 remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
62 remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
63 remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
64
65 // Removes TinyMCE emojis.
66 add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) );
67
68 // Removes emoji dns-preftech.
69 add_filter( 'wp_resource_hints', array( $this, 'filter_remove_emoji_dns_prefetch' ), 10, 2 );
70 }
71
72 public function filter_disable_emojis_tinymce( $plugins )
73 {
74 if ( is_array( $plugins ) ) {
75 return array_diff( $plugins, array( 'wpemoji' ) );
76 } else {
77 return array();
78 }
79 }
80
81 public function filter_remove_qs( $src )
82 {
83 if ( strpos( $src, '?ver=' ) ) {
84 $src = remove_query_arg( 'ver', $src );
85 }
86
87 return $src;
88 }
89
90 public function extra_async_js( $in )
91 {
92 $exclusions = array();
93 if ( ! empty( $in ) ) {
94 $exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' );
95 }
96
97 $settings = $this->options['autoptimize_extra_text_field_3'];
98 $async = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' );
99 $attr = apply_filters( 'autoptimize_filter_extra_async', 'async' );
100 foreach ( $async as $k => $v ) {
101 $async[ $k ] = $attr;
102 }
103
104 // Merge exclusions & asyncs in one array and return to AO API.
105 $merged = array_merge( $exclusions, $async );
106
107 return $merged;
108 }
109
110 protected function run_on_frontend()
111 {
112 $options = $this->options;
113
114 // Disable emojis if specified.
115 if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) {
116 $this->disable_emojis();
117 }
118
119 // Remove version query parameters.
120 if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) {
121 add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
122 add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
123 }
124
125 // Avoiding conflicts of interest when async-javascript plugin is active!
126 $async_js_plugin_active = autoptimizeUtils::is_plugin_active( 'async-javascript/async-javascript.php' );
127 if ( ! empty( $options['autoptimize_extra_text_field_3'] ) && ! $async_js_plugin_active ) {
128 add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 );
129 }
130
131 // Optimize google fonts!
132 if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) {
133 add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 );
134 add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 );
135 add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 );
136 }
137
138 // Preconnect!
139 if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) {
140 add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 );
141 }
142 }
143
144 public function filter_remove_emoji_dns_prefetch( $urls, $relation_type )
145 {
146 $emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
147
148 return $this->filter_remove_dns_prefetch( $urls, $relation_type, $emoji_svg_url );
149 }
150
151 public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type )
152 {
153 return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' );
154 }
155
156 public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove )
157 {
158 $url_to_remove = (string) $url_to_remove;
159
160 if ( ! empty( $url_to_remove ) && 'dns-prefetch' === $relation_type ) {
161 $cnt = 0;
162 foreach ( $urls as $url ) {
163 if ( false !== strpos( $url, $url_to_remove ) ) {
164 unset( $urls[ $cnt ] );
165 }
166 $cnt++;
167 }
168 }
169
170 return $urls;
171 }
172
173 public function filter_optimize_google_fonts( $in )
174 {
175 // Extract fonts, partly based on wp rocket's extraction code.
176 $markup = preg_replace( '/<!--(.*)-->/Uis', '', $in );
177 preg_match_all( '#<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches );
178
179 $fonts_collection = array();
180 if ( ! $matches[2] ) {
181 return $in;
182 }
183
184 // Store them in $fonts array.
185 $i = 0;
186 foreach ( $matches[2] as $font ) {
187 if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) {
188 // Get fonts name.
189 $font = str_replace( array( '%7C', '%7c' ), '|', $font );
190 $font = explode( 'family=', $font );
191 $font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array();
192 // Add font to $fonts[$i] but make sure not to pollute with an empty family!
193 $_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) );
194 if ( ! empty( $_thisfont ) ) {
195 $fonts_collection[ $i ]['fonts'] = $_thisfont;
196 // And add subset if any!
197 $subset = ( is_array( $font ) ) ? end( $font ) : '';
198 if ( false !== strpos( $subset, 'subset=' ) ) {
199 $subset = str_replace( array( '%2C', '%2c' ), ',', $subset );
200 $subset = explode( 'subset=', $subset );
201 $fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] );
202 }
203 }
204 // And remove Google Fonts.
205 $in = str_replace( $matches[0][ $i ], '', $in );
206 }
207 $i++;
208 }
209
210 $options = $this->options;
211 $fonts_markup = '';
212 if ( '2' === $options['autoptimize_extra_radio_field_4'] ) {
213 // Remove Google Fonts.
214 unset( $fonts_collection );
215 return $in;
216 } elseif ( '3' === $options['autoptimize_extra_radio_field_4'] || '5' === $options['autoptimize_extra_radio_field_4'] ) {
217 // Aggregate & link!
218 $fonts_string = '';
219 $subset_string = '';
220 foreach ( $fonts_collection as $font ) {
221 $fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' );
222 if ( ! empty( $font['subsets'] ) ) {
223 $subset_string .= ',' . trim( implode( ',', $font['subsets'] ), ',' );
224 }
225 }
226
227 if ( ! empty( $subset_string ) ) {
228 $subset_string = str_replace( ',', '%2C', ltrim( $subset_string, ',' ) );
229 $fonts_string = $fonts_string . '&#038;subset=' . $subset_string;
230 }
231
232 $fonts_string = apply_filters( 'autoptimize_filter_extra_gfont_fontstring', str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) ) );
233
234 if ( ! empty( $fonts_string ) ) {
235 if ( '5' === $options['autoptimize_extra_radio_field_4'] ) {
236 $rel_string = 'rel="preload" as="style" onload="' . autoptimizeConfig::get_ao_css_preload_onload() . '"';
237 } else {
238 $rel_string = 'rel="stylesheet"';
239 }
240 $fonts_markup = '<link ' . $rel_string . ' id="ao_optimized_gfonts" href="https://fonts.googleapis.com/css?family=' . $fonts_string . '" />';
241 }
242 } elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) {
243 // Aggregate & load async (webfont.js impl.)!
244 $fonts_array = array();
245 foreach ( $fonts_collection as $_fonts ) {
246 if ( ! empty( $_fonts['subsets'] ) ) {
247 $_subset = implode( ',', $_fonts['subsets'] );
248 foreach ( $_fonts['fonts'] as $key => $_one_font ) {
249 $_one_font = $_one_font . ':' . $_subset;
250 $_fonts['fonts'][ $key ] = $_one_font;
251 }
252 }
253 $fonts_array = array_merge( $fonts_array, $_fonts['fonts'] );
254 }
255
256 $fonts_array = array_map( 'urldecode', $fonts_array );
257 $fonts_markup = '<script data-cfasync="false" id="ao_optimized_gfonts_config" type="text/javascript">WebFontConfig={google:{families:' . wp_json_encode( $fonts_array ) . ' },classes:false, events:false, timeout:1500};</script>';
258 $fonts_library_markup = '<script data-cfasync="false" id="ao_optimized_gfonts_webfontloader" type="text/javascript">(function() {var wf = document.createElement(\'script\');wf.src=\'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js\';wf.type=\'text/javascript\';wf.async=\'true\';var s=document.getElementsByTagName(\'script\')[0];s.parentNode.insertBefore(wf, s);})();</script>';
259 $in = substr_replace( $in, $fonts_library_markup . '</head>', strpos( $in, '</head>' ), strlen( '</head>' ) );
260 }
261
262 // Replace back in markup.
263 $inject_point = apply_filters( 'autoptimize_filter_extra_gfont_injectpoint', '<link' );
264 $out = substr_replace( $in, $fonts_markup . $inject_point, strpos( $in, $inject_point ), strlen( $inject_point ) );
265 unset( $fonts_collection );
266
267 // and insert preload polyfill if "link preload" and if the polyfill isn't there yet (courtesy of inline&defer).
268 $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
269 if ( '5' === $options['autoptimize_extra_radio_field_4'] && strpos( $out, $preload_polyfill ) === false ) {
270 $out = str_replace( '</body>', $preload_polyfill . '</body>', $out );
271 }
272 return $out;
273 }
274
275 public function filter_preconnect( $hints, $relation_type )
276 {
277 $options = $this->options;
278
279 // Get settings and store in array.
280 $preconns = array_filter( array_map( 'trim', explode( ',', $options['autoptimize_extra_text_field_2'] ) ) );
281 $preconns = apply_filters( 'autoptimize_extra_filter_tobepreconn', $preconns );
282
283 // Walk array, extract domain and add to new array with crossorigin attribute.
284 foreach ( $preconns as $preconn ) {
285 $parsed = parse_url( $preconn );
286 if ( is_array( $parsed ) && empty( $parsed['scheme'] ) ) {
287 $domain = '//' . $parsed['host'];
288 } elseif ( is_array( $parsed ) ) {
289 $domain = $parsed['scheme'] . '://' . $parsed['host'];
290 }
291
292 if ( ! empty( $domain ) ) {
293 $hint = array( 'href' => $domain );
294 // Fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set
295 // so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed.
296 $crossorigins = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array( 'https://fonts.gstatic.com' ) );
297 if ( in_array( $domain, $crossorigins ) ) {
298 $hint['crossorigin'] = 'anonymous';
299 }
300 $new_hints[] = $hint;
301 }
302 }
303
304 // Merge in WP's preconnect hints.
305 if ( 'preconnect' === $relation_type && ! empty( $new_hints ) ) {
306 $hints = array_merge( $hints, $new_hints );
307 }
308
309 return $hints;
310 }
311
312 public function filter_preconnect_google_fonts( $in )
313 {
314 if ( '2' !== $this->options['autoptimize_extra_radio_field_4'] ) {
315 // Preconnect to fonts.gstatic.com unless we remove gfonts.
316 $in[] = 'https://fonts.gstatic.com';
317 }
318
319 if ( '4' === $this->options['autoptimize_extra_radio_field_4'] ) {
320 // Preconnect even more hosts for webfont.js!
321 $in[] = 'https://ajax.googleapis.com';
322 $in[] = 'https://fonts.googleapis.com';
323 }
324
325 return $in;
326 }
327
328 public function admin_menu()
329 {
330 add_submenu_page(
331 null,
332 'autoptimize_extra',
333 'autoptimize_extra',
334 'manage_options',
335 'autoptimize_extra',
336 array( $this, 'options_page' )
337 );
338 register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' );
339 }
340
341 public function add_extra_tab( $in )
342 {
343 $in = array_merge( $in, array( 'autoptimize_extra' => __( 'Extra', 'autoptimize' ) ) );
344
345 return $in;
346 }
347
348 public function options_page()
349 {
350 // Working with actual option values from the database here.
351 // That way any saves are still processed as expected, but we can still
352 // override behavior by using `new autoptimizeExtra($custom_options)` and not have that custom
353 // behavior being persisted in the DB even if save is done here.
354 $options = $this->fetch_options();
355 $gfonts = $options['autoptimize_extra_radio_field_4'];
356 ?>
357 <style>
358 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
359 #ao_settings_form .form-table th {font-weight: normal;}
360 #autoptimize_extra_descr{font-size: 120%;}
361 </style>
362 <div class="wrap">
363 <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
364 <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
365 <?php if ( 'on' !== get_option( 'autoptimize_js' ) && 'on' !== get_option( 'autoptimize_css' ) && 'on' !== get_option( 'autoptimize_html' ) && ! autoptimizeImages::imgopt_active() ) { ?>
366 <div class="notice-warning notice"><p>
367 <?php _e( 'Most of below Extra optimizations require at least one of HTML, JS, CSS or Image autoptimizations being active.', 'autoptimize' ); ?>
368 </p></div>
369 <?php } ?>
370
371 <form id='ao_settings_form' action='options.php' method='post'>
372 <?php settings_fields( 'autoptimize_extra_settings' ); ?>
373 <h2><?php _e( 'Extra Auto-Optimizations', 'autoptimize' ); ?></h2>
374 <span id='autoptimize_extra_descr'><?php _e( 'The following settings can improve your site\'s performance even more.', 'autoptimize' ); ?></span>
375 <table class="form-table">
376 <tr>
377 <th scope="row"><?php _e( 'Google Fonts', 'autoptimize' ); ?></th>
378 <td>
379 <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="1" <?php if ( ! in_array( $gfonts, array( 2, 3, 4, 5 ) ) ) { echo 'checked'; } ?> ><?php _e( 'Leave as is', 'autoptimize' ); ?><br/>
380 <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="2" <?php checked( 2, $gfonts, true ); ?> ><?php _e( 'Remove Google Fonts', 'autoptimize' ); ?><br/>
381 <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="3" <?php checked( 3, $gfonts, true ); ?> ><?php _e( 'Combine and link in head (fonts load fast but are render-blocking)', 'autoptimize' ); ?><br/>
382 <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="5" <?php checked( 5, $gfonts, true ); ?> ><?php _e( 'Combine and preload in head (fonts load late, but are not render-blocking)', 'autoptimize' ); ?><br/>
383 <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="4" <?php checked( 4, $gfonts, true ); ?> ><?php _e( 'Combine and load fonts asynchronously with <a href="https://github.com/typekit/webfontloader#readme" target="_blank">webfont.js</a>', 'autoptimize' ); ?><br/>
384 </td>
385 </tr>
386 <tr>
387 <th scope="row"><?php _e( 'Remove emojis', 'autoptimize' ); ?></th>
388 <td>
389 <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) && '1' === $options['autoptimize_extra_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Removes WordPress\' core emojis\' inline CSS, inline JavaScript, and an otherwise un-autoptimized JavaScript file.', 'autoptimize' ); ?></label>
390 </td>
391 </tr>
392 <tr>
393 <th scope="row"><?php _e( 'Remove query strings from static resources', 'autoptimize' ); ?></th>
394 <td>
395 <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_0]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) && '1' === $options['autoptimize_extra_checkbox_field_0'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Removing query strings (or more specifically the <code>ver</code> parameter) will not improve load time, but might improve performance scores.', 'autoptimize' ); ?></label>
396 </td>
397 </tr>
398 <tr>
399 <th scope="row"><?php _e( 'Preconnect to 3rd party domains <em>(advanced users)</em>', 'autoptimize' ); ?></th>
400 <td>
401 <label><input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_2]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_2', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_2'] ); } ?>'><br /><?php _e( 'Add 3rd party domains you want the browser to <a href="https://www.keycdn.com/support/preconnect/#primary" target="_blank">preconnect</a> to, separated by comma\'s. Make sure to include the correct protocol (HTTP or HTTPS).', 'autoptimize' ); ?></label>
402 </td>
403 </tr>
404 <tr>
405 <th scope="row"><?php _e( 'Async Javascript-files <em>(advanced users)</em>', 'autoptimize' ); ?></th>
406 <td>
407 <?php
408 if ( autoptimizeUtils::is_plugin_active( 'async-javascript/async-javascript.php' ) ) {
409 // translators: link points Async Javascript settings page.
410 printf( __( 'You have "Async JavaScript" installed, %1$sconfiguration of async javascript is best done there%2$s.', 'autoptimize' ), '<a href="' . 'options-general.php?page=async-javascript' . '">', '</a>' );
411 } else {
412 ?>
413 <input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_3]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_3', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_3'] ); } ?>'>
414 <br />
415 <?php
416 _e( 'Comma-separated list of local or 3rd party JS-files that should loaded with the <code>async</code> flag. JS-files from your own site will be automatically excluded if added here. ', 'autoptimize' );
417 // translators: %s will be replaced by a link to the "async javascript" plugin.
418 echo sprintf( __( 'Configuration of async javascript is easier and more flexible using the %s plugin.', 'autoptimize' ), '"<a href="https://wordpress.org/plugins/async-javascript" target="_blank">Async Javascript</a>"' );
419 $asj_install_url = network_admin_url() . 'plugin-install.php?s=async+javascript&tab=search&type=term';
420 echo sprintf( ' <a href="' . $asj_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
421 }
422 ?>
423 </td>
424 </tr>
425 <tr>
426 <th scope="row"><?php _e( 'Optimize YouTube videos', 'autoptimize' ); ?></th>
427 <td>
428 <?php
429 if ( autoptimizeUtils::is_plugin_active( 'wp-youtube-lyte/wp-youtube-lyte.php' ) ) {
430 _e( 'Great, you have WP YouTube Lyte installed.', 'autoptimize' );
431 $lyte_config_url = 'options-general.php?page=lyte_settings_page';
432 echo sprintf( ' <a href="' . $lyte_config_url . '">%s</a>', __( 'Click here to configure it.', 'autoptimize' ) );
433 } else {
434 // translators: %s will be replaced by a link to "wp youtube lyte" plugin.
435 echo sprintf( __( '%s allows you to “lazy load” your videos, by inserting responsive “Lite YouTube Embeds". ', 'autoptimize' ), '<a href="https://wordpress.org/plugins/wp-youtube-lyte" target="_blank">WP YouTube Lyte</a>' );
436 $lyte_install_url = network_admin_url() . 'plugin-install.php?s=lyte&tab=search&type=term';
437 echo sprintf( ' <a href="' . $lyte_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
438 }
439 ?>
440 </td>
441 </tr>
442 </table>
443 <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
444 </form>
445 <?php
446 }
447 }
448