Passed
Push — develop ( 2c752a...e3ae39 )
by Andrew
06:50
created

Redirects::resolveRedirect()   F

Complexity

Conditions 20
Paths 155

Size

Total Lines 150
Code Lines 100

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 420

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 100
c 5
b 0
f 0
dl 0
loc 150
ccs 0
cts 91
cp 0
rs 2.9666
cc 20
nc 155
nop 3
crap 420

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Retour plugin for Craft CMS 3.x
4
 *
5
 * Retour allows you to intelligently redirect legacy URLs, so that you don't
6
 * lose SEO value when rebuilding & restructuring a website
7
 *
8
 * @link      https://nystudio107.com/
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
9
 * @copyright Copyright (c) 2018 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
10
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
11
12
namespace nystudio107\retour\services;
13
14
use nystudio107\retour\Retour;
15
use nystudio107\retour\events\RedirectEvent;
16
use nystudio107\retour\events\ResolveRedirectEvent;
17
use nystudio107\retour\events\RedirectResolvedEvent;
18
use nystudio107\retour\helpers\UrlHelper;
19
use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel;
20
21
use Craft;
22
use craft\base\Component;
23
use craft\base\Plugin;
24
use craft\db\Query;
25
use craft\errors\SiteNotFoundException;
26
use craft\helpers\Db;
27
28
use yii\base\ExitException;
29
use yii\base\InvalidConfigException;
30
use yii\base\InvalidRouteException;
31
use yii\caching\TagDependency;
32
use yii\db\Exception;
33
34
/** @noinspection MissingPropertyAnnotationsInspection */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
35
36
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
37
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
38
 * @package   Retour
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
39
 * @since     3.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
40
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
41
class Redirects extends Component
42
{
43
    // Constants
44
    // =========================================================================
45
46
    const CACHE_KEY = 'retour_redirect_';
47
48
    const GLOBAL_REDIRECTS_CACHE_TAG = 'retour_redirects';
49
50
    const EVENT_REDIRECT_ID = 0;
51
52
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
53
     * @event RedirectEvent The event that is triggered before the redirect is saved
54
     * You may set [[RedirectEvent::isValid]] to `false` to prevent the redirect from getting saved.
55
     *
56
     * ```php
57
     * use nystudio107\retour\services\Redirects;
58
     * use nystudio107\retour\events\RedirectEvent;
59
     *
60
     * Event::on(Redirects::class,
61
     *     Redirects::EVENT_BEFORE_SAVE_REDIRECT,
62
     *     function(RedirectEvent $event) {
63
     *         // potentially set $event->isValid;
64
     *     }
65
     * );
66
     * ```
67
     */
68
    const EVENT_BEFORE_SAVE_REDIRECT = 'beforeSaveRedirect';
69
70
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
71
     * @event RedirectEvent The event that is triggered after the redirect is saved
72
     *
73
     * ```php
74
     * use nystudio107\retour\services\Redirects;
75
     * use nystudio107\retour\events\RedirectEvent;
76
     *
77
     * Event::on(Redirects::class,
78
     *     Redirects::EVENT_AFTER_SAVE_REDIRECT,
79
     *     function(RedirectEvent $event) {
80
     *         // the redirect was saved
81
     *     }
82
     * );
83
     * ```
84
     */
85
    const EVENT_AFTER_SAVE_REDIRECT = 'afterSaveRedirect';
86
87
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
88
     * @event ResolveRedirectEvent The event that is triggered before Retour has attempted
89
     *        to resolve redirects. You may set [[ResolveRedirectEvent::redirectDestUrl]] to
90
     *        to the URL that it should redirect to, or null if no redirect should happen
91
     *
92
     * ```php
93
     * use nystudio107\retour\services\Redirects;
94
     * use nystudio107\retour\events\ResolveRedirectEvent;
95
     *
96
     * Event::on(Redirects::class,
97
     *     Redirects::EVENT_AFTER_SAVE_REDIRECT,
98
     *     function(ResolveRedirectEvent $event) {
99
     *         // potentially set $event->redirectDestUrl;
100
     *     }
101
     * );
102
     * ```
103
     */
104
    const EVENT_BEFORE_RESOLVE_REDIRECT = 'beforeResolveRedirect';
105
106
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
107
     * @event ResolveRedirectEvent The event that is triggered after Retour has attempted
108
     *        to resolve redirects. You may set [[ResolveRedirectEvent::redirectDestUrl]] to
109
     *        to the URL that it should redirect to, or null if no redirect should happen
110
     *
111
     * ```php
112
     * use nystudio107\retour\services\Redirects;
113
     * use nystudio107\retour\events\ResolveRedirectEvent;
114
     *
115
     * Event::on(Redirects::class,
116
     *     Redirects::EVENT_AFTER_RESOLVE_REDIRECT,
117
     *     function(ResolveRedirectEvent $event) {
118
     *         // potentially set $event->redirectDestUrl;
119
     *     }
120
     * );
121
     * ```
122
     */
123
    const EVENT_AFTER_RESOLVE_REDIRECT = 'afterResolveRedirect';
124
125
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
126
     * @event RedirectResolvedEvent The event that is triggered once Retour has resolved
127
     *        a redirect. [[RedirectResolvedEvent::redirect]] will be set to the redirect
128
     *        that was resolved. You may set [[RedirectResolvedEvent::redirectDestUrl]] to
129
     *        to a different URL that it should redirect to, or leave it null if the
130
     *        redirect should happen as resolved.
131
     *
132
     * ```php
133
     * use nystudio107\retour\services\Redirects;
134
     * use nystudio107\retour\events\RedirectResolvedEvent;
135
     *
136
     * Event::on(Redirects::class,
137
     *     Redirects::EVENT_REDIRECT_RESOLVED,
138
     *     function(RedirectResolvedEvent $event) {
139
     *         // potentially set $event->redirectDestUrl;
140
     *     }
141
     * );
142
     * ```
143
     */
144
    const EVENT_REDIRECT_RESOLVED = 'redirectResolved';
145
146
    // Protected Properties
147
    // =========================================================================
148
149
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
150
     * @var null|array
151
     */
152
    protected $cachedStaticRedirects;
153
154
    // Public Methods
155
    // =========================================================================
156
157
    /**
158
     * Handle 404s by looking for redirects
159
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
160
    public function handle404()
161
    {
162
        Craft::info(
163
            Craft::t(
164
                'retour',
165
                'A 404 exception occurred'
166
            ),
167
            __METHOD__
168
        );
169
        $request = Craft::$app->getRequest();
170
        // We only want site requests that are not live preview or console requests
171
        if ($request->getIsSiteRequest() && !$request->getIsLivePreview() && !$request->getIsConsoleRequest()) {
0 ignored issues
show
Deprecated Code introduced by
The function craft\console\Request::getIsLivePreview() has been deprecated: in 3.2.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

171
        if ($request->getIsSiteRequest() && !/** @scrutinizer ignore-deprecated */ $request->getIsLivePreview() && !$request->getIsConsoleRequest()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
172
            // See if we should redirect
173
            try {
174
                $fullUrl = urldecode($request->getAbsoluteUrl());
175
                $pathOnly = urldecode($request->getUrl());
176
            } catch (InvalidConfigException $e) {
177
                Craft::error(
178
                    $e->getMessage(),
179
                    __METHOD__
180
                );
181
                $pathOnly = '';
182
                $fullUrl = '';
183
            }
184
            // Strip the query string if `alwaysStripQueryString` is set
185
            if (Retour::$settings->alwaysStripQueryString) {
186
                $fullUrl = UrlHelper::stripQueryString($fullUrl);
187
                $pathOnly = UrlHelper::stripQueryString($pathOnly);
188
            }
189
            Craft::info(
190
                Craft::t(
191
                    'retour',
192
                    '404 full URL: {fullUrl}, 404 path only: {pathOnly}',
193
                    ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
194
                ),
195
                __METHOD__
196
            );
197
            if (!$this->excludeUri($pathOnly)) {
198
                // Redirect if we find a match, otherwise let Craft handle it
199
                $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
200
                if (!$this->doRedirect($fullUrl, $pathOnly, $redirect) && !Retour::$settings->alwaysStripQueryString) {
201
                    // Try it again without the query string
202
                    $fullUrl = UrlHelper::stripQueryString($fullUrl);
203
                    $pathOnly = UrlHelper::stripQueryString($pathOnly);
204
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
205
                    $this->doRedirect($fullUrl, $pathOnly, $redirect);
206
                }
207
                // Increment the stats
208
                Retour::$plugin->statistics->incrementStatistics($pathOnly, false);
209
            }
210
        }
211
    }
212
213
    /**
214
     * Do the redirect
215
     *
216
     * @param string     $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
217
     * @param string     $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
218
     * @param null|array $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
219
     *
220
     * @return bool false if not redirected
221
     */
222
    public function doRedirect(string $fullUrl, string $pathOnly, $redirect): bool
223
    {
224
        $response = Craft::$app->getResponse();
225
        if ($redirect !== null) {
226
            // Figure out what type of source matching was done
227
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
228
            switch ($redirectSrcMatch) {
229
                case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
230
                    $url = $pathOnly;
231
                    break;
232
                case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
233
                    $url = $fullUrl;
234
                    break;
235
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
236
                    $url = $pathOnly;
237
                    break;
238
            }
239
            $dest = $redirect['redirectDestUrl'];
240
            // If this isn't a full URL, make it one based on the appropriate site
241
            if (!UrlHelper::isFullUrl($dest)) {
242
                try {
243
                    $siteId = $redirect['siteId'] ?? null;
244
                    if ($siteId !== null) {
245
                        $siteId = (int)$siteId;
246
                    }
247
                    $dest = UrlHelper::siteUrl($dest, null, null, $siteId);
248
                } catch (\yii\base\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
249
                }
250
            }
251
            if (Retour::$settings->preserveQueryString) {
252
                $request = Craft::$app->getRequest();
253
                if (!empty($request->getQueryStringWithoutPath())) {
254
                    $dest .= '?' . $request->getQueryStringWithoutPath();
255
                }
256
            }
257
            $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
258
            // Parse reference tags for exact matches
259
            if ($redirectMatchType === 'exactmatch') {
260
                $dest = Craft::$app->elements->parseRefs($dest, $redirect['siteId'] ?? null);
261
            }
262
            $status = $redirect['redirectHttpCode'];
263
            Craft::info(
264
                Craft::t(
265
                    'retour',
266
                    'Redirecting {url} to {dest} with status {status}',
267
                    ['url' => $url, 'dest' => $dest, 'status' => $status]
268
                ),
269
                __METHOD__
270
            );
271
            // Increment the stats
272
            Retour::$plugin->statistics->incrementStatistics($url, true);
273
            // Handle a Retour return status > 400 to render the actual error template
274
            if ($status >= 400) {
275
                Retour::$currentException->statusCode = $status;
276
                $errorHandler = Craft::$app->getErrorHandler();
277
                $errorHandler->exception = Retour::$currentException;
278
                try {
279
                    $response = Craft::$app->runAction('templates/render-error');
280
                } catch (InvalidRouteException $e) {
281
                    Craft::error($e->getMessage(), __METHOD__);
282
                } catch (\yii\console\Exception $e) {
283
                    Craft::error($e->getMessage(), __METHOD__);
284
                }
285
            }
286
            // Sanitize the URL
287
            $dest = UrlHelper::sanitizeUrl($dest);
288
            // Redirect the request away;
289
            $response->redirect($dest, $status)->send();
290
            try {
291
                Craft::$app->end();
292
            } catch (ExitException $e) {
293
                Craft::error($e->getMessage(), __METHOD__);
294
            }
295
        }
296
297
        return false;
298
    }
299
300
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
301
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
302
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
303
     * @param null   $siteId
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $siteId is correct as it would always require null to be passed?
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
304
     *
305
     * @return array|null
306
     */
307
    public function findRedirectMatch(string $fullUrl, string $pathOnly, $siteId = null)
308
    {
309
        // Get the current site
310
        if ($siteId === null) {
0 ignored issues
show
introduced by
The condition $siteId === null is always true.
Loading history...
311
            $currentSite = Craft::$app->getSites()->currentSite;
312
            if ($currentSite) {
313
                $siteId = $currentSite->id;
314
            }
315
        }
316
        // Try getting the full URL redirect from the cache
317
        $redirect = $this->getRedirectFromCache($fullUrl, $siteId);
0 ignored issues
show
Bug introduced by
It seems like $siteId can also be of type null; however, parameter $siteId of nystudio107\retour\servi...:getRedirectFromCache() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

317
        $redirect = $this->getRedirectFromCache($fullUrl, /** @scrutinizer ignore-type */ $siteId);
Loading history...
318
        if ($redirect) {
319
            $this->incrementRedirectHitCount($redirect);
320
            $this->saveRedirectToCache($fullUrl, $redirect);
321
322
            return $redirect;
323
        }
324
        // Try getting the path only redirect from the cache
325
        $redirect = $this->getRedirectFromCache($pathOnly, $siteId);
326
        if ($redirect) {
327
            $this->incrementRedirectHitCount($redirect);
328
            $this->saveRedirectToCache($pathOnly, $redirect);
329
330
            return $redirect;
331
        }
332
        // Resolve static redirects
333
        $redirects = $this->getAllStaticRedirects(null, $siteId);
334
        $redirect = $this->resolveRedirect($fullUrl, $pathOnly, $redirects);
335
        if ($redirect) {
336
            return $redirect;
337
        }
338
339
        return null;
340
    }
341
342
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
343
     * @param          $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 10
Loading history...
344
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
345
     *
346
     * @return bool|array
347
     */
348
    public function getRedirectFromCache($url, int $siteId = 0)
349
    {
350
        $cache = Craft::$app->getCache();
351
        $cacheKey = $this::CACHE_KEY.md5($url).$siteId;
352
        $redirect = $cache->get($cacheKey);
353
        Craft::info(
354
            Craft::t(
355
                'retour',
356
                'Cached redirect hit for {url}',
357
                ['url' => $url]
358
            ),
359
            __METHOD__
360
        );
361
362
        return $redirect;
363
    }
364
365
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
366
     * @param  string $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 2
Loading history...
367
     * @param  array  $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 2
Loading history...
368
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
369
    public function saveRedirectToCache($url, $redirect)
370
    {
371
        $cache = Craft::$app->getCache();
372
        // Get the current site id
373
        $sites = Craft::$app->getSites();
374
        try {
375
            $siteId = $sites->getCurrentSite()->id;
376
        } catch (SiteNotFoundException $e) {
377
            $siteId = 1;
378
        }
379
        $cacheKey = $this::CACHE_KEY.md5($url).$siteId;
380
        // Create the dependency tags
381
        $dependency = new TagDependency([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
382
            'tags' => [
383
                $this::GLOBAL_REDIRECTS_CACHE_TAG,
384
                $this::GLOBAL_REDIRECTS_CACHE_TAG.$siteId,
385
            ],
386
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
387
        $cache->set($cacheKey, $redirect, Retour::$cacheDuration, $dependency);
388
        Craft::info(
389
            Craft::t(
390
                'retour',
391
                'Cached redirect saved for {url}',
392
                ['url' => $url]
393
            ),
394
            __METHOD__
395
        );
396
    }
397
398
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
399
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
400
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
401
     * @param array  $redirects
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
402
     *
403
     * @return array|null
404
     */
405
    public function resolveRedirect(string $fullUrl, string $pathOnly, array $redirects)
406
    {
407
        $result = null;
408
        // Throw the Redirects::EVENT_BEFORE_RESOLVE_REDIRECT event
409
        $event = new ResolveRedirectEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
410
            'fullUrl' => $fullUrl,
411
            'pathOnly' => $pathOnly,
412
            'redirectDestUrl' => null,
413
            'redirectHttpCode' => 301,
414
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
415
        $this->trigger(self::EVENT_BEFORE_RESOLVE_REDIRECT, $event);
416
        if ($event->redirectDestUrl !== null) {
417
            return $this->resolveEventRedirect($event);
418
        }
419
        // Iterate through the redirects
420
        foreach ($redirects as $redirect) {
421
            // Figure out what type of source matching to do
422
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
423
            $redirectEnabled = (bool)$redirect['enabled'];
424
            if ($redirectEnabled === true) {
425
                switch ($redirectSrcMatch) {
426
                    case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
427
                        $url = $pathOnly;
428
                        break;
429
                    case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
430
                        $url = $fullUrl;
431
                        break;
432
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
433
                        $url = $pathOnly;
434
                        break;
435
                }
436
                $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
437
                switch ($redirectMatchType) {
438
                    // Do a straight up match
439
                    case 'exactmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
440
                        if (strcasecmp($redirect['redirectSrcUrlParsed'], $url) === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
441
                            $this->incrementRedirectHitCount($redirect);
442
                            $this->saveRedirectToCache($url, $redirect);
443
444
                            // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
445
                            $event = new RedirectResolvedEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
446
                                'fullUrl' => $fullUrl,
447
                                'pathOnly' => $pathOnly,
448
                                'redirectDestUrl' => null,
449
                                'redirectHttpCode' => 301,
450
                                'redirect' => $redirect,
451
                            ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
452
                            $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
453
                            if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
454
                                return $this->resolveEventRedirect($event);
455
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
456
457
                            return $redirect;
458
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
459
                        break;
460
461
                    // Do a regex match
462
                    case 'regexmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
463
                        $matchRegEx = '`'.$redirect['redirectSrcUrlParsed'].'`i';
464
                        try {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
465
                            if (preg_match($matchRegEx, $url) === 1) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
466
                                $this->incrementRedirectHitCount($redirect);
467
                                // If we're not associated with an EntryID, handle capture group replacement
468
                                if ((int)$redirect['associatedElementId'] === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
469
                                    $redirect['redirectDestUrl'] = preg_replace(
470
                                        $matchRegEx,
471
                                        $redirect['redirectDestUrl'],
472
                                        $url
473
                                    );
474
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
475
                                $url = preg_replace('/([^:])(\/{2,})/', '$1/', $url);
476
                                $this->saveRedirectToCache($url, $redirect);
477
478
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
479
                                $event = new RedirectResolvedEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
480
                                    'fullUrl' => $fullUrl,
481
                                    'pathOnly' => $pathOnly,
482
                                    'redirectDestUrl' => null,
483
                                    'redirectHttpCode' => 301,
484
                                    'redirect' => $redirect,
485
                                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
486
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
487
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
488
                                    return $this->resolveEventRedirect($event);
489
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
490
491
                                return $redirect;
492
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
493
                        } catch (\Exception $e) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
494
                            // That's fine
495
                            Craft::error('Invalid Redirect Regex: '.$matchRegEx, __METHOD__);
496
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
497
498
                        break;
499
500
                    // Otherwise try to look up a plugin's method by and call it for the match
501
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
502
                        $plugin = $redirectMatchType ? Craft::$app->getPlugins()->getPlugin($redirectMatchType) : null;
503
                        if ($plugin && method_exists($plugin, 'retourMatch')) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
504
                            $args = [
505
                                [
506
                                    'redirect' => &$redirect,
507
                                ],
508
                            ];
509
                            $result = \call_user_func_array([$plugin, 'retourMatch'], $args);
510
                            if ($result) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
511
                                $this->incrementRedirectHitCount($redirect);
512
                                $this->saveRedirectToCache($url, $redirect);
513
514
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
515
                                $event = new RedirectResolvedEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
516
                                    'fullUrl' => $fullUrl,
517
                                    'pathOnly' => $pathOnly,
518
                                    'redirectDestUrl' => null,
519
                                    'redirectHttpCode' => 301,
520
                                    'redirect' => $redirect,
521
                                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
522
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
523
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
524
                                    return $this->resolveEventRedirect($event);
525
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
526
527
                                return $redirect;
528
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
529
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
530
                        break;
531
                }
532
            }
533
        }
534
        // Throw the Redirects::EVENT_AFTER_RESOLVE_REDIRECT event
535
        $event = new ResolveRedirectEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
536
            'fullUrl' => $fullUrl,
537
            'pathOnly' => $pathOnly,
538
            'redirectDestUrl' => null,
539
            'redirectHttpCode' => 301,
540
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
541
        $this->trigger(self::EVENT_AFTER_RESOLVE_REDIRECT, $event);
542
        if ($event->redirectDestUrl !== null) {
543
            return $this->resolveEventRedirect($event);
544
        }
545
        Craft::info(
546
            Craft::t(
547
                'retour',
548
                'Not handled-> full URL: {fullUrl}, path only: {pathOnly}',
549
                ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
550
            ),
551
            __METHOD__
552
        );
553
554
        return $result;
555
    }
556
557
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
558
     * @param ResolveRedirectEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
559
     *
560
     * @return null|array
561
     */
562
    public function resolveEventRedirect(ResolveRedirectEvent $event)
563
    {
564
        $result = null;
565
566
        if ($event->redirectDestUrl !== null) {
567
            $redirect = new StaticRedirectsModel([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
568
                'id' => self::EVENT_REDIRECT_ID,
569
                'redirectDestUrl' => $event->redirectDestUrl,
570
                'redirectHttpCode' => $event->redirectHttpCode,
571
            ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
572
            $result = $redirect->toArray();
573
        }
574
575
        return $result;
576
    }
577
578
    /**
579
     * Returns the list of matching schemes
580
     *
581
     * @return  array
0 ignored issues
show
Coding Style introduced by
Tag value for @return tag indented incorrectly; expected 1 spaces but found 2
Loading history...
582
     */
583
    public function getMatchesList(): array
584
    {
585
        $result = [
586
            'exactmatch' => Craft::t('retour', 'Exact Match'),
587
            'regexmatch' => Craft::t('retour', 'RegEx Match'),
588
        ];
589
590
        // Add any plugins that offer the retourMatch() method
591
        foreach (Craft::$app->getPlugins()->getAllPlugins() as $plugin) {
592
            /** @var Plugin $plugin */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
593
            if (method_exists($plugin, 'retourMatch')) {
594
                $result[$plugin->getHandle()] = $plugin->name.Craft::t('retour', ' Match');
595
            }
596
        }
597
598
        return $result;
599
    }
600
601
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
602
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
603
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
604
     *
605
     * @return array All of the statistics
606
     */
607
    public function getAllStaticRedirects($limit = null, int $siteId = null): array
608
    {
609
        // Cache it in our class; no need to fetch it more than once
610
        if ($this->cachedStaticRedirects !== null) {
611
            return $this->cachedStaticRedirects;
612
        }
613
        // Query the db table
614
        $query = (new Query())
615
            ->from(['{{%retour_static_redirects}}'])
616
            ->orderBy('redirectMatchType ASC, redirectSrcMatch ASC, hitCount DESC');
617
        if ($siteId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $siteId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
618
            $query
619
                ->where(['siteId' => $siteId])
620
                ->orWhere(['siteId' => null]);
621
        }
622
        if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
623
            $query->limit($limit);
624
        }
625
        $redirects = $query->all();
626
        // Cache for future accesses
627
        $this->cachedStaticRedirects = $redirects;
628
629
        return $redirects;
630
    }
631
632
    /**
633
     * Return a redirect by id
634
     *
635
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
636
     *
637
     * @return null|array The static redirect
638
     */
639
    public function getRedirectById(int $id)
640
    {
641
        // Query the db table
642
        $redirect = (new Query())
643
            ->from(['{{%retour_static_redirects}}'])
644
            ->where(['id' => $id])
645
            ->one();
646
647
        return $redirect;
648
    }
649
650
    /**
651
     * Return a redirect by redirectSrcUrl
652
     *
653
     * @param string   $redirectSrcUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
654
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
655
     *
656
     * @return null|array
657
     */
658
    public function getRedirectByRedirectSrcUrl(string $redirectSrcUrl, int $siteId = null)
659
    {
660
        // Query the db table
661
        $query = (new Query())
662
            ->from(['{{%retour_static_redirects}}'])
663
            ->where(['redirectSrcUrl' => $redirectSrcUrl])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
664
            ;
665
        if ($siteId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $siteId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
666
            $query
667
                ->andWhere(['or', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
668
                    'siteId' => $siteId,
669
                ], [
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 20 spaces, but found 16.
Loading history...
670
                    'siteId' => null,
671
                ]]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
672
        }
673
        $redirect = $query->one();
674
675
        return $redirect;
676
    }
677
678
    /**
679
     * Delete a redirect by id
680
     *
681
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
682
     *
683
     * @return int The result
684
     */
685
    public function deleteRedirectById(int $id): int
686
    {
687
        $db = Craft::$app->getDb();
688
        // Delete a row from the db table
689
        try {
690
            $result = $db->createCommand()->delete(
691
                '{{%retour_static_redirects}}',
692
                [
693
                    'id' => $id,
694
                ]
695
            )->execute();
696
        } catch (Exception $e) {
697
            Craft::error($e->getMessage(), __METHOD__);
698
            $result = 0;
699
        }
700
701
        return $result;
702
    }
703
704
    /**
705
     * Increment the retour_static_redirects record
706
     *
707
     * @param $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
708
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
709
    public function incrementRedirectHitCount(&$redirectConfig)
710
    {
711
        if ($redirectConfig !== null) {
712
            $db = Craft::$app->getDb();
713
            $redirectConfig['hitCount']++;
714
            $redirectConfig['hitLastTime'] = Db::prepareDateForDb(new \DateTime());
715
            Craft::debug(
716
                Craft::t(
717
                    'retour',
718
                    'Incrementing statistics for: {redirect}',
719
                    ['redirect' => print_r($redirectConfig, true)]
720
                ),
721
                __METHOD__
722
            );
723
            // Update the existing record
724
            try {
725
                $rowsAffected = $db->createCommand()->update(
726
                    '{{%retour_static_redirects}}',
727
                    [
728
                        'hitCount' => $redirectConfig['hitCount'],
729
                        'hitLastTime' => $redirectConfig['hitLastTime'],
730
                    ],
731
                    [
732
                        'id' => $redirectConfig['id'],
733
                    ]
734
                )->execute();
735
                Craft::debug('Rows affected: '.$rowsAffected, __METHOD__);
736
            } catch (\Exception $e) {
737
                Craft::error($e->getMessage(), __METHOD__);
738
            }
739
        }
740
    }
741
742
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
743
     * @param array $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
744
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
745
    public function saveRedirect(array $redirectConfig)
746
    {
747
        // Validate the model before saving it to the db
748
        $redirect = new StaticRedirectsModel($redirectConfig);
749
        if ($redirect->validate() === false) {
750
            Craft::error(
751
                Craft::t(
752
                    'retour',
753
                    'Error validating redirect {id}: {errors}',
754
                    ['id' => $redirect->id, 'errors' => print_r($redirect->getErrors(), true)]
755
                ),
756
                __METHOD__
757
            );
758
759
            return;
760
        }
761
        // Get the validated model attributes and save them to the db
762
        $redirectConfig = $redirect->getAttributes();
763
        // 0 for a siteId needs to be converted to null
764
        if (empty($redirectConfig['siteId']) || (int)$redirectConfig['siteId'] === 0) {
765
            $redirectConfig['siteId'] = null;
766
        }
767
        // Throw an event to before saving the redirect
768
        $db = Craft::$app->getDb();
769
        // See if a redirect exists with this source URL already
770
        if ((int)$redirectConfig['id'] === 0) {
771
            // Query the db table
772
            $redirect = (new Query())
773
                ->from(['{{%retour_static_redirects}}'])
774
                ->where(['redirectSrcUrlParsed' => $redirectConfig['redirectSrcUrlParsed']])
775
                ->andWhere(['siteId' => $redirectConfig['siteId']])
776
                ->one();
777
            // If it exists, update it rather than having duplicates
778
            if (!empty($redirect)) {
779
                $redirectConfig['id'] = $redirect['id'];
780
            }
781
        }
782
        // Trigger a 'beforeSaveRedirect' event
783
        $isNew = (int)$redirectConfig['id'] === 0;
784
        $event = new RedirectEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
785
            'isNew' => $isNew,
786
            'legacyUrl' => $redirectConfig['redirectSrcUrlParsed'],
787
            'destinationUrl' => $redirectConfig['redirectDestUrl'],
788
            'matchType' => $redirectConfig['redirectSrcMatch'],
789
            'redirectType' => $redirectConfig['redirectHttpCode'],
790
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
791
        $this->trigger(self::EVENT_BEFORE_SAVE_REDIRECT, $event);
792
        if (!$event->isValid) {
793
            return;
794
        }
795
        // See if this is an existing redirect
796
        if (!$isNew) {
797
            Craft::debug(
798
                Craft::t(
799
                    'retour',
800
                    'Updating existing redirect: {redirect}',
801
                    ['redirect' => print_r($redirectConfig, true)]
802
                ),
803
                __METHOD__
804
            );
805
            // Update the existing record
806
            try {
807
                $db->createCommand()->update(
808
                    '{{%retour_static_redirects}}',
809
                    $redirectConfig,
810
                    [
811
                        'id' => $redirectConfig['id'],
812
                    ]
813
                )->execute();
814
            } catch (Exception $e) {
815
                Craft::error($e->getMessage(), __METHOD__);
816
            }
817
        } else {
818
            Craft::debug(
819
                Craft::t(
820
                    'retour',
821
                    'Creating new redirect: {redirect}',
822
                    ['redirect' => print_r($redirectConfig, true)]
823
                ),
824
                __METHOD__
825
            );
826
            unset($redirectConfig['id']);
827
            // Create a new record
828
            try {
829
                $db->createCommand()->insert(
830
                    '{{%retour_static_redirects}}',
831
                    $redirectConfig
832
                )->execute();
833
            } catch (Exception $e) {
834
                Craft::error($e->getMessage(), __METHOD__);
835
            }
836
        }
837
        // To prevent redirect loops, see if any static redirects have our redirectDestUrl as their redirectSrcUrl
838
        $testRedirectConfig = $this->getRedirectByRedirectSrcUrl(
839
            $redirectConfig['redirectDestUrl'],
840
            $redirectConfig['siteId']
841
        );
842
        if ($testRedirectConfig !== null) {
843
            Craft::debug(
844
                Craft::t(
845
                    'retour',
846
                    'Deleting redirect to prevent a loop: {redirect}',
847
                    ['redirect' => print_r($testRedirectConfig, true)]
848
                ),
849
                __METHOD__
850
            );
851
            // Delete the redirect that has a redirectSrcUrl the same as this record's redirectDestUrl
852
            try {
853
                $db->createCommand()->delete(
854
                    '{{%retour_static_redirects}}',
855
                    ['id' => $testRedirectConfig['id']]
856
                )->execute();
857
            } catch (Exception $e) {
858
                Craft::error($e->getMessage(), __METHOD__);
859
            }
860
        }
861
        // Trigger a 'afterSaveRedirect' event
862
        $this->trigger(self::EVENT_AFTER_SAVE_REDIRECT, $event);
863
    }
864
865
    /**
866
     * Invalidate all of the redirects caches
867
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
868
    public function invalidateCaches()
869
    {
870
        $cache = Craft::$app->getCache();
871
        TagDependency::invalidate($cache, $this::GLOBAL_REDIRECTS_CACHE_TAG);
872
        Craft::info(
873
            Craft::t(
874
                'retour',
875
                'All redirect caches cleared'
876
            ),
877
            __METHOD__
878
        );
879
    }
880
881
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
882
     * @param $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
883
     *
884
     * @return bool
885
     */
886
    public function excludeUri($uri): bool
887
    {
888
        $uri = '/'.ltrim($uri, '/');
889
        if (!empty(Retour::$settings->excludePatterns)) {
890
            foreach (Retour::$settings->excludePatterns as $excludePattern) {
891
                $pattern = '`'.$excludePattern['pattern'].'`i';
892
                try {
893
                    if (preg_match($pattern, $uri) === 1) {
894
                        return true;
895
                    }
896
                } catch (\Exception $e) {
897
                    // That's fine
898
                    Craft::error('Invalid exclude URI Regex: '.$pattern, __METHOD__);
899
                }
900
            }
901
        }
902
903
        return false;
904
    }
905
}
906