Passed
Push — v3 ( 61fdab...d4dfba )
by Andrew
14:38 queued 16s
created

Redirects::saveRedirect()   D

Complexity

Conditions 15
Paths 170

Size

Total Lines 117
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 74
c 2
b 0
f 0
dl 0
loc 117
ccs 0
cts 81
cp 0
rs 4.8339
cc 15
nc 170
nop 1
crap 240

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;
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
    const CACHE_KEY = 'retour_redirect_';
54
55
    const GLOBAL_REDIRECTS_CACHE_TAG = 'retour_redirects';
56
57
    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
    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
    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
    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
    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
    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
    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
    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 $purgedShortLinkElementIds = [];
192
193
    // Public Methods
194
    // =========================================================================
195
196
    /**
197
     * Handle 404s by looking for redirects
198
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
199
    public function handle404()
200
    {
201
        Craft::info(
202
            Craft::t(
203
                'retour',
204
                'A 404 exception occurred'
205
            ),
206
            __METHOD__
207
        );
208
        $request = Craft::$app->getRequest();
209
        // We only want site requests that are not live preview or console requests
210
        if ($request->getIsSiteRequest() && !$this->isPreview($request) && !$request->getIsConsoleRequest()) {
211
            // See if we should redirect
212
            try {
213
                $fullUrl = urldecode($request->getAbsoluteUrl());
214
                $pathOnly = urldecode($request->getUrl());
215
            } catch (InvalidConfigException $e) {
216
                Craft::error(
217
                    $e->getMessage(),
218
                    __METHOD__
219
                );
220
                $pathOnly = '';
221
                $fullUrl = '';
222
            }
223
            // Strip the query string if `alwaysStripQueryString` is set
224
            if (Retour::$settings->alwaysStripQueryString) {
225
                $fullUrl = UrlHelper::stripQueryString($fullUrl);
226
                $pathOnly = UrlHelper::stripQueryString($pathOnly);
227
            }
228
            // Stash the $pathOnly for use when incrementing the statistics
229
            $originalPathOnly = $pathOnly;
230
            Craft::info(
231
                Craft::t(
232
                    'retour',
233
                    '404 full URL: {fullUrl}, 404 path only: {pathOnly}',
234
                    ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
235
                ),
236
                __METHOD__
237
            );
238
            if (!$this->excludeUri($pathOnly)) {
239
                // Redirect if we find a match, otherwise let Craft handle it
240
                $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
241
                if (!$this->doRedirect($fullUrl, $pathOnly, $redirect) && !Retour::$settings->alwaysStripQueryString) {
242
                    // Try it again without the query string
243
                    $fullUrl = UrlHelper::stripQueryString($fullUrl);
244
                    $pathOnly = UrlHelper::stripQueryString($pathOnly);
245
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
246
                    $this->doRedirect($fullUrl, $pathOnly, $redirect);
247
                }
248
                // Increment the stats
249
                Retour::$plugin->statistics->incrementStatistics($originalPathOnly, false);
250
            }
251
        }
252
    }
253
254
    /**
255
     * Do the redirect
256
     *
257
     * @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...
258
     * @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...
259
     * @param null|array $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
260
     *
261
     * @return bool false if not redirected
262
     */
263
    public function doRedirect(string $fullUrl, string $pathOnly, $redirect): bool
264
    {
265
        $response = Craft::$app->getResponse();
266
        if ($redirect !== null) {
267
            // Figure out what type of source matching was done
268
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
269
            switch ($redirectSrcMatch) {
270
                case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
271
                    $url = $pathOnly;
272
                    break;
273
                case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
274
                    $url = $fullUrl;
275
                    break;
276
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
277
                    $url = $pathOnly;
278
                    break;
279
            }
280
            $dest = $redirect['redirectDestUrl'];
281
            // If this isn't a full URL, make it one based on the appropriate site
282
            if (!UrlHelper::isFullUrl($dest)) {
283
                try {
284
                    $siteId = $redirect['siteId'] ?? null;
285
                    if ($siteId !== null) {
286
                        $siteId = (int)$siteId;
287
                    }
288
                    $dest = UrlHelper::siteUrl($dest, null, null, $siteId);
289
                } 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...
290
                }
291
            }
292
            if (Retour::$settings->preserveQueryString) {
293
                $request = Craft::$app->getRequest();
294
                $queryString = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $queryString is dead and can be removed.
Loading history...
295
                try {
296
                    $queryString = UrlHelper::combineQueryStringsFromUrls($dest, $request->getUrl());
297
                } catch (InvalidConfigException $e) {
298
                    // That's ok
299
                }
300
                if (!empty($queryString)) {
301
                    $dest = strtok($dest, '?') . '?' . $queryString;
302
                }
303
            }
304
            $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
305
            // Parse reference tags for exact matches
306
            if ($redirectMatchType === 'exactmatch') {
307
                $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

307
                /** @scrutinizer ignore-call */ 
308
                $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...
308
            }
309
            $status = $redirect['redirectHttpCode'];
310
            Craft::info(
311
                Craft::t(
312
                    'retour',
313
                    'Redirecting {url} to {dest} with status {status}',
314
                    ['url' => $url, 'dest' => $dest, 'status' => $status]
315
                ),
316
                __METHOD__
317
            );
318
            // Increment the stats
319
            Retour::$plugin->statistics->incrementStatistics($url, true);
320
            // Handle a Retour return status > 400 to render the actual error template
321
            if ($status >= 400) {
322
                Retour::$currentException->statusCode = $status;
323
                $errorHandler = Craft::$app->getErrorHandler();
324
                $errorHandler->exception = Retour::$currentException;
325
                try {
326
                    $response = Craft::$app->runAction('templates/render-error');
327
                } catch (InvalidRouteException $e) {
328
                    Craft::error($e->getMessage(), __METHOD__);
329
                } catch (\yii\console\Exception $e) {
330
                    Craft::error($e->getMessage(), __METHOD__);
331
                }
332
            }
333
            // Sanitize the URL
334
            $dest = UrlHelper::sanitizeUrl($dest);
335
            // Optionally set the no-cache headers
336
            if (Retour::$settings->setNoCacheHeaders && $response instanceof WebResponse) {
337
                $response->setNoCacheHeaders();
338
            }
339
            // Add any additional headers (existing ones will be replaced)
340
            if (!empty(Retour::$settings->additionalHeaders)) {
341
                foreach (Retour::$settings->additionalHeaders as $additionalHeader) {
342
                    $response->headers->set($additionalHeader['name'], $additionalHeader['value']);
343
                }
344
            }
345
            // Redirect the request away;
346
            $response->redirect($dest, $status)->send();
347
            try {
348
                Craft::$app->end();
349
            } catch (ExitException $e) {
350
                Craft::error($e->getMessage(), __METHOD__);
351
            }
352
        }
353
354
        return false;
355
    }
356
357
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
358
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
359
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
360
     * @param null $siteId
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $siteId is correct as it would always require null to be passed?
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
361
     *
362
     * @return array|null
363
     */
364
    public function findRedirectMatch(string $fullUrl, string $pathOnly, $siteId = null)
365
    {
366
        // Get the current site
367
        if ($siteId === null) {
0 ignored issues
show
introduced by
The condition $siteId === null is always true.
Loading history...
368
            $currentSite = Craft::$app->getSites()->currentSite;
369
            if ($currentSite) {
370
                $siteId = $currentSite->id;
371
            } else {
372
                $primarySite = Craft::$app->getSites()->primarySite;
373
                $siteId = $primarySite->id;
374
            }
375
        }
376
        // Try getting the full URL redirect from the cache
377
        $redirect = $this->getRedirectFromCache($fullUrl, $siteId);
378
        if ($redirect) {
379
            $this->incrementRedirectHitCount($redirect);
380
            $this->saveRedirectToCache($fullUrl, $redirect);
381
382
            return $redirect;
383
        }
384
        // Try getting the path only redirect from the cache
385
        $redirect = $this->getRedirectFromCache($pathOnly, $siteId);
386
        if ($redirect) {
387
            $this->incrementRedirectHitCount($redirect);
388
            $this->saveRedirectToCache($pathOnly, $redirect);
389
390
            return $redirect;
391
        }
392
393
        $redirect = $this->getStaticRedirect($fullUrl, $pathOnly, $siteId, true);
394
        if ($redirect) {
395
            $this->incrementRedirectHitCount($redirect);
396
            $this->saveRedirectToCache($pathOnly, $redirect);
397
398
            return $redirect;
399
        }
400
401
        // Resolve static redirects
402
        $redirects = $this->getAllRegExRedirects(null, $siteId, true);
403
        $redirect = $this->resolveRedirect($fullUrl, $pathOnly, $redirects, $siteId);
404
        if ($redirect) {
405
            return $redirect;
406
        }
407
408
        return null;
409
    }
410
411
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
412
     * @param          $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 10
Loading history...
413
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
414
     *
415
     * @return bool|array
416
     */
417
    public function getRedirectFromCache($url, ?int $siteId = 0)
418
    {
419
        $cache = Craft::$app->getCache();
420
        $cacheKey = $this::CACHE_KEY . md5($url) . $siteId;
421
        $redirect = $cache->get($cacheKey);
422
        Craft::info(
423
            Craft::t(
424
                'retour',
425
                'Cached redirect hit for {url}',
426
                ['url' => $url]
427
            ),
428
            __METHOD__
429
        );
430
431
        return $redirect;
432
    }
433
434
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
435
     * @param string $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
436
     * @param array $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
437
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
438
    public function saveRedirectToCache($url, $redirect)
439
    {
440
        $cache = Craft::$app->getCache();
441
        // Get the current site id
442
        $sites = Craft::$app->getSites();
443
        try {
444
            $siteId = $sites->getCurrentSite()->id;
445
        } catch (SiteNotFoundException $e) {
446
            $siteId = 1;
447
        }
448
        $cacheKey = $this::CACHE_KEY . md5($url) . $siteId;
449
        // Create the dependency tags
450
        $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...
451
            'tags' => [
452
                $this::GLOBAL_REDIRECTS_CACHE_TAG,
453
                $this::GLOBAL_REDIRECTS_CACHE_TAG . $siteId,
454
            ],
455
        ]);
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...
456
        $cache->set($cacheKey, $redirect, Retour::$cacheDuration, $dependency);
457
        Craft::info(
458
            Craft::t(
459
                'retour',
460
                'Cached redirect saved for {url}',
461
                ['url' => $url]
462
            ),
463
            __METHOD__
464
        );
465
    }
466
467
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $siteId should have a doc-comment as per coding-style.
Loading history...
468
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
469
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
470
     * @param array $redirects
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
471
     *
472
     * @return array|null
473
     */
474
    public function resolveRedirect(string $fullUrl, string $pathOnly, array $redirects, $siteId)
475
    {
476
        $result = null;
477
        // Throw the Redirects::EVENT_BEFORE_RESOLVE_REDIRECT event
478
        $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...
479
            'fullUrl' => $fullUrl,
480
            'pathOnly' => $pathOnly,
481
            'redirectDestUrl' => null,
482
            'redirectHttpCode' => 301,
483
            'siteId' => $siteId,
484
        ]);
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...
485
        $this->trigger(self::EVENT_BEFORE_RESOLVE_REDIRECT, $event);
486
        if ($event->redirectDestUrl !== null) {
487
            return $this->resolveEventRedirect($event);
488
        }
489
        // Iterate through the redirects
490
        foreach ($redirects as $redirect) {
491
            // Figure out what type of source matching to do
492
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
493
            $redirectEnabled = (bool)$redirect['enabled'];
494
            if ($redirectEnabled === true) {
495
                switch ($redirectSrcMatch) {
496
                    case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
497
                        $url = $pathOnly;
498
                        break;
499
                    case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
500
                        $url = $fullUrl;
501
                        break;
502
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
503
                        $url = $pathOnly;
504
                        break;
505
                }
506
                $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
507
                switch ($redirectMatchType) {
508
                    // Do a straight up match
509
                    case 'exactmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
510
                        if (strcasecmp($redirect['redirectSrcUrlParsed'], $url) === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
511
                            $this->incrementRedirectHitCount($redirect);
512
                            $this->saveRedirectToCache($url, $redirect);
513
514
                            // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
515
                            $event = new RedirectResolvedEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
516
                                'fullUrl' => $fullUrl,
517
                                'pathOnly' => $pathOnly,
518
                                'redirectDestUrl' => null,
519
                                'redirectHttpCode' => 301,
520
                                'redirect' => $redirect,
521
                                'siteId' => $siteId,
522
                            ]);
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...
523
                            $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
524
                            if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
525
                                return $this->resolveEventRedirect($event, $url, $redirect);
526
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
527
528
                            return $redirect;
529
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
530
                        break;
531
532
                    // Do a regex match
533
                    case 'regexmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
534
                        $matchRegEx = '`' . $redirect['redirectSrcUrlParsed'] . '`i';
535
                        try {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
536
                            if (preg_match($matchRegEx, $url) === 1) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
537
                                $this->incrementRedirectHitCount($redirect);
538
                                // If we're not associated with an EntryID, handle capture group replacement
539
                                if ((int)$redirect['associatedElementId'] === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
540
                                    $redirect['redirectDestUrl'] = preg_replace(
541
                                        $matchRegEx,
542
                                        $redirect['redirectDestUrl'],
543
                                        $url
544
                                    );
545
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
546
                                $url = preg_replace('/([^:])(\/{2,})/', '$1/', $url);
547
                                $this->saveRedirectToCache($url, $redirect);
548
549
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
550
                                $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...
551
                                    'fullUrl' => $fullUrl,
552
                                    'pathOnly' => $pathOnly,
553
                                    'redirectDestUrl' => null,
554
                                    'redirectHttpCode' => 301,
555
                                    'redirect' => $redirect,
556
                                    'siteId' => $siteId,
557
                                ]);
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...
558
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
559
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
560
                                    return $this->resolveEventRedirect($event, $url, $redirect);
561
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
562
563
                                return $redirect;
564
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
565
                        } catch (\Exception $e) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
566
                            // That's fine
567
                            Craft::error('Invalid Redirect Regex: ' . $matchRegEx, __METHOD__);
568
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
569
570
                        break;
571
572
                    // Otherwise try to look up a plugin's method by and call it for the match
573
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
574
                        $plugin = $redirectMatchType ? Craft::$app->getPlugins()->getPlugin($redirectMatchType) : null;
575
                        if ($plugin && method_exists($plugin, 'retourMatch')) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
576
                            $args = [
577
                                [
578
                                    'redirect' => &$redirect,
579
                                ],
580
                            ];
581
                            $result = call_user_func_array([$plugin, 'retourMatch'], $args);
582
                            if ($result) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
583
                                $this->incrementRedirectHitCount($redirect);
584
                                $this->saveRedirectToCache($url, $redirect);
585
586
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
587
                                $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...
588
                                    'fullUrl' => $fullUrl,
589
                                    'pathOnly' => $pathOnly,
590
                                    'redirectDestUrl' => null,
591
                                    'redirectHttpCode' => 301,
592
                                    'redirect' => $redirect,
593
                                    'siteId' => $siteId,
594
                                ]);
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...
595
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
596
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
597
                                    return $this->resolveEventRedirect($event, $url, $redirect);
598
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
599
600
                                return $redirect;
601
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
602
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
603
                        break;
604
                }
605
            }
606
        }
607
        // Throw the Redirects::EVENT_AFTER_RESOLVE_REDIRECT event
608
        $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...
609
            'fullUrl' => $fullUrl,
610
            'pathOnly' => $pathOnly,
611
            'redirectDestUrl' => null,
612
            'redirectHttpCode' => 301,
613
            'siteId' => $siteId,
614
        ]);
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...
615
        $this->trigger(self::EVENT_AFTER_RESOLVE_REDIRECT, $event);
616
        if ($event->redirectDestUrl !== null) {
617
            return $this->resolveEventRedirect($event);
618
        }
619
        Craft::info(
620
            Craft::t(
621
                'retour',
622
                'Not handled-> full URL: {fullUrl}, path only: {pathOnly}',
623
                ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
624
            ),
625
            __METHOD__
626
        );
627
628
        return $result;
629
    }
630
631
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $url should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $redirect should have a doc-comment as per coding-style.
Loading history...
632
     * @param ResolveRedirectEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
633
     *
634
     * @return null|array
635
     */
636
    public function resolveEventRedirect(ResolveRedirectEvent $event, $url = null, $redirect = null)
637
    {
638
        $result = null;
639
640
        if ($event->redirectDestUrl !== null) {
641
            $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...
642
                'id' => self::EVENT_REDIRECT_ID,
643
                'redirectDestUrl' => $event->redirectDestUrl,
644
                'redirectHttpCode' => $event->redirectHttpCode,
645
            ]);
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...
646
            $result = $resolvedRedirect->toArray();
647
648
            if ($url !== null && $redirect !== null) {
649
                // Save the modified redirect to the cache
650
                $redirect['redirectDestUrl'] = $event->redirectDestUrl;
651
                $redirect['redirectHttpCode'] = $event->redirectHttpCode;
652
                $this->saveRedirectToCache($url, $redirect);
653
            }
654
        }
655
656
        return $result;
657
    }
658
659
    /**
660
     * Returns the list of matching schemes
661
     *
662
     * @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...
663
     */
664
    public function getMatchesList(): array
665
    {
666
        $result = [
667
            'exactmatch' => Craft::t('retour', 'Exact Match'),
668
            'regexmatch' => Craft::t('retour', 'RegEx Match'),
669
        ];
670
671
        // Add any plugins that offer the retourMatch() method
672
        foreach (Craft::$app->getPlugins()->getAllPlugins() as $plugin) {
673
            /** @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...
674
            if (method_exists($plugin, 'retourMatch')) {
675
                $result[$plugin->getHandle()] = $plugin->name . Craft::t('retour', ' Match');
676
            }
677
        }
678
679
        return $result;
680
    }
681
682
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
683
     * @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...
684
     * @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...
685
     * @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...
686
     * @param bool $enabledOnly
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...
687
     * @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...
688
     */
689
    public function getStaticRedirect(string $fullUrl, string $pathOnly, $siteId, bool $enabledOnly = false)
690
    {
691
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
692
        // Throw the Redirects::EVENT_BEFORE_RESOLVE_REDIRECT event
693
        $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...
694
            'fullUrl' => $fullUrl,
695
            'pathOnly' => $pathOnly,
696
            'redirectDestUrl' => null,
697
            'redirectHttpCode' => 301,
698
            'siteId' => $siteId,
699
        ]);
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...
700
        $this->trigger(self::EVENT_BEFORE_RESOLVE_REDIRECT, $event);
701
        if ($event->redirectDestUrl !== null) {
702
            return $this->resolveEventRedirect($event);
703
        }
704
        // Query for the static redirect
705
        $staticCondition = ['redirectMatchType' => 'exactmatch'];
706
        $siteCondition = [
707
            'or',
708
            ['siteId' => $siteId],
709
            ['siteId' => null],
710
        ];
711
        $pathCondition = [
712
            'or',
713
            ['and',
714
                ['redirectSrcMatch' => 'pathonly'],
715
                ['redirectSrcUrlParsed' => TextHelper::cleanupText($pathOnly)],
716
            ],
717
            ['and',
718
                ['redirectSrcMatch' => 'fullurl'],
719
                ['redirectSrcUrlParsed' => TextHelper::cleanupText($fullUrl)],
720
            ],
721
        ];
722
723
        $query = (new Query())
724
            ->from('{{%retour_static_redirects}}')
725
            ->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...
726
                $staticCondition,
727
                $pathCondition,
728
                $siteCondition,
729
            ])
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...
730
            ->limit(1);
731
732
        if ($enabledOnly) {
733
            $query->andWhere(['enabled' => 1]);
734
        }
735
        $result = $query->one();
736
        if ($result) {
737
            // Figure out what type of source matching to do
738
            $redirectSrcMatch = $result['redirectSrcMatch'] ?? 'pathonly';
739
            switch ($redirectSrcMatch) {
740
                case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
741
                    $url = $pathOnly;
742
                    break;
743
                case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
744
                    $url = $fullUrl;
745
                    break;
746
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
747
                    $url = $pathOnly;
748
                    break;
749
            }
750
            // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
751
            $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...
752
                'fullUrl' => $fullUrl,
753
                'pathOnly' => $pathOnly,
754
                'redirectDestUrl' => null,
755
                'redirectHttpCode' => 301,
756
                'redirect' => $result,
757
                'siteId' => $siteId,
758
            ]);
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...
759
            $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
760
            if ($event->redirectDestUrl !== null) {
761
                return $this->resolveEventRedirect($event, $url, $result);
762
            }
763
764
            return $result;
765
        }
766
767
        // Throw the Redirects::EVENT_AFTER_RESOLVE_REDIRECT event
768
        $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...
769
            'fullUrl' => $fullUrl,
770
            'pathOnly' => $pathOnly,
771
            'redirectDestUrl' => null,
772
            'redirectHttpCode' => 301,
773
            'siteId' => $siteId,
774
        ]);
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...
775
        $this->trigger(self::EVENT_AFTER_RESOLVE_REDIRECT, $event);
776
        if ($event->redirectDestUrl !== null) {
777
            return $this->resolveEventRedirect($event);
778
        }
779
        Craft::info(
780
            Craft::t(
781
                'retour',
782
                'Not handled-> full URL: {fullUrl}, path only: {pathOnly}',
783
                ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
784
            ),
785
            __METHOD__
786
        );
787
788
        return $result;
789
    }
790
791
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
792
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
793
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
794
     * @param bool $enabledOnly
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...
795
     *
796
     * @return array All of the regex match redirects
797
     */
798
    public function getAllRegExRedirects(int $limit = null, int $siteId = null, bool $enabledOnly = false): array
799
    {
800
        return $this->getRedirectsByMatchType($limit, $siteId, 'regexmatch', $enabledOnly);
801
    }
802
803
    /**
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...
804
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
805
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
806
     *
807
     * @return array All of the regex match redirects
808
     */
809
    public function getAllExactMatchRedirects(int $limit = null, int $siteId = null, bool $enabledOnly = false): array
810
    {
811
        return $this->getRedirectsByMatchType($limit, $siteId, 'exactmatch', $enabledOnly);
812
    }
813
814
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
815
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
816
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
817
     *
818
     * @return array All of the statistics
819
     */
820
    public function getAllStaticRedirects($limit = null, int $siteId = null): array
821
    {
822
        // Query the db table
823
        $query = (new Query())
824
            ->from(['{{%retour_static_redirects}}'])
825
            ->orderBy('redirectMatchType ASC, priority ASC');
826
        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...
827
            $query
828
                ->where(['siteId' => $siteId])
829
                ->orWhere(['siteId' => null]);
830
        }
831
        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...
832
            $query->limit($limit);
833
        }
834
835
        return $query->all();
836
    }
837
838
    /**
839
     * Return a redirect by id
840
     *
841
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
842
     *
843
     * @return null|array The static redirect
844
     */
845
    public function getRedirectById(int $id)
846
    {
847
        // Query the db table
848
        $redirect = (new Query())
849
            ->from(['{{%retour_static_redirects}}'])
850
            ->where(['id' => $id])
851
            ->one();
852
853
        return $redirect;
854
    }
855
856
    /**
857
     * Return a redirect by redirectSrcUrl
858
     *
859
     * @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...
860
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
861
     *
862
     * @return null|array
863
     */
864
    public function getRedirectByRedirectSrcUrl(string $redirectSrcUrl, int $siteId = null)
865
    {
866
        // Query the db table
867
        $query = (new Query())
868
            ->from(['{{%retour_static_redirects}}'])
869
            ->where(['redirectSrcUrl' => TextHelper::cleanupText($redirectSrcUrl)]);
870
        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...
871
            $query
872
                ->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...
873
                    'siteId' => $siteId,
874
                ], [
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...
875
                    'siteId' => null,
876
                ]]);
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...
877
        }
878
        $redirect = $query->one();
879
880
        return $redirect;
881
    }
882
883
    /**
884
     * Return redirects for a given element.
885
     *
886
     * @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...
887
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
888
     *
889
     * @return null|array
890
     */
891
    public function getRedirectsByElementId(int $elementId, int $siteId = null)
892
    {
893
        // Query the db table
894
        $query = (new Query())
895
            ->from(['{{%retour_static_redirects}}'])
896
            ->where(['associatedElementId' => $elementId]);
897
        if ($siteId !== null) {
898
            $query->andWhere(['siteId' => $siteId]);
899
        }
900
901
        return $query->all();
902
    }
903
904
    /**
905
     * Delete a redirect by id
906
     *
907
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
908
     *
909
     * @return int The result
910
     */
911
    public function deleteRedirectById(int $id): int
912
    {
913
        $db = Craft::$app->getDb();
914
        // Trigger a 'beforeDeleteRedirect' event
915
        $redirectConfig = $this->getRedirectById($id);
916
        $isNew = (int)$redirectConfig['id'] === 0;
917
        $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...
918
            'isNew' => $isNew,
919
            'legacyUrl' => $redirectConfig['redirectSrcUrlParsed'],
920
            'destinationUrl' => $redirectConfig['redirectDestUrl'],
921
            'matchType' => $redirectConfig['redirectSrcMatch'],
922
            'redirectType' => $redirectConfig['redirectHttpCode'],
923
            'siteId' => $redirectConfig['siteId'],
924
        ]);
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...
925
        $this->trigger(self::EVENT_BEFORE_DELETE_REDIRECT, $event);
926
        if (!$event->isValid) {
927
            return 0;
928
        }
929
        // Delete a row from the db table
930
        try {
931
            $result = $db->createCommand()->delete(
932
                '{{%retour_static_redirects}}',
933
                [
934
                    'id' => $id,
935
                ]
936
            )->execute();
937
        } catch (Exception $e) {
938
            Craft::error($e->getMessage(), __METHOD__);
939
            $result = 0;
940
        }
941
        $this->trigger(self::EVENT_AFTER_DELETE_REDIRECT, $event);
942
943
        return $result;
944
    }
945
946
    /**
947
     * Increment the retour_static_redirects record
948
     *
949
     * @param $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
950
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
951
    public function incrementRedirectHitCount(&$redirectConfig)
952
    {
953
        if ($redirectConfig !== null) {
954
            $db = Craft::$app->getDb();
955
            $redirectConfig['hitCount']++;
956
            $redirectConfig['hitLastTime'] = Db::prepareDateForDb(new DateTime());
957
            Craft::debug(
958
                Craft::t(
959
                    'retour',
960
                    'Incrementing statistics for: {redirect}',
961
                    ['redirect' => print_r($redirectConfig, true)]
962
                ),
963
                __METHOD__
964
            );
965
            // Update the existing record
966
            try {
967
                $rowsAffected = $db->createCommand()->update(
968
                    '{{%retour_static_redirects}}',
969
                    [
970
                        'hitCount' => $redirectConfig['hitCount'],
971
                        'hitLastTime' => $redirectConfig['hitLastTime'],
972
                    ],
973
                    [
974
                        'id' => $redirectConfig['id'],
975
                    ]
976
                )->execute();
977
                Craft::debug('Rows affected: ' . $rowsAffected, __METHOD__);
978
            } catch (\Exception $e) {
979
                Craft::error($e->getMessage(), __METHOD__);
980
            }
981
        }
982
    }
983
984
    /**
985
     * Save an element redirect.
986
     *
987
     * @param ElementInterface $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
988
     * @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...
989
     * @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...
990
     * @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...
991
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
992
    public function enableElementRedirect(ElementInterface $element, string $sourceUrl, string $redirectSrcMatch = 'pathonly', int $redirectHttpCode = 301)
993
    {
994
        $siteId = $element->siteId;
0 ignored issues
show
Bug introduced by
Accessing siteId on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
995
996
        $destUrl = $redirectSrcMatch === 'pathonly' ? $element->uri : $element->getUrl();
0 ignored issues
show
Bug introduced by
Accessing uri on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
997
        if ($destUrl === null) {
998
            $destUrl = $element->getUrl();
999
        }
1000
        $redirectConfig = [
1001
            'redirectMatchType' => 'exactmatch',
1002
            'redirectSrcUrl' => $sourceUrl,
1003
            'siteId' => $siteId,
1004
            'associatedElementId' => $element->getCanonicalId(),
1005
            'enabled' => $element->getEnabledForSite($siteId),
1006
            'redirectSrcMatch' => $redirectSrcMatch,
1007
            'redirectDestUrl' => $destUrl,
1008
            'redirectHttpCode' => $redirectHttpCode,
1009
        ];
1010
1011
        $this->saveRedirect($redirectConfig);
1012
    }
1013
1014
    /**
1015
     * Delete an element redirect.
1016
     *
1017
     * @param ElementInterface $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1018
     * @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...
1019
     * @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...
1020
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1021
    public function removeElementRedirect(ElementInterface $element, bool $allSites = false, bool $force = false)
1022
    {
1023
        if (!$force) {
1024
            if (!empty(self::$purgedShortLinkElementIds[$element->id])) {
0 ignored issues
show
Bug introduced by
Accessing id on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1025
                return;
1026
            }
1027
1028
            self::$purgedShortLinkElementIds[$element->id] = true;
1029
        }
1030
1031
        $redirects = $this->getRedirectsByElementId($element->getCanonicalId(), $allSites ? null : $element->siteId);
0 ignored issues
show
Bug introduced by
It seems like $element->getCanonicalId() can also be of type null; however, parameter $elementId of nystudio107\retour\servi...tRedirectsByElementId() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

1031
        $redirects = $this->getRedirectsByElementId(/** @scrutinizer ignore-type */ $element->getCanonicalId(), $allSites ? null : $element->siteId);
Loading history...
Bug introduced by
Accessing siteId on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1032
1033
        if (!empty($redirects)) {
1034
            foreach ($redirects as $redirect) {
1035
                $this->deleteRedirectById($redirect['id']);
1036
            }
1037
        }
1038
    }
1039
1040
    /**
1041
     * Delete a short link by its ID.
1042
     *
1043
     * @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...
1044
     * @throws Throwable
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
1045
     * @throws ElementNotFoundException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
1046
     * @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...
1047
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1048
    public function deleteShortlinkById(int $redirectId)
1049
    {
1050
        $redirect = $this->getRedirectById($redirectId);
1051
        $elementId = $redirect['associatedElementId'];
1052
        $siteId = $redirect['siteId'];
1053
        $element = Craft::$app->getElements()->getElementById($elementId, null, $siteId);
1054
1055
        if ($element) {
1056
            $fieldUpdated = $this->setShortLinkFieldValue($element, $redirect['redirectSrcUrl'], null);
1057
1058
            if ($fieldUpdated) {
1059
                // This will also delete the redirect from the table
1060
                Craft::$app->getElements()->saveElement($element);
1061
            }
1062
        }
1063
    }
1064
1065
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
1066
     * @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...
1067
     * @return bool whether the redirect was saved or not
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
1068
     */
1069
    public function saveRedirect(array $redirectConfig): bool
1070
    {
1071
        // Handle URL encoded URLs by decoding them before saving them
1072
        if (isset($redirectConfig['redirectMatchType']) && $redirectConfig['redirectMatchType'] === 'exactmatch') {
1073
            $redirectConfig['redirectSrcUrl'] = urldecode($redirectConfig['redirectSrcUrl'] ?? '');
1074
            $redirectConfig['redirectSrcUrlParsed'] = urldecode($redirectConfig['redirectSrcUrlParsed'] ?? '');
1075
        }
1076
        // Validate the model before saving it to the db
1077
        $redirect = new StaticRedirectsModel($redirectConfig);
1078
        if ($redirect->validate() === false) {
1079
            Craft::error(
1080
                Craft::t(
1081
                    'retour',
1082
                    'Error validating redirect {id}: {errors}',
1083
                    ['id' => $redirect->id, 'errors' => print_r($redirect->getErrors(), true)]
1084
                ),
1085
                __METHOD__
1086
            );
1087
1088
            return false;
1089
        }
1090
        // Get the validated model attributes and save them to the db
1091
        $redirectConfig = $redirect->getAttributes();
1092
        // 0 for a siteId needs to be converted to null
1093
        if (empty($redirectConfig['siteId']) || (int)$redirectConfig['siteId'] === 0) {
1094
            $redirectConfig['siteId'] = null;
1095
        }
1096
        // Throw an event to before saving the redirect
1097
        $db = Craft::$app->getDb();
1098
        if (!empty($redirectConfig['associatedElementId'])) {
1099
            $existingRedirect = (new Query())->from(['{{%retour_static_redirects}}'])->where(['id' => $redirectConfig['id']])->one();
1100
        }
1101
1102
        // See if a redirect exists with this source URL already
1103
        if ((int)$redirectConfig['id'] === 0) {
1104
            // Query the db table
1105
            $redirect = (new Query())
1106
                ->from(['{{%retour_static_redirects}}'])
1107
                ->where(['redirectSrcUrlParsed' => $redirectConfig['redirectSrcUrlParsed']])
1108
                ->andWhere(['siteId' => $redirectConfig['siteId']])
1109
                ->one();
1110
            // If it exists, update it rather than having duplicates
1111
            if (!empty($redirect)) {
1112
                $redirectConfig['id'] = $redirect['id'];
1113
            }
1114
        }
1115
        // Trigger a 'beforeSaveRedirect' event
1116
        $isNew = (int)$redirectConfig['id'] === 0;
1117
        $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...
1118
            'isNew' => $isNew,
1119
            'legacyUrl' => $redirectConfig['redirectSrcUrlParsed'],
1120
            'destinationUrl' => $redirectConfig['redirectDestUrl'],
1121
            'matchType' => $redirectConfig['redirectSrcMatch'],
1122
            'redirectType' => $redirectConfig['redirectHttpCode'],
1123
            'siteId' => $redirectConfig['siteId'],
1124
        ]);
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...
1125
        $this->trigger(self::EVENT_BEFORE_SAVE_REDIRECT, $event);
1126
        if (!$event->isValid) {
1127
            return false;
1128
        }
1129
        // See if this is an existing redirect
1130
        if (!$isNew) {
1131
            Craft::debug(
1132
                Craft::t(
1133
                    'retour',
1134
                    'Updating existing redirect: {redirect}',
1135
                    ['redirect' => print_r($redirectConfig, true)]
1136
                ),
1137
                __METHOD__
1138
            );
1139
            // Update the existing record
1140
            try {
1141
                $db->createCommand()->update(
1142
                    '{{%retour_static_redirects}}',
1143
                    $redirectConfig,
1144
                    [
1145
                        'id' => $redirectConfig['id'],
1146
                    ]
1147
                )->execute();
1148
            } catch (Exception $e) {
1149
                Craft::error($e->getMessage(), __METHOD__);
1150
1151
                return false;
1152
            }
1153
        } else {
1154
            Craft::debug(
1155
                Craft::t(
1156
                    'retour',
1157
                    'Creating new redirect: {redirect}',
1158
                    ['redirect' => print_r($redirectConfig, true)]
1159
                ),
1160
                __METHOD__
1161
            );
1162
            unset($redirectConfig['id']);
1163
            // Create a new record
1164
            try {
1165
                $db->createCommand()->insert(
1166
                    '{{%retour_static_redirects}}',
1167
                    $redirectConfig
1168
                )->execute();
1169
            } catch (Exception $e) {
1170
                Craft::error($e->getMessage(), __METHOD__);
1171
1172
                return false;
1173
            }
1174
        }
1175
        // Trigger a 'afterSaveRedirect' event
1176
        $this->trigger(self::EVENT_AFTER_SAVE_REDIRECT, $event);
1177
1178
        if (!empty($redirectConfig['associatedElementId']) && !empty($existingRedirect)) {
1179
            $this->updateAssociatedElementShortLink($redirectConfig, $existingRedirect);
1180
        }
1181
1182
        // Invalidate caches after saving a redirect
1183
        $this->invalidateCaches();
1184
1185
        return true;
1186
    }
1187
1188
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
1189
     * @param $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1190
     *
1191
     * @return bool
1192
     */
1193
    public function excludeUri($uri): bool
1194
    {
1195
        $uri = '/' . ltrim($uri, '/');
1196
        if (!empty(Retour::$settings->excludePatterns)) {
1197
            foreach (Retour::$settings->excludePatterns as $excludePattern) {
1198
                if (empty($excludePattern['pattern'])) {
1199
                    continue;
1200
                }
1201
                $pattern = '`' . $excludePattern['pattern'] . '`i';
1202
                try {
1203
                    if (preg_match($pattern, $uri) === 1) {
1204
                        Craft::info(
1205
                            Craft::t(
1206
                                'retour',
1207
                                'Excluded URI: {uri} due to match of pattern: {pattern}',
1208
                                ['uri' => $uri, 'pathOnly' => $pattern]
1209
                            ),
1210
                            __METHOD__
1211
                        );
1212
1213
                        return true;
1214
                    }
1215
                } catch (\Exception $e) {
1216
                    // That's fine
1217
                    Craft::error('Invalid exclude URI Regex: ' . $pattern, __METHOD__);
1218
                }
1219
            }
1220
        }
1221
1222
        return false;
1223
    }
1224
1225
    /**
1226
     * Invalidate all of the redirects caches
1227
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1228
    public function invalidateCaches()
1229
    {
1230
        $cache = Craft::$app->getCache();
1231
        TagDependency::invalidate($cache, $this::GLOBAL_REDIRECTS_CACHE_TAG);
1232
        // If they are using Craft 3.3 or later, clear the GraphQL caches too
1233
        if (Retour::$craft33) {
1234
            $gql = Craft::$app->getGql();
1235
            if (method_exists($gql, 'invalidateCaches')) {
1236
                $gql->invalidateCaches();
1237
            }
1238
        }
1239
        Craft::debug(
1240
            Craft::t(
1241
                'retour',
1242
                'All redirect caches cleared'
1243
            ),
1244
            __METHOD__
1245
        );
1246
    }
1247
1248
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $request should have a doc-comment as per coding-style.
Loading history...
1249
     * Return whether this is a preview request of any kind
1250
     *
1251
     * @return bool
1252
     */
1253
    public function isPreview($request): bool
1254
    {
1255
        $isPreview = false;
1256
        if (Retour::$craft32) {
1257
            $isPreview = $request->getIsPreview();
1258
        }
1259
        $isLivePreview = $request->getIsLivePreview();
1260
1261
        return ($isPreview || $isLivePreview);
1262
    }
1263
1264
    /**
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...
1265
     * @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...
1266
     * @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...
1267
     * @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...
1268
     * @return array
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
1269
     */
1270
    protected function getRedirectsByMatchType(int $limit = null, int $siteId = null, string $type, bool $enabledOnly = false): array
1271
    {
1272
        // Query the db table
1273
        $query = (new Query())
1274
            ->from(['{{%retour_static_redirects}}'])
1275
            ->orderBy('redirectMatchType ASC, priority ASC');
1276
1277
        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...
1278
            $query
1279
                ->where(['siteId' => $siteId])
1280
                ->orWhere(['siteId' => null]);
1281
        }
1282
1283
        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...
1284
            $query->limit($limit);
1285
        }
1286
1287
        $query->andWhere(['redirectMatchType' => $type]);
1288
1289
        if ($enabledOnly) {
1290
            $query->andWhere(['enabled' => 1]);
1291
        }
1292
1293
        return $query->all();
1294
    }
1295
1296
    /**
1297
     * Updates an associated element short link value.
1298
     *
1299
     * @param array $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1300
     * @param array $existingData
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1301
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1302
    protected function updateAssociatedElementShortLink(array $redirectConfig, array $existingData)
1303
    {
1304
        if (empty($redirectConfig['associatedElementId'])) {
1305
            return;
1306
        }
1307
        // Get the element and set the scenario
1308
        $associatedElement = Craft::$app->getElements()->getElementById($redirectConfig['associatedElementId']);
1309
1310
        if (!$associatedElement) {
1311
            return;
1312
        }
1313
1314
        $fieldUpdated = $this->setShortLinkFieldValue($associatedElement, $existingData['redirectSrcUrl'], $redirectConfig['redirectSrcUrl']);
1315
1316
        if ($fieldUpdated) {
1317
            // Prevent element from triggering an infinite loop.
1318
            ShortLink::preventShortLinkUpdates();
1319
            Craft::$app->getElements()->saveElement($associatedElement);
1320
            ShortLink::allowShortLinkUpdates();
1321
        }
1322
    }
1323
1324
    /**
1325
     * Find all short link fields on an element that have a matching redirect source url and update the value
1326
     *
1327
     * @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...
1328
     * @param string $redirectSrcUrl
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 11 spaces after parameter type; 1 found
Loading history...
1329
     * @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...
1330
     * @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...
1331
     */
1332
    protected function setShortLinkFieldValue(ElementInterface $element, string $redirectSrcUrl, $newValue): bool
1333
    {
1334
        $layout = $element->getFieldLayout();
1335
        $srcUrl = $redirectSrcUrl;
1336
        $site = $element->getSite();
1337
        $urlLess = StringHelper::removeLeft($srcUrl, $site->getBaseUrl());
0 ignored issues
show
Bug introduced by
It seems like $site->getBaseUrl() can also be of type null; however, parameter $substring of craft\helpers\StringHelper::removeLeft() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1337
        $urlLess = StringHelper::removeLeft($srcUrl, /** @scrutinizer ignore-type */ $site->getBaseUrl());
Loading history...
1338
1339
        $matchFound = false;
1340
        foreach ($element->getFieldValues() as $fieldHandle => $fieldValue) {
1341
            if (!is_string($fieldValue)) {
1342
                continue;
1343
            }
1344
            // Compare field value with starting slashes dropped to the redirectSrcUrl value as well as one with site URL removed, just in case
1345
            if (in_array(ltrim($fieldValue, '/'), [ltrim($srcUrl, '/'), ltrim($urlLess, '/')], true)) {
1346
                $field = $layout->getFieldByHandle($fieldHandle);
1347
1348
                if ($field instanceof ShortLink) {
1349
                    $element->setFieldValue($fieldHandle, $newValue);
1350
                    $matchFound = true;
1351
                }
1352
            }
1353
        }
1354
1355
        return $matchFound;
1356
    }
1357
}
1358