Completed
Push — master ( f1d861...5b581f )
by frank
01:49
created

autoptimizeExtra   F

Complexity

Total Complexity 114

Size/Duplication

Total Lines 575
Duplicated Lines 3.83 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
dl 22
loc 575
rs 2
c 0
b 0
f 0
wmc 114
lcom 2
cbo 5

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A instance() 0 8 2
A run() 13 13 5
A set_options() 0 6 1
A fetch_options() 0 10 2
A disable_emojis() 0 17 1
A filter_disable_emojis_tinymce() 0 8 2
A filter_remove_qs() 0 8 2
A extra_async_js() 3 19 3
A filter_remove_emoji_dns_prefetch() 0 6 1
A filter_remove_gfonts_dnsprefetch() 0 4 1
A filter_remove_dns_prefetch() 0 16 5
F filter_optimize_google_fonts() 0 122 25
C filter_preconnect() 3 40 12
A filter_preconnect_google_fonts() 0 15 3
C filter_preload() 3 49 16
A admin_menu() 0 15 2
A add_extra_tab() 0 8 2
F options_page() 0 108 15
C run_on_frontend() 0 46 12

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like autoptimizeExtra often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use autoptimizeExtra, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Handles autoptimizeExtra frontend features + admin options page
4
 */
5
6
if ( ! defined( 'ABSPATH' ) ) {
7
    exit;
8
}
9
10
class autoptimizeExtra
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
    /**
27
     * Creates an instance and calls run().
28
     *
29
     * @param array $options Optional. Allows overriding options without having to specify them via admin options page.
30
     */
31
    public function __construct( $options = array() )
32
    {
33
        if ( empty( $options ) ) {
34
            $options = self::fetch_options();
35
        }
36
37
        $this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
It seems like $options of type * is incompatible with the declared type array of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
38
    }
39
40
    /**
41
     * Helper for getting a singleton instance. While being an
42
     * anti-pattern generally, it comes in handy for now from a
43
     * readability/maintainability perspective, until we get some
44
     * proper dependency injection going.
45
     *
46
     * @return self
47
     */
48
    public static function instance()
49
    {
50
        if ( null === self::$instance ) {
51
            self::$instance = new self();
52
        }
53
54
        return self::$instance;
55
    }
56
57 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...
58
    {
59
        if ( is_admin() ) {
60
            if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
61
                add_action( 'network_admin_menu', array( $this, 'admin_menu' ) );
62
            } else {
63
                add_action( 'admin_menu', array( $this, 'admin_menu' ) );
64
            }
65
            add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_extra_tab' ) );
66
        } else {
67
            add_action( 'wp', array( $this, 'run_on_frontend' ) );
68
        }
69
    }
70
71
    public function set_options( array $options )
72
    {
73
        $this->options = $options;
74
75
        return $this;
76
    }
77
78
    public static function fetch_options()
79
    {
80
        $value = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings' );
81
        if ( empty( $value ) ) {
82
            // Fallback to returning defaults when no stored option exists yet.
83
            $value = autoptimizeConfig::get_ao_extra_default_options();
84
        }
85
86
        return $value;
87
    }
88
89
    public function disable_emojis()
90
    {
91
        // Removing all actions related to emojis!
92
        remove_action( 'admin_print_styles', 'print_emoji_styles' );
93
        remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
94
        remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
95
        remove_action( 'wp_print_styles', 'print_emoji_styles' );
96
        remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
97
        remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
98
        remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
99
100
        // Removes TinyMCE emojis.
101
        add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) );
102
103
        // Removes emoji dns-preftech.
104
        add_filter( 'wp_resource_hints', array( $this, 'filter_remove_emoji_dns_prefetch' ), 10, 2 );
105
    }
106
107
    public function filter_disable_emojis_tinymce( $plugins )
108
    {
109
        if ( is_array( $plugins ) ) {
110
            return array_diff( $plugins, array( 'wpemoji' ) );
111
        } else {
112
            return array();
113
        }
114
    }
115
116
    public function filter_remove_qs( $src )
117
    {
118
        if ( strpos( $src, '?ver=' ) ) {
119
            $src = remove_query_arg( 'ver', $src );
120
        }
121
122
        return $src;
123
    }
124
125
    public function extra_async_js( $in )
126
    {
127
        $exclusions = array();
128 View Code Duplication
        if ( ! empty( $in ) ) {
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...
129
            $exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' );
130
        }
131
132
        $settings = $this->options['autoptimize_extra_text_field_3'];
133
        $async    = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' );
134
        $attr     = apply_filters( 'autoptimize_filter_extra_async', 'async' );
135
        foreach ( $async as $k => $v ) {
136
            $async[ $k ] = $attr;
137
        }
138
139
        // Merge exclusions & asyncs in one array and return to AO API.
140
        $merged = array_merge( $exclusions, $async );
141
142
        return $merged;
143
    }
144
145
    public function run_on_frontend()
146
    {
147
        // only run the Extra optimizations on frontend if general conditions
148
        // for optimizations are met, this to ensure e.g. removing querystrings
149
        // is not done when optimizing for logged in users is off, breaking
150
        // some pagebuilders (Divi & Elementor).
151
        if ( false === autoptimizeMain::should_buffer() ) {
152
            return;
153
        }
154
155
        $options = $this->options;
156
157
        // Disable emojis if specified.
158
        if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) {
159
            $this->disable_emojis();
160
        }
161
162
        // Remove version query parameters.
163
        if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) {
164
            add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
165
            add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
166
        }
167
168
        // Avoiding conflicts of interest when async-javascript plugin is active!
169
        $async_js_plugin_active = autoptimizeUtils::is_plugin_active( 'async-javascript/async-javascript.php' );
170
        if ( ! empty( $options['autoptimize_extra_text_field_3'] ) && ! $async_js_plugin_active ) {
171
            add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 );
172
        }
173
174
        // Optimize google fonts!
175
        if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) {
176
            add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 );
177
            add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 );
178
            add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 );
179
        }
180
181
        // Preconnect!
182
        if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) {
183
            add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 );
184
        }
185
186
        // Preload!
187
        if ( ! empty( $options['autoptimize_extra_text_field_7'] ) || has_filter( 'autoptimize_filter_extra_tobepreloaded' ) ) {
188
            add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_preload' ), 10, 2 );
189
        }
190
    }
191
192
    public function filter_remove_emoji_dns_prefetch( $urls, $relation_type )
193
    {
194
        $emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
195
196
        return $this->filter_remove_dns_prefetch( $urls, $relation_type, $emoji_svg_url );
197
    }
198
199
    public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type )
200
    {
201
        return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' );
202
    }
203
204
    public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove )
205
    {
206
        $url_to_remove = (string) $url_to_remove;
207
208
        if ( ! empty( $url_to_remove ) && 'dns-prefetch' === $relation_type ) {
209
            $cnt = 0;
210
            foreach ( $urls as $url ) {
211
                if ( false !== strpos( $url, $url_to_remove ) ) {
212
                    unset( $urls[ $cnt ] );
213
                }
214
                $cnt++;
215
            }
216
        }
217
218
        return $urls;
219
    }
220
221
    public function filter_optimize_google_fonts( $in )
222
    {
223
        // Extract fonts, partly based on wp rocket's extraction code.
224
        $markup = preg_replace( '/<!--(.*)-->/Uis', '', $in );
225
        preg_match_all( '#<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches );
226
227
        $fonts_collection = array();
228
        if ( ! $matches[2] ) {
229
            return $in;
230
        }
231
232
        // Store them in $fonts array.
233
        $i = 0;
234
        foreach ( $matches[2] as $font ) {
235
            if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) {
236
                // Get fonts name.
237
                $font = str_replace( array( '%7C', '%7c' ), '|', $font );
238
                if ( strpos( $font, 'fonts.googleapis.com/css2' ) !== false ) {
239
                    // (Somewhat) change Google Fonts APIv2 syntax back to v1.
240
                    $font = str_replace( array( 'wght@', 'wght%40', ';', '%3B' ), array( '', '', ',', ',' ), $font );
241
                }
242
                $font = explode( 'family=', $font );
243
                $font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array();
244
                // Add font to $fonts[$i] but make sure not to pollute with an empty family!
245
                $_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) );
246
                if ( ! empty( $_thisfont ) ) {
247
                    $fonts_collection[ $i ]['fonts'] = $_thisfont;
248
                    // And add subset if any!
249
                    $subset = ( is_array( $font ) ) ? end( $font ) : '';
250
                    if ( false !== strpos( $subset, 'subset=' ) ) {
251
                        $subset                            = str_replace( array( '%2C', '%2c' ), ',', $subset );
252
                        $subset                            = explode( 'subset=', $subset );
253
                        $fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] );
254
                    }
255
                }
256
                // And remove Google Fonts.
257
                $in = str_replace( $matches[0][ $i ], '', $in );
258
            }
259
            $i++;
260
        }
261
262
        $options      = $this->options;
263
        $fonts_markup = '';
264
        if ( '2' === $options['autoptimize_extra_radio_field_4'] ) {
265
            // Remove Google Fonts.
266
            unset( $fonts_collection );
267
            return $in;
268
        } elseif ( '3' === $options['autoptimize_extra_radio_field_4'] || '5' === $options['autoptimize_extra_radio_field_4'] ) {
269
            // Aggregate & link!
270
            $fonts_string  = '';
271
            $subset_string = '';
272
            foreach ( $fonts_collection as $font ) {
273
                $fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' );
274
                if ( ! empty( $font['subsets'] ) ) {
275
                    $subset_string .= ',' . trim( implode( ',', $font['subsets'] ), ',' );
276
                }
277
            }
278
279
            if ( ! empty( $subset_string ) ) {
280
                $subset_string = str_replace( ',', '%2C', ltrim( $subset_string, ',' ) );
281
                $fonts_string  = $fonts_string . '&#038;subset=' . $subset_string;
282
            }
283
284
            $fonts_string = apply_filters( 'autoptimize_filter_extra_gfont_fontstring', str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) ) );
285
            // only add display parameter if there is none in $fonts_string (by virtue of the filter).
286
            if ( strpos( $fonts_string, 'display=' ) === false ) {
287
                $fonts_string .= apply_filters( 'autoptimize_filter_extra_gfont_display', '&amp;display=swap' );
288
            }
289
290
            if ( ! empty( $fonts_string ) ) {
291
                if ( '5' === $options['autoptimize_extra_radio_field_4'] ) {
292
                    $rel_string = 'rel="preload" as="style" onload="' . autoptimizeConfig::get_ao_css_preload_onload() . '"';
293
                } else {
294
                    $rel_string = 'rel="stylesheet"';
295
                }
296
                $fonts_markup = '<link ' . $rel_string . ' id="ao_optimized_gfonts" href="https://fonts.googleapis.com/css?family=' . $fonts_string . '" />';
297
            }
298
        } elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) {
299
            // Aggregate & load async (webfont.js impl.)!
300
            $fonts_array = array();
301
            foreach ( $fonts_collection as $_fonts ) {
302
                if ( ! empty( $_fonts['subsets'] ) ) {
303
                    $_subset = implode( ',', $_fonts['subsets'] );
304
                    foreach ( $_fonts['fonts'] as $key => $_one_font ) {
305
                        $_one_font               = $_one_font . ':' . $_subset;
306
                        $_fonts['fonts'][ $key ] = $_one_font;
307
                    }
308
                }
309
                $fonts_array = array_merge( $fonts_array, $_fonts['fonts'] );
310
            }
311
312
            $fonts_array = array_map( 'urldecode', $fonts_array );
313
            $fonts_array = array_map(
314
                function( $_f ) {
315
                    return trim( $_f, ',' );
316
                },
317
                $fonts_array
318
            );
319
320
            // type attrib on <script not added by default.
321
            $type_js = '';
322
            if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
323
                $type_js = 'type="text/javascript" ';
324
            }
325
326
            $fonts_markup         = '<script ' . $type_js . 'data-cfasync="false" id="ao_optimized_gfonts_config">WebFontConfig={google:{families:' . wp_json_encode( $fonts_array ) . ' },classes:false, events:false, timeout:1500};</script>';
327
            $fonts_library_markup = '<script ' . $type_js . 'data-cfasync="false" id="ao_optimized_gfonts_webfontloader">(function() {var wf = document.createElement(\'script\');wf.src=\'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js\';wf.type=\'text/javascript\';wf.async=\'true\';var s=document.getElementsByTagName(\'script\')[0];s.parentNode.insertBefore(wf, s);})();</script>';
328
            $in                   = substr_replace( $in, $fonts_library_markup . '</head>', strpos( $in, '</head>' ), strlen( '</head>' ) );
329
        }
330
331
        // Replace back in markup.
332
        $inject_point = apply_filters( 'autoptimize_filter_extra_gfont_injectpoint', '<link' );
333
        $out          = substr_replace( $in, $fonts_markup . $inject_point, strpos( $in, $inject_point ), strlen( $inject_point ) );
334
        unset( $fonts_collection );
335
336
        // and insert preload polyfill if "link preload" and if the polyfill isn't there yet (courtesy of inline&defer).
337
        $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
338
        if ( '5' === $options['autoptimize_extra_radio_field_4'] && strpos( $out, $preload_polyfill ) === false ) {
339
            $out = str_replace( '</body>', $preload_polyfill . '</body>', $out );
340
        }
341
        return $out;
342
    }
343
344
    public function filter_preconnect( $hints, $relation_type )
345
    {
346
        $options  = $this->options;
347
        $preconns = array();
348
349
        // Get settings and store in array.
350 View Code Duplication
        if ( array_key_exists( 'autoptimize_extra_text_field_2', $options ) ) {
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...
351
            $preconns = array_filter( array_map( 'trim', explode( ',', $options['autoptimize_extra_text_field_2'] ) ) );
352
        }
353
        $preconns = apply_filters( 'autoptimize_extra_filter_tobepreconn', $preconns );
354
355
        // Walk array, extract domain and add to new array with crossorigin attribute.
356
        foreach ( $preconns as $preconn ) {
357
            $domain = '';
358
            $parsed = parse_url( $preconn );
359
            if ( is_array( $parsed ) && ! empty( $parsed['host'] ) && empty( $parsed['scheme'] ) ) {
360
                $domain = '//' . $parsed['host'];
361
            } elseif ( is_array( $parsed ) && ! empty( $parsed['host'] ) ) {
362
                $domain = $parsed['scheme'] . '://' . $parsed['host'];
363
            }
364
365
            if ( ! empty( $domain ) ) {
366
                $hint = array( 'href' => $domain );
367
                // Fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set
368
                // so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed.
369
                $crossorigins = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array( 'https://fonts.gstatic.com' ) );
370
                if ( in_array( $domain, $crossorigins ) ) {
371
                    $hint['crossorigin'] = 'anonymous';
372
                }
373
                $new_hints[] = $hint;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$new_hints was never initialized. Although not strictly required by PHP, it is generally a good practice to add $new_hints = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
374
            }
375
        }
376
377
        // Merge in WP's preconnect hints.
378
        if ( 'preconnect' === $relation_type && ! empty( $new_hints ) ) {
379
            $hints = array_merge( $hints, $new_hints );
380
        }
381
382
        return $hints;
383
    }
384
385
    public function filter_preconnect_google_fonts( $in )
386
    {
387
        if ( '2' !== $this->options['autoptimize_extra_radio_field_4'] ) {
388
            // Preconnect to fonts.gstatic.com unless we remove gfonts.
389
            $in[] = 'https://fonts.gstatic.com';
390
        }
391
392
        if ( '4' === $this->options['autoptimize_extra_radio_field_4'] ) {
393
            // Preconnect even more hosts for webfont.js!
394
            $in[] = 'https://ajax.googleapis.com';
395
            $in[] = 'https://fonts.googleapis.com';
396
        }
397
398
        return $in;
399
    }
400
401
    public function filter_preload( $in ) {
402
        // make array from comma separated list.
403
        $options  = $this->options;
404
        $preloads = array();
405 View Code Duplication
        if ( array_key_exists( 'autoptimize_extra_text_field_7', $options ) ) {
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...
406
            $preloads = array_filter( array_map( 'trim', explode( ',', $options['autoptimize_extra_text_field_7'] ) ) );
407
        }
408
        $preloads = apply_filters( 'autoptimize_filter_extra_tobepreloaded', $preloads );
409
410
        // immediately return if nothing to be preloaded.
411
        if ( empty( $preloads ) ) {
412
            return $in;
413
        }
414
415
        // iterate through array and add preload link to tmp string.
416
        $preload_output = '';
417
        foreach ( $preloads as $preload ) {
418
            $crossorigin = '';
419
            $preload_as  = '';
0 ignored issues
show
Unused Code introduced by
$preload_as 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...
420
            $mime_type   = '';
421
            $_preload    = strtok( $preload, '?' );
422
423
            if ( autoptimizeUtils::str_ends_in( $_preload, '.css' ) ) {
424
                $preload_as = 'style';
425
            } elseif ( autoptimizeUtils::str_ends_in( $_preload, '.js' ) ) {
426
                $preload_as = 'script';
427
            } elseif ( autoptimizeUtils::str_ends_in( $_preload, '.woff' ) || autoptimizeUtils::str_ends_in( $_preload, '.woff2' ) || autoptimizeUtils::str_ends_in( $_preload, '.ttf' ) || autoptimizeUtils::str_ends_in( $_preload, '.eot' ) ) {
428
                $preload_as  = 'font';
429
                $crossorigin = ' crossorigin';
430
                $mime_type   = ' type="font/' . pathinfo( $_preload, PATHINFO_EXTENSION ) . '"';
431
                if ( ' type="font/eot"' === $mime_type ) {
432
                    $mime_type = 'application/vnd.ms-fontobject';
433
                }
434
            } elseif ( autoptimizeUtils::str_ends_in( $_preload, '.jpeg' ) || autoptimizeUtils::str_ends_in( $_preload, '.jpg' ) || autoptimizeUtils::str_ends_in( $_preload, '.webp' ) || autoptimizeUtils::str_ends_in( $_preload, '.png' ) || autoptimizeUtils::str_ends_in( $_preload, '.gif' ) ) {
435
                $preload_as = 'image';
436
            } else {
437
                $preload_as = 'other';
438
            }
439
440
            $preload_output .= '<link rel="preload" href="' . $preload . '" as="' . $preload_as . '"' . $mime_type . $crossorigin . '>';
441
        }
442
        $preload_output = apply_filters( 'autoptimize_filter_extra_preload_output', $preload_output );
443
444
        // add string to head (before first link node by default).
445
        $preload_inject = apply_filters( 'autoptimize_filter_extra_preload_inject', '<link' );
446
        $position       = autoptimizeUtils::strpos( $in, $preload_inject );
447
448
        return autoptimizeUtils::substr_replace( $in, $preload_output . $preload_inject, $position, strlen( $preload_inject ) );
0 ignored issues
show
Security Bug introduced by
It seems like $position defined by \autoptimizeUtils::strpos($in, $preload_inject) on line 446 can also be of type false; however, autoptimizeUtils::substr_replace() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
449
    }
450
451
    public function admin_menu()
452
    {
453
        // no acces if multisite and not network admin and no site config allowed.
454
        if ( autoptimizeConfig::should_show_menu_tabs() ) {
455
            add_submenu_page(
456
                null,
457
                'autoptimize_extra',
458
                'autoptimize_extra',
459
                'manage_options',
460
                'autoptimize_extra',
461
                array( $this, 'options_page' )
462
            );
463
        }
464
        register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' );
465
    }
466
467
    public function add_extra_tab( $in )
468
    {
469
        if ( autoptimizeConfig::should_show_menu_tabs() ) {
470
            $in = array_merge( $in, array( 'autoptimize_extra' => __( 'Extra', 'autoptimize' ) ) );
471
        }
472
473
        return $in;
474
    }
475
476
    public function options_page()
477
    {
478
        // Working with actual option values from the database here.
479
        // That way any saves are still processed as expected, but we can still
480
        // override behavior by using `new autoptimizeExtra($custom_options)` and not have that custom
481
        // behavior being persisted in the DB even if save is done here.
482
        $options = $this->fetch_options();
483
        $gfonts  = $options['autoptimize_extra_radio_field_4'];
484
        ?>
485
    <style>
486
        #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
487
        #ao_settings_form .form-table th {font-weight: normal;}
488
        #autoptimize_extra_descr{font-size: 120%;}
489
    </style>
490
    <script>document.title = "Autoptimize: <?php _e( 'Extra', 'autoptimize' ); ?> " + document.title;</script>
491
    <div class="wrap">
492
    <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
493
        <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
494
        <?php if ( 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_js' ) && 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_css' ) && 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_html' ) && ! autoptimizeImages::imgopt_active() ) { ?>
0 ignored issues
show
Bug Best Practice introduced by
The expression \autoptimizeImages::imgopt_active() 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...
495
            <div class="notice-warning notice"><p>
496
            <?php _e( 'Most of below Extra optimizations require at least one of HTML, JS, CSS or Image autoptimizations being active.', 'autoptimize' ); ?>
497
            </p></div>
498
        <?php } ?>
499
500
    <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
501
        <?php settings_fields( 'autoptimize_extra_settings' ); ?>
502
        <h2><?php _e( 'Extra Auto-Optimizations', 'autoptimize' ); ?></h2>
503
        <span id='autoptimize_extra_descr'><?php _e( 'The following settings can improve your site\'s performance even more.', 'autoptimize' ); ?></span>
504
        <table class="form-table">
505
            <tr>
506
                <th scope="row"><?php _e( 'Google Fonts', 'autoptimize' ); ?></th>
507
                <td>
508
                    <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="1" <?php if ( ! in_array( $gfonts, array( 2, 3, 4, 5 ) ) ) { echo 'checked'; } ?> ><?php _e( 'Leave as is', 'autoptimize' ); ?><br/>
509
                    <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="2" <?php checked( 2, $gfonts, true ); ?> ><?php _e( 'Remove Google Fonts', 'autoptimize' ); ?><br/>
510
                    <?php // translators: "display:swap" should remain untranslated, will be shown in code tags. ?>
511
                    <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="3" <?php checked( 3, $gfonts, true ); ?> ><?php echo __( 'Combine and link in head (fonts load fast but are render-blocking)', 'autoptimize' ) . ', ' . sprintf( __( 'includes %1$sdisplay:swap%2$s.', 'autoptimize' ), '<code>', '</code>' ); ?><br/>
512
                    <?php // translators: "display:swap" should remain untranslated, will be shown in code tags. ?>
513
                    <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="5" <?php checked( 5, $gfonts, true ); ?> ><?php echo __( 'Combine and preload in head (fonts load late, but are not render-blocking)', 'autoptimize' ) . ', ' . sprintf( __( 'includes %1$sdisplay:swap%2$s.', 'autoptimize' ), '<code>', '</code>' ); ?><br/>
514
                    <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="4" <?php checked( 4, $gfonts, true ); ?> ><?php _e( 'Combine and load fonts asynchronously with <a href="https://github.com/typekit/webfontloader#readme" target="_blank">webfont.js</a>', 'autoptimize' ); ?><br/>
515
                </td>
516
            </tr>
517
            <tr>
518
                <th scope="row"><?php _e( 'Remove emojis', 'autoptimize' ); ?></th>
519
                <td>
520
                    <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) && '1' === $options['autoptimize_extra_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Removes WordPress\' core emojis\' inline CSS, inline JavaScript, and an otherwise un-autoptimized JavaScript file.', 'autoptimize' ); ?></label>
521
                </td>
522
            </tr>
523
            <tr>
524
                <th scope="row"><?php _e( 'Remove query strings from static resources', 'autoptimize' ); ?></th>
525
                <td>
526
                    <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_0]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) && '1' === $options['autoptimize_extra_checkbox_field_0'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Removing query strings (or more specifically the <code>ver</code> parameter) will not improve load time, but might improve performance scores.', 'autoptimize' ); ?></label>
527
                </td>
528
            </tr>
529
            <tr>
530
                <th scope="row"><?php _e( 'Preconnect to 3rd party domains <em>(advanced users)</em>', 'autoptimize' ); ?></th>
531
                <td>
532
                    <label><input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_2]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_2', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_2'] ); } ?>'><br /><?php _e( 'Add 3rd party domains you want the browser to <a href="https://www.keycdn.com/support/preconnect/#primary" target="_blank">preconnect</a> to, separated by comma\'s. Make sure to include the correct protocol (HTTP or HTTPS).', 'autoptimize' ); ?></label>
533
                </td>
534
            </tr>
535
            <tr>
536
                <th scope="row"><?php _e( 'Preload specific requests <em>(advanced users)</em>', 'autoptimize' ); ?></th>
537
                <td>
538
                    <label><input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_7]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_7', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_7'] ); } ?>'><br /><?php _e( 'Comma-separated list with full URL\'s of to to-be-preloaded resources. To be used sparingly!', 'autoptimize' ); ?></label>
539
                </td>
540
            </tr>
541
            <tr>
542
                <th scope="row"><?php _e( 'Async Javascript-files <em>(advanced users)</em>', 'autoptimize' ); ?></th>
543
                <td>
544
                    <?php
545
                    if ( autoptimizeUtils::is_plugin_active( 'async-javascript/async-javascript.php' ) ) {
546
                        // translators: link points Async Javascript settings page.
547
                        printf( __( 'You have "Async JavaScript" installed, %1$sconfiguration of async javascript is best done there%2$s.', 'autoptimize' ), '<a href="' . 'options-general.php?page=async-javascript' . '">', '</a>' );
548
                    } else {
549
                    ?>
550
                        <input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_3]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_3', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_3'] ); } ?>'>
551
                        <br />
552
                        <?php
553
                            _e( 'Comma-separated list of local or 3rd party JS-files that should loaded with the <code>async</code> flag. JS-files from your own site will be automatically excluded if added here. ', 'autoptimize' );
554
                            // translators: %s will be replaced by a link to the "async javascript" plugin.
555
                            echo sprintf( __( 'Configuration of async javascript is easier and more flexible using the %s plugin.', 'autoptimize' ), '"<a href="https://wordpress.org/plugins/async-javascript" target="_blank">Async Javascript</a>"' );
556
                            $asj_install_url = network_admin_url() . 'plugin-install.php?s=async+javascript&tab=search&type=term';
557
                            echo sprintf( ' <a href="' . $asj_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
558
                    }
559
                    ?>
560
                </td>
561
            </tr>
562
            <tr>
563
                <th scope="row"><?php _e( 'Optimize YouTube videos', 'autoptimize' ); ?></th>
564
                <td>
565
                    <?php
566
                    if ( autoptimizeUtils::is_plugin_active( 'wp-youtube-lyte/wp-youtube-lyte.php' ) ) {
567
                        _e( 'Great, you have WP YouTube Lyte installed.', 'autoptimize' );
568
                        $lyte_config_url = 'options-general.php?page=lyte_settings_page';
569
                        echo sprintf( ' <a href="' . $lyte_config_url . '">%s</a>', __( 'Click here to configure it.', 'autoptimize' ) );
570
                    } else {
571
                        // translators: %s will be replaced by a link to "wp youtube lyte" plugin.
572
                        echo sprintf( __( '%s allows you to “lazy load” your videos, by inserting responsive “Lite YouTube Embeds". ', 'autoptimize' ), '<a href="https://wordpress.org/plugins/wp-youtube-lyte" target="_blank">WP YouTube Lyte</a>' );
573
                        $lyte_install_url = network_admin_url() . 'plugin-install.php?s=lyte&tab=search&type=term';
574
                        echo sprintf( ' <a href="' . $lyte_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
575
                    }
576
                    ?>
577
                </td>
578
            </tr>
579
        </table>
580
        <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
581
    </form>
582
        <?php
583
    }
584
}
585