Completed
Push — master ( eba476...c2cea0 )
by frank
02:07
created

autoptimizeImages::add_lazyload()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

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