autoptimizeUtils::str_is_valid_regex()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
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
    public static function mbstring_available( $override = null )
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
     * Multibyte-capable strpos() if support is available on the server.
36
     * If not, it falls back to using \strpos().
37
     *
38
     * @param string      $haystack Haystack.
39
     * @param string      $needle   Needle.
40
     * @param int         $offset   Offset.
41
     * @param string|null $encoding Encoding. Default null.
42
     *
43
     * @return int|false
44
     */
45
    public static function strpos( $haystack, $needle, $offset = 0, $encoding = null )
46
    {
47
        if ( self::mbstring_available() ) {
48
            return ( null === $encoding ) ? \mb_strpos( $haystack, $needle, $offset ) : \mb_strpos( $haystack, $needle, $offset, $encoding );
49
        } else {
50
            return \strpos( $haystack, $needle, $offset );
51
        }
52
    }
53
54
    /**
55
     * Attempts to return the number of characters in the given $string if
56
     * mbstring is available. Returns the number of bytes
57
     * (instead of characters) as fallback.
58
     *
59
     * @param string      $string   String.
60
     * @param string|null $encoding Encoding.
61
     *
62
     * @return int Number of characters or bytes in given $string
63
     *             (characters if/when supported, bytes otherwise).
64
     */
65
    public static function strlen( $string, $encoding = null )
66
    {
67
        if ( self::mbstring_available() ) {
68
            return ( null === $encoding ) ? \mb_strlen( $string ) : \mb_strlen( $string, $encoding );
69
        } else {
70
            return \strlen( $string );
71
        }
72
    }
73
74
    /**
75
     * Our wrapper around implementations of \substr_replace()
76
     * that attempts to not break things horribly if at all possible.
77
     * Uses mbstring if available, before falling back to regular
78
     * substr_replace() (which works just fine in the majority of cases).
79
     *
80
     * @param string      $string      String.
81
     * @param string      $replacement Replacement.
82
     * @param int         $start       Start offset.
83
     * @param int|null    $length      Length.
84
     * @param string|null $encoding    Encoding.
85
     *
86
     * @return string
87
     */
88
    public static function substr_replace( $string, $replacement, $start, $length = null, $encoding = null )
89
    {
90
        if ( self::mbstring_available() ) {
91
            $strlen = self::strlen( $string, $encoding );
92
93
            if ( $start < 0 ) {
94
                if ( -$start < $strlen ) {
95
                    $start = $strlen + $start;
96
                } else {
97
                    $start = 0;
98
                }
99
            } elseif ( $start > $strlen ) {
100
                $start = $strlen;
101
            }
102
103
            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...
104
                $start2 = $strlen;
105
            } elseif ( $length < 0 ) {
106
                $start2 = $strlen + $length;
107
                if ( $start2 < $start ) {
108
                    $start2 = $start;
109
                }
110
            } else {
111
                $start2 = $start + $length;
112
            }
113
114
            if ( null === $encoding ) {
115
                $leader  = $start ? \mb_substr( $string, 0, $start ) : '';
116
                $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null ) : '';
117
            } else {
118
                $leader  = $start ? \mb_substr( $string, 0, $start, $encoding ) : '';
119
                $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null, $encoding ) : '';
120
            }
121
122
            return "{$leader}{$replacement}{$trailer}";
123
        }
124
125
        return ( null === $length ) ? \substr_replace( $string, $replacement, $start ) : \substr_replace( $string, $replacement, $start, $length );
126
    }
127
128
    /**
129
     * Decides whether this is a "subdirectory site" or not.
130
     *
131
     * @param bool $override Allows overriding the decision when needed.
132
     *
133
     * @return bool
134
     */
135
    public static function siteurl_not_root( $override = null )
136
    {
137
        static $subdir = null;
138
139
        if ( null === $subdir ) {
140
            $parts  = self::get_ao_wp_site_url_parts();
141
            $subdir = ( isset( $parts['path'] ) && ( '/' !== $parts['path'] ) );
142
        }
143
144
        if ( null !== $override ) {
145
            $subdir = $override;
146
        }
147
148
        return $subdir;
149
    }
150
151
    /**
152
     * Parse AUTOPTIMIZE_WP_SITE_URL into components using \parse_url(), but do
153
     * so only once per request/lifecycle.
154
     *
155
     * @return array
156
     */
157
    public static function get_ao_wp_site_url_parts()
158
    {
159
        static $parts = array();
160
161
        if ( empty( $parts ) ) {
162
            $parts = \parse_url( AUTOPTIMIZE_WP_SITE_URL );
163
        }
164
165
        return $parts;
166
    }
167
168
    /**
169
     * Modify given $cdn_url to include the site path when needed.
170
     *
171
     * @param string $cdn_url          CDN URL to tweak.
172
     * @param bool   $force_cache_miss Force a cache miss in order to be able
173
     *                                 to re-run the filter.
174
     *
175
     * @return string
176
     */
177
    public static function tweak_cdn_url_if_needed( $cdn_url, $force_cache_miss = false )
178
    {
179
        static $results = array();
180
181
        if ( ! isset( $results[ $cdn_url ] ) || $force_cache_miss ) {
182
183
            // In order to return unmodified input when there's no need to tweak.
184
            $results[ $cdn_url ] = $cdn_url;
185
186
            // Behind a default true filter for backcompat, and only for sites
187
            // in a subfolder/subdirectory, but still easily turned off if
188
            // not wanted/needed...
189
            if ( autoptimizeUtils::siteurl_not_root() ) {
190
                $check = apply_filters( 'autoptimize_filter_cdn_magic_path_check', true, $cdn_url );
191
                if ( $check ) {
192
                    $site_url_parts = autoptimizeUtils::get_ao_wp_site_url_parts();
193
                    $cdn_url_parts  = \parse_url( $cdn_url );
194
                    $schemeless     = self::is_protocol_relative( $cdn_url );
195
                    $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 192 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 195 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...
196
                    if ( false !== $cdn_url_parts ) {
197
                        $results[ $cdn_url ] = self::assemble_parsed_url( $cdn_url_parts, $schemeless );
198
                    }
199
                }
200
            }
201
        }
202
203
        return $results[ $cdn_url ];
204
    }
205
206
    /**
207
     * When siteurl contains a path other than '/' and the CDN URL does not have
208
     * a path or it's path is '/', this will modify the CDN URL's path component
209
     * to match that of the siteurl.
210
     * This is to support "magic" CDN urls that worked that way before v2.4...
211
     *
212
     * @param array $site_url_parts Site URL components array.
213
     * @param array $cdn_url_parts  CDN URL components array.
214
     *
215
     * @return array|false
216
     */
217
    public static function maybe_replace_cdn_path( array $site_url_parts, array $cdn_url_parts )
218
    {
219
        if ( isset( $site_url_parts['path'] ) && '/' !== $site_url_parts['path'] ) {
220
            if ( ! isset( $cdn_url_parts['path'] ) || '/' === $cdn_url_parts['path'] ) {
221
                $cdn_url_parts['path'] = $site_url_parts['path'];
222
                return $cdn_url_parts;
223
            }
224
        }
225
226
        return false;
227
    }
228
229
    /**
230
     * Given an array or components returned from \parse_url(), assembles back
231
     * the complete URL.
232
     * If optional
233
     *
234
     * @param array $parsed_url URL components array.
235
     * @param bool  $schemeless Whether the assembled URL should be
236
     *                          protocol-relative (schemeless) or not.
237
     *
238
     * @return string
239
     */
240
    public static function assemble_parsed_url( array $parsed_url, $schemeless = false )
241
    {
242
        $scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
243
        if ( $schemeless ) {
244
            $scheme = '//';
245
        }
246
        $host     = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
247
        $port     = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
248
        $user     = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
249
        $pass     = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
250
        $pass     = ( $user || $pass ) ? "$pass@" : '';
251
        $path     = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
252
        $query    = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
253
        $fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';
254
255
        return "$scheme$user$pass$host$port$path$query$fragment";
256
    }
257
258
    /**
259
     * Returns true if given $url is protocol-relative.
260
     *
261
     * @param string $url URL to check.
262
     *
263
     * @return bool
264
     */
265
    public static function is_protocol_relative( $url )
266
    {
267
        $result = false;
268
269
        if ( ! empty( $url ) ) {
270
            $result = ( 0 === strpos( $url, '//' ) );
271
        }
272
273
        return $result;
274
    }
275
276
    /**
277
     * Canonicalizes the given path regardless of it existing or not.
278
     *
279
     * @param string $path Path to normalize.
280
     *
281
     * @return string
282
     */
283
    public static function path_canonicalize( $path )
284
    {
285
        $patterns     = array(
286
            '~/{2,}~',
287
            '~/(\./)+~',
288
            '~([^/\.]+/(?R)*\.{2,}/)~',
289
            '~\.\./~',
290
        );
291
        $replacements = array(
292
            '/',
293
            '/',
294
            '',
295
            '',
296
        );
297
298
        return preg_replace( $patterns, $replacements, $path );
299
    }
300
301
    /**
302
     * Checks to see if 3rd party services are available and stores result in option
303
     *
304
     * TODO This should be two separate methods.
305
     *
306
     * @param string $return_result should we return resulting service status array (default no).
307
     *
308
     * @return null|array Service status or null.
309
     */
310
    public static function check_service_availability( $return_result = false )
311
    {
312
        $service_availability_resp = wp_remote_get( 'https://misc.optimizingmatters.com/api/autoptimize_service_availablity.json?from=aomain&ver=' . AUTOPTIMIZE_PLUGIN_VERSION );
313 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...
314
            if ( '200' == wp_remote_retrieve_response_code( $service_availability_resp ) ) {
315
                $availabilities = json_decode( wp_remote_retrieve_body( $service_availability_resp ), true );
316
                if ( is_array( $availabilities ) ) {
317
                    autoptimizeOptionWrapper::update_option( 'autoptimize_service_availablity', $availabilities );
318
                    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...
319
                        return $availabilities;
320
                    }
321
                }
322
            }
323
        }
324
        return null;
325
    }
326
327
    /**
328
     * Returns true if the string is a valid regex.
329
     *
330
     * @param string $string String, duh.
331
     *
332
     * @return bool
333
     */
334
    public static function str_is_valid_regex( $string )
335
    {
336
        set_error_handler( function() {}, E_WARNING );
337
        $is_regex = ( false !== preg_match( $string, '' ) );
338
        restore_error_handler();
339
340
        return $is_regex;
341
    }
342
343
    /**
344
     * Returns true if a certain WP plugin is active/loaded.
345
     *
346
     * @param string $plugin_file Main plugin file.
347
     *
348
     * @return bool
349
     */
350
    public static function is_plugin_active( $plugin_file )
351
    {
352
        static $ipa_exists = null;
353
        if ( null === $ipa_exists ) {
354
            if ( ! function_exists( '\is_plugin_active' ) ) {
355
                require_once ABSPATH . 'wp-admin/includes/plugin.php';
356
            }
357
            $ipa_exists = function_exists( '\is_plugin_active' );
358
        }
359
360
        return $ipa_exists && \is_plugin_active( $plugin_file );
361
    }
362
363
    /**
364
     * Returns a node without ID attrib for use in noscript tags
365
     *
366
     * @param string $node an html tag.
367
     *
368
     * @return string
369
     */
370
    public static function remove_id_from_node( $node ) {
371
        if ( strpos( $node, 'id=' ) === false || apply_filters( 'autoptimize_filter_utils_keep_ids', false ) ) {
372
            return $node;
373
        } else {
374
            return preg_replace( '#(.*) id=[\'|"].*[\'|"] (.*)#Um', '$1 $2', $node );
375
        }
376
    }
377
378
    /**
379
     * Returns true if given $str ends with given $test.
380
     *
381
     * @param string $str String to check.
382
     * @param string $test Ending to match.
383
     *
384
     * @return bool
385
     */
386
    public static function str_ends_in( $str, $test )
387
    {
388
        // @codingStandardsIgnoreStart
389
        // substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
390
        // return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
391
        // @codingStandardsIgnoreEnd
392
393
        $length = strlen( $test );
394
395
        return ( substr( $str, -$length, $length ) === $test );
396
    }
397
}
398