Completed
Push — master ( 30736d...dd59c6 )
by frank
01:47
created

autoptimizeImages::normalize_img_url()   B

Complexity

Conditions 10
Paths 60

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 60
nop 1
dl 0
loc 55
rs 7.1151
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Handles optimizing images.
4
 */
5
6
if ( ! defined( 'ABSPATH' ) ) {
7
    exit;
8
}
9
10
class autoptimizeImages
11
{
12
    /**
13
     * Options.
14
     *
15
     * @var array
16
     */
17
    protected $options = array();
18
19
    /**
20
     * Singleton instance.
21
     *
22
     * @var self|null
23
     */
24
    protected static $instance = null;
25
26
    public function __construct( array $options = array() )
27
    {
28
        // If options are not provided, fetch them.
29
        if ( empty( $options ) ) {
30
            $options = $this->fetch_options();
31
        }
32
33
        $this->set_options( $options );
34
    }
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
                $result .= $in;
337
            } elseif ( ! empty( $cdn_domain ) && strpos( $in, $cdn_domain ) !== 0 ) {
338
                $result = str_replace( $cdn_domain, $parsed_site_url['host'], $in );
339
            }
340
341
            $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
342
343
            // Store in cache.
344
            $cache[ $in ] = $result;
345
        }
346
347
        return $cache[ $in ];
348
    }
349
350
    public function filter_optimize_css_images( $in )
351
    {
352
        $in = $this->normalize_img_url( $in );
353
354
        if ( $this->can_optimize_image( $in ) ) {
355
            return $this->build_imgopt_url( $in, '', '' );
356
        } else {
357
            return $in;
358
        }
359
    }
360
361
    private function get_imgopt_base_url()
362
    {
363
        static $imgopt_base_url = null;
364
365
        if ( null === $imgopt_base_url ) {
366
            $imgopt_host     = $this->get_imgopt_host();
367
            $quality         = $this->get_img_quality_string();
368
            $ret_val         = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
369
            $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
370
            $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
371
        }
372
373
        return $imgopt_base_url;
374
    }
375
376
    private function can_optimize_image( $url )
377
    {
378
        static $cdn_url      = null;
379
        static $nopti_images = null;
380
381
        if ( null === $cdn_url ) {
382
            $cdn_url = apply_filters(
383
                'autoptimize_filter_base_cdnurl',
384
                get_option( 'autoptimize_cdn_url', '' )
385
            );
386
        }
387
388
        if ( null === $nopti_images ) {
389
            $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', '' );
390
        }
391
392
        $site_host  = AUTOPTIMIZE_SITE_DOMAIN;
393
        $url        = $this->normalize_img_url( $url );
394
        $url_parsed = parse_url( $url );
395
396
        if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
397
            return false;
398
        } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
399
            return false;
400
        } elseif ( strpos( $url, '.php' ) !== false ) {
401
            return false;
402
        } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
403
            // fixme: better check against end of string.
404
            return false;
405
        } elseif ( ! empty( $nopti_images ) ) {
406
            $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
407
            foreach ( $nopti_images_array as $nopti_image ) {
408
                if ( strpos( $url, $nopti_image ) !== false ) {
409
                    return false;
410
                }
411
            }
412
        }
413
        return true;
414
    }
415
416
    private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
417
    {
418
        // sanitize width and height.
419
        if ( strpos( $width, '%' ) !== false ) {
420
            $width = 0;
421
        }
422
        if ( strpos( $height, '%' ) !== false ) {
423
            $height = 0;
424
        }
425
        $width  = (int) $width;
426
        $height = (int) $height;
427
428
        $filtered_url = apply_filters(
429
            'autoptimize_filter_imgopt_build_url',
430
            $orig_url,
431
            $width,
432
            $height
433
        );
434
435
        // If filter modified the url, return that.
436
        if ( $filtered_url !== $orig_url ) {
437
            return $filtered_url;
438
        }
439
440
        $orig_url        = $this->normalize_img_url( $orig_url );
441
        $imgopt_base_url = $this->get_imgopt_base_url();
442
        $imgopt_size     = '';
443
444
        if ( $width && 0 !== $width ) {
445
            $imgopt_size = ',w_' . $width;
446
        }
447
448
        if ( $height && 0 !== $height ) {
449
            $imgopt_size .= ',h_' . $height;
450
        }
451
452
        $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
453
454
        return $url;
455
    }
456
457
    public function replace_data_thumbs( $matches )
458
    {
459
        return $this->replace_img_callback( $matches, 150, 150 );
460
    }
461
462
    public function replace_img_callback( $matches, $width = 0, $height = 0 )
463
    {
464
        if ( $this->can_optimize_image( $matches[1] ) ) {
465
            return str_replace( $matches[1], $this->build_imgopt_url( $matches[1], $width, $height ), $matches[0] );
466
        } else {
467
            return $matches[0];
468
        }
469
    }
470
471
    public function filter_optimize_images( $in )
472
    {
473
        /*
474
         * potential future functional improvements:
475
         *
476
         * picture element.
477
         * filter for critical CSS.
478
         */
479
        $to_replace = array();
480
481
        // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
482
        if ( $this->should_lazyload() ) {
483
            $in = autoptimizeBase::replace_contents_with_marker_if_exists(
484
                'SCRIPT',
485
                '<script',
486
                '#<(?:no)?script.*?<\/(?:no)?script>#is',
487
                $in
488
            );
489
        }
490
491
        // extract img tags.
492
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
493
            foreach ( $matches[0] as $tag ) {
494
                $orig_tag = $tag;
495
                $imgopt_w = '';
496
                $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...
497
498
                // first do (data-)srcsets.
499
                if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
500
                    foreach ( $allsrcsets as $srcset ) {
501
                        $srcset  = $srcset[2];
502
                        $srcsets = explode( ',', $srcset );
503
                        foreach ( $srcsets as $indiv_srcset ) {
504
                            $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
505
                            if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
506
                                $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
507
                            }
508
                            if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
509
                                $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
510
                                $tag        = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
511
                            }
512
                        }
513
                    }
514
                }
515
516
                // proceed with img src.
517
                // get width and height and add to $imgopt_size.
518
                $_get_size = $this->get_size_from_tag( $tag );
519
                $imgopt_w  = $_get_size['width'];
520
                $imgopt_h  = $_get_size['height'];
521
522
                // then start replacing images src.
523
                if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
524
                    foreach ( $urls as $url ) {
525
                        $full_src_orig = $url[0];
526
                        $url           = $url[1];
527
                        if ( $this->can_optimize_image( $url ) ) {
528
                            $imgopt_url      = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
529
                            $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
530
                            $tag             = str_replace( $full_src_orig, $full_imgopt_src, $tag );
531
                        }
532
                    }
533
                }
534
535
                // do lazyload stuff.
536
                if ( $this->should_lazyload() && str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
537
                    $tag          = $this->maybe_fix_missing_quotes( $tag );
538
                    $noscript_tag = '<noscript>' . $tag . '</noscript>';
539
                    $tag          = str_replace( 'srcset=', 'data-srcset=', $tag );
540
541
                    // add lazyload class.
542
                    $tag = $this->inject_classes_in_tag( $tag, 'lazyload ' );
543
544
                    // set placeholder.
545
                    if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
546
                        // if all img src have been replaced during srcset, we have to extract the
547
                        // origin url from the imgopt one to be able to set a lqip placeholder.
548
                        $_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...
549
                    } else {
550
                        $_url = $url;
551
                    }
552
                    if ( $this->can_optimize_image( $_url ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', true ) ) {
553
                        $lqip_w = '';
554
                        $lqip_h = '';
555
                        if ( isset( $imgopt_w ) ) {
556
                            $lqip_w = ',w_' . $imgopt_w;
557
                        }
558
                        if ( isset( $imgopt_h ) ) {
559
                            $lqip_h = ',h_' . $imgopt_h;
560
                        }
561
                        $placeholder = $this->get_imgopt_host() . 'client/q_lqip,ret_wait' . $lqip_w . $lqip_h . '/' . $_url;
562
                    } else {
563
                        $placeholder = $this->get_default_lazyload_placeholder( $imgopt_w, $imgopt_h );
564
                    }
565
                    $placeholder = ' src=\'' . apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $placeholder );
566
567
                    // add min-heigth off by default as it can deform images, can be enabled with filter.
568
                    $min_height = '';
569
                    if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_addminheight', false ) ) {
570
                        $min_height = ' style="min-height:' . $imgopt_h . 'px;"';
571
                    }
572
573
                    // add noscript & placeholder.
574
                    $tag = $noscript_tag . str_replace( ' src=', $min_height . $placeholder . '\' data-src=', $tag );
575
                    $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
576
                }
577
578
                // add tag to array for later replacement.
579
                if ( $tag !== $orig_tag ) {
580
                    $to_replace[ $orig_tag ] = $tag;
581
                }
582
            }
583
        }
584
585
        // and replace all.
586
        $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
587
588
        // img thumbnails in e.g. woocommerce.
589
        if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_imgopt_datathumbs', true ) ) {
590
            $out = preg_replace_callback(
591
                '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
592
                array( $this, 'replace_data_thumbs' ),
593
                $out
594
            );
595
        }
596
597
        // background-image in inline style.
598
        if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
599
            $out = preg_replace_callback(
600
                '/style=(?:"|\').*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
601
                array( $this, 'replace_img_callback' ),
602
                $out
603
            );
604
        }
605
606
        // lazyload: restore noscript tags + lazyload picture source tags.
607
        if ( $this->should_lazyload() ) {
608
            $out = autoptimizeBase::restore_marked_content(
609
                'SCRIPT',
610
                $out
611
            );
612
613
            $out = $this->process_picture_tag( $out, true, true );
614
        } else {
615
            $out = $this->process_picture_tag( $out, true, false );
616
        }
617
618
        return $out;
619
    }
620
621
    public function get_size_from_tag( $tag ) {
622
        // reusable function to extract widht and height from an image tag
623
        // enforcing a filterable maximum width and height (default 4999X4999).
624
        $width  = '';
625
        $height = '';
626
627
        if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $_width ) ) {
628
            if ( strpos( $_width[2], '%' ) === false ) {
629
                $width = (int) $_width[2];
630
            }
631
        }
632
        if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $_height ) ) {
633
            if ( strpos( $_height[2], '%' ) === false ) {
634
                $height = (int) $_height[2];
635
            }
636
        }
637
638
        // check for and enforce (filterable) max sizes.
639
        $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
640
        if ( $width > $_max_width ) {
641
            $_width = $_max_width;
642
            $height = $_width / $width * $height;
643
            $width  = $_width;
644
        }
645
        $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
646
        if ( $height > $_max_height ) {
647
            $_height = $_max_height;
648
            $width   = $_height / $height * $width;
649
            $height  = $_height;
650
        }
651
652
        return array(
653
            'width'  => $width,
654
            'height' => $height,
655
        );
656
    }
657
658
    /**
659
     * Lazyload functions
660
     */
661
    public function should_lazyload() {
662
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) ) {
663
            $lazyload_return = true;
664
        } else {
665
            $lazyload_return = false;
666
        }
667
        $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return );
668
669
        return $lazyload_return;
670
    }
671
672
    public static function should_lazyload_wrapper() {
673
        // needed in autoptimizeMain.php.
674
        $self = new self();
675
        return $self->should_lazyload();
676
    }
677
678
    public function filter_lazyload_images( $in )
679
    {
680
        // only used is image optimization is NOT active but lazyload is.
681
        $to_replace = array();
682
683
        // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
684
        $out = autoptimizeBase::replace_contents_with_marker_if_exists(
685
            'SCRIPT',
686
            '<script',
687
            '#<(?:no)?script.*?<\/(?:no)?script>#is',
688
            $in
689
        );
690
691
        // extract img tags and add lazyload attribs.
692
        if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
693
            foreach ( $matches[0] as $tag ) {
694
                $to_replace[ $tag ] = $this->add_lazyload( $tag );
695
            }
696
            $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $out );
697
        }
698
699
        // and also lazyload picture tag.
700
        $out = $this->process_picture_tag( $out, false, true );
701
702
        // restore noscript tags.
703
        $out = autoptimizeBase::restore_marked_content(
704
            'SCRIPT',
705
            $out
706
        );
707
708
        return $out;
709
    }
710
711
    public function add_lazyload( $tag ) {
712
        // adds actual lazyload-attributes to an image node.
713
        if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
714
            $tag = $this->maybe_fix_missing_quotes( $tag );
715
716
            // store original tag for use in noscript version.
717
            $noscript_tag = '<noscript>' . $tag . '</noscript>';
718
719
            // insert lazyload class.
720
            $tag = $this->inject_classes_in_tag( $tag, 'lazyload ' );
721
722
            // get image width & heigth for placeholder fun (and to prevent content reflow).
723
            $_get_size = $this->get_size_from_tag( $tag );
724
            $width     = $_get_size['width'];
725
            $height    = $_get_size['height'];
726
            if ( false === $width ) {
727
                $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...
728
            }
729
            if ( false === $height ) {
730
                $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...
731
            }
732
733
            // insert the actual lazyload stuff.
734
            // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
735
            $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
736
            $tag         = str_replace( ' src=', ' src=\'' . $placeholder . '\' data-src=', $tag );
737
            $tag         = str_replace( ' srcset=', ' data-srcset=', $tag );
738
739
            // add the noscript-tag from earlier.
740
            $tag = $noscript_tag . $tag;
741
            $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
742
        }
743
744
        return $tag;
745
    }
746
747
    public function add_lazyload_js_footer() {
748
        // The JS will by default be excluded form autoptimization but this can be changed with a filter.
749
        $noptimize_flag = '';
750
        if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
751
            $noptimize_flag = ' data-noptimize="1"';
752
        }
753
754
        // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
755
        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>' );
756
        echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
757
        echo '<script async' . $noptimize_flag . ' src=\'' . plugins_url( 'external/js/lazysizes.min.js', __FILE__ ) . '\'></script>';
758
759
        // And add webp detection and loading JS.
760
        if ( $this->should_webp() ) {
761
            $_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);";
762
            $_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,'))})});";
763
            echo apply_filters( 'autoptimize_filter_imgopt_webp_js', '<script' . $noptimize_flag . '>' . $_webp_detect . $_webp_load . '</script>' );
764
        }
765
    }
766
767
    public function get_lazyload_exclusions() {
768
        // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
769
        static $exclude_lazyload_array = null;
770
771
        if ( null === $exclude_lazyload_array ) {
772
            $options = $this->options;
773
774
            // set default exclusions.
775
            $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg' );
776
777
            // add from setting.
778
            if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
779
                $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
780 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...
781
                    $exclude_lazyload_array = array_merge( $exclude_lazyload_array, array_filter( array_map( 'trim', explode( ',', $options['autoptimize_imgopt_text_field_5'] ) ) ) );
782
                }
783
            }
784
785
            // and filter for developer-initiated changes.
786
            $exclude_lazyload_array = apply_filters( 'autoptimize_filter_imgopt_lazyload_exclude_array', $exclude_lazyload_array );
787
        }
788
789
        return $exclude_lazyload_array;
790
    }
791
792
    public function inject_classes_in_tag( $tag, $target_class ) {
793
        if ( strpos( $tag, 'class=' ) !== false ) {
794
            $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
795
        } else {
796
            $tag = str_replace( '<img ', '<img class="' . trim( $target_class ) . '" ', $tag );
797
        }
798
799
        return $tag;
800
    }
801
802
    public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
803
        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';
804
    }
805
806
    public function should_webp() {
807
        static $webp_return = null;
808
809
        if ( is_null( $webp_return ) ) {
810
            // webp only works if imgopt and lazyload are also active.
811
            if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && $this->imgopt_active() ) {
812
                $webp_return = true;
813
            } else {
814
                $webp_return = false;
815
            }
816
        }
817
818
        return $webp_return;
819
    }
820
821
    public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
822
        // check if "<picture" is present and if filter allows us to process <picture>.
823
        if ( strpos( $in, '<picture' ) === false || apply_filters( 'autoptimize_filter_imgopt_dopicture', true ) === false ) {
824
            return $in;
825
        }
826
827
        $_exclusions     = $this->get_lazyload_exclusions();
828
        $to_replace_pict = array();
829
830
        // extract and process each picture-node.
831
        preg_match_all( '#<picture.*</picture>#Usmi', $in, $_pictures, PREG_SET_ORDER );
832
        foreach ( $_pictures as $_picture ) {
833
            $_picture = $this->maybe_fix_missing_quotes( $_picture );
834
            if ( strpos( $_picture[0], '<source ' ) !== false && preg_match_all( '#<source .*srcset=(?:"|\')(?!data)(.*)(?:"|\').*>#Usmi', $_picture[0], $_sources, PREG_SET_ORDER ) !== false ) {
835
                foreach ( $_sources as $_source ) {
836
                    $_picture_replacement = $_source[0];
837
838
                    // should we optimize the image?
839
                    if ( $imgopt && $this->can_optimize_image( $_source[1] ) ) {
840
                        $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
841
                    }
842
                    // should we lazy-load?
843
                    if ( $lazy && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
844
                        $_picture_replacement = str_replace( ' srcset=', ' data-srcset=', $_picture_replacement );
845
                    }
846
                    $to_replace_pict[ $_source[0] ] = $_picture_replacement;
847
                }
848
            }
849
        }
850
851
        // and return the fully procesed $in.
852
        $out = str_replace( array_keys( $to_replace_pict ), array_values( $to_replace_pict ), $in );
853
854
        return $out;
855
    }
856
857
    public function maybe_fix_missing_quotes( $tag_in ) {
858
        // W3TC's Minify_HTML class removes quotes around attribute value, this re-adds them.
859
        if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
860
            return preg_replace( '/=([^("|\')]*)(\s|>)/U', '=\'$1\'$2', $tag_in );
861
        } else {
862
            return $tag_in;
863
        }
864
    }
865
866
    /**
867
     * Admin page logic and related functions below.
868
     */
869
    public function imgopt_admin_menu()
870
    {
871
        add_submenu_page(
872
            null,
873
            'autoptimize_imgopt',
874
            'autoptimize_imgopt',
875
            'manage_options',
876
            'autoptimize_imgopt',
877
            array( $this, 'imgopt_options_page' )
878
        );
879
        register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
880
    }
881
882
    public function add_imgopt_tab( $in )
883
    {
884
        $in = array_merge( $in, array( 'autoptimize_imgopt' => __( 'Images', 'autoptimize' ) ) );
885
886
        return $in;
887
    }
888
889
    public function imgopt_options_page()
890
    {
891
        // Check querystring for "refreshCacheChecker" and call cachechecker if so.
892
        if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
893
            $this->query_img_provider_stats();
894
        }
895
896
        $options       = $this->fetch_options();
897
        $sp_url_suffix = $this->get_service_url_suffix();
898
        ?>
899
    <style>
900
        #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
901
        #ao_settings_form .form-table th {font-weight: normal;}
902
        #autoptimize_imgopt_descr{font-size: 120%;}
903
    </style>
904
    <div class="wrap">
905
    <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
906
        <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
907
        <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
908
            <div class="notice-warning notice"><p>
909
            <?php
910
            // translators: "Autoptimize support forum" will appear in a "a href".
911
            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>' );
912
            ?>
913
            </p></div>
914
        <?php } ?>
915
916
        <?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...
917
            <div class="notice-warning notice"><p>
918
            <?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' ); ?>
919
            </p></div>
920
        <?php } ?>
921
922
        <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
923
            <div class="notice-warning notice"><p>
924
            <?php
925
            // translators: "disable  Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
926
            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>' );
927
            ?>
928
            </p></div>
929
        <?php } ?>
930
    <form id='ao_settings_form' action='options.php' method='post'>
931
        <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
932
        <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
933
        <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>
934
        <table class="form-table">
935
            <tr>
936
                <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
937
                <td>
938
                    <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>
939
                    <?php
940
                    // show shortpixel status.
941
                    $_notice = autoptimizeImages::instance()->get_status_notice();
942
                    if ( $_notice ) {
943
                        switch ( $_notice['status'] ) {
944
                            case 2:
945
                                $_notice_color = 'green';
946
                                break;
947
                            case 1:
948
                                $_notice_color = 'orange';
949
                                break;
950
                            case -1:
951
                                $_notice_color = 'red';
952
                                break;
953
                            case -2:
954
                                $_notice_color = 'red';
955
                                break;
956
                            default:
957
                                $_notice_color = 'green';
958
                        }
959
                        echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
960
                    } else {
961
                        // translators: link points to shortpixel.
962
                        $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>' );
963
                        if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
964
                            $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' );
965
                        } else {
966
                            // translators: link points to shortpixel.
967
                            $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>' );
968
                        }
969
                        echo apply_filters( 'autoptimize_imgopt_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
970
                    }
971
                    // translators: link points to shortpixel FAQ.
972
                    $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>' );
973
                    // translators: links points to shortpixel TOS & Privacy Policy.
974
                    $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>' );
975
                    echo apply_filters( 'autoptimize_imgopt_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
976
                    ?>
977
                </td>
978
            </tr>
979 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...
980
                <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
981
                <td>
982
                    <label>
983
                    <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
984
                        <?php
985
                        $_imgopt_array = autoptimizeImages::instance()->get_img_quality_array();
986
                        $_imgopt_val   = autoptimizeImages::instance()->get_img_quality_setting();
987
988
                        foreach ( $_imgopt_array as $key => $value ) {
989
                            echo '<option value="' . $key . '"';
990
                            if ( $_imgopt_val == $key ) {
991
                                echo ' selected';
992
                            }
993
                            echo '>' . ucfirst( $value ) . '</option>';
994
                        }
995
                        echo "\n";
996
                        ?>
997
                    </select>
998
                    </label>
999
                    <p>
1000
                        <?php
1001
                            // translators: link points to shortpixel image test page.
1002
                            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>' ) );
1003
                        ?>
1004
                    </p>
1005
                </td>
1006
            </tr>
1007 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...
1008
                <th scope="row"><?php _e( 'Load WebP in supported browsers?', 'autoptimize' ); ?></th>
1009
                <td>
1010
                    <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>
1011
                </td>
1012
            </tr>
1013
            <tr>
1014
                <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1015
                <td>
1016
                    <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>
1017
                </td>
1018
            </tr>
1019 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...
1020
                <th scope="row"><?php _e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1021
                <td>
1022
                    <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>
1023
                </td>
1024
            </tr>
1025
        </table>
1026
        <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1027
    </form>
1028
    <script>
1029
        jQuery(document).ready(function() {
1030
            jQuery("#autoptimize_imgopt_checkbox").change(function() {
1031
                if (this.checked) {
1032
                    jQuery("#autoptimize_imgopt_quality").show("slow");
1033
                    jQuery("#autoptimize_imgopt_webp").show("slow");
1034
                } else {
1035
                    jQuery("#autoptimize_imgopt_quality").hide("slow");
1036
                    jQuery("#autoptimize_imgopt_webp").hide("slow");
1037
                }
1038
            });
1039
            jQuery("#autoptimize_imgopt_webp_checkbox").change(function() {
1040
                if (this.checked) {
1041
                    jQuery("#autoptimize_imgopt_lazyload_checkbox")[0].checked = true;
1042
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1043
                }
1044
            });
1045
            jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1046
                if (this.checked) {
1047
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1048
                } else {
1049
                    jQuery("#autoptimize_imgopt_lazyload_exclusions").hide("slow");
1050
                    jQuery("#autoptimize_imgopt_webp_checkbox")[0].checked = false;
1051
                }
1052
            });
1053
        });
1054
    </script>
1055
        <?php
1056
    }
1057
1058
    /**
1059
     * Ïmg opt status as used on dashboard.
1060
     */
1061
    public function get_imgopt_status_notice() {
1062
        if ( $this->imgopt_active() ) {
1063
            $_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...
1064
            $_stat          = get_option( 'autoptimize_imgopt_provider_stat', '' );
1065
            $_site_host     = AUTOPTIMIZE_SITE_DOMAIN;
1066
            $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
1067
1068
            if ( is_array( $_stat ) ) {
1069
                if ( 1 == $_stat['Status'] ) {
1070
                    // translators: "add more credits" will appear in a "a href".
1071
                    $_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>' );
1072
                } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1073
                    // translators: "add more credits" will appear in a "a href".
1074
                    $_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>' );
1075
                    $_imgopt_stats_refresh_url = add_query_arg( array(
1076
                        'page'                => 'autoptimize_imgopt',
1077
                        'refreshImgProvStats' => '1',
1078
                    ), admin_url( 'options-general.php' ) );
1079
                    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...
1080
                        $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( get_option( 'time_format' ), $_stat['timestamp'] );
1081
                    } else {
1082
                        $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1083
                    }
1084
                    $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1085
                    // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1086
                    $_imgopt_notice .= sprintf( __( 'click %1$shere to refresh%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1087
                } else {
1088
                    $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1089
                    // translators: "log in to check your account" will appear in a "a href".
1090
                    $_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>' );
1091
                }
1092
                $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1093
1094
                return array(
1095
                    'status' => $_stat['Status'],
1096
                    'notice' => $_imgopt_notice,
1097
                );
1098
            }
1099
        }
1100
        return false;
1101
    }
1102
1103
    public static function get_imgopt_status_notice_wrapper() {
1104
        // needed for notice being shown in autoptimizeCacheChecker.php.
1105
        $self = new self();
1106
        return $self->get_imgopt_status_notice();
1107
    }
1108
1109
    /**
1110
     * Get img provider stats (used to display notice).
1111
     */
1112
    public function query_img_provider_stats() {
1113
        if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) ) {
1114
            $url      = '';
1115
            $endpoint = $this->get_imgopt_host() . 'read-domain/';
1116
            $domain   = AUTOPTIMIZE_SITE_DOMAIN;
1117
1118
            // make sure parse_url result makes sense, keeping $url empty if not.
1119
            if ( $domain && ! empty( $domain ) ) {
1120
                $url = $endpoint . $domain;
1121
            }
1122
1123
            $url = apply_filters(
1124
                'autoptimize_filter_imgopt_stat_url',
1125
                $url
1126
            );
1127
1128
            // only do the remote call if $url is not empty to make sure no parse_url
1129
            // weirdness results in useless calls.
1130
            if ( ! empty( $url ) ) {
1131
                $response = wp_remote_get( $url );
1132
                if ( ! is_wp_error( $response ) ) {
1133
                    if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1134
                        $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1135
                        update_option( 'autoptimize_imgopt_provider_stat', $stats );
1136
                    }
1137
                }
1138
            }
1139
        }
1140
    }
1141
1142
    public static function get_img_provider_stats()
1143
    {
1144
        // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
1145
        $self = new self();
1146
        return $self->query_img_provider_stats();
1147
    }
1148
1149
    /**
1150
     * Determines and returns the service launch status.
1151
     *
1152
     * @return bool
1153
     */
1154
    public function launch_ok()
1155
    {
1156
        static $launch_status = null;
1157
1158
        if ( null === $launch_status ) {
1159
            $avail_imgopt  = $this->options['availabilities']['extra_imgopt'];
1160
            $magic_number  = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1161
            $has_launched  = get_option( 'autoptimize_imgopt_launched', '' );
1162
            $launch_status = false;
1163
            if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1164
                $launch_status = true;
1165
                if ( ! $has_launched ) {
1166
                    update_option( 'autoptimize_imgopt_launched', 'on' );
1167
                }
1168
            }
1169
        }
1170
1171
        return $launch_status;
1172
    }
1173
1174
    public static function launch_ok_wrapper() {
1175
        // needed for "plug" notice in autoptimizeMain.php.
1176
        $self = new self();
1177
        return $self->launch_ok();
1178
    }
1179
1180
    public function get_imgopt_provider_userstatus() {
1181
        static $_provider_userstatus = null;
1182
1183
        if ( is_null( $_provider_userstatus ) ) {
1184
            $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
1185
            if ( is_array( $_stat ) ) {
1186
                if ( array_key_exists( 'Status', $_stat ) ) {
1187
                    $_provider_userstatus['Status'] = $_stat['Status'];
1188
                } else {
1189
                    // if no stats then we assume all is well.
1190
                    $_provider_userstatus['Status'] = 2;
1191
                }
1192
                if ( array_key_exists( 'timestamp', $_stat ) ) {
1193
                    $_provider_userstatus['timestamp'] = $_stat['timestamp'];
1194
                } else {
1195
                    // if no timestamp then we return "".
1196
                    $_provider_userstatus['timestamp'] = '';
1197
                }
1198
            }
1199
        }
1200
1201
        return $_provider_userstatus;
1202
    }
1203
1204
    public function get_status_notice() {
1205
        if ( $this->imgopt_active() ) {
1206
            $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...
1207
            $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...
1208
            $upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
1209
            $assoc  = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1210
1211
            if ( is_array( $stat ) ) {
1212
                if ( 1 == $stat['Status'] ) {
1213
                    // translators: "add more credits" will appear in a "a href".
1214
                    $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>' );
1215
                } elseif ( -1 == $stat['Status'] || -2 == $stat['Status'] ) {
1216
                    // translators: "add more credits" will appear in a "a href".
1217
                    $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>' );
1218
                    // translators: "associate your domain" will appear in a "a href".
1219
                    $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>' );
1220
                } else {
1221
                    $upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1222
                    // translators: "log in to check your account" will appear in a "a href".
1223
                    $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>' );
1224
                }
1225
                $notice = apply_filters( 'autoptimize_filter_imgopt_notice', $notice );
1226
1227
                return array(
1228
                    'status' => $stat['Status'],
1229
                    'notice' => $notice,
1230
                );
1231
            }
1232
        }
1233
        return false;
1234
    }
1235
}
1236