Completed
Push — master ( ba7d0e...7911d3 )
by frank
04:13 queued 14s
created

autoptimizeImages::build_imgopt_url()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 20
nop 3
dl 0
loc 40
rs 8.0355
c 0
b 0
f 0
1
<?php
2
/**
3
 * Handles optimizing images.
4
 */
5
6
if ( ! defined( 'ABSPATH' ) ) {
7
    exit;
8
}
9
10
class autoptimizeImages
11
{
12
    /**
13
     * Options.
14
     *
15
     * @var array
16
     */
17
    protected $options = array();
18
19
    /**
20
     * Singleton instance.
21
     *
22
     * @var self|null
23
     */
24
    protected static $instance = null;
25
26
    public function __construct( array $options = array() )
27
    {
28
        // If options are not provided, fetch them.
29
        if ( empty( $options ) ) {
30
            $options = $this->fetch_options();
31
        }
32
33
        $this->set_options( $options );
34
    }
35
36
    public function set_options( array $options )
37
    {
38
        $this->options = $options;
39
40
        return $this;
41
    }
42
43
    public static function fetch_options()
44
    {
45
        $value = get_option( 'autoptimize_imgopt_settings' );
46
        if ( empty( $value ) ) {
47
            // Fallback to returning defaults when no stored option exists yet.
48
            $value = autoptimizeConfig::get_ao_imgopt_default_options();
49
        }
50
51
        // get service availability and add it to the options-array.
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 static function imgopt_active()
62
    {
63
        // function to quickly check if imgopt is active, used below but also in
64
        // autoptimizeMain.php to start ob_ even if no HTML, JS or CSS optimizing is done
65
        // and does not use/ request the availablity data (which could slow things down).
66
        static $imgopt_active = null;
67
68
        if ( null === $imgopt_active ) {
69
            $opts = get_option( 'autoptimize_imgopt_settings', '' );
70
            if ( ! empty( $opts ) && is_array( $opts ) && array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $opts ) && ! empty( $opts['autoptimize_imgopt_checkbox_field_1'] ) && '1' === $opts['autoptimize_imgopt_checkbox_field_1'] ) {
71
                $imgopt_active = true;
72
            } else {
73
                $imgopt_active = false;
74
            }
75
        }
76
77
        return $imgopt_active;
78
    }
79
80
    /**
81
     * Helper for getting a singleton instance. While being an
82
     * anti-pattern generally, it comes in handy for now from a
83
     * readability/maintainability perspective, until we get some
84
     * proper dependency injection going.
85
     *
86
     * @return self
87
     */
88
    public static function instance()
89
    {
90
        if ( null === self::$instance ) {
91
            self::$instance = new self();
92
        }
93
94
        return self::$instance;
95
    }
96
97
    public function run()
98
    {
99
        if ( is_admin() ) {
100
            add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
101
            add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_imgopt_tab' ), 9 );
102
        } else {
103
            $this->run_on_frontend();
104
        }
105
    }
106
107
    public function run_on_frontend() {
108
        if ( ! $this->should_run() ) {
109
            if ( $this->should_lazyload() ) {
110
                add_filter(
111
                    'autoptimize_html_after_minify',
112
                    array( $this, 'filter_lazyload_images' ),
113
                    10,
114
                    1
115
                );
116
                add_action(
117
                    'wp_footer',
118
                    array( $this, 'add_lazyload_js_footer' ),
119
                    10,
120
                    0
121
                );
122
            }
123
            return;
124
        }
125
126
        $active = false;
127
128
        if ( apply_filters( 'autoptimize_filter_imgopt_do', true ) ) {
129
            add_filter(
130
                'autoptimize_html_after_minify',
131
                array( $this, 'filter_optimize_images' ),
132
                10,
133
                1
134
            );
135
            $active = true;
136
        }
137
138
        if ( apply_filters( 'autoptimize_filter_imgopt_do_css', true ) ) {
139
            add_filter(
140
                'autoptimize_filter_base_replace_cdn',
141
                array( $this, 'filter_optimize_css_images' ),
142
                10,
143
                1
144
            );
145
            $active = true;
146
        }
147
148
        if ( $active ) {
149
            add_filter(
150
                'autoptimize_extra_filter_tobepreconn',
151
                array( $this, 'filter_preconnect_imgopt_url' ),
152
                10,
153
                1
154
            );
155
        }
156
157
        if ( $this->should_lazyload() ) {
158
            add_action(
159
                'wp_footer',
160
                array( $this, 'add_lazyload_js_footer' )
161
            );
162
        }
163
    }
164
165
    /**
166
     * Basic checks before we can run.
167
     *
168
     * @return bool
169
     */
170
    protected function should_run()
171
    {
172
        $opts              = $this->options;
173
        $service_not_down  = ( 'down' !== $opts['availabilities']['extra_imgopt']['status'] );
174
        $not_launch_status = ( 'launch' !== $opts['availabilities']['extra_imgopt']['status'] );
175
176
        $do_cdn      = true;
177
        $_userstatus = $this->get_imgopt_provider_userstatus();
178
        if ( -2 == $_userstatus['Status'] ) {
179
            $do_cdn = false;
180
        }
181
182
        if (
183
            $this->imgopt_active()
184
            && $do_cdn
185
            && $service_not_down
186
            && ( $not_launch_status || $this->launch_ok() )
187
        ) {
188
            return true;
189
        }
190
        return false;
191
    }
192
193
    public function get_imgopt_host()
194
    {
195
        static $imgopt_host = null;
196
197
        if ( null === $imgopt_host ) {
198
            $imgopt_host  = 'https://cdn.shortpixel.ai/';
199
            $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
200
            if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
201
                $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
202
            }
203
            $imgopt_host = apply_filters('autoptimize_filter_imgopt_host', $imgopt_host );
204
        }
205
206
        return $imgopt_host;
207
    }
208
209
    public static function get_imgopt_host_wrapper()
210
    {
211
        // needed for CI tests.
212
        $self = new self();
213
        return $self->get_imgopt_host();
214
    }
215
216
    public static function get_service_url_suffix()
217
    {
218
        $suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
219
220
        return $suffix;
221
    }
222
223
    public function get_img_quality_string()
224
    {
225
        static $quality = null;
226
227
        if ( null === $quality ) {
228
            $q_array = $this->get_img_quality_array();
229
            $setting = $this->get_img_quality_setting();
230
            $quality = apply_filters(
231
                'autoptimize_filter_imgopt_quality',
232
                'q_' . $q_array[ $setting ]
233
            );
234
        }
235
236
        return $quality;
237
    }
238
239
    public function get_img_quality_array()
240
    {
241
        static $map = null;
242
243
        if ( null === $map ) {
244
            $map = array(
245
                '1' => 'lossy',
246
                '2' => 'glossy',
247
                '3' => 'lossless',
248
            );
249
            $map = apply_filters(
250
                'autoptimize_filter_imgopt_quality_array',
251
                $map
252
            );
253
        }
254
255
        return $map;
256
    }
257
258
    public function get_img_quality_setting()
259
    {
260
        static $q = null;
261
262
        if ( null === $q ) {
263
            if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_select_field_2', $this->options ) ) {
264
                $setting = $this->options['autoptimize_imgopt_select_field_2'];
265
            }
266
267
            if ( ! isset( $setting ) || empty( $setting ) || ( '1' !== $setting && '3' !== $setting ) ) {
268
                // default image opt. value is 2 ("glossy").
269
                $q = '2';
270
            } else {
271
                $q = $setting;
272
            }
273
        }
274
275
        return $q;
276
    }
277
278
    public function filter_preconnect_imgopt_url( array $in )
279
    {
280
        $url_parts = parse_url( $this->get_imgopt_base_url() );
281
        $in[]      = $url_parts['scheme'] . '://' . $url_parts['host'];
282
283
        return $in;
284
    }
285
286
    /**
287
     * Makes sure given url contains the full scheme and hostname
288
     * in case they're not present already.
289
     *
290
     * @param string $in Image url to normalize.
291
     *
292
     * @return string
293
     */
294
    private function normalize_img_url( $in )
295
    {
296
        // Only parse the site url once.
297
        static $parsed_site_url = null;
298
        if ( null === $parsed_site_url ) {
299
            $parsed_site_url = parse_url( site_url() );
300
        }
301
302
        // get CDN domain once.
303
        static $cdn_domain = null;
304
        if ( is_null( $cdn_domain ) ) {
305
            $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', get_option( 'autoptimize_cdn_url', '' ) );
306
            if ( ! empty( $cdn_url ) ) {
307
                $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
308
            } else {
309
                $cdn_domain = '';
310
            }
311
        }
312
313
        /**
314
         * This method gets called a lot, often for identical urls it seems.
315
         * `filter_optimize_css_images()` calls us, uses the resulting url and
316
         * gives it to `can_optimize_image()`, and if that returns trueish
317
         * then `build_imgopt_url()` is called (which, again, calls this method).
318
         * Until we dig deeper into whether this all must really happen that
319
         * way, having an internal cache here helps (to avoid doing repeated
320
         * identical string operations).
321
         */
322
        static $cache = null;
323
        if ( null === $cache ) {
324
            $cache = array();
325
        }
326
327
        // Do the work on cache miss only.
328
        if ( ! isset( $cache[ $in ] ) ) {
329
            // Default to what was given to us.
330
            $result = $in;
331
            if ( autoptimizeUtils::is_protocol_relative( $in ) ) {
332
                $result = $parsed_site_url['scheme'] . ':' . $in;
333
            } elseif ( 0 === strpos( $in, '/' ) ) {
334
                // Root-relative...
335
                $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'];
336
                // Add the path for subfolder installs.
337
                if ( isset( $parsed_site_url['path'] ) ) {
338
                    $result .= $parsed_site_url['path'];
339
                }
340
                $result .= $in;
341
            } elseif ( ! empty( $cdn_domain ) && strpos( $in, $cdn_domain ) !== 0 ) {
342
                $result = str_replace( $cdn_domain, $parsed_site_url['host'], $in );
343
            }
344
345
            $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
346
347
            // Store in cache.
348
            $cache[ $in ] = $result;
349
        }
350
351
        return $cache[ $in ];
352
    }
353
354
    public function filter_optimize_css_images( $in )
355
    {
356
        $in = $this->normalize_img_url( $in );
357
358
        if ( $this->can_optimize_image( $in ) ) {
359
            return $this->build_imgopt_url( $in, '', '' );
360
        } else {
361
            return $in;
362
        }
363
    }
364
365
    private function get_imgopt_base_url()
366
    {
367
        static $imgopt_base_url = null;
368
369
        if ( null === $imgopt_base_url ) {
370
            $imgopt_host     = $this->get_imgopt_host();
371
            $quality         = $this->get_img_quality_string();
372
            $ret_val         = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
373
            $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
374
            $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
375
        }
376
377
        return $imgopt_base_url;
378
    }
379
380
    private function can_optimize_image( $url )
381
    {
382
        static $cdn_url      = null;
383
        static $nopti_images = null;
384
385
        if ( null === $cdn_url ) {
386
            $cdn_url = apply_filters(
387
                'autoptimize_filter_base_cdnurl',
388
                get_option( 'autoptimize_cdn_url', '' )
389
            );
390
        }
391
392
        if ( null === $nopti_images ) {
393
            $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', '' );
394
        }
395
396
        $site_host  = AUTOPTIMIZE_SITE_DOMAIN;
397
        $url        = $this->normalize_img_url( $url );
398
        $url_parsed = parse_url( $url );
399
400
        if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
401
            return false;
402
        } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
403
            return false;
404
        } elseif ( strpos( $url, '.php' ) !== false ) {
405
            return false;
406
        } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
407
            // fixme: better check against end of string.
408
            return false;
409
        } elseif ( ! empty( $nopti_images ) ) {
410
            $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
411
            foreach ( $nopti_images_array as $nopti_image ) {
412
                if ( strpos( $url, $nopti_image ) !== false ) {
413
                    return false;
414
                }
415
            }
416
        }
417
        return true;
418
    }
419
420
    private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
421
    {
422
        // sanitize width and height.
423
        if ( strpos( $width, '%' ) !== false ) {
424
            $width = 0;
425
        }
426
        if ( strpos( $height, '%' ) !== false ) {
427
            $height = 0;
428
        }
429
        $width  = (int) $width;
430
        $height = (int) $height;
431
432
        $filtered_url = apply_filters(
433
            'autoptimize_filter_imgopt_build_url',
434
            $orig_url,
435
            $width,
436
            $height
437
        );
438
439
        // If filter modified the url, return that.
440
        if ( $filtered_url !== $orig_url ) {
441
            return $filtered_url;
442
        }
443
444
        $orig_url        = $this->normalize_img_url( $orig_url );
445
        $imgopt_base_url = $this->get_imgopt_base_url();
446
        $imgopt_size     = '';
447
448
        if ( $width && 0 !== $width ) {
449
            $imgopt_size = ',w_' . $width;
450
        }
451
452
        if ( $height && 0 !== $height ) {
453
            $imgopt_size .= ',h_' . $height;
454
        }
455
456
        $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
457
458
        return $url;
459
    }
460
461
    public function replace_data_thumbs( $matches )
462
    {
463
        return $this->replace_img_callback( $matches, 150, 150 );
464
    }
465
466
    public function replace_img_callback( $matches, $width = 0, $height = 0 )
467
    {
468
        if ( $this->can_optimize_image( $matches[1] ) ) {
469
            return str_replace( $matches[1], $this->build_imgopt_url( $matches[1], $width, $height ), $matches[0] );
470
        } else {
471
            return $matches[0];
472
        }
473
    }
474
475
    public function filter_optimize_images( $in )
476
    {
477
        /*
478
         * potential future functional improvements:
479
         *
480
         * picture element.
481
         * filter for critical CSS.
482
         */
483
        $to_replace = array();
484
485
        // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
486
        if ( $this->should_lazyload() ) {
487
            $in = autoptimizeBase::replace_contents_with_marker_if_exists(
488
                'NOSCRIPT',
489
                '<noscript',
490
                '#<noscript.*?<\/noscript>#is',
491
                $in
492
            );
493
        }
494
495
        // extract img tags.
496
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
497
            foreach ( $matches[0] as $tag ) {
498
                $orig_tag = $tag;
499
                $imgopt_w = '';
500
                $imgopt_h = '';
501
502
                // first do (data-)srcsets.
503
                if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
504
                    foreach ( $allsrcsets as $srcset ) {
505
                        $srcset  = $srcset[2];
506
                        $srcsets = explode( ',', $srcset );
507
                        foreach ( $srcsets as $indiv_srcset ) {
508
                            $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
509
                            if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
510
                                $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
511
                            }
512
                            if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
513
                                $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
514
                                $tag        = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
515
                            }
516
                        }
517
                    }
518
                }
519
520
                // proceed with img src.
521
                // get width and height and add to $imgopt_size.
522
                $_get_size = $this->get_size_from_tag( $tag );
523
                $imgopt_w  = $_get_size['width'];
524
                $imgopt_h  = $_get_size['height'];
525
526
                // then start replacing images src.
527
                if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
528
                    foreach ( $urls as $url ) {
529
                        $full_src_orig = $url[0];
530
                        $url           = $url[1];
531
                        if ( $this->can_optimize_image( $url ) ) {
532
                            $imgopt_url      = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
533
                            $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
534
                            $tag             = str_replace( $full_src_orig, $full_imgopt_src, $tag );
535
                        }
536
                    }
537
                }
538
539
                // do lazyload stuff.
540
                if ( $this->should_lazyload() && str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
541
                    $noscript_tag = '<noscript>' . $tag . '</noscript>';
542
                    $tag          = str_replace( 'srcset=', 'data-srcset=', $tag );
543
544
                    // add lazyload class.
545
                    $tag = $this->inject_classes_in_tag( $tag, 'lazyload ' );
546
547
                    // set placeholder.
548
                    if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
549
                        // if all img src have been replaced during srcset, we have to extract the
550
                        // origin url from the imgopt one to be able to set a lqip placeholder.
551
                        $_url = substr( $url, strpos( $url, '/http' ) + 1 );
552
                    } else {
553
                        $_url = $url;
554
                    }
555
                    if ( $this->can_optimize_image( $_url ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', true ) ) {
556
                        $lqip_w = '';
557
                        $lqip_h = '';
558
                        if isset( $imgopt_w ) {
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_ISSET, expecting '('
Loading history...
559
                            $lqip_w = ',w_' . $imgopt_w;
560
                        }
561
                        if isset( $imgopt_h ) {
562
                            $lqip_h = ',h_' . $imgopt_h;
563
                        }
564
                        $placeholder = $this->get_imgopt_host() . 'client/q_lqip,ret_wait' . $lqip_w . $lqip_h . '/' . $_url;
565
                    } else {
566
                        $placeholder = $this->get_default_lazyload_placeholder( $imgopt_w, $imgopt_h );
567
                    }
568
                    $placeholder = ' src=\'' . apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $placeholder );
569
570
                    // add min-heigth off by default as it can deform images, can be enabled with filter.
571
                    $min_height = '';
572
                    if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_addminheight', false ) ) {
573
                        $min_height = ' style="min-height:' . $imgopt_h . 'px;"';
574
                    }
575
576
                    // add noscript & placeholder.
577
                    $tag = $noscript_tag . str_replace( ' src=', $min_height . $placeholder . '\' data-src=', $tag );
578
                    $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
579
                }
580
581
                // add tag to array for later replacement.
582
                if ( $tag !== $orig_tag ) {
583
                    $to_replace[ $orig_tag ] = $tag;
584
                }
585
            }
586
        }
587
588
        // and replace all.
589
        $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
590
591
        // img thumbnails in e.g. woocommerce.
592
        if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_imgopt_datathumbs', true ) ) {
593
            $out = preg_replace_callback(
594
                '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
595
                array( $this, 'replace_data_thumbs' ),
596
                $out
597
            );
598
        }
599
600
        // background-image in inline style.
601
        if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
602
            $out = preg_replace_callback(
603
                '/style=(?:"|\').*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
604
                array( $this, 'replace_img_callback' ),
605
                $out
606
            );
607
        }
608
609
        // and restore noscript tags if these were hidden for lazyload purposes.
610
        if ( $this->should_lazyload() ) {
611
            $out = autoptimizeBase::restore_marked_content(
612
                'NOSCRIPT',
613
                $out
614
            );
615
        }
616
617
        return $out;
618
    }
619
620
    public function get_size_from_tag( $tag ) {
621
        // reusable function to extract widht and height from an image tag
622
        // enforcing a filterable maximum width and height (default 4999X4999).
623
        $width  = '';
624
        $height = '';
625
626
        if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $_width ) ) {
627
            if ( strpos( $_width[2], '%' ) === false ) {
628
                $width = (int) $_width[2];
629
            }
630
        }
631
        if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $_height ) ) {
632
            if ( strpos( $_height[2], '%' ) === false ) {
633
                $height = (int) $_height[2];
634
            }
635
        }
636
637
        // check for and enforce (filterable) max sizes.
638
        $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
639
        if ( $width > $_max_width ) {
640
            $_width = $_max_width;
641
            $height = $_width / $width * $height;
642
            $width  = $_width;
643
        }
644
        $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
645
        if ( $height > $_max_height ) {
646
            $_height = $_max_height;
647
            $width   = $_height / $height * $width;
648
            $height  = $_height;
649
        }
650
651
        return array(
652
            'width'  => $width,
653
            'height' => $height,
654
        );
655
    }
656
657
    /**
658
     * Lazyload functions
659
     */
660
    public function should_lazyload() {
661
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) ) {
662
            $lazyload_return = true;
663
        } else {
664
            $lazyload_return = false;
665
        }
666
667
        return $lazyload_return;
668
    }
669
670
    public static function should_lazyload_wrapper() {
671
        // needed in autoptimizeMain.php.
672
        $self = new self();
673
        return $self->should_lazyload();
674
    }
675
676
    public function filter_lazyload_images( $in )
677
    {
678
        // only used is image optimization is NOT active but lazyload is.
679
        $to_replace = array();
680
681
        // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
682
        $out = autoptimizeBase::replace_contents_with_marker_if_exists(
683
            'NOSCRIPT',
684
            '<noscript',
685
            '#<noscript.*?<\/noscript>#is',
686
            $in
687
        );
688
689
        // extract img tags and add lazyload attribs.
690
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
691
            foreach ( $matches[0] as $tag ) {
692
                $to_replace[ $tag ] = $this->add_lazyload( $tag );
693
            }
694
            $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $out );
695
        }
696
697
        // restore noscript tags.
698
        $out = autoptimizeBase::restore_marked_content(
699
            'NOSCRIPT',
700
            $out
701
        );
702
703
        return $out;
704
    }
705
706
    public function add_lazyload( $tag ) {
707
        // adds actual lazyload-attributes to an image node.
708
        if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
709
            // store original tag for use in noscript version.
710
            $noscript_tag = '<noscript>' . $tag . '</noscript>';
711
712
            // insert lazyload class.
713
            $tag = $this->inject_classes_in_tag( $tag, 'lazyload ' );
714
715
            // get image width & heigth for placeholder fun (and to prevent content reflow).
716
            $_get_size = $this->get_size_from_tag( $tag );
717
            $width     = $_get_size['width'];
718
            $height    = $_get_size['height'];
719
            if ( false === $width ) {
720
                $widht = 210; // default width for SVG placeholder.
721
            }
722
            if ( false === $height ) {
723
                $heigth = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
724
            }
725
726
            // insert the actual lazyload stuff.
727
            // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
728
            $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
729
            $tag         = str_replace( ' src=', ' src=\'' . $placeholder . '\' data-src=', $tag );
730
            $tag         = str_replace( ' srcset=', ' data-srcset=', $tag );
731
732
            // add the noscript-tag from earlier.
733
            $tag = $noscript_tag . $tag;
734
            $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
735
        }
736
737
        return $tag;
738
    }
739
740
    public function add_lazyload_js_footer() {
741
        // The JS will by default be excluded form autoptimization but this can be changed with a filter.
742
        $noptimize_flag = '';
743
        if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
744
            $noptimize_flag = ' data-noptimize="1"';
745
        }
746
747
        // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
748
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<style>.lazyload,.lazyloading{opacity:0;}.lazyloaded{opacity:1;transition:opacity 300ms;}</style><noscript><style>.lazyload{display:none;}</style></noscript>' );
749
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
750
        echo '<script async' . $noptimize_flag . ' src=\'' . plugins_url( 'external/js/lazysizes.min.js', __FILE__ ) . '\'></script>';
751
752
        // And add webp detection and loading JS.
753
        if ( $this->should_webp() ) {
754
            $_webp_detect = "function c_webp(A){var n=new Image;n.onload=function(){var e=0<n.width&&0<n.height;A(e)},n.onerror=function(){A(!1)},n.src='data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA=='}function s_webp(e){window.supportsWebP=e}c_webp(s_webp);";
755
            $_webp_load   = "document.addEventListener('lazybeforeunveil',function({target:c}){supportsWebP&&['data-src','data-srcset'].forEach(function(a){attr=c.getAttribute(a),null!==attr&&c.setAttribute(a,attr.replace(/\/client\//,'/client/to_webp,'))})});";
756
            echo apply_filters( 'autoptimize_filter_imgopt_webp_js', '<script' . $noptimize_flag . '>' . $_webp_detect . $_webp_load . '</script>' );
757
        }
758
    }
759
760
    public function get_lazyload_exclusions() {
761
        // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
762
        static $exclude_lazyload_array = null;
763
764
        if ( null === $exclude_lazyload_array ) {
765
            $options = $this->options;
766
767
            // set default exclusions.
768
            $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset' );
769
770
            // add from setting.
771
            if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
772
                $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
773
                if ( ! empty( $exclude_lazyload_option ) ) {
774
                    $exclude_lazyload_array = array_merge( $exclude_lazyload_array, array_filter( array_map( 'trim', explode( ',', $options['autoptimize_imgopt_text_field_5'] ) ) ) );
775
                }
776
            }
777
778
            // and filter for developer-initiated changes.
779
            $exclude_lazyload_array = apply_filters( 'autoptimize_filter_imgopt_lazyload_exclude_array', $exclude_lazyload_array );
780
        }
781
782
        return $exclude_lazyload_array;
783
    }
784
785
    public function inject_classes_in_tag( $tag, $target_class ) {
786
        if ( strpos( $tag, 'class=' ) !== false ) {
787
            $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
788
        } else {
789
            $tag = str_replace( '<img ', '<img class="' . trim( $target_class ) . '" ', $tag );
790
        }
791
792
        return $tag;
793
    }
794
795
    public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
796
        return 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20' . $imgopt_w . '%20' . $imgopt_h . '%22%3E%3C/svg%3E';
797
    }
798
799
    public function should_webp() {
800
        static $webp_return = null;
801
802
        if ( is_null( $webp_return ) ) {
803
            // webp only works if imgopt and lazyload are also active.
804
            if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && $this->imgopt_active() ) {
805
                $webp_return = true;
806
            } else {
807
                $webp_return = false;
808
            }
809
        }
810
811
        return $webp_return;
812
    }
813
814
    /**
815
     * Admin page logic and related functions below.
816
     */
817
    public function imgopt_admin_menu()
818
    {
819
        add_submenu_page(
820
            null,
821
            'autoptimize_imgopt',
822
            'autoptimize_imgopt',
823
            'manage_options',
824
            'autoptimize_imgopt',
825
            array( $this, 'imgopt_options_page' )
826
        );
827
        register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
828
    }
829
830
    public function add_imgopt_tab( $in )
831
    {
832
        $in = array_merge( $in, array( 'autoptimize_imgopt' => __( 'Images', 'autoptimize' ) ) );
833
834
        return $in;
835
    }
836
837
    public function imgopt_options_page()
838
    {
839
        // Check querystring for "refreshCacheChecker" and call cachechecker if so.
840
        if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
841
            $this->query_img_provider_stats();
842
        }
843
844
        $options       = $this->fetch_options();
845
        $sp_url_suffix = $this->get_service_url_suffix();
846
        ?>
847
    <style>
848
        #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
849
        #ao_settings_form .form-table th {font-weight: normal;}
850
        #autoptimize_imgopt_descr{font-size: 120%;}
851
    </style>
852
    <div class="wrap">
853
    <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
854
        <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
855
        <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
856
            <div class="notice-warning notice"><p>
857
            <?php
858
            // translators: "Autoptimize support forum" will appear in a "a href".
859
            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>' );
860
            ?>
861
            </p></div>
862
        <?php } ?>
863
864
        <?php if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! autoptimizeImages::instance()->launch_ok() ) { ?>
865
            <div class="notice-warning notice"><p>
866
            <?php _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' ); ?>
867
            </p></div>
868
        <?php } ?>
869
870
        <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
871
            <div class="notice-warning notice"><p>
872
            <?php
873
            // translators: "disable  Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
874
            echo sprintf( __( 'Please %1$sdisable Jetpack\'s site accelerator for images%2$s to be able to use Autoptomize\'s advanced image optimization features below.', 'autoptimize' ), '<a href="admin.php?page=jetpack#/settings">', '</a>' );
875
            ?>
876
            </p></div>
877
        <?php } ?>
878
    <form id='ao_settings_form' action='options.php' method='post'>
879
        <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
880
        <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
881
        <span id='autoptimize_imgopt_descr'><?php _e( 'Make your site significantly faster by just ticking a couple of checkboxes to optimize and lazy load your images, WebP support included!', 'autoptimize' ); ?></span>
882
        <table class="form-table">
883
            <tr>
884
                <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
885
                <td>
886
                    <label><input id='autoptimize_imgopt_checkbox' type='checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Optimize images on the fly and serve them from Shortpixel\'s global CDN.', 'autoptimize' ); ?></label>
887
                    <?php
888
                    // show shortpixel status.
889
                    $_notice = autoptimizeImages::instance()->get_status_notice();
890
                    if ( $_notice ) {
891
                        switch ( $_notice['status'] ) {
892
                            case 2:
893
                                $_notice_color = 'green';
894
                                break;
895
                            case 1:
896
                                $_notice_color = 'orange';
897
                                break;
898
                            case -1:
899
                                $_notice_color = 'red';
900
                                break;
901
                            case -2:
902
                                $_notice_color = 'red';
903
                                break;
904
                            default:
905
                                $_notice_color = 'green';
906
                        }
907
                        echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
908
                    } else {
909
                        // translators: link points to shortpixel.
910
                        $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love and improve your website\'s loading speed by having the images optimized on the fly (also in the "next-gen" WebP image format) by %1$sShortPixel%2$s and then cached and served fast from Shortpixel\'s global CDN.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
911
                        if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
912
                            $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' );
913
                        } else {
914
                            // translators: link points to shortpixel.
915
                            $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>' );
916
                        }
917
                        echo apply_filters( 'autoptimize_imgopt_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
918
                    }
919
                    // translators: link points to shortpixel FAQ.
920
                    $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>' );
921
                    // translators: links points to shortpixel TOS & Privacy Policy.
922
                    $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>' );
923
                    echo apply_filters( 'autoptimize_imgopt_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
924
                    ?>
925
                </td>
926
            </tr>
927
            <tr id='autoptimize_imgopt_quality' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
928
                <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
929
                <td>
930
                    <label>
931
                    <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
932
                        <?php
933
                        $_imgopt_array = autoptimizeImages::instance()->get_img_quality_array();
934
                        $_imgopt_val   = autoptimizeImages::instance()->get_img_quality_setting();
935
936
                        foreach ( $_imgopt_array as $key => $value ) {
937
                            echo '<option value="' . $key . '"';
938
                            if ( $_imgopt_val == $key ) {
939
                                echo ' selected';
940
                            }
941
                            echo '>' . ucfirst( $value ) . '</option>';
942
                        }
943
                        echo "\n";
944
                        ?>
945
                    </select>
946
                    </label>
947
                    <p>
948
                        <?php
949
                            // translators: link points to shortpixel image test page.
950
                            echo apply_filters( 'autoptimize_imgopt_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>' ) );
951
                        ?>
952
                    </p>
953
                </td>
954
            </tr>
955
            <tr id='autoptimize_imgopt_webp' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
956
                <th scope="row"><?php _e( 'Load WebP in supported browsers?', 'autoptimize' ); ?></th>
957
                <td>
958
                    <label><input type='checkbox' id='autoptimize_imgopt_webp_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_4'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_3'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Automatically serve "next-gen" WebP image format to any browser that supports it (requires lazy load to be active).', 'autoptimize' ); ?></label>
959
                </td>
960
            </tr>
961
            <tr>
962
                <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
963
                <td>
964
                    <label><input type='checkbox' id='autoptimize_imgopt_lazyload_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_3]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_3'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Image lazy-loading will delay the loading of non-visible images to allow the browser to optimally load all resources for the "above the fold"-page first.', 'autoptimize' ); ?></label>
965
                </td>
966
            </tr>
967
            <tr id='autoptimize_imgopt_lazyload_exclusions' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_3', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_3'] ) ) { echo 'class="hidden"'; } ?>>
968
                <th scope="row"><?php _e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
969
                <td>
970
                    <label><input type='text' style='width:80%' id='autoptimize_imgopt_lazyload_exclusions' name='autoptimize_imgopt_settings[autoptimize_imgopt_text_field_5]' value='<?php if ( ! empty( $options['autoptimize_imgopt_text_field_5'] ) ) { echo esc_attr( $options['autoptimize_imgopt_text_field_5'] ); } ?>'><br /><?php _e( 'Comma-separated list of to be excluded image classes or filenames.', 'autoptimize' ); ?></label>
971
                </td>
972
            </tr>
973
        </table>
974
        <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
975
    </form>
976
    <script>
977
        jQuery(document).ready(function() {
978
            jQuery("#autoptimize_imgopt_checkbox").change(function() {
979
                if (this.checked) {
980
                    jQuery("#autoptimize_imgopt_quality").show("slow");
981
                    jQuery("#autoptimize_imgopt_webp").show("slow");
982
                } else {
983
                    jQuery("#autoptimize_imgopt_quality").hide("slow");
984
                    jQuery("#autoptimize_imgopt_webp").hide("slow");
985
                }
986
            });
987
            jQuery("#autoptimize_imgopt_webp_checkbox").change(function() {
988
                if (this.checked) {
989
                    jQuery("#autoptimize_imgopt_lazyload_checkbox")[0].checked = true;
990
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
991
                }
992
            });
993
            jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
994
                if (this.checked) {
995
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
996
                } else {
997
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").hide("slow");
998
                    jQuery("#autoptimize_imgopt_webp_checkbox")[0].checked = false;
999
                }
1000
            });
1001
        });
1002
    </script>
1003
        <?php
1004
    }
1005
1006
    /**
1007
     * Ïmg opt status as used on dashboard.
1008
     */
1009
    public function get_imgopt_status_notice() {
1010
        if ( $this->imgopt_active() ) {
1011
            $_imgopt_notice = '';
1012
            $_stat          = get_option( 'autoptimize_imgopt_provider_stat', '' );
1013
            $_site_host     = AUTOPTIMIZE_SITE_DOMAIN;
1014
            $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
1015
1016
            if ( is_array( $_stat ) ) {
1017
                if ( 1 == $_stat['Status'] ) {
1018
                    // translators: "add more credits" will appear in a "a href".
1019
                    $_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>' );
1020
                } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1021
                    // translators: "add more credits" will appear in a "a href".
1022
                    $_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>' );
1023
                    $_imgopt_stats_refresh_url = add_query_arg( array(
1024
                        'page'                => 'autoptimize_imgopt',
1025
                        'refreshImgProvStats' => '1',
1026
                    ), admin_url( 'options-general.php' ) );
1027
                    if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
1028
                        $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( get_option( 'time_format' ), $_stat['timestamp'] );
1029
                    } else {
1030
                        $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1031
                    }
1032
                    $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1033
                    // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1034
                    $_imgopt_notice .= sprintf( __( 'click %1$shere to refresh%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1035
                } else {
1036
                    $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1037
                    // translators: "log in to check your account" will appear in a "a href".
1038
                    $_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>' );
1039
                }
1040
                $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1041
1042
                return array(
1043
                    'status' => $_stat['Status'],
1044
                    'notice' => $_imgopt_notice,
1045
                );
1046
            }
1047
        }
1048
        return false;
1049
    }
1050
1051
    public static function get_imgopt_status_notice_wrapper() {
1052
        // needed for notice being shown in autoptimizeCacheChecker.php.
1053
        $self = new self();
1054
        return $self->get_imgopt_status_notice();
1055
    }
1056
1057
    /**
1058
     * Get img provider stats (used to display notice).
1059
     */
1060
    public function query_img_provider_stats() {
1061
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) ) {
1062
            $url      = '';
1063
            $endpoint = $this->get_imgopt_host() . 'read-domain/';
1064
            $domain   = AUTOPTIMIZE_SITE_DOMAIN;
1065
1066
            // make sure parse_url result makes sense, keeping $url empty if not.
1067
            if ( $domain && ! empty( $domain ) ) {
1068
                $url = $endpoint . $domain;
1069
            }
1070
1071
            $url = apply_filters(
1072
                'autoptimize_filter_imgopt_stat_url',
1073
                $url
1074
            );
1075
1076
            // only do the remote call if $url is not empty to make sure no parse_url
1077
            // weirdness results in useless calls.
1078
            if ( ! empty( $url ) ) {
1079
                $response = wp_remote_get( $url );
1080
                if ( ! is_wp_error( $response ) ) {
1081
                    if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1082
                        $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1083
                        update_option( 'autoptimize_imgopt_provider_stat', $stats );
1084
                    }
1085
                }
1086
            }
1087
        }
1088
    }
1089
1090
    public static function get_img_provider_stats()
1091
    {
1092
        // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
1093
        $self = new self();
1094
        return $self->query_img_provider_stats();
1095
    }
1096
1097
    /**
1098
     * Determines and returns the service launch status.
1099
     *
1100
     * @return bool
1101
     */
1102
    public function launch_ok()
1103
    {
1104
        static $launch_status = null;
1105
1106
        if ( null === $launch_status ) {
1107
            $avail_imgopt  = $this->options['availabilities']['extra_imgopt'];
1108
            $magic_number  = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1109
            $has_launched  = get_option( 'autoptimize_imgopt_launched', '' );
1110
            $launch_status = false;
1111
            if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1112
                $launch_status = true;
1113
                if ( ! $has_launched ) {
1114
                    update_option( 'autoptimize_imgopt_launched', 'on' );
1115
                }
1116
            }
1117
        }
1118
1119
        return $launch_status;
1120
    }
1121
1122
    public static function launch_ok_wrapper() {
1123
        // needed for "plug" notice in autoptimizeMain.php.
1124
        $self = new self();
1125
        return $self->launch_ok();
1126
    }
1127
1128
    public function get_imgopt_provider_userstatus() {
1129
        static $_provider_userstatus = null;
1130
1131
        if ( is_null( $_provider_userstatus ) ) {
1132
            $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
1133
            if ( is_array( $_stat ) ) {
1134
                if ( array_key_exists( 'Status', $_stat ) ) {
1135
                    $_provider_userstatus['Status'] = $_stat['Status'];
1136
                } else {
1137
                    // if no stats then we assume all is well.
1138
                    $_provider_userstatus['Status'] = 2;
1139
                }
1140
                if ( array_key_exists( 'timestamp', $_stat ) ) {
1141
                    $_provider_userstatus['timestamp'] = $_stat['timestamp'];
1142
                } else {
1143
                    // if no timestamp then we return "".
1144
                    $_provider_userstatus['timestamp'] = '';
1145
                }
1146
            }
1147
        }
1148
1149
        return $_provider_userstatus;
1150
    }
1151
1152
    public function get_status_notice() {
1153
        if ( $this->imgopt_active() ) {
1154
            $notice = '';
1155
            $stat   = $this->get_imgopt_provider_userstatus();
1156
            $upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
1157
            $assoc  = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1158
1159
            if ( is_array( $stat ) ) {
1160
                if ( 1 == $stat['Status'] ) {
1161
                    // translators: "add more credits" will appear in a "a href".
1162
                    $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 rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1163
                } elseif ( -1 == $stat['Status'] || -2 == $stat['Status'] ) {
1164
                    // translators: "add more credits" will appear in a "a href".
1165
                    $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 rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1166
                    // translators: "associate your domain" will appear in a "a href".
1167
                    $notice = $notice . ' ' . sprintf( __( 'If you already have enough credits then you may need to %1$sassociate your domain%2$s to your Shortpixel account.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $assoc . '" target="_blank">', '</a>' );
1168
                } else {
1169
                    $upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1170
                    // translators: "log in to check your account" will appear in a "a href".
1171
                    $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1172
                }
1173
                $notice = apply_filters( 'autoptimize_filter_imgopt_notice', $notice );
1174
1175
                return array(
1176
                    'status' => $stat['Status'],
1177
                    'notice' => $notice,
1178
                );
1179
            }
1180
        }
1181
        return false;
1182
    }
1183
}
1184