Completed
Push — master ( b4100c...ed2097 )
by frank
01:49
created

autoptimizeImages::get_img_quality_setting()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 5
nop 0
dl 0
loc 19
rs 8.4444
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 = autoptimizeOptionWrapper::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'] = autoptimizeOptionWrapper::get_option( 'autoptimize_service_availablity' );
53
54
        if ( empty( $value['availabilities'] ) ) {
55
            $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...
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 = autoptimizeOptionWrapper::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 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...
98
    {
99
        if ( is_admin() ) {
100
            if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
101
                add_action( 'network_admin_menu', array( $this, 'imgopt_admin_menu' ) );
102
            } else {
103
                add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
104
            }
105
            add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_imgopt_tab' ), 9 );
106
        } else {
107
            add_action( 'wp', array( $this, 'run_on_frontend' ) );
108
        }
109
    }
110
111
    public function run_on_frontend() {
112
        if ( ! $this->should_run() ) {
113
            if ( $this->should_lazyload() ) {
114
                add_filter(
115
                    'wp_lazy_loading_enabled',
116
                    '__return_false'
117
                );
118
                add_filter(
119
                    'autoptimize_html_after_minify',
120
                    array( $this, 'filter_lazyload_images' ),
121
                    10,
122
                    1
123
                );
124
                add_action(
125
                    'wp_footer',
126
                    array( $this, 'add_lazyload_js_footer' ),
127
                    10,
128
                    0
129
                );
130
            }
131
            return;
132
        }
133
134
        $active = false;
135
136
        if ( apply_filters( 'autoptimize_filter_imgopt_do', true ) ) {
137
            add_filter(
138
                'autoptimize_html_after_minify',
139
                array( $this, 'filter_optimize_images' ),
140
                10,
141
                1
142
            );
143
            $active = true;
144
        }
145
146
        if ( apply_filters( 'autoptimize_filter_imgopt_do_css', true ) ) {
147
            add_filter(
148
                'autoptimize_filter_base_replace_cdn',
149
                array( $this, 'filter_optimize_css_images' ),
150
                10,
151
                1
152
            );
153
            $active = true;
154
        }
155
156
        if ( $active ) {
157
            add_filter(
158
                'autoptimize_extra_filter_tobepreconn',
159
                array( $this, 'filter_preconnect_imgopt_url' ),
160
                10,
161
                1
162
            );
163
        }
164
165
        if ( $this->should_lazyload() ) {
166
            add_filter(
167
                'wp_lazy_loading_enabled',
168
                '__return_false'
169
            );
170
            add_action(
171
                'wp_footer',
172
                array( $this, 'add_lazyload_js_footer' ),
173
                10,
174
                0
175
            );
176
        }
177
    }
178
179
    /**
180
     * Basic checks before we can run.
181
     *
182
     * @return bool
183
     */
184
    protected function should_run()
185
    {
186
        $opts              = $this->options;
187
        $service_not_down  = ( 'down' !== $opts['availabilities']['extra_imgopt']['status'] );
188
        $not_launch_status = ( 'launch' !== $opts['availabilities']['extra_imgopt']['status'] );
189
190
        $do_cdn      = true;
191
        $_userstatus = $this->get_imgopt_provider_userstatus();
192
        if ( isset( $_userstatus['Status'] ) && ( -2 == $_userstatus['Status'] || -3 == $_userstatus['Status'] ) ) {
193
            // don't even attempt to put images on CDN if heavily exceeded threshold or if site not reachable.
194
            $do_cdn = false;
195
        }
196
197
        if (
198
            $this->imgopt_active()
199
            && $do_cdn
200
            && $service_not_down
201
            && ( $not_launch_status || $this->launch_ok() )
202
        ) {
203
            return true;
204
        }
205
        return false;
206
    }
207
208
    public function get_imgopt_host()
209
    {
210
        static $imgopt_host = null;
211
212
        if ( null === $imgopt_host ) {
213
            $imgopt_host  = 'https://cdn.shortpixel.ai/';
214
            $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
215
            if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
216
                $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
217
            }
218
            $imgopt_host = apply_filters( 'autoptimize_filter_imgopt_host', $imgopt_host );
219
        }
220
221
        return $imgopt_host;
222
    }
223
224
    public static function get_imgopt_host_wrapper()
225
    {
226
        // needed for CI tests.
227
        $self = new self();
228
        return $self->get_imgopt_host();
229
    }
230
231
    public static function get_service_url_suffix()
232
    {
233
        $suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
234
235
        return $suffix;
236
    }
237
238
    public function get_img_quality_string()
239
    {
240
        static $quality = null;
241
242
        if ( null === $quality ) {
243
            $q_array = $this->get_img_quality_array();
244
            $setting = $this->get_img_quality_setting();
245
            $quality = apply_filters(
246
                'autoptimize_filter_imgopt_quality',
247
                'q_' . $q_array[ $setting ]
248
            );
249
        }
250
251
        return $quality;
252
    }
253
254
    public function get_img_quality_array()
255
    {
256
        static $map = null;
257
258
        if ( null === $map ) {
259
            $map = array(
260
                '1' => 'lossy',
261
                '2' => 'glossy',
262
                '3' => 'lossless',
263
            );
264
            $map = apply_filters(
265
                'autoptimize_filter_imgopt_quality_array',
266
                $map
267
            );
268
        }
269
270
        return $map;
271
    }
272
273
    public function get_img_quality_setting()
274
    {
275
        static $q = null;
276
277
        if ( null === $q ) {
278
            if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_select_field_2', $this->options ) ) {
279
                $setting = $this->options['autoptimize_imgopt_select_field_2'];
280
            }
281
282
            if ( ! isset( $setting ) || empty( $setting ) || ( '1' !== $setting && '3' !== $setting ) ) {
283
                // default image opt. value is 2 ("glossy").
284
                $q = '2';
285
            } else {
286
                $q = $setting;
287
            }
288
        }
289
290
        return $q;
291
    }
292
293
    public function filter_preconnect_imgopt_url( array $in )
294
    {
295
        $url_parts = parse_url( $this->get_imgopt_base_url() );
296
        $in[]      = $url_parts['scheme'] . '://' . $url_parts['host'];
297
298
        return $in;
299
    }
300
301
    /**
302
     * Makes sure given url contains the full scheme and hostname
303
     * in case they're not present already.
304
     *
305
     * @param string $in Image url to normalize.
306
     *
307
     * @return string
308
     */
309
    private function normalize_img_url( $in )
310
    {
311
        // Only parse the site url once.
312
        static $parsed_site_url = null;
313
        if ( null === $parsed_site_url ) {
314
            $parsed_site_url = parse_url( site_url() );
315
        }
316
317
        // get CDN domain once.
318
        static $cdn_domain = null;
319
        if ( is_null( $cdn_domain ) ) {
320
            $cdn_url = $this->get_cdn_url();
321
            if ( ! empty( $cdn_url ) ) {
322
                $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
323
            } else {
324
                $cdn_domain = '';
325
            }
326
        }
327
328
        /**
329
         * This method gets called a lot, often for identical urls it seems.
330
         * `filter_optimize_css_images()` calls us, uses the resulting url and
331
         * gives it to `can_optimize_image()`, and if that returns trueish
332
         * then `build_imgopt_url()` is called (which, again, calls this method).
333
         * Until we dig deeper into whether this all must really happen that
334
         * way, having an internal cache here helps (to avoid doing repeated
335
         * identical string operations).
336
         */
337
        static $cache = null;
338
        if ( null === $cache ) {
339
            $cache = array();
340
        }
341
342
        // Do the work on cache miss only.
343
        if ( ! isset( $cache[ $in ] ) ) {
344
            // Default to (the trimmed version of) what was given to us.
345
            $result = trim( $in );
346
347
            // Some silly plugins wrap background images in html-encoded quotes, so remove those from the img url.
348
            if ( strpos( $result, '&quot;' ) !== false ) {
349
                $result = str_replace( '&quot;', '', $result );
350
            }
351
352
            if ( autoptimizeUtils::is_protocol_relative( $result ) ) {
353
                $result = $parsed_site_url['scheme'] . ':' . $result;
354
            } elseif ( 0 === strpos( $result, '/' ) ) {
355
                // Root-relative...
356
                $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'] . $result;
357
            } elseif ( ! empty( $cdn_domain ) && strpos( $result, $cdn_domain ) !== 0 ) {
358
                $result = str_replace( $cdn_domain, $parsed_site_url['host'], $result );
359
            }
360
361
            $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
362
363
            // Store in cache.
364
            $cache[ $in ] = $result;
365
        }
366
367
        return $cache[ $in ];
368
    }
369
370
    public function filter_optimize_css_images( $in )
371
    {
372
        $in = $this->normalize_img_url( $in );
373
374
        if ( $this->can_optimize_image( $in ) ) {
375
            return $this->build_imgopt_url( $in, '', '' );
376
        } else {
377
            return $in;
378
        }
379
    }
380
381
    private function get_imgopt_base_url()
382
    {
383
        static $imgopt_base_url = null;
384
385
        if ( null === $imgopt_base_url ) {
386
            $imgopt_host     = $this->get_imgopt_host();
387
            $quality         = $this->get_img_quality_string();
388
            $ret_val         = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
389
            $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
390
            $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
391
        }
392
393
        return $imgopt_base_url;
394
    }
395
396
    private function can_optimize_image( $url )
397
    {
398
        static $cdn_url      = null;
399
        static $nopti_images = null;
400
401
        if ( null === $cdn_url ) {
402
            $cdn_url = apply_filters(
403
                'autoptimize_filter_base_cdnurl',
404
                autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' )
405
            );
406
        }
407
408
        if ( null === $nopti_images ) {
409
            $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', '' );
410
        }
411
412
        $site_host  = AUTOPTIMIZE_SITE_DOMAIN;
413
        $url        = $this->normalize_img_url( $url );
414
        $url_parsed = parse_url( $url );
415
416
        if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
417
            return false;
418
        } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
419
            return false;
420
        } elseif ( strpos( $url, '.php' ) !== false ) {
421
            return false;
422
        } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
423
            // fixme: better check against end of string.
424
            return false;
425
        } elseif ( ! empty( $nopti_images ) ) {
426
            $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
427
            foreach ( $nopti_images_array as $nopti_image ) {
428
                if ( strpos( $url, $nopti_image ) !== false ) {
429
                    return false;
430
                }
431
            }
432
        }
433
        return true;
434
    }
435
436
    private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
437
    {
438
        // sanitize width and height.
439
        if ( strpos( $width, '%' ) !== false ) {
440
            $width = 0;
441
        }
442
        if ( strpos( $height, '%' ) !== false ) {
443
            $height = 0;
444
        }
445
        $width  = (int) $width;
446
        $height = (int) $height;
447
448
        $filtered_url = apply_filters(
449
            'autoptimize_filter_imgopt_build_url',
450
            $orig_url,
451
            $width,
452
            $height
453
        );
454
455
        // If filter modified the url, return that.
456
        if ( $filtered_url !== $orig_url ) {
457
            return $filtered_url;
458
        }
459
460
        $orig_url        = $this->normalize_img_url( $orig_url );
461
        $imgopt_base_url = $this->get_imgopt_base_url();
462
        $imgopt_size     = '';
463
464
        if ( $width && 0 !== $width ) {
465
            $imgopt_size = ',w_' . $width;
466
        }
467
468
        if ( $height && 0 !== $height ) {
469
            $imgopt_size .= ',h_' . $height;
470
        }
471
472
        $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
473
474
        return $url;
475
    }
476
477
    public function replace_data_thumbs( $matches )
478
    {
479
        return $this->replace_img_callback( $matches, 150, 150 );
480
    }
481
482
    public function replace_img_callback( $matches, $width = 0, $height = 0 )
483
    {
484
        $_normalized_img_url = $this->normalize_img_url( $matches[1] );
485
        if ( $this->can_optimize_image( $matches[1] ) ) {
486
            return str_replace( $matches[1], $this->build_imgopt_url( $_normalized_img_url, $width, $height ), $matches[0] );
487
        } else {
488
            return $matches[0];
489
        }
490
    }
491
492
    public function filter_optimize_images( $in )
493
    {
494
        /*
495
         * potential future functional improvements:
496
         *
497
         * filter for critical CSS.
498
         */
499
        $to_replace = array();
500
501
        // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
502
        if ( $this->should_lazyload() ) {
503
            $in = autoptimizeBase::replace_contents_with_marker_if_exists(
504
                'SCRIPT',
505
                '<script',
506
                '#<(?:no)?script.*?<\/(?:no)?script>#is',
507
                $in
508
            );
509
        }
510
511
        // extract img tags.
512
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
513
            foreach ( $matches[0] as $tag ) {
514
                $orig_tag = $tag;
515
                $imgopt_w = '';
516
                $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...
517
518
                // first do (data-)srcsets.
519
                if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
520
                    foreach ( $allsrcsets as $srcset ) {
521
                        $srcset  = $srcset[2];
522
                        $srcsets = explode( ',', $srcset );
523
                        foreach ( $srcsets as $indiv_srcset ) {
524
                            $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
525
                            if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
526
                                $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
527
                            }
528
                            if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
529
                                $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
530
                                $tag        = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
531
                            }
532
                        }
533
                    }
534
                }
535
536
                // proceed with img src.
537
                // get width and height and add to $imgopt_size.
538
                $_get_size = $this->get_size_from_tag( $tag );
539
                $imgopt_w  = $_get_size['width'];
540
                $imgopt_h  = $_get_size['height'];
541
542
                // then start replacing images src.
543
                if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
544
                    foreach ( $urls as $url ) {
545
                        $full_src_orig = $url[0];
546
                        $url           = $url[1];
547
                        if ( $this->can_optimize_image( $url ) ) {
548
                            $imgopt_url      = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
549
                            $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
550
                            $tag             = str_replace( $full_src_orig, $full_imgopt_src, $tag );
551
                        }
552
                    }
553
                }
554
555
                // do lazyload stuff.
556
                if ( $this->should_lazyload( $in ) && ! empty( $url ) ) {
557
                    // first do lpiq placeholder logic.
558
                    if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
559
                        // if all img src have been replaced during srcset, we have to extract the
560
                        // origin url from the imgopt one to be able to set a lqip placeholder.
561
                        $_url = substr( $url, strpos( $url, '/http' ) + 1 );
562
                    } else {
563
                        $_url = $url;
564
                    }
565
566
                    $_url = $this->normalize_img_url( $_url );
567
568
                    $placeholder = '';
569
                    if ( $this->can_optimize_image( $_url ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', true ) ) {
570
                        $lqip_w = '';
571
                        $lqip_h = '';
572
                        if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
573
                            $lqip_w = ',w_' . $imgopt_w;
574
                        }
575
                        if ( isset( $imgopt_h ) && ! empty( $imgopt_h ) ) {
576
                            $lqip_h = ',h_' . $imgopt_h;
577
                        }
578
                        $placeholder = $this->get_imgopt_host() . 'client/q_lqip,ret_wait' . $lqip_w . $lqip_h . '/' . $_url;
579
                    }
580
                    // then call add_lazyload-function with lpiq placeholder if set.
581
                    $tag = $this->add_lazyload( $tag, $placeholder );
582
                }
583
584
                // and add tag to array for later replacement.
585
                if ( $tag !== $orig_tag ) {
586
                    $to_replace[ $orig_tag ] = $tag;
587
                }
588
            }
589
        }
590
591
        // and replace all.
592
        $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
593
594
        // img thumbnails in e.g. woocommerce.
595
        if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_imgopt_datathumbs', true ) ) {
596
            $out = preg_replace_callback(
597
                '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
598
                array( $this, 'replace_data_thumbs' ),
599
                $out
600
            );
601
        }
602
603
        // background-image in inline style.
604 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...
605
            $out = preg_replace_callback(
606
                '/style=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
607
                array( $this, 'replace_img_callback' ),
608
                $out
609
            );
610
        }
611
612
        // lazyload: restore noscript tags + lazyload picture source tags and bgimage.
613
        if ( $this->should_lazyload() ) {
614
            $out = autoptimizeBase::restore_marked_content(
615
                'SCRIPT',
616
                $out
617
            );
618
619
            $out = $this->process_picture_tag( $out, true, true );
620
            $out = $this->process_bgimage( $out );
621
        } else {
622
            $out = $this->process_picture_tag( $out, true, false );
623
        }
624
625
        return $out;
626
    }
627
628
    public function get_size_from_tag( $tag ) {
629
        // reusable function to extract widht and height from an image tag
630
        // enforcing a filterable maximum width and height (default 4999X4999).
631
        $width  = '';
632
        $height = '';
633
634
        if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $_width ) ) {
635
            if ( strpos( $_width[2], '%' ) === false ) {
636
                $width = (int) $_width[2];
637
            }
638
        }
639
        if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $_height ) ) {
640
            if ( strpos( $_height[2], '%' ) === false ) {
641
                $height = (int) $_height[2];
642
            }
643
        }
644
645
        // check for and enforce (filterable) max sizes.
646
        $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
647
        if ( $width > $_max_width ) {
648
            $_width = $_max_width;
649
            $height = $_width / $width * $height;
650
            $width  = $_width;
651
        }
652
        $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
653
        if ( $height > $_max_height ) {
654
            $_height = $_max_height;
655
            $width   = $_height / $height * $width;
656
            $height  = $_height;
657
        }
658
659
        return array(
660
            'width'  => $width,
661
            'height' => $height,
662
        );
663
    }
664
665
    /**
666
     * Lazyload functions
667
     */
668
    public static function should_lazyload_wrapper() {
669
        // needed in autoptimizeMain.php.
670
        $self = new self();
671
        return $self->should_lazyload();
672
    }
673
674
    public function should_lazyload( $context = '' ) {
675
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && false === $this->check_nolazy() ) {
676
            $lazyload_return = true;
677
        } else {
678
            $lazyload_return = false;
679
        }
680
        $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
681
682
        return $lazyload_return;
683
    }
684
685
    public function check_nolazy() {
686
        if ( array_key_exists( 'ao_nolazy', $_GET ) && '1' === $_GET['ao_nolazy'] ) {
687
            return true;
688
        } else {
689
            return false;
690
        }
691
    }
692
693
    public function filter_lazyload_images( $in )
694
    {
695
        // only used is image optimization is NOT active but lazyload is.
696
        $to_replace = array();
697
698
        // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
699
        $out = autoptimizeBase::replace_contents_with_marker_if_exists(
700
            'SCRIPT',
701
            '<script',
702
            '#<(?:no)?script.*?<\/(?:no)?script>#is',
703
            $in
704
        );
705
706
        // extract img tags and add lazyload attribs.
707
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
708
            foreach ( $matches[0] as $tag ) {
709
                if ( $this->should_lazyload( $out ) ) {
710
                    $to_replace[ $tag ] = $this->add_lazyload( $tag );
711
                }
712
            }
713
            $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $out );
714
        }
715
716
        // and also lazyload picture tag.
717
        $out = $this->process_picture_tag( $out, false, true );
718
719
        // and inline style blocks with background-image.
720
        $out = $this->process_bgimage( $out );
721
722
        // restore noscript tags.
723
        $out = autoptimizeBase::restore_marked_content(
724
            'SCRIPT',
725
            $out
726
        );
727
728
        return $out;
729
    }
730
731
    public function add_lazyload( $tag, $placeholder = '' ) {
732
        // adds actual lazyload-attributes to an image node.
733
        if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
734
            $tag = $this->maybe_fix_missing_quotes( $tag );
735
736
            // store original tag for use in noscript version.
737
            $noscript_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>';
738
739
            $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
740
741
            // insert lazyload class.
742
            $tag = $this->inject_classes_in_tag( $tag, "$lazyload_class " );
743
744
            if ( ! $placeholder || empty( $placeholder ) ) {
745
                // get image width & heigth for placeholder fun (and to prevent content reflow).
746
                $_get_size = $this->get_size_from_tag( $tag );
747
                $width     = $_get_size['width'];
748
                $height    = $_get_size['height'];
749
                if ( false === $width || empty( $width ) ) {
750
                    $width = 210; // default width for SVG placeholder.
751
                }
752
                if ( false === $height || empty( $height ) ) {
753
                    $height = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
754
                }
755
756
                // insert the actual lazyload stuff.
757
                // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
758
                $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
759
            }
760
761
            $tag = preg_replace( '/(\s)src=/', ' src=\'' . $placeholder . '\' data-src=', $tag );
762
            $tag = preg_replace( '/(\s)srcset=/', ' data-srcset=', $tag );
763
764
            // move sizes to data-sizes unless filter says no.
765
            if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
766
                $tag = str_replace( ' sizes=', ' data-sizes=', $tag );
767
            }
768
769
            // add the noscript-tag from earlier.
770
            $tag = $noscript_tag . $tag;
771
            $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
772
        }
773
774
        return $tag;
775
    }
776
777
    public function add_lazyload_js_footer() {
778
        if ( false === autoptimizeMain::should_buffer() ) {
779
            return;
780
        }
781
782
        // The JS will by default be excluded form autoptimization but this can be changed with a filter.
783
        $noptimize_flag = '';
784
        if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
785
            $noptimize_flag = ' data-noptimize="1"';
786
        }
787
788
        $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js?ao_version=' . AUTOPTIMIZE_PLUGIN_VERSION, __FILE__ );
789
        $cdn_url      = $this->get_cdn_url();
790
        if ( ! empty( $cdn_url ) ) {
791
            $lazysizes_js = str_replace( AUTOPTIMIZE_WP_SITE_URL, $cdn_url, $lazysizes_js );
792
        }
793
794
        $type_js = '';
795
        if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
796
            $type_js = ' type="text/javascript"';
797
        }
798
799
        // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
800
        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>' );
801
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $type_js . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
802
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_js', '<script async' . $type_js . $noptimize_flag . ' src=\'' . $lazysizes_js . '\'></script>' );
803
804
        // And add webp detection and loading JS.
805
        if ( $this->should_webp() ) {
806
            $_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);";
807
            $_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,'))})});";
808
            echo apply_filters( 'autoptimize_filter_imgopt_webp_js', '<script' . $type_js . $noptimize_flag . '>' . $_webp_detect . $_webp_load . '</script>' );
809
        }
810
    }
811
812
    public function get_cdn_url() {
813
        // getting CDN url here to avoid having to make bigger changes to autoptimizeBase.
814
        static $cdn_url = null;
815
816
        if ( null === $cdn_url ) {
817
            $cdn_url = autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' );
818
            $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $cdn_url );
819
            $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
820
        }
821
822
        return $cdn_url;
823
    }
824
825
    public function get_lazyload_exclusions() {
826
        // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
827
        static $exclude_lazyload_array = null;
828
829
        if ( null === $exclude_lazyload_array ) {
830
            $options = $this->options;
831
832
            // set default exclusions.
833
            $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg', 'loading="eager"' );
834
835
            // add from setting.
836
            if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
837
                $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
838 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...
839
                    $exclude_lazyload_array = array_merge( $exclude_lazyload_array, array_filter( array_map( 'trim', explode( ',', $options['autoptimize_imgopt_text_field_5'] ) ) ) );
840
                }
841
            }
842
843
            // and filter for developer-initiated changes.
844
            $exclude_lazyload_array = apply_filters( 'autoptimize_filter_imgopt_lazyload_exclude_array', $exclude_lazyload_array );
845
        }
846
847
        return $exclude_lazyload_array;
848
    }
849
850
    public function inject_classes_in_tag( $tag, $target_class ) {
851
        if ( strpos( $tag, 'class=' ) !== false ) {
852
            $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
853
        } else {
854
            $tag = preg_replace( '/(<img)\s/', '$1 class="' . trim( $target_class ) . '" ', $tag );
855
        }
856
857
        return $tag;
858
    }
859
860
    public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
861
        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';
862
    }
863
864
    public function should_webp() {
865
        static $webp_return = null;
866
867
        if ( is_null( $webp_return ) ) {
868
            // webp only works if imgopt and lazyload are also active.
869
            if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && $this->imgopt_active() ) {
870
                $webp_return = true;
871
            } else {
872
                $webp_return = false;
873
            }
874
        }
875
876
        return $webp_return;
877
    }
878
879
    public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
880
        // check if "<picture" is present and if filter allows us to process <picture>.
881
        if ( strpos( $in, '<picture' ) === false || apply_filters( 'autoptimize_filter_imgopt_dopicture', true ) === false ) {
882
            return $in;
883
        }
884
885
        $_exclusions     = $this->get_lazyload_exclusions();
886
        $to_replace_pict = array();
887
888
        // extract and process each picture-node.
889
        preg_match_all( '#<picture.*</picture>#Usmi', $in, $_pictures, PREG_SET_ORDER );
890
        foreach ( $_pictures as $_picture ) {
891
            $_picture = $this->maybe_fix_missing_quotes( $_picture );
892
            if ( strpos( $_picture[0], '<source ' ) !== false && preg_match_all( '#<source .*srcset=(?:"|\')(?!data)(.*)(?:"|\').*>#Usmi', $_picture[0], $_sources, PREG_SET_ORDER ) !== false ) {
893
                foreach ( $_sources as $_source ) {
894
                    $_picture_replacement = $_source[0];
895
896
                    // should we optimize the image?
897
                    if ( $imgopt && $this->can_optimize_image( $_source[1] ) ) {
898
                        $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
899
                    }
900
                    // should we lazy-load?
901
                    if ( $lazy && $this->should_lazyload() && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
902
                        $_picture_replacement = str_replace( ' srcset=', ' data-srcset=', $_picture_replacement );
903
                    }
904
                    $to_replace_pict[ $_source[0] ] = $_picture_replacement;
905
                }
906
            }
907
        }
908
909
        // and return the fully procesed $in.
910
        $out = str_replace( array_keys( $to_replace_pict ), array_values( $to_replace_pict ), $in );
911
912
        return $out;
913
    }
914
915
    public function process_bgimage( $in ) {
916 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...
917
            $out = preg_replace_callback(
918
                '/(<(?:article|aside|body|div|footer|header|p|section|table)[^>]*)\sstyle=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
919
                array( $this, 'lazyload_bgimg_callback' ),
920
                $in
921
            );
922
            return $out;
923
        }
924
        return $in;
925
    }
926
927
    public function lazyload_bgimg_callback( $matches ) {
928
        if ( str_ireplace( $this->get_lazyload_exclusions(), '', $matches[0] ) === $matches[0] ) {
929
            // get placeholder & lazyload class strings.
930
            $placeholder    = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( 500, 300 ) );
931
            $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
932
            // replace background-image URL with SVG placeholder.
933
            $out = str_replace( $matches[2], $placeholder, $matches[0] );
934
            // add data-bg attribute with real background-image URL for lazyload to pick up.
935
            $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . trim( str_replace( "\r\n", '', $matches[2] ) ) . '"', $out );
936
            // add lazyload class to tag.
937
            $out = $this->inject_classes_in_tag( $out, "$lazyload_class " );
938
            return $out;
939
        }
940
        return $matches[0];
941
    }
942
943
    public function maybe_fix_missing_quotes( $tag_in ) {
944
        // 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.
945
        if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
946
            $tag_out = preg_replace( '/class\s?=([^("|\')]*)(\s|>)/U', 'class=\'$1\'$2', $tag_in );
947
            $tag_out = preg_replace( '/\s(width|height)=(?:"|\')?([^\s"\'>]*)(?:"|\')?/', ' $1=\'$2\'', $tag_out );
948
            return $tag_out;
949
        } else {
950
            return $tag_in;
951
        }
952
    }
953
954
    /**
955
     * Admin page logic and related functions below.
956
     */
957
    public function imgopt_admin_menu()
958
    {
959
        // no acces if multisite and not network admin and no site config allowed.
960
        if ( autoptimizeConfig::should_show_menu_tabs() ) {
961
            add_submenu_page(
962
                null,
963
                'autoptimize_imgopt',
964
                'autoptimize_imgopt',
965
                'manage_options',
966
                'autoptimize_imgopt',
967
                array( $this, 'imgopt_options_page' )
968
            );
969
        }
970
        register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
971
    }
972
973
    public function add_imgopt_tab( $in )
974
    {
975
        if ( autoptimizeConfig::should_show_menu_tabs() ) {
976
            $in = array_merge( $in, array( 'autoptimize_imgopt' => __( 'Images', 'autoptimize' ) ) );
977
        }
978
979
        return $in;
980
    }
981
982
    public function imgopt_options_page()
983
    {
984
        // Check querystring for "refreshCacheChecker" and call cachechecker if so.
985
        if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
986
            $this->query_img_provider_stats();
987
        }
988
989
        $options       = $this->fetch_options();
990
        $sp_url_suffix = $this->get_service_url_suffix();
991
        ?>
992
    <style>
993
        #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
994
        #ao_settings_form .form-table th {font-weight: normal;}
995
        #autoptimize_imgopt_descr{font-size: 120%;}
996
    </style>
997
    <script>document.title = "Autoptimize: <?php _e( 'Images', 'autoptimize' ); ?> " + document.title;</script>
998
    <div class="wrap">
999
    <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
1000
        <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
1001
        <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
1002
            <div class="notice-warning notice"><p>
1003
            <?php
1004
            // translators: "Autoptimize support forum" will appear in a "a href".
1005
            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>' );
1006
            ?>
1007
            </p></div>
1008
        <?php } ?>
1009
1010
        <?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...
1011
            <div class="notice-warning notice"><p>
1012
            <?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' ); ?>
1013
            </p></div>
1014
        <?php } ?>
1015
1016
        <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
1017
            <div class="notice-warning notice"><p>
1018
            <?php
1019
            // translators: "disable  Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
1020
            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>' );
1021
            ?>
1022
            </p></div>
1023
        <?php } ?>
1024
    <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
1025
        <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
1026
        <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
1027
        <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>
1028
        <table class="form-table">
1029
            <tr>
1030
                <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
1031
                <td>
1032
                    <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>
1033
                    <?php
1034
                    // show shortpixel status.
1035
                    $_notice = autoptimizeImages::instance()->get_imgopt_status_notice();
1036
                    if ( $_notice ) {
1037
                        switch ( $_notice['status'] ) {
1038
                            case 2:
1039
                                $_notice_color = 'green';
1040
                                break;
1041
                            case 1:
1042
                                $_notice_color = 'orange';
1043
                                break;
1044
                            case -1:
1045
                            case -2:
1046
                            case -3:
1047
                                $_notice_color = 'red';
1048
                                break;
1049
                            default:
1050
                                $_notice_color = 'green';
1051
                        }
1052
                        echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
1053
                    } else {
1054
                        // translators: link points to shortpixel.
1055
                        $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love and improve your website\'s loading speed by having your publicly available 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>' );
1056
                        if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
1057
                            $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' );
1058
                        } else {
1059
                            // translators: link points to shortpixel.
1060
                            $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>' );
1061
                        }
1062
                        echo apply_filters( 'autoptimize_imgopt_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
1063
                    }
1064
                    // translators: link points to shortpixel FAQ.
1065
                    $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>' );
1066
                    $faqcopy = $faqcopy . ' ' . __( 'Only works for sites/ images that are publicly available.', 'autoptimize' );
1067
                    // translators: links points to shortpixel TOS & Privacy Policy.
1068
                    $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>' );
1069
                    echo apply_filters( 'autoptimize_imgopt_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
1070
                    ?>
1071
                </td>
1072
            </tr>
1073 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...
1074
                <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
1075
                <td>
1076
                    <label>
1077
                    <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
1078
                        <?php
1079
                        $_imgopt_array = autoptimizeImages::instance()->get_img_quality_array();
1080
                        $_imgopt_val   = autoptimizeImages::instance()->get_img_quality_setting();
1081
1082
                        foreach ( $_imgopt_array as $key => $value ) {
1083
                            echo '<option value="' . $key . '"';
1084
                            if ( $_imgopt_val == $key ) {
1085
                                echo ' selected';
1086
                            }
1087
                            echo '>' . ucfirst( $value ) . '</option>';
1088
                        }
1089
                        echo "\n";
1090
                        ?>
1091
                    </select>
1092
                    </label>
1093
                    <p>
1094
                        <?php
1095
                            // translators: link points to shortpixel image test page.
1096
                            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>' ) );
1097
                        ?>
1098
                    </p>
1099
                </td>
1100
            </tr>
1101 View Code Duplication
            <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"'; } ?>>
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...
1102
                <th scope="row"><?php _e( 'Load WebP in supported browsers?', 'autoptimize' ); ?></th>
1103
                <td>
1104
                    <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>
1105
                </td>
1106
            </tr>
1107
            <tr>
1108
                <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1109
                <td>
1110
                    <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>
1111
                </td>
1112
            </tr>
1113 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="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...
1114
                <th scope="row"><?php _e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1115
                <td>
1116
                    <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>
1117
                </td>
1118
            </tr>
1119
        </table>
1120
        <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1121
    </form>
1122
    <script>
1123
        jQuery(document).ready(function() {
1124
            jQuery("#autoptimize_imgopt_checkbox").change(function() {
1125
                if (this.checked) {
1126
                    jQuery("#autoptimize_imgopt_quality").show("slow");
1127
                    jQuery("#autoptimize_imgopt_webp").show("slow");
1128
                } else {
1129
                    jQuery("#autoptimize_imgopt_quality").hide("slow");
1130
                    jQuery("#autoptimize_imgopt_webp").hide("slow");
1131
                }
1132
            });
1133
            jQuery("#autoptimize_imgopt_webp_checkbox").change(function() {
1134
                if (this.checked) {
1135
                    jQuery("#autoptimize_imgopt_lazyload_checkbox")[0].checked = true;
1136
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1137
                }
1138
            });
1139
            jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1140
                if (this.checked) {
1141
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1142
                } else {
1143
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").hide("slow");
1144
                    jQuery("#autoptimize_imgopt_webp_checkbox")[0].checked = false;
1145
                }
1146
            });
1147
        });
1148
    </script>
1149
        <?php
1150
    }
1151
1152
    /**
1153
     * Ïmg opt status as used on dashboard.
1154
     */
1155
    public function get_imgopt_status_notice() {
1156
        if ( $this->imgopt_active() ) {
1157
            $_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...
1158
            $_stat           = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1159
            $_site_host      = AUTOPTIMIZE_SITE_DOMAIN;
1160
            $_imgopt_upsell  = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
1161
            $_imgopt_assoc   = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1162
            $_imgopt_unreach = 'https://shortpixel.helpscoutdocs.com/article/148-why-are-my-images-redirected-from-cdn-shortpixel-ai';
1163
1164
            if ( is_array( $_stat ) ) {
1165
                if ( 1 == $_stat['Status'] ) {
1166
                    // translators: "add more credits" will appear in a "a href".
1167
                    $_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>' );
1168
                } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1169
                    // translators: "add more credits" will appear in a "a href".
1170
                    $_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>' );
1171
                    // translators: "associate your domain" will appear in a "a href".
1172
                    $_imgopt_notice = $_imgopt_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="' . $_imgopt_assoc . '" target="_blank">', '</a>' );
1173
                } elseif ( -3 == $_stat['Status'] ) {
1174
                    // translators: "check the documentation here" will appear in a "a href".
1175
                    $_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>' );
1176
                } else {
1177
                    $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1178
                    // translators: "log in to check your account" will appear in a "a href".
1179
                    $_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>' );
1180
                }
1181
1182
                // add info on freshness + refresh link if status is not 2 (good shape).
1183
                if ( 2 != $_stat['Status'] ) {
1184
                    $_imgopt_stats_refresh_url = add_query_arg( array(
1185
                        'page'                => 'autoptimize_imgopt',
1186
                        'refreshImgProvStats' => '1',
1187
                    ), admin_url( 'options-general.php' ) );
1188
                    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...
1189
                        $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( autoptimizeOptionWrapper::get_option( 'time_format' ), $_stat['timestamp'] );
1190
                    } else {
1191
                        $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1192
                    }
1193
                    $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1194
                    // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1195
                    $_imgopt_notice .= sprintf( __( 'click %1$shere to refresh%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1196
                }
1197
1198
                // and make the full notice filterable.
1199
                $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1200
1201
                return array(
1202
                    'status' => $_stat['Status'],
1203
                    'notice' => $_imgopt_notice,
1204
                );
1205
            }
1206
        }
1207
        return false;
1208
    }
1209
1210
    public static function get_imgopt_status_notice_wrapper() {
1211
        // needed for notice being shown in autoptimizeCacheChecker.php.
1212
        $self = new self();
1213
        return $self->get_imgopt_status_notice();
1214
    }
1215
1216
    /**
1217
     * Get img provider stats (used to display notice).
1218
     */
1219
    public function query_img_provider_stats() {
1220
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) ) {
1221
            $url      = '';
1222
            $endpoint = $this->get_imgopt_host() . 'read-domain/';
1223
            $domain   = AUTOPTIMIZE_SITE_DOMAIN;
1224
1225
            // make sure parse_url result makes sense, keeping $url empty if not.
1226
            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...
1227
                $url = $endpoint . $domain;
1228
            }
1229
1230
            $url = apply_filters(
1231
                'autoptimize_filter_imgopt_stat_url',
1232
                $url
1233
            );
1234
1235
            // only do the remote call if $url is not empty to make sure no parse_url
1236
            // weirdness results in useless calls.
1237 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...
1238
                $response = wp_remote_get( $url );
1239
                if ( ! is_wp_error( $response ) ) {
1240
                    if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1241
                        $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1242
                        autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_provider_stat', $stats );
1243
                    }
1244
                }
1245
            }
1246
        }
1247
    }
1248
1249
    public static function get_img_provider_stats()
1250
    {
1251
        // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
1252
        $self = new self();
1253
        return $self->query_img_provider_stats();
1254
    }
1255
1256
    /**
1257
     * Determines and returns the service launch status.
1258
     *
1259
     * @return bool
1260
     */
1261
    public function launch_ok()
1262
    {
1263
        static $launch_status = null;
1264
1265
        if ( null === $launch_status ) {
1266
            $avail_imgopt  = $this->options['availabilities']['extra_imgopt'];
1267
            $magic_number  = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1268
            $has_launched  = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_launched', '' );
1269
            $launch_status = false;
1270
            if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1271
                $launch_status = true;
1272
                if ( ! $has_launched ) {
1273
                    autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_launched', 'on' );
1274
                }
1275
            }
1276
        }
1277
1278
        return $launch_status;
1279
    }
1280
1281
    public static function launch_ok_wrapper() {
1282
        // needed for "plug" notice in autoptimizeMain.php.
1283
        $self = new self();
1284
        return $self->launch_ok();
1285
    }
1286
1287
    public function get_imgopt_provider_userstatus() {
1288
        static $_provider_userstatus = null;
1289
1290
        if ( is_null( $_provider_userstatus ) ) {
1291
            $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1292
            if ( is_array( $_stat ) ) {
1293
                if ( array_key_exists( 'Status', $_stat ) ) {
1294
                    $_provider_userstatus['Status'] = $_stat['Status'];
1295
                } else {
1296
                    // if no stats then we assume all is well.
1297
                    $_provider_userstatus['Status'] = 2;
1298
                }
1299
                if ( array_key_exists( 'timestamp', $_stat ) ) {
1300
                    $_provider_userstatus['timestamp'] = $_stat['timestamp'];
1301
                } else {
1302
                    // if no timestamp then we return "".
1303
                    $_provider_userstatus['timestamp'] = '';
1304
                }
1305
            } else {
1306
                // no provider_stat yet, assume/ return all OK.
1307
                $_provider_userstatus['Status']    = 2;
1308
                $_provider_userstatus['timestamp'] = '';
1309
            }
1310
        }
1311
1312
        return $_provider_userstatus;
1313
    }
1314
}
1315