Redirects::resolveRedirect()   F
last analyzed

Complexity

Conditions 20
Paths 155

Size

Total Lines 155
Code Lines 105

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 105
c 1
b 0
f 0
dl 0
loc 155
rs 2.9666
cc 20
nc 155
nop 4

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
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 Craft;
15
use craft\base\Component;
16
use craft\base\ElementInterface;
0 ignored issues
show
Bug introduced by
The type craft\base\ElementInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use craft\base\Plugin;
18
use craft\db\Query;
19
use craft\errors\ElementNotFoundException;
20
use craft\errors\SiteNotFoundException;
21
use craft\helpers\Db;
22
use craft\helpers\StringHelper;
23
use craft\web\Response as WebResponse;
24
use DateTime;
25
use nystudio107\retour\events\RedirectEvent;
26
use nystudio107\retour\events\RedirectResolvedEvent;
27
use nystudio107\retour\events\ResolveRedirectEvent;
28
use nystudio107\retour\fields\ShortLink;
29
use nystudio107\retour\helpers\Text as TextHelper;
30
use nystudio107\retour\helpers\UrlHelper;
31
use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel;
32
use nystudio107\retour\Retour;
33
use Throwable;
34
use yii\base\ExitException;
35
use yii\base\InvalidConfigException;
36
use yii\base\InvalidRouteException;
37
use yii\caching\TagDependency;
38
use yii\db\Exception;
39
use function call_user_func_array;
40
41
/** @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...
42
43
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
44
 * @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...
45
 * @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...
46
 * @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...
47
 */
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...
48
class Redirects extends Component
49
{
50
    // Constants
51
    // =========================================================================
52
53
    public const CACHE_KEY = 'retour_redirect_';
54
55
    public const GLOBAL_REDIRECTS_CACHE_TAG = 'retour_redirects';
56
57
    public const EVENT_REDIRECT_ID = 0;
58
59
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
60
     * @event RedirectEvent The event that is triggered before the redirect is saved
61
     * You may set [[RedirectEvent::isValid]] to `false` to prevent the redirect from getting saved.
62
     *
63
     * ```php
64
     * use nystudio107\retour\services\Redirects;
65
     * use nystudio107\retour\events\RedirectEvent;
66
     *
67
     * Event::on(Redirects::class,
68
     *     Redirects::EVENT_BEFORE_SAVE_REDIRECT,
69
     *     function(RedirectEvent $event) {
70
     *         // potentially set $event->isValid;
71
     *     }
72
     * );
73
     * ```
74
     */
75
    public const EVENT_BEFORE_SAVE_REDIRECT = 'beforeSaveRedirect';
76
77
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
78
     * @event RedirectEvent The event that is triggered after the redirect is saved
79
     *
80
     * ```php
81
     * use nystudio107\retour\services\Redirects;
82
     * use nystudio107\retour\events\RedirectEvent;
83
     *
84
     * Event::on(Redirects::class,
85
     *     Redirects::EVENT_AFTER_SAVE_REDIRECT,
86
     *     function(RedirectEvent $event) {
87
     *         // the redirect was saved
88
     *     }
89
     * );
90
     * ```
91
     */
92
    public const EVENT_AFTER_SAVE_REDIRECT = 'afterSaveRedirect';
93
94
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
95
     * @event RedirectEvent The event that is triggered before the redirect is deleted
96
     * You may set [[RedirectEvent::isValid]] to `false` to prevent the redirect from getting deleted.
97
     *
98
     * ```php
99
     * use nystudio107\retour\services\Redirects;
100
     * use nystudio107\retour\events\RedirectEvent;
101
     *
102
     * Event::on(Redirects::class,
103
     *     Redirects::EVENT_BEFORE_DELETE_REDIRECT,
104
     *     function(RedirectEvent $event) {
105
     *         // potentially set $event->isValid;
106
     *     }
107
     * );
108
     * ```
109
     */
110
    public const EVENT_BEFORE_DELETE_REDIRECT = 'beforeDeleteRedirect';
111
112
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
113
     * @event RedirectEvent The event that is triggered after the redirect is deleted
114
     *
115
     * ```php
116
     * use nystudio107\retour\services\Redirects;
117
     * use nystudio107\retour\events\RedirectEvent;
118
     *
119
     * Event::on(Redirects::class,
120
     *     Redirects::EVENT_AFTER_DELETE_REDIRECT,
121
     *     function(RedirectEvent $event) {
122
     *         // the redirect was deleted
123
     *     }
124
     * );
125
     * ```
126
     */
127
    public const EVENT_AFTER_DELETE_REDIRECT = 'afterDeleteRedirect';
128
129
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
130
     * @event ResolveRedirectEvent The event that is triggered before Retour has attempted
131
     *        to resolve redirects. You may set [[ResolveRedirectEvent::redirectDestUrl]] to
132
     *        to the URL that it should redirect to, or null if no redirect should happen
133
     *
134
     * ```php
135
     * use nystudio107\retour\services\Redirects;
136
     * use nystudio107\retour\events\ResolveRedirectEvent;
137
     *
138
     * Event::on(Redirects::class,
139
     *     Redirects::EVENT_AFTER_SAVE_REDIRECT,
140
     *     function(ResolveRedirectEvent $event) {
141
     *         // potentially set $event->redirectDestUrl;
142
     *     }
143
     * );
144
     * ```
145
     */
146
    public const EVENT_BEFORE_RESOLVE_REDIRECT = 'beforeResolveRedirect';
147
148
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
149
     * @event ResolveRedirectEvent The event that is triggered after Retour has attempted
150
     *        to resolve redirects. You may set [[ResolveRedirectEvent::redirectDestUrl]] to
151
     *        to the URL that it should redirect to, or null if no redirect should happen
152
     *
153
     * ```php
154
     * use nystudio107\retour\services\Redirects;
155
     * use nystudio107\retour\events\ResolveRedirectEvent;
156
     *
157
     * Event::on(Redirects::class,
158
     *     Redirects::EVENT_AFTER_RESOLVE_REDIRECT,
159
     *     function(ResolveRedirectEvent $event) {
160
     *         // potentially set $event->redirectDestUrl;
161
     *     }
162
     * );
163
     * ```
164
     */
165
    public const EVENT_AFTER_RESOLVE_REDIRECT = 'afterResolveRedirect';
166
167
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
168
     * @event RedirectResolvedEvent The event that is triggered once Retour has resolved
169
     *        a redirect. [[RedirectResolvedEvent::redirect]] will be set to the redirect
170
     *        that was resolved. You may set [[RedirectResolvedEvent::redirectDestUrl]] to
171
     *        to a different URL that it should redirect to, or leave it null if the
172
     *        redirect should happen as resolved.
173
     *
174
     * ```php
175
     * use nystudio107\retour\services\Redirects;
176
     * use nystudio107\retour\events\RedirectResolvedEvent;
177
     *
178
     * Event::on(Redirects::class,
179
     *     Redirects::EVENT_REDIRECT_RESOLVED,
180
     *     function(RedirectResolvedEvent $event) {
181
     *         // potentially set $event->redirectDestUrl;
182
     *     }
183
     * );
184
     * ```
185
     */
186
    public const EVENT_REDIRECT_RESOLVED = 'redirectResolved';
187
188
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
189
     * @var array A list of all the element ids for which the short links have been purged.
190
     */
191
    protected static array $purgedShortLinkElementIds = [];
192
193
194
    // Public Methods
195
    // =========================================================================
196
197
    /**
198
     * Handle 404s by looking for redirects
199
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
200
    public function handle404(): void
201
    {
202
        Craft::info(
203
            Craft::t(
204
                'retour',
205
                'A 404 exception occurred'
206
            ),
207
            __METHOD__
208
        );
209
        $request = Craft::$app->getRequest();
210
        // We only want site requests that are not live preview or console requests
211
        if ($request->getIsSiteRequest() && !$this->isPreview($request) && !$request->getIsConsoleRequest()) {
212
            // See if we should redirect
213
            try {
214
                $fullUrl = urldecode($request->getAbsoluteUrl());
215
                $pathOnly = urldecode($request->getUrl());
216
            } catch (InvalidConfigException $e) {
217
                Craft::error(
218
                    $e->getMessage(),
219
                    __METHOD__
220
                );
221
                $pathOnly = '';
222
                $fullUrl = '';
223
            }
224
            // Stash the $pathOnly for use when incrementing the statistics
225
            $originalPathOnly = $pathOnly;
226
            // Strip the query string if `alwaysStripQueryString` is set
227
            if (Retour::$settings->alwaysStripQueryString) {
228
                $fullUrl = UrlHelper::stripQueryString($fullUrl);
229
                $pathOnly = UrlHelper::stripQueryString($pathOnly);
230
            }
231
            Craft::info(
232
                Craft::t(
233
                    'retour',
234
                    '404 full URL: {fullUrl}, 404 path only: {pathOnly}',
235
                    ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
236
                ),
237
                __METHOD__
238
            );
239
            if (!$this->excludeUri($pathOnly)) {
240
                // Redirect if we find a match, otherwise let Craft handle it
241
                $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
242
                // If the redirect wasn't found, look for it without the Site-defined prefix
243
                if ($redirect === null) {
244
                    // Strip out any site-defined baseUrl path prefixes
245
                    $pathOnly = UrlHelper::stripSitePathPrefix($pathOnly);
246
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
247
                }
248
                if (!$this->doRedirect($fullUrl, $pathOnly, $redirect) && !Retour::$settings->alwaysStripQueryString) {
249
                    // Try it again without the query string
250
                    $fullUrl = UrlHelper::stripQueryString($fullUrl);
251
                    $pathOnly = UrlHelper::stripQueryString($pathOnly);
252
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
253
                    $this->doRedirect($fullUrl, $pathOnly, $redirect);
254
                }
255
                // Increment the stats
256
                Retour::$plugin->statistics->incrementStatistics($originalPathOnly, false);
0 ignored issues
show
Bug introduced by
The method incrementStatistics() does not exist on null. ( Ignorable by Annotation )

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

256
                Retour::$plugin->statistics->/** @scrutinizer ignore-call */ 
257
                                             incrementStatistics($originalPathOnly, false);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
257
            }
258
        }
259
    }
260
261
    /**
262
     * Return whether this is a preview request of any kind
263
     *
264
     * @param $request
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 2 spaces but found 1
Loading history...
265
     * @return bool
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
266
     */
267
    public function isPreview($request): bool
268
    {
269
        $isPreview = $request->getIsPreview();
270
        $isLivePreview = $request->getIsLivePreview();
271
272
        return ($isPreview || $isLivePreview);
273
    }
274
275
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
276
     * @param $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
277
     *
278
     * @return bool
279
     */
280
    public function excludeUri($uri): bool
281
    {
282
        $uri = '/' . ltrim($uri, '/');
283
        if (!empty(Retour::$settings->excludePatterns)) {
284
            foreach (Retour::$settings->excludePatterns as $excludePattern) {
285
                if (empty($excludePattern['pattern'])) {
286
                    continue;
287
                }
288
                $pattern = '`' . $excludePattern['pattern'] . '`i';
289
                try {
290
                    if (preg_match($pattern, $uri) === 1) {
291
                        Craft::info(
292
                            Craft::t(
293
                                'retour',
294
                                'Excluded URI: {uri} due to match of pattern: {pattern}',
295
                                ['uri' => $uri, 'pathOnly' => $pattern]
296
                            ),
297
                            __METHOD__
298
                        );
299
300
                        return true;
301
                    }
302
                } catch (\Exception $e) {
303
                    // That's fine
304
                    Craft::error('Invalid exclude URI Regex: ' . $pattern, __METHOD__);
305
                }
306
            }
307
        }
308
309
        return false;
310
    }
311
312
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
313
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
314
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
315
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
316
     *
317
     * @return bool|array|null
318
     */
319
    public function findRedirectMatch(string $fullUrl, string $pathOnly, $siteId = null): bool|array|null
320
    {
321
        // Get the current site
322
        if ($siteId === null) {
323
            $currentSite = Craft::$app->getSites()->currentSite;
324
            if ($currentSite) {
325
                $siteId = $currentSite->id;
326
            } else {
327
                $primarySite = Craft::$app->getSites()->primarySite;
328
                $siteId = $primarySite->id;
329
            }
330
        }
331
        // Try getting the full URL redirect from the cache
332
        $redirect = $this->getRedirectFromCache($fullUrl, $siteId);
333
        if ($redirect) {
334
            $this->incrementRedirectHitCount($redirect);
335
            $this->saveRedirectToCache($fullUrl, $redirect);
336
337
            return $redirect;
338
        }
339
        // Try getting the path only redirect from the cache
340
        $redirect = $this->getRedirectFromCache($pathOnly, $siteId);
341
        if ($redirect) {
342
            $this->incrementRedirectHitCount($redirect);
343
            $this->saveRedirectToCache($pathOnly, $redirect);
344
345
            return $redirect;
346
        }
347
348
        $redirect = $this->getStaticRedirect($fullUrl, $pathOnly, $siteId, true);
349
        if ($redirect) {
350
            $this->incrementRedirectHitCount($redirect);
351
            $this->saveRedirectToCache($pathOnly, $redirect);
352
353
            return $redirect;
354
        }
355
356
        // Resolve static redirects
357
        $redirects = $this->getAllRegExRedirects(null, $siteId, true);
358
        $redirect = $this->resolveRedirect($fullUrl, $pathOnly, $redirects, $siteId);
359
        if ($redirect) {
360
            return $redirect;
361
        }
362
363
        return null;
364
    }
365
366
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
367
     * @param string $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
368
     * @param int $siteId
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
369
     *
370
     * @return bool|array
371
     */
372
    public function getRedirectFromCache(string $url, int $siteId = 0): bool|array
373
    {
374
        $cache = Craft::$app->getCache();
375
        $cacheKey = $this::CACHE_KEY . md5($url) . $siteId;
376
        $redirect = $cache->get($cacheKey);
377
        Craft::info(
378
            Craft::t(
379
                'retour',
380
                'Cached redirect hit for {url}',
381
                ['url' => $url]
382
            ),
383
            __METHOD__
384
        );
385
386
        return $redirect;
387
    }
388
389
    /**
390
     * Increment the retour_static_redirects record
391
     *
392
     * @param $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
393
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
394
    public function incrementRedirectHitCount(&$redirectConfig): void
395
    {
396
        if ($redirectConfig !== null) {
397
            $db = Craft::$app->getDb();
398
            $redirectConfig['hitCount']++;
399
            $redirectConfig['hitLastTime'] = Db::prepareDateForDb(new DateTime());
400
            Craft::debug(
401
                Craft::t(
402
                    'retour',
403
                    'Incrementing statistics for: {redirect}',
404
                    ['redirect' => print_r($redirectConfig, true)]
405
                ),
406
                __METHOD__
407
            );
408
            // Update the existing record
409
            try {
410
                $rowsAffected = $db->createCommand()->update(
411
                    '{{%retour_static_redirects}}',
412
                    [
413
                        'hitCount' => $redirectConfig['hitCount'],
414
                        'hitLastTime' => $redirectConfig['hitLastTime'],
415
                    ],
416
                    [
417
                        'id' => $redirectConfig['id'],
418
                    ]
419
                )->execute();
420
                Craft::debug('Rows affected: ' . $rowsAffected, __METHOD__);
421
            } catch (\Exception $e) {
422
                Craft::error($e->getMessage(), __METHOD__);
423
            }
424
        }
425
    }
426
427
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
428
     * @param string $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
429
     * @param array $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
430
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
431
    public function saveRedirectToCache(string $url, array $redirect): void
432
    {
433
        $cache = Craft::$app->getCache();
434
        // Get the current site id
435
        $sites = Craft::$app->getSites();
436
        try {
437
            $siteId = $sites->getCurrentSite()->id;
438
        } catch (SiteNotFoundException $e) {
439
            $siteId = 1;
440
        }
441
        $cacheKey = $this::CACHE_KEY . md5($url) . $siteId;
442
        // Create the dependency tags
443
        $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...
444
            'tags' => [
445
                $this::GLOBAL_REDIRECTS_CACHE_TAG,
446
                $this::GLOBAL_REDIRECTS_CACHE_TAG . $siteId,
447
            ],
448
        ]);
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...
449
        $cache->set($cacheKey, $redirect, Retour::$cacheDuration, $dependency);
450
        Craft::info(
451
            Craft::t(
452
                'retour',
453
                'Cached redirect saved for {url}',
454
                ['url' => $url]
455
            ),
456
            __METHOD__
457
        );
458
    }
459
460
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $enabledOnly should have a doc-comment as per coding-style.
Loading history...
461
     * @param string $fullUrl
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 2 spaces but found 1
Loading history...
462
     * @param string $pathOnly
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 2 spaces but found 1
Loading history...
463
     * @param $siteId
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 2 spaces but found 1
Loading history...
464
     * @return mixed|null
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
465
     */
466
    public function getStaticRedirect(string $fullUrl, string $pathOnly, $siteId, bool $enabledOnly = false)
467
    {
468
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
469
        // Throw the Redirects::EVENT_BEFORE_RESOLVE_REDIRECT event
470
        $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...
471
            'fullUrl' => $fullUrl,
472
            'pathOnly' => $pathOnly,
473
            'redirectDestUrl' => null,
474
            'redirectHttpCode' => 301,
475
            'siteId' => $siteId,
476
        ]);
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...
477
        $this->trigger(self::EVENT_BEFORE_RESOLVE_REDIRECT, $event);
478
        if ($event->redirectDestUrl !== null) {
479
            return $this->resolveEventRedirect($event);
480
        }
481
        // Query for the static redirect
482
        $staticCondition = ['redirectMatchType' => 'exactmatch'];
483
        $siteCondition = [
484
            'or',
485
            ['siteId' => $siteId],
486
            ['siteId' => null],
487
        ];
488
        $pathCondition = [
489
            'or',
490
            ['and',
491
                ['redirectSrcMatch' => 'pathonly'],
492
                ['redirectSrcUrlParsed' => TextHelper::cleanupText($pathOnly)],
493
            ],
494
            ['and',
495
                ['redirectSrcMatch' => 'fullurl'],
496
                ['redirectSrcUrlParsed' => TextHelper::cleanupText($fullUrl)],
497
            ],
498
        ];
499
500
        $query = (new Query())
501
            ->from('{{%retour_static_redirects}}')
502
            ->where(['and',
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...
503
                $staticCondition,
504
                $pathCondition,
505
                $siteCondition,
506
            ])
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...
507
            ->limit(1);
508
509
        if ($enabledOnly) {
510
            $query->andWhere(['enabled' => 1]);
511
        }
512
        $result = $query->one();
513
        if ($result) {
514
            // Figure out what type of source matching to do
515
            $redirectSrcMatch = $result['redirectSrcMatch'] ?? 'pathonly';
516
            switch ($redirectSrcMatch) {
517
                case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
518
                    $url = $pathOnly;
519
                    break;
520
                case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
521
                    $url = $fullUrl;
522
                    break;
523
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
524
                    $url = $pathOnly;
525
                    break;
526
            }
527
            // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
528
            $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...
529
                'fullUrl' => $fullUrl,
530
                'pathOnly' => $pathOnly,
531
                'redirectDestUrl' => null,
532
                'redirectHttpCode' => 301,
533
                'redirect' => $result,
534
                'siteId' => $siteId,
535
            ]);
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...
536
            $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
537
            if ($event->redirectDestUrl !== null) {
538
                return $this->resolveEventRedirect($event, $url, $result);
539
            }
540
541
            return $result;
542
        }
543
544
        // Throw the Redirects::EVENT_AFTER_RESOLVE_REDIRECT event
545
        $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...
546
            'fullUrl' => $fullUrl,
547
            'pathOnly' => $pathOnly,
548
            'redirectDestUrl' => null,
549
            'redirectHttpCode' => 301,
550
            'siteId' => $siteId,
551
        ]);
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...
552
        $this->trigger(self::EVENT_AFTER_RESOLVE_REDIRECT, $event);
553
        if ($event->redirectDestUrl !== null) {
554
            return $this->resolveEventRedirect($event);
555
        }
556
        Craft::info(
557
            Craft::t(
558
                'retour',
559
                'Not handled-> full URL: {fullUrl}, path only: {pathOnly}',
560
                ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
561
            ),
562
            __METHOD__
563
        );
564
565
        return $result;
566
    }
567
568
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $enabledOnly should have a doc-comment as per coding-style.
Loading history...
569
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
570
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
571
     *
572
     * @return array All of the regex match redirects
573
     */
574
    public function getAllRegExRedirects(int $limit = null, int $siteId = null, bool $enabledOnly = false): array
575
    {
576
        return $this->getRedirectsByMatchType($limit, $siteId, 'regexmatch', $enabledOnly);
577
    }
578
579
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $enabledOnly should have a doc-comment as per coding-style.
Loading history...
580
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
581
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
582
     *
583
     * @return array All of the regex match redirects
584
     */
585
    public function getAllExactMatchRedirects(int $limit = null, int $siteId = null, bool $enabledOnly = false): array
586
    {
587
        return $this->getRedirectsByMatchType($limit, $siteId, 'exactmatch', $enabledOnly);
588
    }
589
590
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
591
     * @param int|null $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
592
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
593
     *
594
     * @return array All of the statistics
595
     */
596
    public function getAllStaticRedirects(int $limit = null, int $siteId = null): array
597
    {
598
        // Query the db table
599
        $query = (new Query())
600
            ->from(['{{%retour_static_redirects}}'])
601
            ->orderBy('redirectMatchType ASC, priority ASC');
602
        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...
603
            $query
604
                ->where(['siteId' => $siteId])
605
                ->orWhere(['siteId' => null]);
606
        }
607
        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...
608
            $query->limit($limit);
609
        }
610
        return $query->all();
611
    }
612
613
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
614
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
615
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
616
     * @param array $redirects
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
617
     * @param $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
618
     *
619
     * @return array|null
620
     */
621
    public function resolveRedirect(string $fullUrl, string $pathOnly, array $redirects, $siteId): ?array
622
    {
623
        $result = null;
624
        // Throw the Redirects::EVENT_BEFORE_RESOLVE_REDIRECT event
625
        $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...
626
            'fullUrl' => $fullUrl,
627
            'pathOnly' => $pathOnly,
628
            'redirectDestUrl' => null,
629
            'redirectHttpCode' => 301,
630
            'siteId' => $siteId,
631
        ]);
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...
632
        $this->trigger(self::EVENT_BEFORE_RESOLVE_REDIRECT, $event);
633
        if ($event->redirectDestUrl !== null) {
634
            return $this->resolveEventRedirect($event);
635
        }
636
        // Iterate through the redirects
637
        foreach ($redirects as $redirect) {
638
            // Figure out what type of source matching to do
639
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
640
            $redirectEnabled = (bool)$redirect['enabled'];
641
            if ($redirectEnabled === true) {
642
                switch ($redirectSrcMatch) {
643
                    case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
644
                        $url = $pathOnly;
645
                        break;
646
                    case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
647
                        $url = $fullUrl;
648
                        break;
649
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
650
                        $url = $pathOnly;
651
                        break;
652
                }
653
                $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
654
                switch ($redirectMatchType) {
655
                    // Do a straight up match
656
                    case 'exactmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
657
                        if (strcasecmp($redirect['redirectSrcUrlParsed'], $url) === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
658
                            $this->incrementRedirectHitCount($redirect);
659
                            $this->saveRedirectToCache($url, $redirect);
660
661
                            // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
662
                            $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...
663
                                'fullUrl' => $fullUrl,
664
                                'pathOnly' => $pathOnly,
665
                                'redirectDestUrl' => null,
666
                                'redirectHttpCode' => 301,
667
                                'redirect' => $redirect,
668
                                'siteId' => $siteId,
669
                            ]);
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...
670
                            $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
671
                            if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
672
                                return $this->resolveEventRedirect($event, $url, $redirect);
673
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
674
675
                            return $redirect;
676
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
677
                        break;
678
679
                    // Do a regex match
680
                    case 'regexmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
681
                        $matchRegEx = '`' . $redirect['redirectSrcUrlParsed'] . '`i';
682
                        try {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
683
                            if (preg_match($matchRegEx, $url) === 1) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
684
                                $this->incrementRedirectHitCount($redirect);
685
                                // If we're not associated with an EntryID, handle capture group replacement
686
                                if ((int)$redirect['associatedElementId'] === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
687
                                    $redirect['redirectDestUrl'] = preg_replace(
688
                                        $matchRegEx,
689
                                        $redirect['redirectDestUrl'],
690
                                        $url
691
                                    );
692
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
693
                                $url = preg_replace('/([^:])(\/{2,})/', '$1/', $url);
694
                                $this->saveRedirectToCache($url, $redirect);
695
696
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
697
                                $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...
698
                                    'fullUrl' => $fullUrl,
699
                                    'pathOnly' => $pathOnly,
700
                                    'redirectDestUrl' => null,
701
                                    'redirectHttpCode' => 301,
702
                                    'redirect' => $redirect,
703
                                    'siteId' => $siteId,
704
                                ]);
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...
705
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
706
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
707
                                    return $this->resolveEventRedirect($event, $url, $redirect);
708
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
709
710
                                return $redirect;
711
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
712
                        } catch (\Exception $e) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
713
                            // That's fine
714
                            Craft::error('Invalid Redirect Regex: ' . $matchRegEx, __METHOD__);
715
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
716
717
                        break;
718
719
                    // Otherwise try to look up a plugin's method by and call it for the match
720
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
721
                        $plugin = $redirectMatchType ? Craft::$app->getPlugins()->getPlugin($redirectMatchType) : null;
722
                        if ($plugin && method_exists($plugin, 'retourMatch')) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
723
                            $args = [
724
                                [
725
                                    'redirect' => &$redirect,
726
                                ],
727
                            ];
728
                            $result = call_user_func_array([$plugin, 'retourMatch'], $args);
729
                            if ($result) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
730
                                $this->incrementRedirectHitCount($redirect);
731
                                $this->saveRedirectToCache($url, $redirect);
732
733
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
734
                                $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...
735
                                    'fullUrl' => $fullUrl,
736
                                    'pathOnly' => $pathOnly,
737
                                    'redirectDestUrl' => null,
738
                                    'redirectHttpCode' => 301,
739
                                    'redirect' => $redirect,
740
                                    'siteId' => $siteId,
741
                                ]);
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...
742
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
743
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
744
                                    return $this->resolveEventRedirect($event, $url, $redirect);
745
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
746
747
                                return $redirect;
748
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
749
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
750
                        break;
751
                }
752
            }
753
        }
754
        // Throw the Redirects::EVENT_AFTER_RESOLVE_REDIRECT event
755
        $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...
756
            'fullUrl' => $fullUrl,
757
            'pathOnly' => $pathOnly,
758
            'redirectDestUrl' => null,
759
            'redirectHttpCode' => 301,
760
            'siteId' => $siteId,
761
        ]);
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...
762
        $this->trigger(self::EVENT_AFTER_RESOLVE_REDIRECT, $event);
763
        if ($event->redirectDestUrl !== null) {
764
            return $this->resolveEventRedirect($event);
765
        }
766
        Craft::info(
767
            Craft::t(
768
                'retour',
769
                'Not handled-> full URL: {fullUrl}, path only: {pathOnly}',
770
                ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
771
            ),
772
            __METHOD__
773
        );
774
775
        return $result;
776
    }
777
778
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
779
     * @param ResolveRedirectEvent $event
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 2 spaces but found 1
Loading history...
780
     * @param string|null $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
781
     * @param array $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 16 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
782
     * @return null|array
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
783
     */
784
    public function resolveEventRedirect(ResolveRedirectEvent $event, ?string $url = null, $redirect = null): ?array
785
    {
786
        $result = null;
787
788
        if ($event->redirectDestUrl !== null) {
789
            $resolvedRedirect = 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...
790
                'id' => self::EVENT_REDIRECT_ID,
791
                'redirectDestUrl' => $event->redirectDestUrl,
792
                'redirectHttpCode' => $event->redirectHttpCode,
793
            ]);
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...
794
            $result = $resolvedRedirect->toArray();
795
796
            if ($url !== null && $redirect !== null) {
797
                // Save the modified redirect to the cache
798
                $redirect['redirectDestUrl'] = $event->redirectDestUrl;
799
                $redirect['redirectHttpCode'] = $event->redirectHttpCode;
800
                $this->saveRedirectToCache($url, $redirect);
801
            }
802
        }
803
804
        return $result;
805
    }
806
807
    /**
808
     * Do the redirect
809
     *
810
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 5 spaces after parameter type; 1 found
Loading history...
811
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 5 spaces after parameter type; 1 found
Loading history...
812
     * @param array|null $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
813
     *
814
     * @return bool false if not redirected
815
     */
816
    public function doRedirect(string $fullUrl, string $pathOnly, ?array $redirect): bool
817
    {
818
        $response = Craft::$app->getResponse();
819
        if ($redirect !== null) {
0 ignored issues
show
introduced by
The condition $redirect !== null is always true.
Loading history...
820
            // Figure out what type of source matching was done
821
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
822
            $dest = $redirect['redirectDestUrl'];
823
            $path = $redirect['redirectDestUrl'];
824
            switch ($redirectSrcMatch) {
825
                case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
826
                    $url = $pathOnly;
827
                    try {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
828
                        $siteId = $redirect['siteId'] ?? null;
829
                        if ($siteId !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
830
                            $siteId = (int)$siteId;
831
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
832
                        if (!UrlHelper::isAbsoluteUrl($dest) && !UrlHelper::pathHasSitePrefix($path)) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
833
                            $dest = UrlHelper::siteUrl('/', null, null, $siteId);
834
                            $dest = parse_url($dest, PHP_URL_PATH);
835
                            $dest = UrlHelper::mergeUrlWithPath($dest, $path);
836
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
837
                    } 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...
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
838
                    }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
839
                    break;
840
                case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
841
                    $url = $fullUrl;
842
                    break;
843
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
844
                    $url = $pathOnly;
845
                    break;
846
            }
847
            // If this isn't a full URL, make it one based on the appropriate site
848
            if (!UrlHelper::isFullUrl($dest)) {
849
                try {
850
                    $siteId = $redirect['siteId'] ?? null;
851
                    if ($siteId !== null) {
852
                        $siteId = (int)$siteId;
853
                    }
854
                    $dest = UrlHelper::siteUrl($dest, null, null, $siteId);
855
                } 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...
856
                }
857
            }
858
            if (Retour::$settings->preserveQueryString) {
859
                $request = Craft::$app->getRequest();
860
                $queryString = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $queryString is dead and can be removed.
Loading history...
861
                try {
862
                    $queryString = UrlHelper::combineQueryStringsFromUrls($dest, $request->getUrl());
863
                } catch (InvalidConfigException $e) {
864
                    // That's ok
865
                }
866
                if (!empty($queryString)) {
867
                    $dest = strtok($dest, '?') . '?' . $queryString;
868
                }
869
            }
870
            $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
871
            // Parse reference tags for exact matches
872
            if ($redirectMatchType === 'exactmatch') {
873
                $dest = Craft::$app->elements->parseRefs($dest, $redirect['siteId'] ?? null);
0 ignored issues
show
Bug introduced by
The method parseRefs() does not exist on null. ( Ignorable by Annotation )

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

873
                /** @scrutinizer ignore-call */ 
874
                $dest = Craft::$app->elements->parseRefs($dest, $redirect['siteId'] ?? null);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
874
            }
875
            $status = $redirect['redirectHttpCode'];
876
            Craft::info(
877
                Craft::t(
878
                    'retour',
879
                    'Redirecting {url} to {dest} with status {status}',
880
                    ['url' => $url, 'dest' => $dest, 'status' => $status]
881
                ),
882
                __METHOD__
883
            );
884
            // Increment the stats
885
            Retour::$plugin->statistics->incrementStatistics($url, true);
886
            // Sanitize the URL
887
            $dest = UrlHelper::sanitizeUrl($dest);
888
            // Optionally set the no-cache headers
889
            if (Retour::$settings->setNoCacheHeaders && $response instanceof WebResponse) {
890
                $response->setNoCacheHeaders();
891
            }
892
            // Add any additional headers (existing ones will be replaced)
893
            if (!empty(Retour::$settings->additionalHeaders)) {
894
                foreach (Retour::$settings->additionalHeaders as $additionalHeader) {
895
                    $response->headers->set($additionalHeader['name'], $additionalHeader['value']);
896
                }
897
            }
898
            // Handle a Retour return status > 400 to render the actual error template
899
            if ($status >= 400) {
900
                Retour::$currentException->statusCode = $status;
901
                $errorHandler = Craft::$app->getErrorHandler();
902
                $errorHandler->exception = Retour::$currentException;
903
                try {
904
                    $response = Craft::$app->runAction('templates/render-error');
905
                    $response->setStatusCode($status);
906
                    $response->send();
907
                } catch (InvalidRouteException $e) {
908
                    Craft::error($e->getMessage(), __METHOD__);
909
                } catch (\yii\console\Exception $e) {
910
                    Craft::error($e->getMessage(), __METHOD__);
911
                }
912
            }
913
            // Redirect the request away;
914
            $response->redirect($dest, $status)->send();
915
            try {
916
                Craft::$app->end();
917
            } catch (ExitException $e) {
918
                Craft::error($e->getMessage(), __METHOD__);
919
            }
920
        }
921
922
        return false;
923
    }
924
925
    /**
926
     * Returns the list of matching schemes
927
     *
928
     * @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...
929
     */
930
    public function getMatchesList(): array
931
    {
932
        $result = [
933
            'exactmatch' => Craft::t('retour', 'Exact Match'),
934
            'regexmatch' => Craft::t('retour', 'RegEx Match'),
935
        ];
936
937
        // Add any plugins that offer the retourMatch() method
938
        foreach (Craft::$app->getPlugins()->getAllPlugins() as $plugin) {
939
            /** @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...
940
            if (method_exists($plugin, 'retourMatch')) {
941
                $result[$plugin->getHandle()] = $plugin->name . Craft::t('retour', ' Match');
942
            }
943
        }
944
945
        return $result;
946
    }
947
948
    /**
949
     * Return a redirect by id
950
     *
951
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
952
     *
953
     * @return null|array The static redirect
954
     */
955
    public function getRedirectById(int $id): ?array
956
    {
957
        // Query the db table
958
        $redirect = (new Query())
959
            ->from(['{{%retour_static_redirects}}'])
960
            ->where(['id' => $id])
961
            ->one();
962
963
        return $redirect;
964
    }
965
966
    /**
967
     * Delete a redirect by id
968
     *
969
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
970
     *
971
     * @return int The result
972
     */
973
    public function deleteRedirectById(int $id): int
974
    {
975
        $db = Craft::$app->getDb();
976
        // Trigger a 'beforeDeleteRedirect' event
977
        $redirectConfig = $this->getRedirectById($id);
978
        $isNew = (int)$redirectConfig['id'] === 0;
979
        $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...
980
            'isNew' => $isNew,
981
            'legacyUrl' => $redirectConfig['redirectSrcUrlParsed'],
982
            'destinationUrl' => $redirectConfig['redirectDestUrl'],
983
            'matchType' => $redirectConfig['redirectSrcMatch'],
984
            'redirectType' => $redirectConfig['redirectHttpCode'],
985
            'siteId' => $redirectConfig['siteId'],
986
        ]);
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...
987
        $this->trigger(self::EVENT_BEFORE_DELETE_REDIRECT, $event);
988
        if (!$event->isValid) {
989
            return 0;
990
        }
991
        // Delete a row from the db table
992
        try {
993
            $result = $db->createCommand()->delete(
994
                '{{%retour_static_redirects}}',
995
                [
996
                    'id' => $id,
997
                ]
998
            )->execute();
999
        } catch (Exception $e) {
1000
            Craft::error($e->getMessage(), __METHOD__);
1001
            $result = 0;
1002
        }
1003
        $this->trigger(self::EVENT_AFTER_DELETE_REDIRECT, $event);
1004
1005
        return $result;
1006
    }
1007
1008
    /**
1009
     * Save an element redirect.
1010
     *
1011
     * @param ElementInterface $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1012
     * @param string $sourceUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 11 spaces after parameter type; 1 found
Loading history...
1013
     * @param string $redirectSrcMatch
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 11 spaces after parameter type; 1 found
Loading history...
1014
     * @param int $redirectHttpCode
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
1015
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1016
    public function enableElementRedirect(ElementInterface $element, string $sourceUrl, string $redirectSrcMatch = 'pathonly', int $redirectHttpCode = 301): void
1017
    {
1018
        $siteId = $element->siteId;
1019
1020
        $destUrl = $redirectSrcMatch === 'pathonly' ? $element->uri : $element->getUrl();
1021
        if ($destUrl === null) {
1022
            $destUrl = $element->getUrl();
1023
        }
1024
        $redirectConfig = [
1025
            'redirectMatchType' => 'exactmatch',
1026
            'redirectSrcUrl' => $sourceUrl,
1027
            'siteId' => $siteId,
1028
            'associatedElementId' => $element->getCanonicalId(),
1029
            'enabled' => $element->getEnabledForSite($siteId),
1030
            'redirectSrcMatch' => $redirectSrcMatch,
1031
            'redirectDestUrl' => $destUrl,
1032
            'redirectHttpCode' => $redirectHttpCode,
1033
        ];
1034
1035
        $this->saveRedirect($redirectConfig);
1036
    }
1037
1038
    /**
1039
     * Delete an element redirect.
1040
     *
1041
     * @param ElementInterface $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1042
     * @param bool $allSites Whether to delete the redirect for all sites
0 ignored issues
show
Coding Style introduced by
Expected 13 spaces after parameter type; 1 found
Loading history...
1043
     * @param bool $force Whether force redirect deletion, defaults to `false`.
0 ignored issues
show
Coding Style introduced by
Expected 13 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
1044
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1045
    public function removeElementRedirect(ElementInterface $element, bool $allSites = false, bool $force = false): void
1046
    {
1047
        if (!$force) {
1048
            if (!empty(self::$purgedShortLinkElementIds[$element->id])) {
1049
                return;
1050
            }
1051
1052
            self::$purgedShortLinkElementIds[$element->id] = true;
1053
        }
1054
1055
        $redirects = $this->getRedirectsByElementId($element->getCanonicalId(), $allSites ? null : $element->siteId);
1056
1057
        if (!empty($redirects)) {
1058
            foreach ($redirects as $redirect) {
1059
                $this->deleteRedirectById($redirect['id']);
1060
            }
1061
        }
1062
    }
1063
1064
    /**
1065
     * Return redirects for a given element.
1066
     *
1067
     * @param int $elementId
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
1068
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1069
     *
1070
     * @return null|array
1071
     */
1072
    public function getRedirectsByElementId(int $elementId, int $siteId = null)
1073
    {
1074
        // Query the db table
1075
        $query = (new Query())
1076
            ->from(['{{%retour_static_redirects}}'])
1077
            ->where(['associatedElementId' => $elementId]);
1078
        if ($siteId !== null) {
1079
            $query->andWhere(['siteId' => $siteId]);
1080
        }
1081
1082
        return $query->all();
1083
    }
1084
1085
    /**
1086
     * Delete a short link by its ID.
1087
     *
1088
     * @param int $redirectId
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 2 spaces but found 1
Loading history...
1089
     * @throws Throwable
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
1090
     * @throws ElementNotFoundException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
1091
     * @throws \yii\base\Exception
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
1092
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1093
    public function deleteShortlinkById(int $redirectId): bool
1094
    {
1095
        $redirect = $this->getRedirectById($redirectId);
1096
        if (!$redirect) {
1097
            return false;
1098
        }
1099
        $elementId = $redirect['associatedElementId'];
1100
        $siteId = $redirect['siteId'];
1101
        $element = Craft::$app->getElements()->getElementById($elementId, null, $siteId);
1102
1103
        if ($element) {
1104
            $fieldUpdated = $this->setShortLinkFieldValue($element, $redirect['redirectSrcUrl'], null);
1105
1106
            if ($fieldUpdated) {
1107
                // This will also delete the redirect from the table
1108
                Craft::$app->getElements()->saveElement($element);
1109
            }
1110
        }
1111
1112
        return true;
1113
    }
1114
1115
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
1116
     * @param array $redirectConfig
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 2 spaces but found 1
Loading history...
1117
     * @return bool
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
1118
     */
1119
    public function saveRedirect(array $redirectConfig): bool
1120
    {
1121
        // Handle URL encoded URLs by decoding them before saving them
1122
        if (isset($redirectConfig['redirectMatchType']) && $redirectConfig['redirectMatchType'] === 'exactmatch') {
1123
            $redirectConfig['redirectSrcUrl'] = urldecode($redirectConfig['redirectSrcUrl'] ?? '');
1124
            $redirectConfig['redirectSrcUrlParsed'] = urldecode($redirectConfig['redirectSrcUrlParsed'] ?? '');
1125
        }
1126
        // Validate the model before saving it to the db
1127
        $redirect = new StaticRedirectsModel($redirectConfig);
1128
        if ($redirect->validate() === false) {
1129
            Craft::error(
1130
                Craft::t(
1131
                    'retour',
1132
                    'Error validating redirect {id}: {errors}',
1133
                    ['id' => $redirect->id, 'errors' => print_r($redirect->getErrors(), true)]
1134
                ),
1135
                __METHOD__
1136
            );
1137
1138
            return false;
1139
        }
1140
        // Get the validated model attributes and save them to the db
1141
        $redirectConfig = $redirect->getAttributes();
1142
        // 0 for a siteId needs to be converted to null
1143
        if (empty($redirectConfig['siteId']) || (int)$redirectConfig['siteId'] === 0) {
1144
            $redirectConfig['siteId'] = null;
1145
        }
1146
        // Throw an event to before saving the redirect
1147
        $db = Craft::$app->getDb();
1148
        if (!empty($redirectConfig['associatedElementId'])) {
1149
            $existingRedirect = (new Query())->from(['{{%retour_static_redirects}}'])->where(['id' => $redirectConfig['id']])->one();
1150
        }
1151
1152
        // See if a redirect exists with this source URL already
1153
        if ((int)$redirectConfig['id'] === 0) {
1154
            // Query the db table
1155
            $redirect = (new Query())
1156
                ->from(['{{%retour_static_redirects}}'])
1157
                ->where(['redirectSrcUrlParsed' => $redirectConfig['redirectSrcUrlParsed']])
1158
                ->andWhere(['siteId' => $redirectConfig['siteId']])
1159
                ->one();
1160
            // If it exists, update it rather than having duplicates
1161
            if (!empty($redirect)) {
1162
                $redirectConfig['id'] = $redirect['id'];
1163
            }
1164
        }
1165
        // Trigger a 'beforeSaveRedirect' event
1166
        $isNew = (int)$redirectConfig['id'] === 0;
1167
        $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...
1168
            'isNew' => $isNew,
1169
            'legacyUrl' => $redirectConfig['redirectSrcUrlParsed'],
1170
            'destinationUrl' => $redirectConfig['redirectDestUrl'],
1171
            'matchType' => $redirectConfig['redirectSrcMatch'],
1172
            'redirectType' => $redirectConfig['redirectHttpCode'],
1173
            'siteId' => $redirectConfig['siteId'],
1174
        ]);
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...
1175
        $this->trigger(self::EVENT_BEFORE_SAVE_REDIRECT, $event);
1176
        if (!$event->isValid) {
1177
            return false;
1178
        }
1179
        // See if this is an existing redirect
1180
        if (!$isNew) {
1181
            Craft::debug(
1182
                Craft::t(
1183
                    'retour',
1184
                    'Updating existing redirect: {redirect}',
1185
                    ['redirect' => print_r($redirectConfig, true)]
1186
                ),
1187
                __METHOD__
1188
            );
1189
            // Update the existing record
1190
            try {
1191
                $db->createCommand()->update(
1192
                    '{{%retour_static_redirects}}',
1193
                    $redirectConfig,
1194
                    [
1195
                        'id' => $redirectConfig['id'],
1196
                    ]
1197
                )->execute();
1198
            } catch (Exception $e) {
1199
                Craft::error($e->getMessage(), __METHOD__);
1200
1201
                return false;
1202
            }
1203
        } else {
1204
            Craft::debug(
1205
                Craft::t(
1206
                    'retour',
1207
                    'Creating new redirect: {redirect}',
1208
                    ['redirect' => print_r($redirectConfig, true)]
1209
                ),
1210
                __METHOD__
1211
            );
1212
            unset($redirectConfig['id']);
1213
            // Create a new record
1214
            try {
1215
                $db->createCommand()->insert(
1216
                    '{{%retour_static_redirects}}',
1217
                    $redirectConfig
1218
                )->execute();
1219
            } catch (Exception $e) {
1220
                Craft::error($e->getMessage(), __METHOD__);
1221
1222
                return false;
1223
            }
1224
        }
1225
        // Trigger a 'afterSaveRedirect' event
1226
        $this->trigger(self::EVENT_AFTER_SAVE_REDIRECT, $event);
1227
1228
        if (!empty($redirectConfig['associatedElementId']) && !empty($existingRedirect)) {
1229
            $this->updateAssociatedElementShortLink($redirectConfig, $existingRedirect);
1230
        }
1231
1232
        // Invalidate caches after saving a redirect
1233
        $this->invalidateCaches();
1234
1235
        return true;
1236
    }
1237
1238
    /**
1239
     * Return a redirect by redirectSrcUrl
1240
     *
1241
     * @param string $redirectSrcUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
1242
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1243
     *
1244
     * @return null|array
1245
     */
1246
    public function getRedirectByRedirectSrcUrl(string $redirectSrcUrl, int $siteId = null): ?array
1247
    {
1248
        // Query the db table
1249
        $query = (new Query())
1250
            ->from(['{{%retour_static_redirects}}'])
1251
            ->where(['redirectSrcUrl' => TextHelper::cleanupText($redirectSrcUrl)]);
1252
        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...
1253
            $query
1254
                ->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...
1255
                    'siteId' => $siteId,
1256
                ], [
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...
1257
                    'siteId' => null,
1258
                ]]);
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...
1259
        }
1260
        $redirect = $query->one();
1261
1262
        return $redirect;
1263
    }
1264
1265
    /**
1266
     * Invalidate all of the redirects caches
1267
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1268
    public function invalidateCaches(): void
1269
    {
1270
        $cache = Craft::$app->getCache();
1271
        TagDependency::invalidate($cache, $this::GLOBAL_REDIRECTS_CACHE_TAG);
1272
        // Clear the GraphQL caches too
1273
        $gql = Craft::$app->getGql();
1274
        if (method_exists($gql, 'invalidateCaches')) {
1275
            $gql->invalidateCaches();
1276
        }
1277
        Craft::debug(
1278
            Craft::t(
1279
                'retour',
1280
                'All redirect caches cleared'
1281
            ),
1282
            __METHOD__
1283
        );
1284
    }
1285
1286
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $enabledOnly should have a doc-comment as per coding-style.
Loading history...
1287
     * @param int|null $limit
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 2 spaces but found 1
Loading history...
1288
     * @param int|null $siteId
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 2 spaces but found 1
Loading history...
1289
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
1290
     * @return array
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
1291
     */
1292
    protected function getRedirectsByMatchType(int $limit = null, int $siteId = null, string $type, bool $enabledOnly = false): array
1293
    {
1294
        // Query the db table
1295
        $query = (new Query())
1296
            ->from(['{{%retour_static_redirects}}'])
1297
            ->orderBy('redirectMatchType ASC, priority ASC');
1298
1299
        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...
1300
            $query
1301
                ->where(['siteId' => $siteId])
1302
                ->orWhere(['siteId' => null]);
1303
        }
1304
1305
        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...
1306
            $query->limit($limit);
1307
        }
1308
1309
        $query->andWhere(['redirectMatchType' => $type]);
1310
1311
        if ($enabledOnly) {
1312
            $query->andWhere(['enabled' => 1]);
1313
        }
1314
1315
        return $query->all();
1316
    }
1317
1318
    /**
1319
     * Updates an associated element short link value.
1320
     *
1321
     * @param array $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1322
     * @param array $existingData
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1323
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1324
    protected function updateAssociatedElementShortLink(array $redirectConfig, array $existingData): void
1325
    {
1326
        if (empty($redirectConfig['associatedElementId'])) {
1327
            return;
1328
        }
1329
        // Get the element and set the scenario
1330
        $associatedElement = Craft::$app->getElements()->getElementById($redirectConfig['associatedElementId']);
1331
1332
        if (!$associatedElement) {
1333
            return;
1334
        }
1335
1336
        $fieldUpdated = $this->setShortLinkFieldValue($associatedElement, $existingData['redirectSrcUrl'], $redirectConfig['redirectSrcUrl']);
1337
1338
        if ($fieldUpdated) {
1339
            // Prevent element from triggering an infinite loop.
1340
            ShortLink::preventShortLinkUpdates();
1341
            Craft::$app->getElements()->saveElement($associatedElement);
1342
            ShortLink::allowShortLinkUpdates();
1343
        }
1344
    }
1345
1346
    /**
1347
     * Find all short link fields on an element that have a matching redirect source url and update the value
1348
     *
1349
     * @param ElementInterface $element
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 2 spaces but found 1
Loading history...
1350
     * @param string $redirectSrcUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 11 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
1351
     * @param mixed $newValue
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 12 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
1352
     * @return bool whether a match was found
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
1353
     */
1354
    protected function setShortLinkFieldValue(ElementInterface $element, string $redirectSrcUrl, mixed $newValue): bool
1355
    {
1356
        $layout = $element->getFieldLayout();
1357
        $srcUrl = $redirectSrcUrl;
1358
        $site = $element->getSite();
1359
        $urlLess = StringHelper::removeLeft($srcUrl, $site->getBaseUrl());
1360
1361
        $matchFound = false;
1362
        foreach ($element->getFieldValues() as $fieldHandle => $fieldValue) {
1363
            if (!is_string($fieldValue)) {
1364
                continue;
1365
            }
1366
            // Compare field value with starting slashes dropped to the redirectSrcUrl value as well as one with site URL removed, just in case
1367
            if (in_array(ltrim($fieldValue, '/'), [ltrim($srcUrl, '/'), ltrim($urlLess, '/')], true)) {
1368
                $field = $layout->getFieldByHandle($fieldHandle);
1369
1370
                if ($field instanceof ShortLink) {
1371
                    $element->setFieldValue($fieldHandle, $newValue);
1372
                    $matchFound = true;
1373
                }
1374
            }
1375
        }
1376
1377
        return $matchFound;
1378
    }
1379
}
1380