PluginProbe
Autoptimize / 2.4.2
Autoptimize v2.4.2
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.4.2, at classes/autoptimizeExtra.php

1,022 lines 49.3 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 = $this->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 protected 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 // get service availability.
52 $value['availabilities'] = get_option( 'autoptimize_service_availablity' );
53
54 if ( empty( $value['availabilities'] ) ) {
55 $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
56 }
57
58 return $value;
59 }
60
61 public function disable_emojis()
62 {
63 // Removing all actions related to emojis!
64 remove_action( 'admin_print_styles', 'print_emoji_styles' );
65 remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
66 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
67 remove_action( 'wp_print_styles', 'print_emoji_styles' );
68 remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
69 remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
70 remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
71
72 // Removes TinyMCE emojis.
73 add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) );
74
75 // Removes emoji dns-preftech.
76 add_filter( 'wp_resource_hints', array( $this, 'filter_remove_emoji_dns_prefetch' ), 10, 2 );
77 }
78
79 public function filter_disable_emojis_tinymce( $plugins )
80 {
81 if ( is_array( $plugins ) ) {
82 return array_diff( $plugins, array( 'wpemoji' ) );
83 } else {
84 return array();
85 }
86 }
87
88 public function filter_remove_qs( $src ) {
89 if ( strpos( $src, '?ver=' ) ) {
90 $src = remove_query_arg( 'ver', $src );
91 }
92
93 return $src;
94 }
95
96 public function extra_async_js( $in )
97 {
98 $exclusions = array();
99 if ( ! empty( $in ) ) {
100 $exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' );
101 }
102
103 $settings = $this->options['autoptimize_extra_text_field_3'];
104 $async = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' );
105 $attr = apply_filters( 'autoptimize_filter_extra_async', 'async' );
106 foreach ( $async as $k => $v ) {
107 $async[ $k ] = $attr;
108 }
109
110 // Merge exclusions & asyncs in one array and return to AO API.
111 $merged = array_merge( $exclusions, $async );
112
113 return $merged;
114 }
115
116 protected function run_on_frontend()
117 {
118 $options = $this->options;
119
120 // Disable emojis if specified.
121 if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) {
122 $this->disable_emojis();
123 }
124
125 // Remove version query parameters.
126 if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) {
127 add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
128 add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
129 }
130
131 // Making sure is_plugin_active() exists when we need it.
132 if ( ! function_exists( 'is_plugin_active' ) ) {
133 require_once ABSPATH . 'wp-admin/includes/plugin.php';
134 }
135 // Avoiding conflicts of interest when async-javascript plugin is active!
136 $async_js_plugin_active = false;
137 if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'async-javascript/async-javascript.php' ) ) {
138 $async_js_plugin_active = true;
139 }
140 if ( ! empty( $options['autoptimize_extra_text_field_3'] ) && ! $async_js_plugin_active ) {
141 add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 );
142 }
143
144 // Optimize google fonts!
145 if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) {
146 add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 );
147 add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 );
148 add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 );
149 }
150
151 // Preconnect!
152 if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) {
153 add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 );
154 }
155
156 // Optimize Images kicks in if;
157 // * the option is activated by user.
158 // * imgopt user stats does not have status -2.
159 // * imgopt status is not "down".
160 // * imgopt status is not "launch" or imgopt_launch_ok() returns be true.
161 $_do_cdn = true;
162 $_userstatus = $this->get_imgopt_provider_userstatus();
163 if ( -2 == $_userstatus['Status'] ) {
164 $_do_cdn = false;
165 }
166
167 if ( ! empty( $options['autoptimize_extra_checkbox_field_5'] ) && $_do_cdn && 'down' !== $options['availabilities']['extra_imgopt']['status'] && ( 'launch' !== $options['availabilities']['extra_imgopt']['status'] || $this->imgopt_launch_ok() ) ) {
168 if ( apply_filters( 'autoptimize_filter_extra_imgopt_do', true ) ) {
169 add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_images' ), 10, 1 );
170 $_imgopt_active = true;
171 }
172 if ( apply_filters( 'autoptimize_filter_extra_imgopt_do_css', true ) ) {
173 add_filter( 'autoptimize_filter_base_replace_cdn', array( $this, 'filter_optimize_css_images' ), 10, 1 );
174 $_imgopt_active = true;
175 }
176 if ( $_imgopt_active ) {
177 add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_imgopt_url' ), 10, 1 );
178 }
179 }
180 }
181
182 public function filter_remove_emoji_dns_prefetch( $urls, $relation_type )
183 {
184 $emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
185
186 return $this->filter_remove_dns_prefetch( $urls, $relation_type, $emoji_svg_url );
187 }
188
189 public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type )
190 {
191 return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' );
192 }
193
194 public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove )
195 {
196 if ( 'dns-prefetch' === $relation_type ) {
197 $cnt = 0;
198 foreach ( $urls as $url ) {
199 if ( false !== strpos( $url, $url_to_remove ) ) {
200 unset( $urls[ $cnt ] );
201 }
202 $cnt++;
203 }
204 }
205
206 return $urls;
207 }
208
209 public function filter_optimize_google_fonts( $in )
210 {
211 // Extract fonts, partly based on wp rocket's extraction code.
212 $markup = preg_replace( '/<!--(.*)-->/Uis', '', $in );
213 preg_match_all( '#<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches );
214
215 $fonts_collection = array();
216 if ( ! $matches[2] ) {
217 return $in;
218 }
219
220 // Store them in $fonts array.
221 $i = 0;
222 foreach ( $matches[2] as $font ) {
223 if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) {
224 // Get fonts name.
225 $font = str_replace( array( '%7C', '%7c' ), '|', $font );
226 $font = explode( 'family=', $font );
227 $font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array();
228 // Add font to $fonts[$i] but make sure not to pollute with an empty family!
229 $_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) );
230 if ( ! empty( $_thisfont ) ) {
231 $fonts_collection[ $i ]['fonts'] = $_thisfont;
232 // And add subset if any!
233 $subset = ( is_array( $font ) ) ? end( $font ) : '';
234 if ( false !== strpos( $subset, 'subset=' ) ) {
235 $subset = str_replace( array( '%2C', '%2c' ), ',', $subset );
236 $subset = explode( 'subset=', $subset );
237 $fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] );
238 }
239 }
240 // And remove Google Fonts.
241 $in = str_replace( $matches[0][ $i ], '', $in );
242 }
243 $i++;
244 }
245
246 $options = $this->options;
247 $fonts_markup = '';
248 if ( '2' === $options['autoptimize_extra_radio_field_4'] ) {
249 // Remove Google Fonts.
250 unset( $fonts_collection );
251 return $in;
252 } elseif ( '3' === $options['autoptimize_extra_radio_field_4'] || '5' === $options['autoptimize_extra_radio_field_4'] ) {
253 // Aggregate & link!
254 $fonts_string = '';
255 $subset_string = '';
256 foreach ( $fonts_collection as $font ) {
257 $fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' );
258 if ( ! empty( $font['subsets'] ) ) {
259 $subset_string .= ',' . trim( implode( ',', $font['subsets'] ), ',' );
260 }
261 }
262
263 if ( ! empty( $subset_string ) ) {
264 $subset_string = str_replace( ',', '%2C', ltrim( $subset_string, ',' ) );
265 $fonts_string = $fonts_string . '&#038;subset=' . $subset_string;
266 }
267
268 $fonts_string = str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) );
269
270 if ( ! empty( $fonts_string ) ) {
271 if ( '5' === $options['autoptimize_extra_radio_field_4'] ) {
272 $rel_string = 'rel="preload" as="style" onload="' . autoptimizeConfig::get_ao_css_preload_onload() . '"';
273 } else {
274 $rel_string = 'rel="stylesheet"';
275 }
276 $fonts_markup = '<link ' . $rel_string . ' id="ao_optimized_gfonts" href="https://fonts.googleapis.com/css?family=' . $fonts_string . '" />';
277 }
278 } elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) {
279 // Aggregate & load async (webfont.js impl.)!
280 $fonts_array = array();
281 foreach ( $fonts_collection as $_fonts ) {
282 if ( ! empty( $_fonts['subsets'] ) ) {
283 $_subset = implode( ',', $_fonts['subsets'] );
284 foreach ( $_fonts['fonts'] as $key => $_one_font ) {
285 $_one_font = $_one_font . ':' . $_subset;
286 $_fonts['fonts'][ $key ] = $_one_font;
287 }
288 }
289 $fonts_array = array_merge( $fonts_array, $_fonts['fonts'] );
290 }
291
292 $fonts_markup = '<script data-cfasync="false" id="ao_optimized_gfonts" type="text/javascript">WebFontConfig={google:{families:[\'';
293 foreach ( $fonts_array as $fnt ) {
294 $fonts_markup .= $fnt . "','";
295 }
296 $fonts_markup = trim( trim( $fonts_markup, "'" ), ',' );
297 $fonts_markup .= '] },classes:false, events:false, timeout:1500};(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>';
298 }
299
300 // Replace back in markup.
301 $out = substr_replace( $in, $fonts_markup . '<link', strpos( $in, '<link' ), strlen( '<link' ) );
302 unset( $fonts_collection );
303
304 // and insert preload polyfill if "link preload" and if the polyfill isn't there yet (courtesy of inline&defer).
305 $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
306 if ( '5' === $options['autoptimize_extra_radio_field_4'] && strpos( $out, $preload_polyfill ) === false ) {
307 $out = str_replace( '</body>', $preload_polyfill . '</body>', $out );
308 }
309 return $out;
310 }
311
312 public function filter_preconnect( $hints, $relation_type )
313 {
314 $options = $this->options;
315
316 // Get settings and store in array.
317 $preconns = array_filter( array_map( 'trim', explode( ',', $options['autoptimize_extra_text_field_2'] ) ) );
318 $preconns = apply_filters( 'autoptimize_extra_filter_tobepreconn', $preconns );
319
320 // Walk array, extract domain and add to new array with crossorigin attribute.
321 foreach ( $preconns as $preconn ) {
322 $parsed = parse_url( $preconn );
323
324 if ( is_array( $parsed ) && empty( $parsed['scheme'] ) ) {
325 $domain = '//' . $parsed['host'];
326 } elseif ( is_array( $parsed ) ) {
327 $domain = $parsed['scheme'] . '://' . $parsed['host'];
328 }
329
330 if ( ! empty( $domain ) ) {
331 $hint = array( 'href' => $domain );
332 // Fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set
333 // so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed.
334 $crossorigins = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array( 'https://fonts.gstatic.com' ) );
335 if ( in_array( $domain, $crossorigins ) ) {
336 $hint['crossorigin'] = 'anonymous';
337 }
338 $new_hints[] = $hint;
339 }
340 }
341
342 // Merge in WP's preconnect hints.
343 if ( 'preconnect' === $relation_type && ! empty( $new_hints ) ) {
344 $hints = array_merge( $hints, $new_hints );
345 }
346
347 return $hints;
348 }
349
350 public function filter_preconnect_google_fonts( $in )
351 {
352 if ( '2' !== $this->options['autoptimize_extra_radio_field_4'] ) {
353 // Preconnect to fonts.gstatic.com unless we remove gfonts.
354 $in[] = 'https://fonts.gstatic.com';
355 }
356
357 if ( '4' === $this->options['autoptimize_extra_radio_field_4'] ) {
358 // Preconnect even more hosts for webfont.js!
359 $in[] = 'https://ajax.googleapis.com';
360 $in[] = 'https://fonts.googleapis.com';
361 }
362
363 return $in;
364 }
365
366 public function filter_optimize_images( $in )
367 {
368 /*
369 * potential future functional improvements:
370 *
371 * picture element.
372 * filter for critical CSS.
373 */
374
375 $imgopt_base_url = $this->get_imgopt_base_url();
376 $to_replace = array();
377
378 // extract img tags.
379 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
380 foreach ( $matches[0] as $tag ) {
381 $orig_tag = $tag;
382 $imgopt_w = '';
383 $imgopt_h = '';
384
385 // first do (data-)srcsets.
386 if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
387 foreach ( $allsrcsets as $srcset ) {
388 $srcset = $srcset[2];
389 $srcsets = explode( ',', $srcset );
390 foreach ( $srcsets as $indiv_srcset ) {
391 $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
392 if ( $indiv_srcset_parts[1] && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
393 $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
394 }
395 if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
396 $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
397 $tag = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
398 $to_replace[ $orig_tag ] = $tag;
399 }
400 }
401 }
402 }
403
404 // proceed with img src.
405 // first reset and then get width and height and add to $imgopt_size.
406 $imgopt_w = '';
407 $imgopt_h = '';
408 if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $width ) ) {
409 $imgopt_w = $width[2];
410 }
411 if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $height ) ) {
412 $imgopt_h = $height[2];
413 }
414
415 // then start replacing images src.
416 if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
417 foreach ( $urls as $url ) {
418 $full_src_orig = $url[0];
419 $url = $url[1];
420 if ( $this->can_optimize_image( $url ) ) {
421 $imgopt_url = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
422 $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
423 $tag = str_replace( $full_src_orig, $full_imgopt_src, $tag );
424 $to_replace[ $orig_tag ] = $tag;
425 }
426 }
427 }
428 }
429 }
430 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
431
432 // img thumbnails in e.g. woocommerce.
433 if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_extra_imgopt_datathumbs', true ) ) {
434 $out = preg_replace_callback(
435 '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
436 array( $this, 'replace_data_thumbs' ),
437 $out
438 );
439 }
440
441 return $out;
442 }
443
444 public function filter_optimize_css_images( $in )
445 {
446 $imgopt_base_url = $this->get_imgopt_base_url();
447 $in = $this->normalize_img_urls( $in );
448
449 if ( $this->can_optimize_image( $in ) ) {
450 return $this->build_imgopt_url( $in, '', '' );
451 } else {
452 return $in;
453 }
454 }
455
456 private function get_imgopt_base_url()
457 {
458 static $imgopt_base_url = null;
459
460 if ( is_null( $imgopt_base_url ) ) {
461 $imgopt_host = $this->get_imgopt_host();
462 $quality = $this->get_img_quality_string();
463 $ret_val = apply_filters( 'autoptimize_filter_extra_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
464 $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
465 $imgopt_base_url = apply_filters( 'autoptimize_filter_extra_imgopt_base_url', $imgopt_base_url );
466 }
467
468 return $imgopt_base_url;
469 }
470
471 private function can_optimize_image( $url )
472 {
473 static $cdn_url = null;
474 static $nopti_images = null;
475
476 if ( is_null( $cdn_url ) ) {
477 $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', get_option( 'autoptimize_cdn_url', '' ) );
478 }
479
480 if ( is_null( $nopti_images ) ) {
481 $nopti_images = apply_filters( 'autoptimize_filter_extra_imgopt_noptimize', '' );
482 }
483
484 $imgopt_base_url = $this->get_imgopt_base_url();
485 $site_host = AUTOPTIMIZE_SITE_DOMAIN;
486 $url_parsed = parse_url( $url );
487
488 if ( $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
489 return false;
490 } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && $url_parsed['host'] !== $site_host ) {
491 return false;
492 } elseif ( strpos( $url, '.php' ) !== false ) {
493 return false;
494 } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
495 // fixme: better check against end of string.
496 return false;
497 } elseif ( ! empty( $nopti_images ) ) {
498 $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
499 foreach ( $nopti_images_array as $nopti_image ) {
500 if ( strpos( $url, $nopti_image ) !== false ) {
501 return false;
502 }
503 }
504 }
505 return true;
506 }
507
508 private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
509 {
510 // sanitize width and height.
511 if ( strpos( $width, '%' ) !== false ) {
512 $width = 0;
513 }
514 if ( strpos( $height, '%' ) !== false ) {
515 $height = 0;
516 }
517 $width = (int) $width;
518 $height = (int) $height;
519
520 $filtered_url = apply_filters( 'autoptimize_filter_extra_imgopt_build_url', $orig_url, $width, $height );
521
522 if ( $filtered_url !== $orig_url ) {
523 return $filtered_url;
524 }
525
526 $orig_url = $this->normalize_img_urls( $orig_url );
527 $imgopt_base_url = $this->get_imgopt_base_url();
528 $imgopt_size = '';
529
530 if ( $width && 0 !== $width ) {
531 $imgopt_size = ',w_' . $width;
532 }
533
534 if ( $height && 0 !== $height ) {
535 $imgopt_size .= ',h_' . $height;
536 }
537
538 $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
539
540 return $url;
541 }
542
543 public function replace_data_thumbs( $matches )
544 {
545 if ( $this->can_optimize_image( $matches[1] ) ) {
546 return str_replace( $matches[1], $this->build_imgopt_url( $matches[1], 150, 150 ), $matches[0] );
547 } else {
548 return $matches[0];
549 }
550 }
551
552 public function filter_preconnect_imgopt_url( $in )
553 {
554 $imgopt_url_array = parse_url( $this->get_imgopt_base_url() );
555 $in[] = $imgopt_url_array['scheme'] . '://' . $imgopt_url_array['host'];
556
557 return $in;
558 }
559
560 private function normalize_img_urls( $in )
561 {
562 static $cdn_domain = null;
563 if ( is_null( $cdn_domain ) ) {
564 $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', get_option( 'autoptimize_cdn_url', '' ) );
565 if ( ! empty( $cdn_url ) ) {
566 $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
567 } else {
568 $cdn_domain = '';
569 }
570 }
571
572 $parsed_site_url = parse_url( site_url() );
573
574 if ( strpos( $in, 'http' ) !== 0 && strpos( $in, '//' ) === 0 ) {
575 $in = $parsed_site_url['scheme'] . ':' . $in;
576 } elseif ( strpos( $in, '/' ) === 0 ) {
577 $in = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'] . $in;
578 } elseif ( ! empty( $cdn_domain ) && strpos( $in, $cdn_domain ) !== 0 ) {
579 $in = str_replace( $cdn_domain, $parsed_site_url['host'], $in );
580 }
581
582 return apply_filters( 'autoptimize_filter_extra_imgopt_normalized_url', $in );
583 }
584
585 private function get_img_quality_array()
586 {
587 static $img_quality_array = null;
588
589 if ( is_null( $img_quality_array ) ) {
590 $img_quality_array = array(
591 '1' => 'lossy',
592 '2' => 'glossy',
593 '3' => 'lossless',
594 );
595 $img_quality_array = apply_filters( 'autoptimize_filter_extra_imgopt_quality_array', $img_quality_array );
596 }
597
598 return $img_quality_array;
599 }
600
601 private function get_img_quality_setting()
602 {
603 static $_img_q = null;
604
605 if ( is_null( $_img_q ) ) {
606 if ( is_array( $this->options ) && array_key_exists( 'autoptimize_extra_select_field_6', $this->options ) ) {
607 $_setting = $this->options['autoptimize_extra_select_field_6'];
608 }
609
610 if ( ! $_setting || empty( $_setting ) || ( '1' !== $_setting && '3' !== $_setting ) ) {
611 // default image opt. value is 2 ("glossy").
612 $_img_q = '2';
613 } else {
614 $_img_q = $_setting;
615 }
616 }
617
618 return $_img_q;
619 }
620
621 private function get_img_quality_string()
622 {
623 static $_img_q_string = null;
624
625 if ( is_null( $_img_q_string ) ) {
626 $_quality_array = $this->get_img_quality_array();
627 $_setting = $this->get_img_quality_setting();
628 $_img_q_string = apply_filters( 'autoptimize_filter_extra_imgopt_quality', 'q_' . $_quality_array[ $_setting ] );
629 }
630
631 return $_img_q_string;
632 }
633
634 public static function get_img_provider_stats()
635 {
636 // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
637 $self = new self();
638 return $self->query_img_provider_stats();
639 }
640
641 public function query_img_provider_stats()
642 {
643 if ( ! empty( $this->options['autoptimize_extra_checkbox_field_5'] ) ) {
644 $_img_provider_stat_url = '';
645 $_img_provider_endpoint = $this->get_imgopt_host() . 'read-domain/';
646 $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
647
648 // make sure parse_url result makes sense, keeping $_img_provider_stat_url empty if not.
649 if ( $_site_host && ! empty( $_site_host ) ) {
650 $_img_provider_stat_url = $_img_provider_endpoint . $_site_host;
651 }
652
653 $_img_provider_stat_url = apply_filters( 'autoptimize_filter_extra_imgopt_stat_url', $_img_provider_stat_url );
654
655 // only do the remote call if $_img_provider_stat_url is not empty to make sure no parse_url weirdness results in useless calls.
656 if ( ! empty( $_img_provider_stat_url ) ) {
657 $_img_stat_resp = wp_remote_get( $_img_provider_stat_url );
658 if ( ! is_wp_error( $_img_stat_resp ) ) {
659 if ( '200' == wp_remote_retrieve_response_code( $_img_stat_resp ) ) {
660 $_img_provider_stat = json_decode( wp_remote_retrieve_body( $_img_stat_resp ), true );
661 $_img_provider_stat['timestamp'] = time();
662 update_option( 'autoptimize_imgopt_provider_stat', $_img_provider_stat );
663 }
664 }
665 }
666 }
667 }
668
669 public function imgopt_launch_ok()
670 {
671 static $launch_status = null;
672
673 if ( is_null( $launch_status ) ) {
674 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
675 $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
676 $has_launched = get_option( 'autoptimize_imgopt_launched', '' );
677 if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
678 $launch_status = true;
679 if ( ! $has_launched ) {
680 update_option( 'autoptimize_imgopt_launched', 'on' );
681 }
682 } else {
683 $launch_status = false;
684 }
685 }
686
687 return $launch_status;
688 }
689
690 public static function imgopt_launch_ok_wrapper()
691 {
692 // needed for "plug" notice in autoptimizeMain.php.
693 $self = new self();
694 return $self->imgopt_launch_ok();
695 }
696
697 public function get_imgopt_host()
698 {
699 static $imgopt_host = null;
700
701 if ( is_null( $imgopt_host ) ) {
702 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
703 if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
704 $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
705 } else {
706 $imgopt_host = 'https://cdn.shortpixel.ai/';
707 }
708 }
709
710 return $imgopt_host;
711 }
712
713 public static function get_imgopt_host_wrapper()
714 {
715 // needed for CI tests.
716 $self = new self();
717 return $self->get_imgopt_host();
718 }
719
720 public function get_imgopt_status_notice() {
721 $_extra_options = $this->options;
722 if ( ! empty( $_extra_options ) && is_array( $_extra_options ) && array_key_exists( 'autoptimize_extra_checkbox_field_5', $_extra_options ) && ! empty( $_extra_options['autoptimize_extra_checkbox_field_5'] ) ) {
723 $_imgopt_notice = '';
724 $_stat = $this->get_imgopt_provider_userstatus();
725 $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
726 $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
727
728 if ( is_array( $_stat ) ) {
729 if ( 1 == $_stat['Status'] ) {
730 // translators: "add more credits" will appear in a "a href".
731 $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota is almost used, make sure you %1$sadd more credits%2$s to avoid slowing down your website.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
732 } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
733 // translators: "add more credits" will appear in a "a href".
734 $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota was used, %1$sadd more credits%2$s to keep fast serving optimized images on your site', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
735 $_imgopt_stats_refresh_url = add_query_arg( array(
736 'page' => 'autoptimize_extra',
737 'refreshImgProvStats' => '1',
738 ), admin_url( 'options-general.php' ) );
739 if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
740 $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( get_option( 'time_format' ), $_stat['timestamp'] );
741 } else {
742 $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
743 }
744 $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
745 // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
746 $_imgopt_notice .= sprintf( __( 'click %1$shere to refresh%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
747 } else {
748 $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
749 // translators: "log in to check your account" will appear in a "a href".
750 $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
751 }
752 $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
753
754 return array(
755 'status' => $_stat['Status'],
756 'notice' => $_imgopt_notice,
757 );
758 }
759 }
760 return false;
761 }
762
763 public static function get_imgopt_status_notice_wrapper() {
764 // needed for notice being shown in autoptimizeCacheChecker.php.
765 $self = new self();
766 return $self->get_imgopt_status_notice();
767 }
768
769 public function get_imgopt_provider_userstatus() {
770 static $_provider_userstatus = null;
771
772 if ( is_null( $_provider_userstatus ) ) {
773 $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
774 if ( is_array( $_stat ) ) {
775 if ( array_key_exists( 'Status', $_stat ) ) {
776 $_provider_userstatus['Status'] = $_stat['Status'];
777 } else {
778 // if no stats then we assume all is well.
779 $_provider_userstatus['Status'] = 2;
780 }
781 if ( array_key_exists( 'timestamp', $_stat ) ) {
782 $_provider_userstatus['timestamp'] = $_stat['timestamp'];
783 } else {
784 // if no timestamp then we return "".
785 $_provider_userstatus['timestamp'] = '';
786 }
787 }
788 }
789
790 return $_provider_userstatus;
791 }
792
793 public function admin_menu()
794 {
795 add_submenu_page( null, 'autoptimize_extra', 'autoptimize_extra', 'manage_options', 'autoptimize_extra', array( $this, 'options_page' ) );
796 register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' );
797 }
798
799 public function add_extra_tab( $in )
800 {
801 $in = array_merge( $in, array( 'autoptimize_extra' => __( 'Extra', 'autoptimize' ) ) );
802
803 return $in;
804 }
805
806 public function options_page()
807 {
808 // Check querystring for "refreshCacheChecker" and call cachechecker if so.
809 if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
810 $this->query_img_provider_stats();
811 }
812
813 // Working with actual option values from the database here.
814 // That way any saves are still processed as expected, but we can still
815 // override behavior by using `new autoptimizeExtra($custom_options)` and not have that custom
816 // behavior being persisted in the DB even if save is done here.
817 $options = $this->fetch_options();
818 $gfonts = $options['autoptimize_extra_radio_field_4'];
819 $sp_url_suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
820 ?>
821 <style>
822 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
823 #ao_settings_form .form-table th {font-weight: normal;}
824 #autoptimize_extra_descr{font-size: 120%;}
825 </style>
826 <div class="wrap">
827 <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
828 <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
829 <?php
830 if ( 'on' !== get_option( 'autoptimize_js' ) && 'on' !== get_option( 'autoptimize_css' ) && 'on' !== get_option( 'autoptimize_html' ) ) {
831 ?>
832 <div class="notice-warning notice"><p>
833 <?php
834 _e( 'Most of below Extra optimizations require at least one of HTML, JS or CSS autoptimizations being active.', 'autoptimize' );
835 ?>
836 </p></div>
837 <?php
838 }
839
840 if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) {
841 ?>
842 <div class="notice-warning notice"><p>
843 <?php
844 // translators: "Autoptimize support forum" will appear in a "a href".
845 echo sprintf( __( 'The image optimization service is currently down, image optimization will be skipped until further notice. Check the %1$sAutoptimize support forum%2$s for more info.', 'autoptimize' ), '<a href="https://wordpress.org/support/plugin/autoptimize/" target="_blank">', '</a>' );
846 ?>
847 </p></div>
848 <?php
849 }
850
851 if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! $this->imgopt_launch_ok() ) {
852 ?>
853 <div class="notice-warning notice"><p>
854 <?php
855 _e( 'The image optimization service is launching, but not yet available for this domain, it should become available in the next couple of days.', 'autoptimize' );
856 ?>
857 </p></div>
858 <?php
859 }
860
861 ?>
862 <form id='ao_settings_form' action='options.php' method='post'>
863 <?php settings_fields( 'autoptimize_extra_settings' ); ?>
864 <h2><?php _e( 'Extra Auto-Optimizations', 'autoptimize' ); ?></h2>
865 <span id='autoptimize_extra_descr'><?php _e( 'The following settings can improve your site\'s performance even more.', 'autoptimize' ); ?></span>
866 <table class="form-table">
867 <tr>
868 <th scope="row"><?php _e( 'Google Fonts', 'autoptimize' ); ?></th>
869 <td>
870 <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/>
871 <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/>
872 <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/>
873 <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/>
874 <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/>
875 </td>
876 </tr>
877 <tr>
878 <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
879 <td>
880 <label><input id='autoptimize_imgopt_checkbox' type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_5]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_5'] ) && '1' === $options['autoptimize_extra_checkbox_field_5'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Optimize images on the fly and serve them from a CDN.', 'autoptimize' ); ?></label>
881 <?php
882 // show shortpixel status.
883 $_notice = $this->get_imgopt_status_notice();
884 if ( $_notice ) {
885 switch ( $_notice['status'] ) {
886 case 2:
887 $_notice_color = 'green';
888 break;
889 case 1:
890 $_notice_color = 'orange';
891 break;
892 case -1:
893 $_notice_color = 'red';
894 break;
895 case -2:
896 $_notice_color = 'red';
897 break;
898 default:
899 $_notice_color = 'green';
900 }
901 echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
902 } else {
903 // translators: link points to shortpixel.
904 $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love and improve your website\'s loading speed by having the images optimized on the fly by %1$sShortPixel%2$s and then cached and served fast from a CDN.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
905 if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
906 $upsell_msg_2 = __( 'For a limited time only, this service is offered free for all Autoptimize users, <b>don\'t miss the chance to test it</b> and see how much it could improve your site\'s speed.', 'autoptimize' );
907 } else {
908 // translators: link points to shortpixel.
909 $upsell_msg_2 = sprintf( __( '%1$sSign-up now%2$s to receive a 1 000 bonus + 50&#37; more image optimization credits regardless of the traffic used. More image optimizations can be purchased starting with $4.99.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
910 }
911 echo apply_filters( 'autoptimize_extra_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
912 }
913 // translators: link points to shortpixel FAQ.
914 $faqcopy = sprintf( __( '<strong>Questions</strong>? Have a look at the %1$sShortPixel FAQ%2$s!', 'autoptimize' ), '<strong><a href="https://shortpixel.helpscoutdocs.com/category/60-shortpixel-ai-cdn" target="_blank">', '</strong></a>' );
915 // translators: links points to shortpixel TOS & Privacy Policy.
916 $toscopy = sprintf( __( 'Usage of this feature is subject to Shortpixel\'s %1$sTerms of Use%2$s and %3$sPrivacy policy%4$s.', 'autoptimize' ), '<a href="https://shortpixel.com/tos' . $sp_url_suffix . '" target="_blank">', '</a>', '<a href="https://shortpixel.com/pp' . $sp_url_suffix . '" target="_blank">', '</a>' );
917 echo apply_filters( 'autoptimize_extra_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
918 ?>
919 </td>
920 </tr>
921 <tr id='autoptimize_imgopt_quality' <?php if ( ! array_key_exists( 'autoptimize_extra_checkbox_field_5', $options ) || ( ! empty( $options['autoptimize_extra_checkbox_field_5'] ) && '1' !== $options['autoptimize_extra_checkbox_field_5'] ) ) { echo 'class="hidden"'; } ?>>
922 <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
923 <td>
924 <label>
925 <select name='autoptimize_extra_settings[autoptimize_extra_select_field_6]'>
926 <?php
927 $_imgopt_array = $this->get_img_quality_array();
928 $_imgopt_val = $this->get_img_quality_setting();
929
930 foreach ( $_imgopt_array as $key => $value ) {
931 echo '<option value="' . $key . '"';
932 if ( $_imgopt_val == $key ) {
933 echo ' selected';
934 }
935 echo '>' . ucfirst( $value ) . '</option>';
936 }
937 echo "\n";
938 ?>
939 </select>
940 </label>
941 <p>
942 <?php
943 // translators: link points to shortpixel image test page.
944 echo apply_filters( 'autoptimize_extra_imgopt_quality_copy', sprintf( __( 'You can %1$stest compression levels here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/oic' . $sp_url_suffix . '" target="_blank">', '</a>' ) );
945 ?>
946 </p>
947 </td>
948 </tr>
949 <tr>
950 <th scope="row"><?php _e( 'Remove emojis', 'autoptimize' ); ?></th>
951 <td>
952 <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>
953 </td>
954 </tr>
955 <tr>
956 <th scope="row"><?php _e( 'Remove query strings from static resources', 'autoptimize' ); ?></th>
957 <td>
958 <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>
959 </td>
960 </tr>
961 <tr>
962 <th scope="row"><?php _e( 'Preconnect to 3rd party domains <em>(advanced users)</em>', 'autoptimize' ); ?></th>
963 <td>
964 <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>
965 </td>
966 </tr>
967 <tr>
968 <th scope="row"><?php _e( 'Async Javascript-files <em>(advanced users)</em>', 'autoptimize' ); ?></th>
969 <td>
970 <?php
971 if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'async-javascript/async-javascript.php' ) ) {
972 // translators: link points Async Javascript settings page.
973 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>' );
974 } else {
975 ?>
976 <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'] ); } ?>'>
977 <br />
978 <?php
979 _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' );
980 // translators: %s will be replaced by a link to the "async javascript" plugin.
981 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>"' );
982 $asj_install_url = network_admin_url() . 'plugin-install.php?s=async+javascript&tab=search&type=term';
983 echo sprintf( ' <a href="' . $asj_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
984 }
985 ?>
986 </td>
987 </tr>
988 <tr>
989 <th scope="row"><?php _e( 'Optimize YouTube videos', 'autoptimize' ); ?></th>
990 <td>
991 <?php
992 if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'wp-youtube-lyte/wp-youtube-lyte.php' ) ) {
993 _e( 'Great, you have WP YouTube Lyte installed.', 'autoptimize' );
994 $lyte_config_url = 'options-general.php?page=lyte_settings_page';
995 echo sprintf( ' <a href="' . $lyte_config_url . '">%s</a>', __( 'Click here to configure it.', 'autoptimize' ) );
996 } else {
997 // translators: %s will be replaced by a link to "wp youtube lyte" plugin.
998 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>' );
999 $lyte_install_url = network_admin_url() . 'plugin-install.php?s=lyte&tab=search&type=term';
1000 echo sprintf( ' <a href="' . $lyte_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
1001 }
1002 ?>
1003 </td>
1004 </tr>
1005 </table>
1006 <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1007 </form>
1008 <script>
1009 jQuery(document).ready(function() {
1010 jQuery( "#autoptimize_imgopt_checkbox" ).change(function() {
1011 if (this.checked) {
1012 jQuery("#autoptimize_imgopt_quality").show("slow");
1013 } else {
1014 jQuery("#autoptimize_imgopt_quality").hide("slow");
1015 }
1016 });
1017 });
1018 </script>
1019 <?php
1020 }
1021 }
1022