Completed
Push — master ( 8e7dca...6ec0d0 )
by frank
01:41
created

autoptimizeUtils::assemble_parsed_url()   F

Complexity

Conditions 12
Paths 2048

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
nc 2048
nop 2
dl 0
loc 17
rs 2.8
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * General helpers.
4
 */
5
6
if ( ! defined( 'ABSPATH' ) ) {
7
    exit;
8
}
9
10
class autoptimizeUtils
11
{
12
    /**
13
     * Returns true when mbstring is available.
14
     *
15
     * @param bool|null $override Allows overriding the decision.
16
     *
17
     * @return bool
18
     */
19 View Code Duplication
    public static function mbstring_available( $override = null )
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...
20
    {
21
        static $available = null;
22
23
        if ( null === $available ) {
24
            $available = \extension_loaded( 'mbstring' );
25
        }
26
27
        if ( null !== $override ) {
28
            $available = $override;
29
        }
30
31
        return $available;
32
    }
33
34
    /**
35
     * Returns true when iconv is available.
36
     *
37
     * @param bool|null $override Allows overriding the decision.
38
     *
39
     * @return bool
40
     */
41 View Code Duplication
    public static function iconv_available( $override = null )
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...
42
    {
43
        static $available = null;
44
45
        if ( null === $available ) {
46
            $available = \extension_loaded( 'iconv' );
47
        }
48
49
        if ( null !== $override ) {
50
            $available = $override;
51
        }
52
53
        return $available;
54
    }
55
56
    /**
57
     * Multibyte-capable strpos() if support is available on the server.
58
     * If not, it falls back to using \strpos().
59
     *
60
     * @param string      $haystack Haystack.
61
     * @param string      $needle   Needle.
62
     * @param int         $offset   Offset.
63
     * @param string|null $encoding Encoding. Default null.
64
     *
65
     * @return int|false
66
     */
67
    public static function strpos( $haystack, $needle, $offset = 0, $encoding = null )
68
    {
69
        if ( self::mbstring_available() ) {
70
            return ( null === $encoding ) ? \mb_strpos( $haystack, $needle, $offset ) : \mb_strlen( $haystack, $needle, $offset, $encoding );
71
        } elseif ( self::iconv_available() ) {
72
            return ( null === $encoding ) ? \iconv_strpos( $haystack, $needle, $offset ) : \iconv_strpos( $haystack, $needle, $offset, $encoding );
73
        } else {
74
            return \strpos( $haystack, $needle, $offset );
75
        }
76
    }
77
78
    /**
79
     * Attempts to return the number of characters in the given $string if
80
     * mbstring or iconv is available. Returns the number of bytes
81
     * (instead of characters) as fallback.
82
     *
83
     * @param string      $string   String.
84
     * @param string|null $encoding Encoding.
85
     *
86
     * @return int Number of charcters or bytes in given $string
87
     *             (characters if/when supported, bytes otherwise).
88
     */
89
    public static function strlen( $string, $encoding = null )
90
    {
91
        if ( self::mbstring_available() ) {
92
            return ( null === $encoding ) ? \mb_strlen( $string ) : \mb_strlen( $string, $encoding );
93
        } elseif ( self::iconv_available() ) {
94
            return ( null === $encoding ) ? @iconv_strlen( $string ) : @iconv_strlen( $string, $encoding );
95
        } else {
96
            return \strlen( $string );
97
        }
98
    }
99
100
    /**
101
     * Our wrapper around implementations of \substr_replace()
102
     * that attempts to not break things horribly if at all possible.
103
     * Uses mbstring and/or iconv if available, before falling back to regular
104
     * substr_replace() (which works just fine in the majority of cases).
105
     *
106
     * @param string      $string      String.
107
     * @param string      $replacement Replacement.
108
     * @param int         $start       Start offset.
109
     * @param int|null    $length      Length.
110
     * @param string|null $encoding    Encoding.
111
     *
112
     * @return string
113
     */
114
    public static function substr_replace( $string, $replacement, $start, $length = null, $encoding = null )
115
    {
116
        if ( self::mbstring_available() ) {
117
            $strlen = self::strlen( $string, $encoding );
118
119
            if ( $start < 0 ) {
120
                if ( -$start < $strlen ) {
121
                    $start = $strlen + $start;
122
                } else {
123
                    $start = 0;
124
                }
125
            } elseif ( $start > $strlen ) {
126
                $start = $strlen;
127
            }
128
129
            if ( null === $length || '' === $length ) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of '' (string) and $length (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
130
                $start2 = $strlen;
131
            } elseif ( $length < 0 ) {
132
                $start2 = $strlen + $length;
133
                if ( $start2 < $start ) {
134
                    $start2 = $start;
135
                }
136
            } else {
137
                $start2 = $start + $length;
138
            }
139
140
            if ( null === $encoding ) {
141
                $leader  = $start ? \mb_substr( $string, 0, $start ) : '';
142
                $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null ) : '';
143
            } else {
144
                $leader  = $start ? \mb_substr( $string, 0, $start, $encoding ) : '';
145
                $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null, $encoding ) : '';
146
            }
147
148
            return "{$leader}{$replacement}{$trailer}";
149
        } elseif ( self::iconv_available() ) {
150
            $strlen = self::strlen( $string, $encoding );
151
152
            if ( $start < 0 ) {
153
                $start = \max( 0, $strlen + $start );
154
                $start = $strlen + $start;
155
                if ( $start < 0 ) {
156
                    $start = 0;
157
                }
158
            } elseif ( $start > $strlen ) {
159
                $start = $strlen;
160
            }
161
162
            if ( $length < 0 ) {
163
                $length = \max( 0, $strlen - $start + $length );
164
            } elseif ( null === $length || ( $length > $strlen ) ) {
165
                $length = $strlen;
166
            }
167
168
            if ( ( $start + $length ) > $strlen ) {
169
                $length = $strlen - $start;
170
            }
171
172
            if ( null === $encoding ) {
173
                return self::iconv_substr( $string, 0, $start ) . $replacement . self::iconv_substr( $string, $start + $length, $strlen - $start - $length );
174
            }
175
176
            return self::iconv_substr( $string, 0, $start, $encoding ) . $replacement . self::iconv_substr( $string, $start + $length, $strlen - $start - $length, $encoding );
177
        }
178
179
        return ( null === $length ) ? \substr_replace( $string, $replacement, $start ) : \substr_replace( $string, $replacement, $start, $length );
180
    }
181
182
    /**
183
     * Wrapper around iconv_substr().
184
     *
185
     * @param string      $s        String.
186
     * @param int         $start    Start offset.
187
     * @param int|null    $length   Length.
188
     * @param string|null $encoding Encoding.
189
     *
190
     * @return string
191
     */
192
    protected static function iconv_substr( $s, $start, $length = null, $encoding = null )
193
    {
194 View Code Duplication
        if ( $start < 0 ) {
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...
195
            $start = self::strlen( $s, $encoding ) + $start;
196
            if ( $start < 0 ) {
197
                $start = 0;
198
            }
199
        }
200
201
        if ( null === $length ) {
202
            $length = 2147483647;
203 View Code Duplication
        } elseif ( $length < 0 ) {
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...
204
            $length = self::strlen( $s, $encoding ) + ( $length - $start );
205
            if ( $length < 0 ) {
206
                return '';
207
            }
208
        }
209
210
        return (string) ( null === $encoding ) ? \iconv_substr( $s, $start, $length ) : \iconv_substr( $s, $start, $length, $encoding );
211
    }
212
213
    /**
214
     * Decides whether this is a "subdirectory site" or not.
215
     *
216
     * @param bool $override Allows overriding the decision when needed.
217
     *
218
     * @return bool
219
     */
220
    public static function siteurl_not_root( $override = null )
221
    {
222
        static $subdir = null;
223
224
        if ( null === $subdir ) {
225
            $parts  = self::get_ao_wp_site_url_parts();
226
            $subdir = ( isset( $parts['path'] ) && ( '/' !== $parts['path'] ) );
227
        }
228
229
        if ( null !== $override ) {
230
            $subdir = $override;
231
        }
232
233
        return $subdir;
234
    }
235
236
    /**
237
     * Parse AUTOPTIMIZE_WP_SITE_URL into components using \parse_url(), but do
238
     * so only once per request/lifecycle.
239
     *
240
     * @return array
241
     */
242
    public static function get_ao_wp_site_url_parts()
243
    {
244
        static $parts = array();
245
246
        if ( empty( $parts ) ) {
247
            $parts = \parse_url( AUTOPTIMIZE_WP_SITE_URL );
248
        }
249
250
        return $parts;
251
    }
252
253
    /**
254
     * Modify given $cdn_url to include the site path when needed.
255
     *
256
     * @param string $cdn_url          CDN URL to tweak.
257
     * @param bool   $force_cache_miss Force a cache miss in order to be able
258
     *                                 to re-run the filter.
259
     *
260
     * @return string
261
     */
262
    public static function tweak_cdn_url_if_needed( $cdn_url, $force_cache_miss = false )
263
    {
264
        static $results = array();
265
266
        if ( ! isset( $results[ $cdn_url ] ) || $force_cache_miss ) {
267
268
            // In order to return unmodified input when there's no need to tweak.
269
            $results[ $cdn_url ] = $cdn_url;
270
271
            // Behind a default true filter for backcompat, and only for sites
272
            // in a subfolder/subdirectory, but still easily turned off if
273
            // not wanted/needed...
274
            if ( autoptimizeUtils::siteurl_not_root() ) {
275
                $check = apply_filters( 'autoptimize_filter_cdn_magic_path_check', true, $cdn_url );
276
                if ( $check ) {
277
                    $site_url_parts = autoptimizeUtils::get_ao_wp_site_url_parts();
278
                    $cdn_url_parts  = \parse_url( $cdn_url );
279
                    $schemeless     = self::is_protocol_relative( $cdn_url );
280
                    $cdn_url_parts  = self::maybe_replace_cdn_path( $site_url_parts, $cdn_url_parts );
0 ignored issues
show
Security Bug introduced by
It seems like $site_url_parts defined by \autoptimizeUtils::get_ao_wp_site_url_parts() on line 277 can also be of type false; however, autoptimizeUtils::maybe_replace_cdn_path() does only seem to accept array, 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...
Security Bug introduced by
It seems like $cdn_url_parts defined by self::maybe_replace_cdn_..._parts, $cdn_url_parts) on line 280 can also be of type false; however, autoptimizeUtils::maybe_replace_cdn_path() does only seem to accept array, 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...
281
                    if ( false !== $cdn_url_parts ) {
282
                        $results[ $cdn_url ] = self::assemble_parsed_url( $cdn_url_parts, $schemeless );
283
                    }
284
                }
285
            }
286
        }
287
288
        return $results[ $cdn_url ];
289
    }
290
291
    /**
292
     * When siteurl contans a path other than '/' and the CDN URL does not have
293
     * a path or it's path is '/', this will modify the CDN URL's path component
294
     * to match that of the siteurl.
295
     * This is to support "magic" CDN urls that worked that way before v2.4...
296
     *
297
     * @param array $site_url_parts Site URL components array.
298
     * @param array $cdn_url_parts  CDN URL components array.
299
     *
300
     * @return array|false
301
     */
302
    public static function maybe_replace_cdn_path( array $site_url_parts, array $cdn_url_parts )
303
    {
304
        if ( isset( $site_url_parts['path'] ) && '/' !== $site_url_parts['path'] ) {
305
            if ( ! isset( $cdn_url_parts['path'] ) || '/' === $cdn_url_parts['path'] ) {
306
                $cdn_url_parts['path'] = $site_url_parts['path'];
307
                return $cdn_url_parts;
308
            }
309
        }
310
311
        return false;
312
    }
313
314
    /**
315
     * Given an array or components returned from \parse_url(), assembles back
316
     * the complete URL.
317
     * If optional
318
     *
319
     * @param array $parsed_url URL components array.
320
     * @param bool  $schemeless Whether the assembled URL should be
321
     *                          protocol-relative (schemeless) or not.
322
     *
323
     * @return string
324
     */
325
    public static function assemble_parsed_url( array $parsed_url, $schemeless = false )
326
    {
327
        $scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
328
        if ( $schemeless ) {
329
            $scheme = '//';
330
        }
331
        $host     = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
332
        $port     = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
333
        $user     = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
334
        $pass     = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
335
        $pass     = ( $user || $pass ) ? "$pass@" : '';
336
        $path     = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
337
        $query    = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
338
        $fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';
339
340
        return "$scheme$user$pass$host$port$path$query$fragment";
341
    }
342
343
    /**
344
     * Returns true if given $url is protocol-relative.
345
     *
346
     * @param string $url URL to check.
347
     *
348
     * @return bool
349
     */
350
    public static function is_protocol_relative( $url )
351
    {
352
        $result = false;
353
354
        if ( ! empty( $url ) ) {
355
            $result = ( '/' === $url{1} ); // second char is `/`.
356
        }
357
358
        return $result;
359
    }
360
361
    /**
362
     * Canonicalizes the given path regardless of it existing or not.
363
     *
364
     * @param string $path Path to normalize.
365
     *
366
     * @return string
367
     */
368
    public static function path_canonicalize( $path )
369
    {
370
        $patterns     = array(
371
            '~/{2,}~',
372
            '~/(\./)+~',
373
            '~([^/\.]+/(?R)*\.{2,}/)~',
374
            '~\.\./~',
375
        );
376
        $replacements = array(
377
            '/',
378
            '/',
379
            '',
380
            '',
381
        );
382
383
        return preg_replace( $patterns, $replacements, $path );
384
    }
385
386
    /**
387
     * Checks to see if 3rd party services are available and stores result in option
388
     *
389
     * @param string $return_result should we return resulting service status array (default no).
390
     *
391
     * @return none if $return_result is false (default), array if $return_result is true.
392
     */
393
    public static function check_service_availability( $return_result = false )
394
    {
395
        $service_availability_resp = wp_remote_get( 'https://misc.optimizingmatters.com/api/autoptimize_service_availablity.json?from=aomain&ver=' . AUTOPTIMIZE_PLUGIN_VERSION );
396 View Code Duplication
        if ( ! is_wp_error( $service_availability_resp ) ) {
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...
397
            if ( '200' == wp_remote_retrieve_response_code( $service_availability_resp ) ) {
398
                $availabilities = json_decode( wp_remote_retrieve_body( $service_availability_resp ), true );
399
                if ( is_array( $availabilities ) ) {
400
                    update_option( 'autoptimize_service_availablity', $availabilities );
401
                    if ( $return_result ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $return_result of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
402
                        return $availabilities;
403
                    }
404
                }
405
            }
406
        }
407
    }
408
409
    /**
410
     * Returns true if the string is a valid regex.
411
     *
412
     * @param string $string String, duh.
413
     *
414
     * @return bool
415
     */
416
    public static function str_is_valid_regex( $string )
417
    {
418
        set_error_handler( function() {}, E_WARNING );
419
        $is_regex = ( false !== preg_match( $string, '' ) );
420
        restore_error_handler();
421
422
        return $is_regex;
423
    }
424
}
425