Completed
Push — master ( 6065f1...5d82c5 )
by frank
02:22
created

autoptimizeImages::maybe_fix_missing_quotes()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 2
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Handles optimizing images.
4
 */
5
6
if ( ! defined( 'ABSPATH' ) ) {
7
    exit;
8
}
9
10
class autoptimizeImages
11
{
12
    /**
13
     * Options.
14
     *
15
     * @var array
16
     */
17
    protected $options = array();
18
19
    /**
20
     * Singleton instance.
21
     *
22
     * @var self|null
23
     */
24
    protected static $instance = null;
25
26
    public function __construct( array $options = array() )
27
    {
28
        // If options are not provided, fetch them.
29
        if ( empty( $options ) ) {
30
            $options = $this->fetch_options();
31
        }
32
33
        $this->set_options( $options );
34
    }
35
36
    public function set_options( array $options )
37
    {
38
        $this->options = $options;
39
40
        return $this;
41
    }
42
43
    public static function fetch_options()
44
    {
45
        $value = get_option( 'autoptimize_imgopt_settings' );
46
        if ( empty( $value ) ) {
47
            // Fallback to returning defaults when no stored option exists yet.
48
            $value = autoptimizeConfig::get_ao_imgopt_default_options();
49
        }
50
51
        // get service availability and add it to the options-array.
52
        $value['availabilities'] = get_option( 'autoptimize_service_availablity' );
53
54
        if ( empty( $value['availabilities'] ) ) {
55
            $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
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 = 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
            add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
101
            add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_imgopt_tab' ), 9 );
102
        } else {
103
            $this->run_on_frontend();
104
        }
105
    }
106
107
    public function run_on_frontend() {
108
        if ( ! $this->should_run() ) {
109
            if ( $this->should_lazyload() ) {
110
                add_filter(
111
                    'autoptimize_html_after_minify',
112
                    array( $this, 'filter_lazyload_images' ),
113
                    10,
114
                    1
115
                );
116
                add_action(
117
                    'wp_footer',
118
                    array( $this, 'add_lazyload_js_footer' ),
119
                    10,
120
                    0
121
                );
122
            }
123
            return;
124
        }
125
126
        $active = false;
127
128
        if ( apply_filters( 'autoptimize_filter_imgopt_do', true ) ) {
129
            add_filter(
130
                'autoptimize_html_after_minify',
131
                array( $this, 'filter_optimize_images' ),
132
                10,
133
                1
134
            );
135
            $active = true;
136
        }
137
138
        if ( apply_filters( 'autoptimize_filter_imgopt_do_css', true ) ) {
139
            add_filter(
140
                'autoptimize_filter_base_replace_cdn',
141
                array( $this, 'filter_optimize_css_images' ),
142
                10,
143
                1
144
            );
145
            $active = true;
146
        }
147
148
        if ( $active ) {
149
            add_filter(
150
                'autoptimize_extra_filter_tobepreconn',
151
                array( $this, 'filter_preconnect_imgopt_url' ),
152
                10,
153
                1
154
            );
155
        }
156
157
        if ( $this->should_lazyload() ) {
158
            add_action(
159
                'wp_footer',
160
                array( $this, 'add_lazyload_js_footer' )
161
            );
162
        }
163
    }
164
165
    /**
166
     * Basic checks before we can run.
167
     *
168
     * @return bool
169
     */
170
    protected function should_run()
171
    {
172
        $opts              = $this->options;
173
        $service_not_down  = ( 'down' !== $opts['availabilities']['extra_imgopt']['status'] );
174
        $not_launch_status = ( 'launch' !== $opts['availabilities']['extra_imgopt']['status'] );
175
176
        $do_cdn      = true;
177
        $_userstatus = $this->get_imgopt_provider_userstatus();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $_userstatus is correct as $this->get_imgopt_provider_userstatus() (which targets autoptimizeImages::get_i...t_provider_userstatus()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
178
        if ( -2 == $_userstatus['Status'] ) {
179
            $do_cdn = false;
180
        }
181
182
        if (
183
            $this->imgopt_active()
184
            && $do_cdn
185
            && $service_not_down
186
            && ( $not_launch_status || $this->launch_ok() )
187
        ) {
188
            return true;
189
        }
190
        return false;
191
    }
192
193
    public function get_imgopt_host()
194
    {
195
        static $imgopt_host = null;
196
197
        if ( null === $imgopt_host ) {
198
            $imgopt_host  = 'https://cdn.shortpixel.ai/';
199
            $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
200
            if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
201
                $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
202
            }
203
            $imgopt_host = apply_filters( 'autoptimize_filter_imgopt_host', $imgopt_host );
204
        }
205
206
        return $imgopt_host;
207
    }
208
209
    public static function get_imgopt_host_wrapper()
210
    {
211
        // needed for CI tests.
212
        $self = new self();
213
        return $self->get_imgopt_host();
214
    }
215
216
    public static function get_service_url_suffix()
217
    {
218
        $suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
219
220
        return $suffix;
221
    }
222
223
    public function get_img_quality_string()
224
    {
225
        static $quality = null;
226
227
        if ( null === $quality ) {
228
            $q_array = $this->get_img_quality_array();
229
            $setting = $this->get_img_quality_setting();
230
            $quality = apply_filters(
231
                'autoptimize_filter_imgopt_quality',
232
                'q_' . $q_array[ $setting ]
233
            );
234
        }
235
236
        return $quality;
237
    }
238
239
    public function get_img_quality_array()
240
    {
241
        static $map = null;
242
243
        if ( null === $map ) {
244
            $map = array(
245
                '1' => 'lossy',
246
                '2' => 'glossy',
247
                '3' => 'lossless',
248
            );
249
            $map = apply_filters(
250
                'autoptimize_filter_imgopt_quality_array',
251
                $map
252
            );
253
        }
254
255
        return $map;
256
    }
257
258
    public function get_img_quality_setting()
259
    {
260
        static $q = null;
261
262
        if ( null === $q ) {
263
            if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_select_field_2', $this->options ) ) {
264
                $setting = $this->options['autoptimize_imgopt_select_field_2'];
265
            }
266
267
            if ( ! isset( $setting ) || empty( $setting ) || ( '1' !== $setting && '3' !== $setting ) ) {
268
                // default image opt. value is 2 ("glossy").
269
                $q = '2';
270
            } else {
271
                $q = $setting;
272
            }
273
        }
274
275
        return $q;
276
    }
277
278
    public function filter_preconnect_imgopt_url( array $in )
279
    {
280
        $url_parts = parse_url( $this->get_imgopt_base_url() );
281
        $in[]      = $url_parts['scheme'] . '://' . $url_parts['host'];
282
283
        return $in;
284
    }
285
286
    /**
287
     * Makes sure given url contains the full scheme and hostname
288
     * in case they're not present already.
289
     *
290
     * @param string $in Image url to normalize.
291
     *
292
     * @return string
293
     */
294
    private function normalize_img_url( $in )
295
    {
296
        // Only parse the site url once.
297
        static $parsed_site_url = null;
298
        if ( null === $parsed_site_url ) {
299
            $parsed_site_url = parse_url( site_url() );
300
        }
301
302
        // get CDN domain once.
303
        static $cdn_domain = null;
304
        if ( is_null( $cdn_domain ) ) {
305
            $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', get_option( 'autoptimize_cdn_url', '' ) );
306
            if ( ! empty( $cdn_url ) ) {
307
                $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
308
            } else {
309
                $cdn_domain = '';
310
            }
311
        }
312
313
        /**
314
         * This method gets called a lot, often for identical urls it seems.
315
         * `filter_optimize_css_images()` calls us, uses the resulting url and
316
         * gives it to `can_optimize_image()`, and if that returns trueish
317
         * then `build_imgopt_url()` is called (which, again, calls this method).
318
         * Until we dig deeper into whether this all must really happen that
319
         * way, having an internal cache here helps (to avoid doing repeated
320
         * identical string operations).
321
         */
322
        static $cache = null;
323
        if ( null === $cache ) {
324
            $cache = array();
325
        }
326
327
        // Do the work on cache miss only.
328
        if ( ! isset( $cache[ $in ] ) ) {
329
            // Default to what was given to us.
330
            $result = $in;
331
            if ( autoptimizeUtils::is_protocol_relative( $in ) ) {
332
                $result = $parsed_site_url['scheme'] . ':' . $in;
333
            } elseif ( 0 === strpos( $in, '/' ) ) {
334
                // Root-relative...
335
                $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'];
336
                // Add the path for subfolder installs.
337
                if ( isset( $parsed_site_url['path'] ) ) {
338
                    $result .= $parsed_site_url['path'];
339
                }
340
                $result .= $in;
341
            } elseif ( ! empty( $cdn_domain ) && strpos( $in, $cdn_domain ) !== 0 ) {
342
                $result = str_replace( $cdn_domain, $parsed_site_url['host'], $in );
343
            }
344
345
            $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
346
347
            // Store in cache.
348
            $cache[ $in ] = $result;
349
        }
350
351
        return $cache[ $in ];
352
    }
353
354
    public function filter_optimize_css_images( $in )
355
    {
356
        $in = $this->normalize_img_url( $in );
357
358
        if ( $this->can_optimize_image( $in ) ) {
359
            return $this->build_imgopt_url( $in, '', '' );
360
        } else {
361
            return $in;
362
        }
363
    }
364
365
    private function get_imgopt_base_url()
366
    {
367
        static $imgopt_base_url = null;
368
369
        if ( null === $imgopt_base_url ) {
370
            $imgopt_host     = $this->get_imgopt_host();
371
            $quality         = $this->get_img_quality_string();
372
            $ret_val         = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
373
            $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
374
            $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
375
        }
376
377
        return $imgopt_base_url;
378
    }
379
380
    private function can_optimize_image( $url )
381
    {
382
        static $cdn_url      = null;
383
        static $nopti_images = null;
384
385
        if ( null === $cdn_url ) {
386
            $cdn_url = apply_filters(
387
                'autoptimize_filter_base_cdnurl',
388
                get_option( 'autoptimize_cdn_url', '' )
389
            );
390
        }
391
392
        if ( null === $nopti_images ) {
393
            $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', '' );
394
        }
395
396
        $site_host  = AUTOPTIMIZE_SITE_DOMAIN;
397
        $url        = $this->normalize_img_url( $url );
398
        $url_parsed = parse_url( $url );
399
400
        if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
401
            return false;
402
        } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
403
            return false;
404
        } elseif ( strpos( $url, '.php' ) !== false ) {
405
            return false;
406
        } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
407
            // fixme: better check against end of string.
408
            return false;
409
        } elseif ( ! empty( $nopti_images ) ) {
410
            $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
411
            foreach ( $nopti_images_array as $nopti_image ) {
412
                if ( strpos( $url, $nopti_image ) !== false ) {
413
                    return false;
414
                }
415
            }
416
        }
417
        return true;
418
    }
419
420
    private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
421
    {
422
        // sanitize width and height.
423
        if ( strpos( $width, '%' ) !== false ) {
424
            $width = 0;
425
        }
426
        if ( strpos( $height, '%' ) !== false ) {
427
            $height = 0;
428
        }
429
        $width  = (int) $width;
430
        $height = (int) $height;
431
432
        $filtered_url = apply_filters(
433
            'autoptimize_filter_imgopt_build_url',
434
            $orig_url,
435
            $width,
436
            $height
437
        );
438
439
        // If filter modified the url, return that.
440
        if ( $filtered_url !== $orig_url ) {
441
            return $filtered_url;
442
        }
443
444
        $orig_url        = $this->normalize_img_url( $orig_url );
445
        $imgopt_base_url = $this->get_imgopt_base_url();
446
        $imgopt_size     = '';
447
448
        if ( $width && 0 !== $width ) {
449
            $imgopt_size = ',w_' . $width;
450
        }
451
452
        if ( $height && 0 !== $height ) {
453
            $imgopt_size .= ',h_' . $height;
454
        }
455
456
        $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
457
458
        return $url;
459
    }
460
461
    public function replace_data_thumbs( $matches )
462
    {
463
        return $this->replace_img_callback( $matches, 150, 150 );
464
    }
465
466
    public function replace_img_callback( $matches, $width = 0, $height = 0 )
467
    {
468
        if ( $this->can_optimize_image( $matches[1] ) ) {
469
            return str_replace( $matches[1], $this->build_imgopt_url( $matches[1], $width, $height ), $matches[0] );
470
        } else {
471
            return $matches[0];
472
        }
473
    }
474
475
    public function filter_optimize_images( $in )
476
    {
477
        /*
478
         * potential future functional improvements:
479
         *
480
         * picture element.
481
         * filter for critical CSS.
482
         */
483
        $to_replace = array();
484
485
        // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
486
        if ( $this->should_lazyload() ) {
487
            $in = autoptimizeBase::replace_contents_with_marker_if_exists(
488
                'SCRIPT',
489
                '<script',
490
                '#<(?:no)?script.*?<\/(?:no)?script>#is',
491
                $in
492
            );
493
        }
494
495
        // extract img tags.
496
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
497
            foreach ( $matches[0] as $tag ) {
498
                $orig_tag = $tag;
499
                $imgopt_w = '';
500
                $imgopt_h = '';
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...
501
502
                // first do (data-)srcsets.
503
                if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
504
                    foreach ( $allsrcsets as $srcset ) {
505
                        $srcset  = $srcset[2];
506
                        $srcsets = explode( ',', $srcset );
507
                        foreach ( $srcsets as $indiv_srcset ) {
508
                            $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
509
                            if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
510
                                $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
511
                            }
512
                            if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
513
                                $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
514
                                $tag        = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
515
                            }
516
                        }
517
                    }
518
                }
519
520
                // proceed with img src.
521
                // get width and height and add to $imgopt_size.
522
                $_get_size = $this->get_size_from_tag( $tag );
523
                $imgopt_w  = $_get_size['width'];
524
                $imgopt_h  = $_get_size['height'];
525
526
                // then start replacing images src.
527
                if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
528
                    foreach ( $urls as $url ) {
529
                        $full_src_orig = $url[0];
530
                        $url           = $url[1];
531
                        if ( $this->can_optimize_image( $url ) ) {
532
                            $imgopt_url      = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
533
                            $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
534
                            $tag             = str_replace( $full_src_orig, $full_imgopt_src, $tag );
535
                        }
536
                    }
537
                }
538
539
                // do lazyload stuff.
540
                if ( $this->should_lazyload() && str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
541
                    $tag          = $this->maybe_fix_missing_quotes( $tag );
542
                    $noscript_tag = '<noscript>' . $tag . '</noscript>';
543
                    $tag          = str_replace( 'srcset=', 'data-srcset=', $tag );
544
545
                    // add lazyload class.
546
                    $tag = $this->inject_classes_in_tag( $tag, 'lazyload ' );
547
548
                    // set placeholder.
549
                    if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
550
                        // if all img src have been replaced during srcset, we have to extract the
551
                        // origin url from the imgopt one to be able to set a lqip placeholder.
552
                        $_url = substr( $url, strpos( $url, '/http' ) + 1 );
0 ignored issues
show
Bug introduced by
The variable $url does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

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

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1212
            $upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
1213
            $assoc  = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1214
1215
            if ( is_array( $stat ) ) {
1216
                if ( 1 == $stat['Status'] ) {
1217
                    // translators: "add more credits" will appear in a "a href".
1218
                    $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota is almost used, make sure you %1$sadd more credits%2$s to avoid slowing down your website.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1219
                } elseif ( -1 == $stat['Status'] || -2 == $stat['Status'] ) {
1220
                    // translators: "add more credits" will appear in a "a href".
1221
                    $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota was used, %1$sadd more credits%2$s to keep fast serving optimized images on your site.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1222
                    // translators: "associate your domain" will appear in a "a href".
1223
                    $notice = $notice . ' ' . sprintf( __( 'If you already have enough credits then you may need to %1$sassociate your domain%2$s to your Shortpixel account.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $assoc . '" target="_blank">', '</a>' );
1224
                } else {
1225
                    $upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1226
                    // translators: "log in to check your account" will appear in a "a href".
1227
                    $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1228
                }
1229
                $notice = apply_filters( 'autoptimize_filter_imgopt_notice', $notice );
1230
1231
                return array(
1232
                    'status' => $stat['Status'],
1233
                    'notice' => $notice,
1234
                );
1235
            }
1236
        }
1237
        return false;
1238
    }
1239
}
1240