Passed
Push — develop ( 813d6d...183ed5 )
by Andrew
05:35
created

Redirects::incrementRedirectHitCount()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 29
rs 9.6
c 0
b 0
f 0
cc 3
nc 4
nop 1
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/
9
 * @copyright Copyright (c) 2018 nystudio107
10
 */
11
12
namespace nystudio107\retour\services;
13
14
use nystudio107\retour\Retour;
15
use nystudio107\retour\events\RedirectEvent;
16
use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel;
17
18
use Craft;
19
use craft\base\Component;
20
use craft\base\Plugin;
21
use craft\db\Query;
22
use craft\errors\SiteNotFoundException;
23
use craft\helpers\Db;
24
use craft\helpers\UrlHelper;
25
26
use yii\base\ExitException;
27
use yii\base\InvalidConfigException;
28
use yii\base\InvalidRouteException;
29
use yii\caching\TagDependency;
30
use yii\db\Exception;
31
use yii\web\HttpException;
32
33
/** @noinspection MissingPropertyAnnotationsInspection */
34
35
/**
36
 * @author    nystudio107
37
 * @package   Retour
38
 * @since     3.0.0
39
 */
40
class Redirects extends Component
41
{
42
    // Constants
43
    // =========================================================================
44
45
    const CACHE_KEY = 'retour_redirect_';
46
47
    const GLOBAL_REDIRECTS_CACHE_TAG = 'retour_redirects';
48
49
    /**
50
     * @event RedirectEvent The event that is triggered before the redirect is saved
51
     * You may set [[RedirectEvent::isValid]] to `false` to prevent the redirect from getting saved.
52
     *
53
     * ```php
54
     * use nystudio107\retour\services\Redirects;
55
     * use nystudio107\retour\events\RedirectEvent;
56
     *
57
     * Event::on(Redirects::class,
58
     *     Redirects::EVENT_BEFORE_SAVE_REDIRECT,
59
     *     function(RedirectEvent $event) {
60
     *         // potentially set $event->isValid;
61
     *     }
62
     * );
63
     * ```
64
     */
65
    const EVENT_BEFORE_SAVE_REDIRECT = 'beforeSaveRedirect';
66
67
    /**
68
     * @event RedirectEvent The event that is triggered after the redirect is saved
69
     *
70
     * ```php
71
     * use nystudio107\retour\services\Redirects;
72
     * use nystudio107\retour\events\RedirectEvent;
73
     *
74
     * Event::on(Redirects::class,
75
     *     Redirects::EVENT_AFTER_SAVE_REDIRECT,
76
     *     function(RedirectEvent $event) {
77
     *         // the redirect was saved
78
     *     }
79
     * );
80
     * ```
81
     */
82
    const EVENT_AFTER_SAVE_REDIRECT = 'afterSaveRedirect';
83
84
    // Protected Properties
85
    // =========================================================================
86
87
    /**
88
     * @var null|array
89
     */
90
    protected $cachedStaticRedirects;
91
92
    // Public Methods
93
    // =========================================================================
94
95
    /**
96
     * Handle 404s by looking for redirects
97
     */
98
    public function handle404()
99
    {
100
        Craft::info(
101
            Craft::t(
102
                'retour',
103
                'A 404 exception occurred'
104
            ),
105
            __METHOD__
106
        );
107
        $request = Craft::$app->getRequest();
108
        // We only want site requests that are not live preview or console requests
109
        if ($request->getIsSiteRequest() && !$request->getIsLivePreview() && !$request->getIsConsoleRequest()) {
110
            // See if we should redirect
111
            try {
112
                $fullUrl = urldecode($request->getAbsoluteUrl());
113
                $pathOnly = urldecode($request->getUrl());
114
            } catch (InvalidConfigException $e) {
115
                Craft::error(
116
                    $e->getMessage(),
117
                    __METHOD__
118
                );
119
                $pathOnly = '';
120
                $fullUrl = '';
121
            }
122
            // Strip the query string if `alwaysStripQueryString` is set
123
            if (Retour::$settings->alwaysStripQueryString) {
124
                $fullUrl = UrlHelper::stripQueryString($fullUrl);
125
                $pathOnly = UrlHelper::stripQueryString($pathOnly);
126
            }
127
            Craft::info(
128
                Craft::t(
129
                    'retour',
130
                    '404 full URL: {fullUrl}, 404 path only: {pathOnly}',
131
                    ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
132
                ),
133
                __METHOD__
134
            );
135
            if (!$this->excludeUri($pathOnly)) {
136
                // Redirect if we find a match, otherwise let Craft handle it
137
                $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
138
                if (!$this->doRedirect($fullUrl, $pathOnly, $redirect) && !Retour::$settings->alwaysStripQueryString) {
139
                    // Try it again without the query string
140
                    $fullUrl = UrlHelper::stripQueryString($fullUrl);
141
                    $pathOnly = UrlHelper::stripQueryString($pathOnly);
142
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
143
                    $this->doRedirect($fullUrl, $pathOnly, $redirect);
144
                }
145
                // Increment the stats
146
                Retour::$plugin->statistics->incrementStatistics($pathOnly, false);
147
            }
148
        }
149
    }
150
151
    /**
152
     * Do the redirect
153
     *
154
     * @param string     $fullUrl
155
     * @param string     $pathOnly
156
     * @param null|array $redirect
157
     *
158
     * @return bool false if not redirected
159
     */
160
    public function doRedirect(string $fullUrl, string $pathOnly, $redirect): bool
161
    {
162
        $response = Craft::$app->getResponse();
163
        if ($redirect !== null) {
164
            // Figure out what type of source matching was done
165
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
166
            switch ($redirectSrcMatch) {
167
                case 'pathonly':
168
                    $url = $pathOnly;
169
                    break;
170
                case 'fullurl':
171
                    $url = $fullUrl;
172
                    break;
173
                default:
174
                    $url = $pathOnly;
175
                    break;
176
            }
177
            $dest = $redirect['redirectDestUrl'];
178
            if (Retour::$settings->preserveQueryString) {
179
                $request = Craft::$app->getRequest();
180
                if (!empty($request->getQueryStringWithoutPath())) {
181
                    $dest .= '?' . $request->getQueryStringWithoutPath();
182
                }
183
            }
184
            $status = $redirect['redirectHttpCode'];
185
            Craft::info(
186
                Craft::t(
187
                    'retour',
188
                    'Redirecting {url} to {dest} with status {status}',
189
                    ['url' => $url, 'dest' => $dest, 'status' => $status]
190
                ),
191
                __METHOD__
192
            );
193
            // Increment the stats
194
            Retour::$plugin->statistics->incrementStatistics($url, true);
195
            // Handle a Retour return status > 400 to render the actual error template
196
            if ($status >= 400) {
197
                Retour::$currentException->statusCode = $status;
198
                $errorHandler = Craft::$app->getErrorHandler();
199
                $errorHandler->exception = Retour::$currentException;
200
                try {
201
                    $response = Craft::$app->runAction('templates/render-error');
202
                } catch (InvalidRouteException $e) {
203
                    Craft::error($e->getMessage(), __METHOD__);
204
                } catch (\yii\console\Exception $e) {
205
                    Craft::error($e->getMessage(), __METHOD__);
206
                }
207
            }
208
            // Redirect the request away;
209
            $response->redirect($dest, $status)->send();
210
            try {
211
                Craft::$app->end();
212
            } catch (ExitException $e) {
213
                Craft::error($e->getMessage(), __METHOD__);
214
            }
215
        }
216
217
        return false;
218
    }
219
220
    /**
221
     * @param string $fullUrl
222
     * @param string $pathOnly
223
     *
224
     * @return array|null
225
     */
226
    public function findRedirectMatch(string $fullUrl, string $pathOnly)
227
    {
228
        // Get the current site
229
        $siteId = null;
230
        $currentSite = Craft::$app->getSites()->currentSite;
231
        if ($currentSite) {
232
            $siteId = $currentSite->id;
233
        }
234
        // Try getting the full URL redirect from the cache
235
        $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

235
        $redirect = $this->getRedirectFromCache($fullUrl, /** @scrutinizer ignore-type */ $siteId);
Loading history...
236
        if ($redirect) {
237
            $this->incrementRedirectHitCount($redirect);
238
            $this->saveRedirectToCache($fullUrl, $redirect);
239
240
            return $redirect;
241
        }
242
        // Try getting the path only redirect from the cache
243
        $redirect = $this->getRedirectFromCache($pathOnly, $siteId);
244
        if ($redirect) {
245
            $this->incrementRedirectHitCount($redirect);
246
            $this->saveRedirectToCache($pathOnly, $redirect);
247
248
            return $redirect;
249
        }
250
        // Resolve static redirects
251
        $redirects = $this->getAllStaticRedirects(null, $siteId);
252
        $redirect = $this->resolveRedirect($fullUrl, $pathOnly, $redirects);
253
        if ($redirect) {
254
            return $redirect;
255
        }
256
257
        return null;
258
    }
259
260
    /**
261
     * @param          $url
262
     * @param int|null $siteId
263
     *
264
     * @return bool|array
265
     */
266
    public function getRedirectFromCache($url, int $siteId = 0)
267
    {
268
        $cache = Craft::$app->getCache();
269
        $cacheKey = $this::CACHE_KEY.md5($url).$siteId;
270
        $redirect = $cache->get($cacheKey);
271
        Craft::info(
272
            Craft::t(
273
                'retour',
274
                'Cached redirect hit for {url}',
275
                ['url' => $url]
276
            ),
277
            __METHOD__
278
        );
279
280
        return $redirect;
281
    }
282
283
    /**
284
     * @param  string $url
285
     * @param  array  $redirect
286
     */
287
    public function saveRedirectToCache($url, $redirect)
288
    {
289
        $cache = Craft::$app->getCache();
290
        // Get the current site id
291
        $sites = Craft::$app->getSites();
292
        try {
293
            $siteId = $sites->getCurrentSite()->id;
294
        } catch (SiteNotFoundException $e) {
295
            $siteId = 1;
296
        }
297
        $cacheKey = $this::CACHE_KEY.md5($url).$siteId;
298
        // Create the dependency tags
299
        $dependency = new TagDependency([
300
            'tags' => [
301
                $this::GLOBAL_REDIRECTS_CACHE_TAG,
302
                $this::GLOBAL_REDIRECTS_CACHE_TAG.$siteId,
303
            ],
304
        ]);
305
        $cache->set($cacheKey, $redirect, Retour::$cacheDuration, $dependency);
306
        Craft::info(
307
            Craft::t(
308
                'retour',
309
                'Cached redirect saved for {url}',
310
                ['url' => $url]
311
            ),
312
            __METHOD__
313
        );
314
    }
315
316
    /**
317
     * @param string $fullUrl
318
     * @param string $pathOnly
319
     * @param array  $redirects
320
     *
321
     * @return array|null
322
     */
323
    public function resolveRedirect(string $fullUrl, string $pathOnly, array $redirects)
324
    {
325
        $result = null;
326
        foreach ($redirects as $redirect) {
327
            // Figure out what type of source matching to do
328
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
329
            $redirectEnabled = (bool)$redirect['enabled'];
330
            if ($redirectEnabled === true) {
331
                switch ($redirectSrcMatch) {
332
                    case 'pathonly':
333
                        $url = $pathOnly;
334
                        break;
335
                    case 'fullurl':
336
                        $url = $fullUrl;
337
                        break;
338
                    default:
339
                        $url = $pathOnly;
340
                        break;
341
                }
342
                $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
343
                switch ($redirectMatchType) {
344
                    // Do a straight up match
345
                    case 'exactmatch':
346
                        if (strcasecmp($redirect['redirectSrcUrlParsed'], $url) === 0) {
347
                            $this->incrementRedirectHitCount($redirect);
348
                            $this->saveRedirectToCache($url, $redirect);
349
350
                            return $redirect;
351
                        }
352
                        break;
353
354
                    // Do a regex match
355
                    case 'regexmatch':
356
                        $matchRegEx = '`'.$redirect['redirectSrcUrlParsed'].'`i';
357
                        if (preg_match($matchRegEx, $url) === 1) {
358
                            $this->incrementRedirectHitCount($redirect);
359
                            // If we're not associated with an EntryID, handle capture group replacement
360
                            if ((int)$redirect['associatedElementId'] === 0) {
361
                                $redirect['redirectDestUrl'] = preg_replace(
362
                                    $matchRegEx,
363
                                    $redirect['redirectDestUrl'],
364
                                    $url
365
                                );
366
                            }
367
                            $this->saveRedirectToCache($url, $redirect);
368
369
                            return $redirect;
370
                        }
371
                        break;
372
373
                    // Otherwise try to look up a plugin's method by and call it for the match
374
                    default:
375
                        $plugin = $redirectMatchType ? Craft::$app->getPlugins()->getPlugin($redirectMatchType) : null;
376
                        if ($plugin && method_exists($plugin, 'retourMatch')) {
377
                            $args = [
378
                                [
379
                                    'redirect' => &$redirect,
380
                                ],
381
                            ];
382
                            $result = \call_user_func_array([$plugin, 'retourMatch'], $args);
383
                            if ($result) {
384
                                $this->incrementRedirectHitCount($redirect);
385
                                $this->saveRedirectToCache($url, $redirect);
386
387
                                return $redirect;
388
                            }
389
                        }
390
                        break;
391
                }
392
            }
393
        }
394
        Craft::info(
395
            Craft::t(
396
                'retour',
397
                'Not handled-> full URL: {fullUrl}, path only: {pathOnly}',
398
                ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
399
            ),
400
            __METHOD__
401
        );
402
403
        return $result;
404
    }
405
406
    /**
407
     * Returns the list of matching schemes
408
     *
409
     * @return  array
410
     */
411
    public function getMatchesList(): array
412
    {
413
        $result = [
414
            'exactmatch' => Craft::t('retour', 'Exact Match'),
415
            'regexmatch' => Craft::t('retour', 'RegEx Match'),
416
        ];
417
418
        // Add any plugins that offer the retourMatch() method
419
        foreach (Craft::$app->getPlugins()->getAllPlugins() as $plugin) {
420
            /** @var Plugin $plugin */
421
            if (method_exists($plugin, 'retourMatch')) {
422
                $result[$plugin->getHandle()] = $plugin->name.Craft::t('retour', ' Match');
423
            }
424
        }
425
426
        return $result;
427
    }
428
429
    /**
430
     * @param null|int $limit
431
     * @param int|null $siteId
432
     *
433
     * @return array All of the statistics
434
     */
435
    public function getAllStaticRedirects($limit = null, int $siteId = null): array
436
    {
437
        // Cache it in our class; no need to fetch it more than once
438
        if ($this->cachedStaticRedirects !== null) {
439
            return $this->cachedStaticRedirects;
440
        }
441
        // Query the db table
442
        $query = (new Query())
443
            ->from(['{{%retour_static_redirects}}'])
444
            ->orderBy('redirectMatchType ASC, redirectSrcMatch ASC, hitCount DESC');
445
        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...
446
            $query
447
                ->where(['siteId' => $siteId])
448
                ->orWhere(['siteId' => null]);
449
        }
450
        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...
451
            $query->limit($limit);
452
        }
453
        $redirects = $query->all();
454
        // Cache for future accesses
455
        $this->cachedStaticRedirects = $redirects;
456
457
        return $redirects;
458
    }
459
460
    /**
461
     * Return a redirect by id
462
     *
463
     * @param int $id
464
     *
465
     * @return null|array The static redirect
466
     */
467
    public function getRedirectById(int $id)
468
    {
469
        // Query the db table
470
        $redirect = (new Query())
471
            ->from(['{{%retour_static_redirects}}'])
472
            ->where(['id' => $id])
473
            ->one();
474
475
        return $redirect;
476
    }
477
478
    /**
479
     * Return a redirect by redirectSrcUrl
480
     *
481
     * @param string   $redirectSrcUrl
482
     * @param int|null $siteId
483
     *
484
     * @return null|array
485
     */
486
    public function getRedirectByRedirectSrcUrl(string $redirectSrcUrl, int $siteId = null)
487
    {
488
        // Query the db table
489
        $query = (new Query())
490
            ->from(['{{%retour_static_redirects}}'])
491
            ->where(['redirectSrcUrl' => $redirectSrcUrl])
492
            ;
493
        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...
494
            $query
495
                ->andWhere(['siteId' => $siteId])
496
                ->orWhere(['siteId' => null]);
497
        }
498
        $redirect = $query->one();
499
500
        return $redirect;
501
    }
502
503
    /**
504
     * Delete a redirect by id
505
     *
506
     * @param int $id
507
     *
508
     * @return int The result
509
     */
510
    public function deleteRedirectById(int $id): int
511
    {
512
        $db = Craft::$app->getDb();
513
        // Delete a row from the db table
514
        try {
515
            $result = $db->createCommand()->delete(
516
                '{{%retour_static_redirects}}',
517
                [
518
                    'id' => $id,
519
                ]
520
            )->execute();
521
        } catch (Exception $e) {
522
            Craft::error($e->getMessage(), __METHOD__);
523
            $result = 0;
524
        }
525
526
        return $result;
527
    }
528
529
    /**
530
     * Increment the retour_static_redirects record
531
     *
532
     * @param $redirectConfig
533
     */
534
    public function incrementRedirectHitCount(&$redirectConfig)
535
    {
536
        if ($redirectConfig !== null) {
537
            $db = Craft::$app->getDb();
538
            $redirectConfig['hitCount']++;
539
            $redirectConfig['hitLastTime'] = Db::prepareDateForDb(new \DateTime());
540
            Craft::debug(
541
                Craft::t(
542
                    'retour',
543
                    'Incrementing statistics for: {redirect}',
544
                    ['redirect' => print_r($redirectConfig, true)]
545
                ),
546
                __METHOD__
547
            );
548
            // Update the existing record
549
            try {
550
                $rowsAffected = $db->createCommand()->update(
551
                    '{{%retour_static_redirects}}',
552
                    [
553
                        'hitCount' => $redirectConfig['hitCount'],
554
                        'hitLastTime' => $redirectConfig['hitLastTime'],
555
                    ],
556
                    [
557
                        'id' => $redirectConfig['id'],
558
                    ]
559
                )->execute();
560
                Craft::debug('Rows affected: '.$rowsAffected, __METHOD__);
561
            } catch (Exception $e) {
562
                Craft::error($e->getMessage(), __METHOD__);
563
            }
564
        }
565
    }
566
567
    /**
568
     * @param array $redirectConfig
569
     */
570
    public function saveRedirect(array $redirectConfig)
571
    {
572
        // Validate the model before saving it to the db
573
        $redirect = new StaticRedirectsModel($redirectConfig);
574
        if ($redirect->validate() === false) {
575
            Craft::error(
576
                Craft::t(
577
                    'retour',
578
                    'Error validating redirect {id}: {errors}',
579
                    ['id' => $redirect->id, 'errors' => print_r($redirect->getErrors(), true)]
580
                ),
581
                __METHOD__
582
            );
583
584
            return;
585
        }
586
        // Get the validated model attributes and save them to the db
587
        $redirectConfig = $redirect->getAttributes();
588
        // 0 for a siteId needs to be converted to null
589
        if (empty($redirectConfig['siteId']) || (int)$redirectConfig['siteId'] === 0) {
590
            $redirectConfig['siteId'] = null;
591
        }
592
        // Throw an event to before saving the redirect
593
        $db = Craft::$app->getDb();
594
        // See if a redirect exists with this source URL already
595
        if ((int)$redirectConfig['id'] === 0) {
596
            // Query the db table
597
            $redirect = (new Query())
598
                ->from(['{{%retour_static_redirects}}'])
599
                ->where(['redirectSrcUrlParsed' => $redirectConfig['redirectSrcUrlParsed']])
600
                ->andWhere(['siteId' => $redirectConfig['siteId']])
601
                ->one();
602
            // If it exists, update it rather than having duplicates
603
            if (!empty($redirect)) {
604
                $redirectConfig['id'] = $redirect['id'];
605
            }
606
        }
607
        // Trigger a 'beforeSaveRedirect' event
608
        $isNew = (int)$redirectConfig['id'] === 0;
609
        $event = new RedirectEvent([
610
            'isNew' => $isNew,
611
            'legacyUrl' => $redirectConfig['redirectSrcUrlParsed'],
612
            'destinationUrl' => $redirectConfig['redirectDestUrl'],
613
            'matchType' => $redirectConfig['redirectSrcMatch'],
614
            'redirectType' => $redirectConfig['redirectHttpCode'],
615
        ]);
616
        $this->trigger(self::EVENT_BEFORE_SAVE_REDIRECT, $event);
617
        if (!$event->isValid) {
618
            return;
619
        }
620
        // See if this is an existing redirect
621
        if (!$isNew) {
622
            Craft::debug(
623
                Craft::t(
624
                    'retour',
625
                    'Updating existing redirect: {redirect}',
626
                    ['redirect' => print_r($redirectConfig, true)]
627
                ),
628
                __METHOD__
629
            );
630
            // Update the existing record
631
            try {
632
                $db->createCommand()->update(
633
                    '{{%retour_static_redirects}}',
634
                    $redirectConfig,
635
                    [
636
                        'id' => $redirectConfig['id'],
637
                    ]
638
                )->execute();
639
            } catch (Exception $e) {
640
                Craft::error($e->getMessage(), __METHOD__);
641
            }
642
        } else {
643
            Craft::debug(
644
                Craft::t(
645
                    'retour',
646
                    'Creating new redirect: {redirect}',
647
                    ['redirect' => print_r($redirectConfig, true)]
648
                ),
649
                __METHOD__
650
            );
651
            unset($redirectConfig['id']);
652
            // Create a new record
653
            try {
654
                $db->createCommand()->insert(
655
                    '{{%retour_static_redirects}}',
656
                    $redirectConfig
657
                )->execute();
658
            } catch (Exception $e) {
659
                Craft::error($e->getMessage(), __METHOD__);
660
            }
661
        }
662
        // To prevent redirect loops, see if any static redirects have our redirectDestUrl as their redirectSrcUrl
663
        $testRedirectConfig = $this->getRedirectByRedirectSrcUrl(
664
            $redirectConfig['redirectDestUrl'],
665
            $redirectConfig['siteId']
666
        );
667
        if ($testRedirectConfig !== null) {
668
            Craft::debug(
669
                Craft::t(
670
                    'retour',
671
                    'Deleting redirect to prevent a loop: {redirect}',
672
                    ['redirect' => print_r($testRedirectConfig, true)]
673
                ),
674
                __METHOD__
675
            );
676
            // Delete the redirect that has a redirectSrcUrl the same as this record's redirectDestUrl
677
            try {
678
                $db->createCommand()->delete(
679
                    '{{%retour_static_redirects}}',
680
                    ['id' => $testRedirectConfig['id']]
681
                )->execute();
682
            } catch (Exception $e) {
683
                Craft::error($e->getMessage(), __METHOD__);
684
            }
685
        }
686
        // Trigger a 'afterSaveRedirect' event
687
        $this->trigger(self::EVENT_AFTER_SAVE_REDIRECT, $event);
688
    }
689
690
    /**
691
     * Invalidate all of the redirects caches
692
     */
693
    public function invalidateCaches()
694
    {
695
        $cache = Craft::$app->getCache();
696
        TagDependency::invalidate($cache, $this::GLOBAL_REDIRECTS_CACHE_TAG);
697
        Craft::info(
698
            Craft::t(
699
                'retour',
700
                'All redirect caches cleared'
701
            ),
702
            __METHOD__
703
        );
704
    }
705
706
    /**
707
     * @param $uri
708
     *
709
     * @return bool
710
     */
711
    public function excludeUri($uri): bool
712
    {
713
        $uri = '/'.ltrim($uri, '/');
714
        foreach (Retour::$settings->excludePatterns as $excludePattern) {
715
            $pattern = '`'.$excludePattern['pattern'].'`i';
716
            if (preg_match($pattern, $uri) === 1) {
717
                return true;
718
            }
719
        }
720
721
        return false;
722
    }
723
}
724