Completed
Push — master ( 8538b3...935035 )
by frank
02:00
created

autoptimizeImages::get_imgopt_status_notice()   C

Complexity

Conditions 11
Paths 14

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 14
nop 0
dl 0
loc 54
rs 6.8569
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        $this->lazyload_counter = 0;
0 ignored issues
show
Bug introduced by
The property lazyload_counter does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
35
    }
36
37
    public function set_options( array $options )
38
    {
39
        $this->options = $options;
40
41
        return $this;
42
    }
43
44
    public static function fetch_options()
45
    {
46
        $value = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_settings' );
47
        if ( empty( $value ) ) {
48
            // Fallback to returning defaults when no stored option exists yet.
49
            $value = autoptimizeConfig::get_ao_imgopt_default_options();
50
        }
51
52
        // get service availability and add it to the options-array.
53
        $value['availabilities'] = autoptimizeOptionWrapper::get_option( 'autoptimize_service_availablity' );
54
55
        if ( empty( $value['availabilities'] ) ) {
56
            $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a false|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
57
        }
58
59
        return $value;
60
    }
61
62
    public static function imgopt_active()
63
    {
64
        // function to quickly check if imgopt is active, used below but also in
65
        // autoptimizeMain.php to start ob_ even if no HTML, JS or CSS optimizing is done
66
        // and does not use/ request the availablity data (which could slow things down).
67
        static $imgopt_active = null;
68
69
        if ( null === $imgopt_active ) {
70
            $opts = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_settings', '' );
71
            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'] ) {
72
                $imgopt_active = true;
73
            } else {
74
                $imgopt_active = false;
75
            }
76
        }
77
78
        return $imgopt_active;
79
    }
80
81
    /**
82
     * Helper for getting a singleton instance. While being an
83
     * anti-pattern generally, it comes in handy for now from a
84
     * readability/maintainability perspective, until we get some
85
     * proper dependency injection going.
86
     *
87
     * @return self
88
     */
89
    public static function instance()
90
    {
91
        if ( null === self::$instance ) {
92
            self::$instance = new self();
93
        }
94
95
        return self::$instance;
96
    }
97
98 View Code Duplication
    public function run()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
99
    {
100
        if ( is_admin() ) {
101
            if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
102
                add_action( 'network_admin_menu', array( $this, 'imgopt_admin_menu' ) );
103
            } else {
104
                add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
105
            }
106
            add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_imgopt_tab' ), 9 );
107
        } else {
108
            add_action( 'wp', array( $this, 'run_on_frontend' ) );
109
        }
110
    }
111
112
    public function run_on_frontend() {
113
        if ( ! $this->should_run() ) {
114
            if ( $this->should_lazyload() ) {
115
                add_filter(
116
                    'wp_lazy_loading_enabled',
117
                    '__return_false'
118
                );
119
                add_filter(
120
                    'autoptimize_html_after_minify',
121
                    array( $this, 'filter_lazyload_images' ),
122
                    10,
123
                    1
124
                );
125
                add_action(
126
                    'wp_footer',
127
                    array( $this, 'add_lazyload_js_footer' ),
128
                    10,
129
                    0
130
                );
131
            }
132
            return;
133
        }
134
135
        $active = false;
136
137
        if ( apply_filters( 'autoptimize_filter_imgopt_do', true ) ) {
138
            add_filter(
139
                'autoptimize_html_after_minify',
140
                array( $this, 'filter_optimize_images' ),
141
                10,
142
                1
143
            );
144
            $active = true;
145
        }
146
147
        if ( apply_filters( 'autoptimize_filter_imgopt_do_css', true ) ) {
148
            add_filter(
149
                'autoptimize_filter_base_replace_cdn',
150
                array( $this, 'filter_optimize_css_images' ),
151
                10,
152
                1
153
            );
154
            $active = true;
155
        }
156
157
        if ( $active ) {
158
            add_filter(
159
                'autoptimize_extra_filter_tobepreconn',
160
                array( $this, 'filter_preconnect_imgopt_url' ),
161
                10,
162
                1
163
            );
164
        }
165
166
        if ( $this->should_lazyload() ) {
167
            add_filter(
168
                'wp_lazy_loading_enabled',
169
                '__return_false'
170
            );
171
            add_action(
172
                'wp_footer',
173
                array( $this, 'add_lazyload_js_footer' ),
174
                10,
175
                0
176
            );
177
        }
178
    }
179
180
    /**
181
     * Basic checks before we can run.
182
     *
183
     * @return bool
184
     */
185
    protected function should_run()
186
    {
187
        $opts              = $this->options;
188
        $service_not_down  = ( 'down' !== $opts['availabilities']['extra_imgopt']['status'] );
189
        $not_launch_status = ( 'launch' !== $opts['availabilities']['extra_imgopt']['status'] );
190
191
        $do_cdn      = true;
192
        $_userstatus = $this->get_imgopt_provider_userstatus();
193
        if ( isset( $_userstatus['Status'] ) && ( -2 == $_userstatus['Status'] || -3 == $_userstatus['Status'] ) ) {
194
            // don't even attempt to put images on CDN if heavily exceeded threshold or if site not reachable.
195
            $do_cdn = false;
196
        }
197
198
        if (
199
            $this->imgopt_active()
200
            && $do_cdn
201
            && $service_not_down
202
            && ( $not_launch_status || $this->launch_ok() )
203
        ) {
204
            return true;
205
        }
206
        return false;
207
    }
208
209
    public function get_imgopt_host()
210
    {
211
        static $imgopt_host = null;
212
213
        if ( null === $imgopt_host ) {
214
            $imgopt_host  = 'https://cdn.shortpixel.ai/';
215
            $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
216
            if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
217
                $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
218
            }
219
            $imgopt_host = apply_filters( 'autoptimize_filter_imgopt_host', $imgopt_host );
220
        }
221
222
        return $imgopt_host;
223
    }
224
225
    public static function get_imgopt_host_wrapper()
226
    {
227
        // needed for CI tests.
228
        $self = new self();
229
        return $self->get_imgopt_host();
230
    }
231
232
    public static function get_service_url_suffix()
233
    {
234
        $suffix = '/af/SPZURYE109483/' . AUTOPTIMIZE_SITE_DOMAIN;
235
236
        return $suffix;
237
    }
238
239
    public function get_img_quality_string()
240
    {
241
        static $quality = null;
242
243
        if ( null === $quality ) {
244
            $q_array = $this->get_img_quality_array();
245
            $setting = $this->get_img_quality_setting();
246
            $quality = apply_filters(
247
                'autoptimize_filter_imgopt_quality',
248
                'q_' . $q_array[ $setting ]
249
            );
250
        }
251
252
        return $quality;
253
    }
254
255
    public function get_img_quality_array()
256
    {
257
        static $map = null;
258
259
        if ( null === $map ) {
260
            $map = array(
261
                '1' => 'lossy',
262
                '2' => 'glossy',
263
                '3' => 'lossless',
264
            );
265
            $map = apply_filters(
266
                'autoptimize_filter_imgopt_quality_array',
267
                $map
268
            );
269
        }
270
271
        return $map;
272
    }
273
274
    public function get_img_quality_setting()
275
    {
276
        static $q = null;
277
278
        if ( null === $q ) {
279
            if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_select_field_2', $this->options ) ) {
280
                $setting = $this->options['autoptimize_imgopt_select_field_2'];
281
            }
282
283
            if ( ! isset( $setting ) || empty( $setting ) || ( '1' !== $setting && '3' !== $setting ) ) {
284
                // default image opt. value is 2 ("glossy").
285
                $q = '2';
286
            } else {
287
                $q = $setting;
288
            }
289
        }
290
291
        return $q;
292
    }
293
294
    public function filter_preconnect_imgopt_url( array $in )
295
    {
296
        $url_parts = parse_url( $this->get_imgopt_base_url() );
297
        $in[]      = $url_parts['scheme'] . '://' . $url_parts['host'];
298
299
        return $in;
300
    }
301
302
    /**
303
     * Makes sure given url contains the full scheme and hostname
304
     * in case they're not present already.
305
     *
306
     * @param string $in Image url to normalize.
307
     *
308
     * @return string
309
     */
310
    private function normalize_img_url( $in )
311
    {
312
        // Only parse the site url once.
313
        static $parsed_site_url = null;
314
        if ( null === $parsed_site_url ) {
315
            $parsed_site_url = parse_url( site_url() );
316
        }
317
318
        // get CDN domain once.
319
        static $cdn_domain = null;
320
        if ( is_null( $cdn_domain ) ) {
321
            $cdn_url = $this->get_cdn_url();
322
            if ( ! empty( $cdn_url ) ) {
323
                $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
324
            } else {
325
                $cdn_domain = '';
326
            }
327
        }
328
329
        /**
330
         * This method gets called a lot, often for identical urls it seems.
331
         * `filter_optimize_css_images()` calls us, uses the resulting url and
332
         * gives it to `can_optimize_image()`, and if that returns trueish
333
         * then `build_imgopt_url()` is called (which, again, calls this method).
334
         * Until we dig deeper into whether this all must really happen that
335
         * way, having an internal cache here helps (to avoid doing repeated
336
         * identical string operations).
337
         */
338
        static $cache = null;
339
        if ( null === $cache ) {
340
            $cache = array();
341
        }
342
343
        // Do the work on cache miss only.
344
        if ( ! isset( $cache[ $in ] ) ) {
345
            // Default to (the trimmed version of) what was given to us.
346
            $result = trim( $in );
347
348
            // Some silly plugins wrap background images in html-encoded quotes, so remove those from the img url.
349
            $result = $this->fix_silly_bgimg_quotes( $result );
350
351
            if ( autoptimizeUtils::is_protocol_relative( $result ) ) {
352
                $result = $parsed_site_url['scheme'] . ':' . $result;
353
            } elseif ( 0 === strpos( $result, '/' ) ) {
354
                // Root-relative...
355
                $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'] . $result;
356
            } elseif ( ! empty( $cdn_domain ) && strpos( $result, $cdn_domain ) !== 0 ) {
357
                $result = str_replace( $cdn_domain, $parsed_site_url['host'], $result );
358
            }
359
360
            // filter (default off) to remove QS from image URL's to avoid eating away optimization credits.
361
            if ( apply_filters( 'autoptimize_filter_imgopt_no_querystring', false ) && strpos( $result, '?' ) !== false ) {
362
                $result = strtok( $result, '?' );
363
            }
364
365
            $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
366
367
            // Store in cache.
368
            $cache[ $in ] = $result;
369
        }
370
371
        return $cache[ $in ];
372
    }
373
374
    public function filter_optimize_css_images( $in )
375
    {
376
        $in = $this->normalize_img_url( $in );
377
378
        if ( $this->can_optimize_image( $in ) ) {
379
            return $this->build_imgopt_url( $in, '', '' );
380
        } else {
381
            return $in;
382
        }
383
    }
384
385
    private function get_imgopt_base_url()
386
    {
387
        static $imgopt_base_url = null;
388
389
        if ( null === $imgopt_base_url ) {
390
            $imgopt_host     = $this->get_imgopt_host();
391
            $quality         = $this->get_img_quality_string();
392
            $ret_val         = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
393
            $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
394
            $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
395
        }
396
397
        return $imgopt_base_url;
398
    }
399
400
    private function can_optimize_image( $url, $tag = '', $testing = false )
401
    {
402
        static $cdn_url      = null;
403
        static $nopti_images = null;
404
405
        if ( null === $cdn_url ) {
406
            $cdn_url = apply_filters(
407
                'autoptimize_filter_base_cdnurl',
408
                autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' )
409
            );
410
        }
411
412
        if ( null === $nopti_images || $testing ) {
413
            if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_text_field_6', $this->options ) ) {
414
                $nopti_images = $this->options['autoptimize_imgopt_text_field_6'];
415
            }
416
            $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', $nopti_images );
417
        }
418
419
        $site_host  = AUTOPTIMIZE_SITE_DOMAIN;
420
        $url        = $this->normalize_img_url( $url );
421
        $url_parsed = parse_url( $url );
422
423
        if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
424
            return false;
425
        } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
426
            return false;
427
        } elseif ( strpos( $url, '.php' ) !== false ) {
428
            return false;
429
        } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp', '.avif' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
430
            // fixme: better check against end of string.
431
            return false;
432
        } elseif ( ! empty( $nopti_images ) ) {
433
            $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
434
            foreach ( $nopti_images_array as $nopti_image ) {
435
                if ( strpos( $url, $nopti_image ) !== false || ( ( '' !== $tag && strpos( $tag, $nopti_image ) !== false ) ) ) {
436
                    return false;
437
                }
438
            }
439
        }
440
        return true;
441
    }
442
443
    private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
444
    {
445
        // sanitize width and height.
446
        if ( strpos( $width, '%' ) !== false ) {
447
            $width = 0;
448
        }
449
        if ( strpos( $height, '%' ) !== false ) {
450
            $height = 0;
451
        }
452
        $width  = (int) $width;
453
        $height = (int) $height;
454
455
        $filtered_url = apply_filters(
456
            'autoptimize_filter_imgopt_build_url',
457
            $orig_url,
458
            $width,
459
            $height
460
        );
461
462
        // If filter modified the url, return that.
463
        if ( $filtered_url !== $orig_url ) {
464
            return $filtered_url;
465
        }
466
467
        $normalized_url = $this->normalize_img_url( $orig_url );
468
469
        // if the URL is ascii we check if we have a real URL with filter_var (which only works on ascii url's) and if not a real URL we return the original one.
470
        if ( apply_filters( 'autoptimize_filter_imgopt_check_normalized_url', true ) && ! preg_match( '/[^\x20-\x7e]/', $normalized_url ) && false === filter_var( $normalized_url, FILTER_VALIDATE_URL ) ) {
471
            return $orig_url;
472
        }
473
474
        $imgopt_base_url = $this->get_imgopt_base_url();
475
        $imgopt_size     = '';
476
477
        if ( $width && 0 !== $width ) {
478
            $imgopt_size = ',w_' . $width;
479
        }
480
481
        if ( $height && 0 !== $height ) {
482
            $imgopt_size .= ',h_' . $height;
483
        }
484
485
        $url = $imgopt_base_url . $imgopt_size . '/' . $normalized_url;
486
487
        return $url;
488
    }
489
490
    public function replace_data_thumbs( $matches )
491
    {
492
        return $this->replace_img_callback( $matches, 150, 150 );
493
    }
494
495
    public function replace_img_callback( $matches, $width = 0, $height = 0 )
496
    {
497
        $_normalized_img_url = $this->normalize_img_url( $matches[1] );
498
        if ( $this->can_optimize_image( $matches[1], $matches[0] ) ) {
499
            return str_replace( $matches[1], $this->build_imgopt_url( $_normalized_img_url, $width, $height ), $matches[0] );
500
        } else {
501
            return $matches[0];
502
        }
503
    }
504
505
    public function replace_icon_callback( $matches )
506
    {
507
        if ( array_key_exists( '2', $matches ) ) {
508
            $sizes  = explode( 'x', $matches[2] );
509
            $width  = $sizes[0];
510
            $height = $sizes[1];
511
        } else {
512
            $width  = 180;
513
            $height = 180;
514
        }
515
516
        // make sure we're not trying to optimize a *.ico file
517
        if ( strpos( $matches[1], '.ico' ) === false ) {
518
            return $this->replace_img_callback( $matches, $width, $height );
519
        } else {
520
            return $matches[0];
521
        }
522
    }
523
524
    public function filter_optimize_images( $in, $testing = false )
525
    {
526
        /*
527
         * potential future functional improvements:
528
         *
529
         * filter for critical CSS.
530
         */
531
        $to_replace = array();
532
533
        // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
534
        if ( $this->should_lazyload() ) {
535
            $in = autoptimizeBase::replace_contents_with_marker_if_exists(
536
                'SCRIPT',
537
                '<script',
538
                '#<(?:no)?script.*?<\/(?:no)?script>#is',
539
                $in
540
            );
541
        }
542
543
        // extract img tags.
544
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
545
            foreach ( $matches[0] as $tag ) {
546
                $tag = apply_filters( 'autoptimize_filter_imgopt_tag_preopt' , $tag );
547
548
                $orig_tag = $tag;
549
                $imgopt_w = '';
550
                $imgopt_h = '';
0 ignored issues
show
Unused Code introduced by
$imgopt_h is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
551
552
                // first do (data-)srcsets.
553
                if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
554
                    foreach ( $allsrcsets as $srcset ) {
555
                        $srcset      = $srcset[2];
556
                        $orig_srcset = $srcset;
557
                        $srcsets     = explode( ',', $srcset );
558
                        foreach ( $srcsets as $indiv_srcset ) {
559
                            $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
560
                            if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
561
                                $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
562
                            }
563
                            if ( $this->can_optimize_image( $indiv_srcset_parts[0], $tag, $testing ) ) {
564
                                $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
565
                                $srcset     = str_replace( $indiv_srcset_parts[0], $imgopt_url, $srcset );
566
                            }
567
                        }
568
                        $tag = str_replace( $orig_srcset, $srcset, $tag );
569
                    }
570
                }
571
572
                // proceed with img src.
573
                // get width and height and add to $imgopt_size.
574
                $_get_size = $this->get_size_from_tag( $tag );
575
                $imgopt_w  = $_get_size['width'];
576
                $imgopt_h  = $_get_size['height'];
577
578
                // then start replacing images src.
579
                if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
580
                    foreach ( $urls as $url ) {
581
                        $full_src_orig = $url[0];
582
                        $url           = $url[1];
583
                        if ( $this->can_optimize_image( $url, $tag, $testing ) ) {
584
                            $imgopt_url      = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
585
                            $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
586
                            $tag             = str_replace( $full_src_orig, $full_imgopt_src, $tag );
587
                        }
588
                    }
589
                }
590
591
                // do lazyload stuff.
592
                if ( $this->should_lazyload( $in ) && ! empty( $url ) ) {
593
                    // first do lpiq placeholder logic.
594
                    if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
595
                        // if all img src have been replaced during srcset, we have to extract the
596
                        // origin url from the imgopt one to be able to set a lqip placeholder.
597
                        $_url = substr( $url, strpos( $url, '/http' ) + 1 );
598
                    } else {
599
                        $_url = $url;
600
                    }
601
602
                    $_url = $this->normalize_img_url( $_url );
603
604
                    $placeholder = '';
605
                    if ( $this->can_optimize_image( $_url, $tag ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', true, $_url ) ) {
606
                        $lqip_w = '';
607
                        $lqip_h = '';
608
                        if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
609
                            $lqip_w = ',w_' . $imgopt_w;
610
                        }
611
                        if ( isset( $imgopt_h ) && ! empty( $imgopt_h ) ) {
612
                            $lqip_h = ',h_' . $imgopt_h;
613
                        }
614
                        $placeholder = $this->get_imgopt_host() . 'client/q_lqip,ret_wait' . $lqip_w . $lqip_h . '/' . $_url;
615
                    }
616
                    // then call add_lazyload-function with lpiq placeholder if set.
617
                    $tag = $this->add_lazyload( $tag, $placeholder );
618
                }
619
620
                $tag = apply_filters( 'autoptimize_filter_imgopt_tag_postopt' , $tag );
621
622
                // and add tag to array for later replacement.
623
                if ( $tag !== $orig_tag ) {
624
                    $to_replace[ $orig_tag ] = $tag;
625
                }
626
            }
627
        }
628
629
        // and replace all.
630
        $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
631
632
        // img thumbnails in e.g. woocommerce.
633
        if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_imgopt_datathumbs', true ) ) {
634
            $out = preg_replace_callback(
635
                '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
636
                array( $this, 'replace_data_thumbs' ),
637
                $out
638
            );
639
        }
640
641
        // background-image in inline style.
642 View Code Duplication
        if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
643
            $out = preg_replace_callback(
644
                '/style=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
645
                array( $this, 'replace_img_callback' ),
646
                $out
647
            );
648
        }
649
650
        // act on icon links.
651
        if ( ( strpos( $out, '<link rel="icon"' ) !== false || ( strpos( $out, "<link rel='icon'" ) !== false ) ) && apply_filters( 'autoptimize_filter_imgopt_linkicon', true ) ) {
652
            $out = preg_replace_callback(
653
                '/<link\srel=(?:"|\')(?:apple-touch-)?icon(?:"|\').*\shref=(?:"|\')(.*)(?:"|\')(?:\ssizes=(?:"|\')(\d*x\d*)(?:"|\'))?\s\/>/Um',
654
                array( $this, 'replace_icon_callback' ),
655
                $out
656
            );
657
        }
658
659
        // lazyload: restore noscript tags + lazyload picture source tags and bgimage.
660
        if ( $this->should_lazyload() ) {
661
            $out = autoptimizeBase::restore_marked_content(
662
                'SCRIPT',
663
                $out
664
            );
665
666
            $out = $this->process_picture_tag( $out, true, true );
667
            $out = $this->process_bgimage( $out );
668
        } else {
669
            $out = $this->process_picture_tag( $out, true, false );
670
        }
671
672
        return $out;
673
    }
674
675
    public function get_size_from_tag( $tag ) {
676
        // reusable function to extract widht and height from an image tag
677
        // enforcing a filterable maximum width and height (default 4999X4999).
678
        $width  = '';
679
        $height = '';
680
681
        if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $_width ) ) {
682
            if ( strpos( $_width[2], '%' ) === false ) {
683
                $width = (int) $_width[2];
684
            }
685
        }
686
        if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $_height ) ) {
687
            if ( strpos( $_height[2], '%' ) === false ) {
688
                $height = (int) $_height[2];
689
            }
690
        }
691
692
        // check for and enforce (filterable) max sizes.
693
        $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
694
        if ( $width > $_max_width ) {
695
            $_width = $_max_width;
696
            $height = $_width / $width * $height;
697
            $width  = $_width;
698
        }
699
        $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
700
        if ( $height > $_max_height ) {
701
            $_height = $_max_height;
702
            $width   = $_height / $height * $width;
703
            $height  = $_height;
704
        }
705
706
        return array(
707
            'width'  => $width,
708
            'height' => $height,
709
        );
710
    }
711
712
    /**
713
     * Lazyload functions
714
     */
715
    public static function should_lazyload_wrapper() {
716
        // needed in autoptimizeMain.php.
717
        $self = new self();
718
        return $self->should_lazyload();
719
    }
720
721
    public function should_lazyload( $context = '' ) {
722
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && false === $this->check_nolazy() ) {
723
            $lazyload_return = true;
724
        } else {
725
            $lazyload_return = false;
726
        }
727
        $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
728
729
        return $lazyload_return;
730
    }
731
732
    public function check_nolazy() {
733
        if ( array_key_exists( 'ao_nolazy', $_GET ) && '1' === $_GET['ao_nolazy'] ) {
734
            return true;
735
        } else {
736
            return false;
737
        }
738
    }
739
740
    public function filter_lazyload_images( $in )
741
    {
742
        // only used is image optimization is NOT active but lazyload is.
743
        $to_replace = array();
744
745
        // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
746
        $out = autoptimizeBase::replace_contents_with_marker_if_exists(
747
            'SCRIPT',
748
            '<script',
749
            '#<(?:no)?script.*?<\/(?:no)?script>#is',
750
            $in
751
        );
752
753
        // extract img tags and add lazyload attribs.
754
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
755
            foreach ( $matches[0] as $tag ) {
756
                if ( $this->should_lazyload( $out ) ) {
757
                    $to_replace[ $tag ] = $this->add_lazyload( $tag );
758
                }
759
            }
760
            $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $out );
761
        }
762
763
        // and also lazyload picture tag.
764
        $out = $this->process_picture_tag( $out, false, true );
765
766
        // and inline style blocks with background-image.
767
        $out = $this->process_bgimage( $out );
768
769
        // restore noscript tags.
770
        $out = autoptimizeBase::restore_marked_content(
771
            'SCRIPT',
772
            $out
773
        );
774
775
        return $out;
776
    }
777
778
    public function add_lazyload( $tag, $placeholder = '' ) {
779
        // adds actual lazyload-attributes to an image node.
780
        $this->lazyload_counter++;
781
782
        $_lazyload_from_nth = '';
783
        if ( array_key_exists( 'autoptimize_imgopt_number_field_7', $this->options ) ) {
784
            $_lazyload_from_nth = $this->options['autoptimize_imgopt_number_field_7'];
785
        }
786
        $_lazyload_from_nth = apply_filters( 'autoptimize_filter_imgopt_lazyload_from_nth', $_lazyload_from_nth );
787
788
        if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag && $this->lazyload_counter >= $_lazyload_from_nth ) {
789
            $tag = $this->maybe_fix_missing_quotes( $tag );
790
791
            // store original tag for use in noscript version.
792
            $noscript_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>';
793
794
            $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
795
796
            // insert lazyload class.
797
            $tag = $this->inject_classes_in_tag( $tag, "$lazyload_class " );
798
799
            if ( ! $placeholder || empty( $placeholder ) ) {
800
                // get image width & heigth for placeholder fun (and to prevent content reflow).
801
                $_get_size = $this->get_size_from_tag( $tag );
802
                $width     = $_get_size['width'];
803
                $height    = $_get_size['height'];
804
                if ( false === $width || empty( $width ) ) {
805
                    $width = 210; // default width for SVG placeholder.
806
                }
807
                if ( false === $height || empty( $height ) ) {
808
                    $height = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
809
                }
810
811
                // insert the actual lazyload stuff.
812
                // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
813
                $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
814
            }
815
816
            $tag = preg_replace( '/(\s)src=/', ' src=\'' . $placeholder . '\' data-src=', $tag );
817
            $tag = preg_replace( '/(\s)srcset=/', ' data-srcset=', $tag );
818
819
            // move sizes to data-sizes unless filter says no.
820
            if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
821
                $tag = str_replace( ' sizes=', ' data-sizes=', $tag );
822
            }
823
824
            // add the noscript-tag from earlier.
825
            $tag = $noscript_tag . $tag;
826
            $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
827
        }
828
829
        return $tag;
830
    }
831
832
    public function add_lazyload_js_footer() {
833
        if ( false === autoptimizeMain::should_buffer() ) {
834
            return;
835
        }
836
837
        // The JS will by default be excluded form autoptimization but this can be changed with a filter.
838
        $noptimize_flag = '';
839
        if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
840
            $noptimize_flag = ' data-noptimize="1"';
841
        }
842
843
        $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js?ao_version=' . AUTOPTIMIZE_PLUGIN_VERSION, __FILE__ );
844
        $cdn_url      = $this->get_cdn_url();
845
        if ( ! empty( $cdn_url ) ) {
846
            $cdn_url      = rtrim( $cdn_url, '/' );
847
            $lazysizes_js = str_replace( AUTOPTIMIZE_WP_SITE_URL, $cdn_url, $lazysizes_js );
848
        }
849
850
        $type_js = '';
851
        if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
852
            $type_js = ' type="text/javascript"';
853
        }
854
855
        // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
856
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<noscript><style>.lazyload{display:none;}</style></noscript>' );
857
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $type_js . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
858
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_js', '<script async' . $type_js . $noptimize_flag . ' src=\'' . $lazysizes_js . '\'></script>' );
859
860
        // And add webp detection and loading JS.
861
        if ( $this->should_ngimg() ) {
862
            // Add AVIF code, can be disabled for now to only do webp.
863
            if ( apply_filters( 'autoptimize_filter_imgopt_do_avif', true ) ) {
864
                $_ngimg_detect = 'function c_img(a,b){src="avif"==b?"data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABoAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACJtZGF0EgAKCBgADsgQEAwgMgwf8AAAWAAAAACvJ+o=":"data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==";var c=new Image;c.onload=function(){var d=0<c.width&&0<c.height;a(d,b)},c.onerror=function(){a(!1,b)},c.src=src}function s_img(a,b){w=window,"avif"==b?!1==a?c_img(s_img,"webp"):w.ngImg="avif":!1==a?w.ngImg=!1:w.ngImg="webp"}c_img(s_img,"avif");';
865
                $_ngimg_load   = 'document.addEventListener("lazybeforeunveil",function({target:a}){window.ngImg&&["data-src","data-srcset"].forEach(function(b){attr=a.getAttribute(b),null!==attr&&-1==attr.indexOf("/client/to_")&&a.setAttribute(b,attr.replace(/\/client\//,"/client/to_"+window.ngImg+","))})});';
866
            } else {
867
                $_ngimg_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);";
868
                $_ngimg_load   = "document.addEventListener('lazybeforeunveil',function({target:b}){window.supportsWebP&&['data-src','data-srcset'].forEach(function(c){attr=b.getAttribute(c),null!==attr&&-1==attr.indexOf('/client/to_webp')&&b.setAttribute(c,attr.replace(/\/client\//,'/client/to_webp,'))})});";
869
            }
870
            // Keeping autoptimize_filter_imgopt_webp_js filter for now, but it is deprecated as not only for webp any more.
871
            $_ngimg_output = apply_filters( 'autoptimize_filter_imgopt_webp_js', '<script' . $type_js . $noptimize_flag . '>' . $_ngimg_detect . $_ngimg_load . '</script>' );
872
            echo apply_filters( 'autoptimize_filter_imgopt_ngimg_js', $_ngimg_output );
873
        }
874
    }
875
876
    public function get_cdn_url() {
877
        // getting CDN url here to avoid having to make bigger changes to autoptimizeBase.
878
        static $cdn_url = null;
879
880
        if ( null === $cdn_url ) {
881
            $cdn_url = autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' );
882
            $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $cdn_url );
883
            $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
884
        }
885
886
        return $cdn_url;
887
    }
888
889
    public function get_lazyload_exclusions() {
890
        // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
891
        static $exclude_lazyload_array = null;
892
893
        if ( null === $exclude_lazyload_array ) {
894
            $options = $this->options;
895
896
            // set default exclusions.
897
            $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg', 'loading="eager"' );
898
899
            // add from setting.
900
            if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
901
                $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
902 View Code Duplication
                if ( ! empty( $exclude_lazyload_option ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
903
                    $exclude_lazyload_array = array_merge( $exclude_lazyload_array, array_filter( array_map( 'trim', explode( ',', $options['autoptimize_imgopt_text_field_5'] ) ) ) );
904
                }
905
            }
906
907
            // and filter for developer-initiated changes.
908
            $exclude_lazyload_array = apply_filters( 'autoptimize_filter_imgopt_lazyload_exclude_array', $exclude_lazyload_array );
909
        }
910
911
        return $exclude_lazyload_array;
912
    }
913
914
    public function inject_classes_in_tag( $tag, $target_class ) {
915
        if ( strpos( $tag, 'class=' ) !== false ) {
916
            $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
917
        } else {
918
            $tag = preg_replace( '/(<[a-zA-Z]*)\s/', '$1 class="' . trim( $target_class ) . '" ', $tag );
919
        }
920
921
        return $tag;
922
    }
923
924
    public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
925
        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';
926
    }
927
928
    public function should_ngimg() {
929
        static $ngimg_return = null;
930
931
        if ( is_null( $ngimg_return ) ) {
932
            // webp only works if imgopt and lazyload are also active.
933
            if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && $this->imgopt_active() ) {
934
                $ngimg_return = true;
935
            } else {
936
                $ngimg_return = false;
937
            }
938
        }
939
940
        return $ngimg_return;
941
    }
942
943
    public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
944
        // check if "<picture" is present and if filter allows us to process <picture>.
945
        if ( strpos( $in, '<picture' ) === false || apply_filters( 'autoptimize_filter_imgopt_dopicture', true ) === false ) {
946
            return $in;
947
        }
948
949
        $_exclusions     = $this->get_lazyload_exclusions();
950
        $to_replace_pict = array();
951
952
        // extract and process each picture-node.
953
        preg_match_all( '#<picture.*</picture>#Usmi', $in, $_pictures, PREG_SET_ORDER );
954
        foreach ( $_pictures as $_picture ) {
0 ignored issues
show
Bug introduced by
The expression $_pictures of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
955
            $_picture = $this->maybe_fix_missing_quotes( $_picture );
956
            if ( strpos( $_picture[0], '<source ' ) !== false && preg_match_all( '#<source .*srcset=(?:"|\')(?!data)(.*)(?:"|\').*>#Usmi', $_picture[0], $_sources, PREG_SET_ORDER ) !== false ) {
957
                foreach ( $_sources as $_source ) {
0 ignored issues
show
Bug introduced by
The expression $_sources of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
958
                    $_picture_replacement = $_source[0];
959
960
                    // should we optimize the image?
961
                    if ( $imgopt && $this->can_optimize_image( $_source[1], $_picture[0] ) ) {
962
                        $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
963
                    }
964
                    // should we lazy-load?
965
                    if ( $lazy && $this->should_lazyload() && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
966
                        $_picture_replacement = str_replace( ' srcset=', ' data-srcset=', $_picture_replacement );
967
                    }
968
                    $to_replace_pict[ $_source[0] ] = $_picture_replacement;
969
                }
970
            }
971
        }
972
973
        // and return the fully procesed $in.
974
        $out = str_replace( array_keys( $to_replace_pict ), array_values( $to_replace_pict ), $in );
975
976
        return $out;
977
    }
978
979
    public function process_bgimage( $in ) {
980 View Code Duplication
        if ( strpos( $in, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_lazyload_backgroundimages', true ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
981
            $out = preg_replace_callback(
982
                '/(<(?:article|aside|body|div|footer|header|p|section|span|table)[^>]*)\sstyle=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
983
                array( $this, 'lazyload_bgimg_callback' ),
984
                $in
985
            );
986
            return $out;
987
        }
988
        return $in;
989
    }
990
991
    public function lazyload_bgimg_callback( $matches ) {
992
        if ( str_ireplace( $this->get_lazyload_exclusions(), '', $matches[0] ) === $matches[0] ) {
993
            // get placeholder & lazyload class strings.
994
            $placeholder    = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( 500, 300 ) );
995
            $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
996
            // replace background-image URL with SVG placeholder.
997
            $out = str_replace( 'url(' . $matches[2], 'url(' . $placeholder, $matches[0] );
998
            // sanitize bgimg src for quote sillyness.
999
            $bgimg_src = $this->fix_silly_bgimg_quotes( $matches[2] );
1000
            // add data-bg attribute with real background-image URL for lazyload to pick up.
1001
            $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . $bgimg_src . '"', $out );
1002
            // and finally add lazyload class to tag.
1003
            $out = $this->inject_classes_in_tag( $out, "$lazyload_class " );
1004
            return $out;
1005
        }
1006
        return $matches[0];
1007
    }
1008
    
1009
    public function fix_silly_bgimg_quotes( $tag_in ) {
1010
        // some themes/ pagebuilders wrap backgroundimages in HTML-encoded quotes (or linebreaks) which breaks imgopt/ lazyloading, this removes them.
1011
        return trim( str_replace( array( "\r\n", '&quot;', '&#034;', '&apos;', '&#039;' ), '', $tag_in ) );
1012
    }
1013
1014
    public function maybe_fix_missing_quotes( $tag_in ) {
1015
        // W3TC's Minify_HTML class removes quotes around attribute value, this re-adds them for the class and width/height attributes so we can lazyload properly.
1016
        if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
1017
            $tag_out = preg_replace( '/class\s?=([^("|\')]*)(\s|>)/U', 'class=\'$1\'$2', $tag_in );
1018
            $tag_out = preg_replace( '/\s(width|height)=(?:"|\')?([^\s"\'>]*)(?:"|\')?/', ' $1=\'$2\'', $tag_out );
1019
            return $tag_out;
1020
        } else {
1021
            return $tag_in;
1022
        }
1023
    }
1024
1025
    /**
1026
     * Admin page logic and related functions below.
1027
     */
1028
    public function imgopt_admin_menu()
1029
    {
1030
        // no acces if multisite and not network admin and no site config allowed.
1031
        if ( autoptimizeConfig::should_show_menu_tabs() ) {
1032
            add_submenu_page(
1033
                null,
1034
                'autoptimize_imgopt',
1035
                'autoptimize_imgopt',
1036
                'manage_options',
1037
                'autoptimize_imgopt',
1038
                array( $this, 'imgopt_options_page' )
1039
            );
1040
        }
1041
        register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
1042
    }
1043
1044
    public function add_imgopt_tab( $in )
1045
    {
1046
        if ( autoptimizeConfig::should_show_menu_tabs() ) {
1047
            $in = array_merge( $in, array( 'autoptimize_imgopt' => __( 'Images', 'autoptimize' ) ) );
1048
        }
1049
1050
        return $in;
1051
    }
1052
1053
    public function imgopt_options_page()
1054
    {
1055
        // Check querystring for "refreshCacheChecker" and call cachechecker if so.
1056
        if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
1057
            $this->query_img_provider_stats( true );
1058
        }
1059
1060
        $options       = $this->fetch_options();
1061
        $sp_url_suffix = $this->get_service_url_suffix();
1062
        ?>
1063
    <style>
1064
        #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
1065
        #ao_settings_form .form-table th {font-weight: normal;}
1066
        #autoptimize_imgopt_descr{font-size: 120%;}
1067
    </style>
1068
    <script>document.title = "Autoptimize: <?php _e( 'Images', 'autoptimize' ); ?> " + document.title;</script>
1069
    <div class="wrap">
1070
    <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
1071
        <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
1072
        <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
1073
            <div class="notice-warning notice"><p>
1074
            <?php
1075
            // translators: "Autoptimize support forum" will appear in a "a href".
1076
            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>' );
1077
            ?>
1078
            </p></div>
1079
        <?php } ?>
1080
1081
        <?php if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! autoptimizeImages::instance()->launch_ok() ) { ?>
0 ignored issues
show
Bug Best Practice introduced by
The expression \autoptimizeImages::instance()->launch_ok() of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1082
            <div class="notice-warning notice"><p>
1083
            <?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' ); ?>
1084
            </p></div>
1085
        <?php } ?>
1086
1087
        <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
1088
            <div class="notice-warning notice"><p>
1089
            <?php
1090
            // translators: "disable  Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
1091
            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>' );
1092
            ?>
1093
            </p></div>
1094
        <?php } ?>
1095
    <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
1096
        <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
1097
        <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
1098
        <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 and AVIF support included!', 'autoptimize' ); ?></span>
1099
        <table class="form-table">
1100
            <tr>
1101
                <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
1102
                <td>
1103
                    <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>
1104
                    <?php
1105
                    // show shortpixel status.
1106
                    $_notice = autoptimizeImages::instance()->get_imgopt_status_notice();
1107
                    if ( $_notice ) {
1108
                        switch ( $_notice['status'] ) {
1109
                            case 2:
1110
                                $_notice_color = 'green';
1111
                                break;
1112
                            case 1:
1113
                                $_notice_color = 'orange';
1114
                                break;
1115
                            case -1:
1116
                            case -2:
1117
                            case -3:
1118
                                $_notice_color = 'red';
1119
                                break;
1120
                            default:
1121
                                $_notice_color = 'green';
1122
                        }
1123
                        echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
1124
                    } else {
1125
                        // translators: link points to shortpixel.
1126
                        $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love by speeding up your website. Start serving on-the-fly optimized images (also in the "next-gen" <strong>WebP</strong> and <strong>AVIF</strong> image formats) by %1$sShortPixel%2$s. The optimized images are cached and served from %3$sShortPixel\'s global CDN%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>', '<a href="https://help.shortpixel.com/article/62-where-does-the-cdn-has-pops" target="_blank">' );
1127
                        if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
1128
                            $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' );
1129
                        } else {
1130
                            // translators: link points to shortpixel.
1131
                            $upsell_msg_2 = sprintf( __( '%1$sSign-up now%2$s to receive x2 more CDN traffic or image optimization credits for free! This offer also applies to any future plan that you\'ll choose to purchase.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
1132
                        }
1133
                        echo apply_filters( 'autoptimize_imgopt_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
1134
                    }
1135
                    // translators: link points to shortpixel FAQ.
1136
                    $faqcopy = sprintf( __( '<strong>Questions</strong>? Have a look at the %1$sAutoptimize + ShortPixel FAQ%2$s!', 'autoptimize' ), '<strong><a href="https://help.shortpixel.com/category/405-autoptimize" target="_blank">', '</strong></a>' );
1137
                    $faqcopy = $faqcopy . ' ' . __( 'Only works for websites and images that are publicly available.', 'autoptimize' );
1138
                    // translators: links points to shortpixel TOS & Privacy Policy.
1139
                    $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>' );
1140
                    echo apply_filters( 'autoptimize_imgopt_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
1141
                    ?>
1142
                </td>
1143
            </tr>
1144 View Code Duplication
            <tr id='autoptimize_imgopt_optimization_exclusions' <?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"'; } ?>>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1145
                <th scope="row"><?php _e( 'Optimization exclusions', 'autoptimize' ); ?></th>
1146
                <td>
1147
                    <label><input type='text' style='width:80%' id='autoptimize_imgopt_optimization_exclusions' name='autoptimize_imgopt_settings[autoptimize_imgopt_text_field_6]' value='<?php if ( ! empty( $options['autoptimize_imgopt_text_field_6'] ) ) { echo esc_attr( $options['autoptimize_imgopt_text_field_6'] ); } ?>'><br /><?php _e( 'Comma-separated list of image classes or filenames that should not be optimized.', 'autoptimize' ); ?></label>
1148
                </td>
1149
            </tr>
1150 View Code Duplication
            <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"'; } ?>>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1151
                <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
1152
                <td>
1153
                    <label>
1154
                    <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
1155
                        <?php
1156
                        $_imgopt_array = autoptimizeImages::instance()->get_img_quality_array();
1157
                        $_imgopt_val   = autoptimizeImages::instance()->get_img_quality_setting();
1158
1159
                        foreach ( $_imgopt_array as $key => $value ) {
1160
                            echo '<option value="' . $key . '"';
1161
                            if ( $_imgopt_val == $key ) {
1162
                                echo ' selected';
1163
                            }
1164
                            echo '>' . ucfirst( $value ) . '</option>';
1165
                        }
1166
                        echo "\n";
1167
                        ?>
1168
                    </select>
1169
                    </label>
1170
                    <p>
1171
                        <?php
1172
                            // translators: link points to shortpixel image test page.
1173
                            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>' ) );
1174
                        ?>
1175
                    </p>
1176
                </td>
1177
            </tr>
1178 View Code Duplication
            <tr id='autoptimize_imgopt_ngimg' <?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"'; } ?>>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1179
                <th scope="row"><?php _e( 'Load WebP or AVIF in supported browsers?', 'autoptimize' ); ?></th>
1180
                <td>
1181
                    <label><input type='checkbox' id='autoptimize_imgopt_ngimg_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 or AVIF image formats to any browser that supports it (requires lazy load to be active).', 'autoptimize' ); ?></label>
1182
                </td>
1183
            </tr>
1184
            <tr>
1185
                <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1186
                <td>
1187
                    <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>
1188
                </td>
1189
            </tr>
1190 View Code Duplication
            <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="autoptimize_lazyload_child hidden"'; } else { echo 'class="autoptimize_lazyload_child"'; } ?>>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1191
                <th scope="row"><?php _e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1192
                <td>
1193
                    <label><input type='text' style='width:80%' id='autoptimize_imgopt_lazyload_exclusions_text' 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>
1194
                </td>
1195
            </tr>
1196 View Code Duplication
            <tr id='autoptimize_imgopt_lazyload_from_nth_image' <?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="autoptimize_lazyload_child hidden"'; } else { echo 'class="autoptimize_lazyload_child"'; } ?>>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1197
                <th scope="row"><?php _e( 'Lazy-load from nth image', 'autoptimize' ); ?></th>
1198
                <td>
1199
                    <label><input type='number' min='0' max='50' style='width:80%' id='autoptimize_imgopt_lazyload_from_nth_image_number' name='autoptimize_imgopt_settings[autoptimize_imgopt_number_field_7]' value='<?php if ( ! empty( $options['autoptimize_imgopt_number_field_7'] ) ) { echo esc_attr( $options['autoptimize_imgopt_number_field_7'] ); } else { echo '0'; } ?>'><br /><?php _e( 'Don\'t lazyload the first X images, \'0\' lazyloads all.', 'autoptimize' ); ?></label>
1200
                </td>
1201
            </tr>
1202
        </table>
1203
        <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1204
    </form>
1205
    <script>
1206
        jQuery(document).ready(function() {
1207
            jQuery("#autoptimize_imgopt_checkbox").change(function() {
1208
                if (this.checked) {
1209
                    jQuery("#autoptimize_imgopt_quality").show("slow");
1210
                    jQuery("#autoptimize_imgopt_ngimg").show("slow");
1211
                    jQuery("#autoptimize_imgopt_optimization_exclusions").show("slow");
1212
                } else {
1213
                    jQuery("#autoptimize_imgopt_quality").hide("slow");
1214
                    jQuery("#autoptimize_imgopt_ngimg").hide("slow");
1215
                    jQuery("#autoptimize_imgopt_optimization_exclusions").hide("slow");
1216
                }
1217
            });
1218
            jQuery("#autoptimize_imgopt_ngimg_checkbox").change(function() {
1219
                if (this.checked) {
1220
                    jQuery("#autoptimize_imgopt_lazyload_checkbox")[0].checked = true;
1221
                    jQuery(".autoptimize_lazyload_child").show("slow");
1222
                }
1223
            });
1224
            jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1225
                if (this.checked) {
1226
                    jQuery(".autoptimize_lazyload_child").show("slow");
1227
                } else {
1228
                    jQuery(".autoptimize_lazyload_child").hide("slow");
1229
                    jQuery("#autoptimize_imgopt_ngimg_checkbox")[0].checked = false;
1230
                }
1231
            });
1232
        });
1233
    </script>
1234
        <?php
1235
    }
1236
1237
    /**
1238
     * Ïmg opt status as used on dashboard.
1239
     */
1240
    public function get_imgopt_status_notice() {
1241
        if ( $this->imgopt_active() ) {
1242
            $_imgopt_notice  = '';
0 ignored issues
show
Unused Code introduced by
$_imgopt_notice is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1243
            $_stat           = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1244
            $_site_host      = AUTOPTIMIZE_SITE_DOMAIN;
1245
            $_imgopt_upsell  = 'https://shortpixel.com/aospai/af/SPZURYE109483/' . $_site_host;
1246
            $_imgopt_assoc   = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1247
            $_imgopt_unreach = 'https://shortpixel.helpscoutdocs.com/article/148-why-are-my-images-redirected-from-cdn-shortpixel-ai';
1248
1249
            if ( is_array( $_stat ) ) {
1250
                if ( 1 == $_stat['Status'] ) {
1251
                    // translators: "add more credits" will appear in a "a href".
1252
                    $_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>' );
1253
                } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1254
                    // translators: "add more credits" will appear in a "a href".
1255
                    $_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>' );
1256
                    // translators: "associate your domain" will appear in a "a href".
1257
                    $_imgopt_notice = $_imgopt_notice . ' ' . sprintf( __( 'If you have enough CDN quota remaining, then you may need to %1$sassociate your domain%2$s to your Shortpixel account.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $_imgopt_assoc . '" target="_blank">', '</a>' );
1258
                } elseif ( -3 == $_stat['Status'] ) {
1259
                    // translators: "check the documentation here" will appear in a "a href".
1260
                    $_imgopt_notice = sprintf( __( 'It seems ShortPixel image optimization is not able to fetch images from your site, %1$scheck the documentation here%2$s for more information', 'autoptimize' ), '<a href="' . $_imgopt_unreach . '" target="_blank">', '</a>' );
1261
                } else {
1262
                    $_imgopt_upsell = 'https://shortpixel.com/g/af/SPZURYE109483';
1263
                    // translators: "log in to check your account" will appear in a "a href".
1264
                    $_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>' );
1265
                }
1266
1267
                // add info on freshness + refresh link if status is not 2 (good shape).
1268
                if ( 2 != $_stat['Status'] ) {
1269
                    $_imgopt_stats_refresh_url = add_query_arg( array(
1270
                        'page'                => 'autoptimize_imgopt',
1271
                        'refreshImgProvStats' => '1',
1272
                    ), admin_url( 'options-general.php' ) );
1273
                    if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $_stat of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1274
                        $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( autoptimizeOptionWrapper::get_option( 'time_format' ), $_stat['timestamp'] );
1275
                    } else {
1276
                        $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1277
                    }
1278
                    $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1279
                    // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1280
                    $_imgopt_notice .= sprintf( __( 'you can click %1$shere to refresh your quota status%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1281
                }
1282
1283
                // and make the full notice filterable.
1284
                $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1285
1286
                return array(
1287
                    'status' => $_stat['Status'],
1288
                    'notice' => $_imgopt_notice,
1289
                );
1290
            }
1291
        }
1292
        return false;
1293
    }
1294
1295
    public static function get_imgopt_status_notice_wrapper() {
1296
        // needed for notice being shown in autoptimizeCacheChecker.php.
1297
        $self = new self();
1298
        return $self->get_imgopt_status_notice();
1299
    }
1300
1301
    /**
1302
     * Get img provider stats (used to display notice).
1303
     */
1304
    public function query_img_provider_stats( $_refresh = false ) {
1305
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) ) {
1306
            $url      = '';
1307
            $stat_dom = 'https://no-cdn.shortpixel.ai/';
1308
            $endpoint = $stat_dom . 'read-domain/';
1309
            $domain   = AUTOPTIMIZE_SITE_DOMAIN;
1310
1311
            // make sure parse_url result makes sense, keeping $url empty if not.
1312
            if ( $domain && ! empty( $domain ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $domain of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1313
                $url = $endpoint . $domain;
1314
                if ( true === $_refresh ) {
1315
                    $url = $url . '/refresh';
1316
                }
1317
            }
1318
1319
            $url = apply_filters(
1320
                'autoptimize_filter_imgopt_stat_url',
1321
                $url
1322
            );
1323
1324
            // only do the remote call if $url is not empty to make sure no parse_url
1325
            // weirdness results in useless calls.
1326 View Code Duplication
            if ( ! empty( $url ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1327
                $response = wp_remote_get( $url );
1328
                if ( ! is_wp_error( $response ) ) {
1329
                    if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1330
                        $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1331
                        autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_provider_stat', $stats );
1332
                    }
1333
                }
1334
            }
1335
        }
1336
    }
1337
1338
    public static function get_img_provider_stats()
1339
    {
1340
        // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
1341
        $self = new self();
1342
        return $self->query_img_provider_stats();
1343
    }
1344
1345
    /**
1346
     * Determines and returns the service launch status.
1347
     *
1348
     * @return bool
1349
     */
1350
    public function launch_ok()
1351
    {
1352
        static $launch_status = null;
1353
1354
        if ( null === $launch_status ) {
1355
            $avail_imgopt  = $this->options['availabilities']['extra_imgopt'];
1356
            $magic_number  = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1357
            $has_launched  = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_launched', '' );
1358
            $launch_status = false;
1359
            if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1360
                $launch_status = true;
1361
                if ( ! $has_launched ) {
1362
                    autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_launched', 'on' );
1363
                }
1364
            }
1365
        }
1366
1367
        return $launch_status;
1368
    }
1369
1370
    public static function launch_ok_wrapper() {
1371
        // needed for "plug" notice in autoptimizeMain.php.
1372
        $self = new self();
1373
        return $self->launch_ok();
1374
    }
1375
1376
    public function get_imgopt_provider_userstatus() {
1377
        static $_provider_userstatus = null;
1378
1379
        if ( is_null( $_provider_userstatus ) ) {
1380
            $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1381
            if ( is_array( $_stat ) ) {
1382
                if ( array_key_exists( 'Status', $_stat ) ) {
1383
                    $_provider_userstatus['Status'] = $_stat['Status'];
1384
                } else {
1385
                    // if no stats then we assume all is well.
1386
                    $_provider_userstatus['Status'] = 2;
1387
                }
1388
                if ( array_key_exists( 'timestamp', $_stat ) ) {
1389
                    $_provider_userstatus['timestamp'] = $_stat['timestamp'];
1390
                } else {
1391
                    // if no timestamp then we return "".
1392
                    $_provider_userstatus['timestamp'] = '';
1393
                }
1394
            } else {
1395
                // no provider_stat yet, assume/ return all OK.
1396
                $_provider_userstatus['Status']    = 2;
1397
                $_provider_userstatus['timestamp'] = '';
1398
            }
1399
        }
1400
1401
        return $_provider_userstatus;
1402
    }
1403
}
1404