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 (!$this->doRedirect($fullUrl, $pathOnly, $redirect) && !Retour::$settings->alwaysStripQueryString) {
243
                    // Try it again without the query string
244
                    $fullUrl = UrlHelper::stripQueryString($fullUrl);
245
                    $pathOnly = UrlHelper::stripQueryString($pathOnly);
246
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
247
                    $this->doRedirect($fullUrl, $pathOnly, $redirect);
248
                }
249
                // If the redirect wasn't found, look for it without the Site-defined prefix
250
                if ($redirect === null) {
251
                    // Strip out any site-defined baseUrl path prefixes
252
                    $pathOnly = UrlHelper::stripSitePathPrefix($pathOnly);
253
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
254
                    $this->doRedirect($fullUrl, $pathOnly, $redirect);
255
                }
256
                // Increment the stats
257
                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

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

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