Passed
Push — develop ( 4f7b5c...dc2d47 )
by Andrew
04:51
created

Redirects::deleteShortlinkById()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 32
ccs 0
cts 21
cp 0
rs 8.6666
cc 7
nc 11
nop 1
crap 56
1
<?php
2
/**
3
 * Retour plugin for Craft CMS 3.x
4
 *
5
 * Retour allows you to intelligently redirect legacy URLs, so that you don't
6
 * lose SEO value when rebuilding & restructuring a website
7
 *
8
 * @link      https://nystudio107.com/
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
9
 * @copyright Copyright (c) 2018 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
10
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
11
12
namespace nystudio107\retour\services;
13
14
use 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\SiteNotFoundException;
20
use craft\helpers\Db;
21
use craft\helpers\StringHelper;
22
use nystudio107\retour\events\RedirectEvent;
23
use nystudio107\retour\events\RedirectResolvedEvent;
24
use nystudio107\retour\events\ResolveRedirectEvent;
25
use nystudio107\retour\fields\ShortLink;
26
use nystudio107\retour\helpers\UrlHelper;
27
use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel;
28
use nystudio107\retour\Retour;
29
use yii\base\ExitException;
30
use yii\base\InvalidConfigException;
31
use yii\base\InvalidRouteException;
32
use yii\caching\TagDependency;
33
use yii\db\Exception;
34
35
/** @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...
36
37
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
38
 * @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...
39
 * @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...
40
 * @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...
41
 */
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...
42
class Redirects extends Component
43
{
44
    // Constants
45
    // =========================================================================
46
47
    const CACHE_KEY = 'retour_redirect_';
48
49
    const GLOBAL_REDIRECTS_CACHE_TAG = 'retour_redirects';
50
51
    const EVENT_REDIRECT_ID = 0;
52
53
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
54
     * @event RedirectEvent The event that is triggered before the redirect is saved
55
     * You may set [[RedirectEvent::isValid]] to `false` to prevent the redirect from getting saved.
56
     *
57
     * ```php
58
     * use nystudio107\retour\services\Redirects;
59
     * use nystudio107\retour\events\RedirectEvent;
60
     *
61
     * Event::on(Redirects::class,
62
     *     Redirects::EVENT_BEFORE_SAVE_REDIRECT,
63
     *     function(RedirectEvent $event) {
64
     *         // potentially set $event->isValid;
65
     *     }
66
     * );
67
     * ```
68
     */
69
    const EVENT_BEFORE_SAVE_REDIRECT = 'beforeSaveRedirect';
70
71
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
72
     * @event RedirectEvent The event that is triggered after the redirect is saved
73
     *
74
     * ```php
75
     * use nystudio107\retour\services\Redirects;
76
     * use nystudio107\retour\events\RedirectEvent;
77
     *
78
     * Event::on(Redirects::class,
79
     *     Redirects::EVENT_AFTER_SAVE_REDIRECT,
80
     *     function(RedirectEvent $event) {
81
     *         // the redirect was saved
82
     *     }
83
     * );
84
     * ```
85
     */
86
    const EVENT_AFTER_SAVE_REDIRECT = 'afterSaveRedirect';
87
88
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
89
     * @event ResolveRedirectEvent The event that is triggered before Retour has attempted
90
     *        to resolve redirects. You may set [[ResolveRedirectEvent::redirectDestUrl]] to
91
     *        to the URL that it should redirect to, or null if no redirect should happen
92
     *
93
     * ```php
94
     * use nystudio107\retour\services\Redirects;
95
     * use nystudio107\retour\events\ResolveRedirectEvent;
96
     *
97
     * Event::on(Redirects::class,
98
     *     Redirects::EVENT_AFTER_SAVE_REDIRECT,
99
     *     function(ResolveRedirectEvent $event) {
100
     *         // potentially set $event->redirectDestUrl;
101
     *     }
102
     * );
103
     * ```
104
     */
105
    const EVENT_BEFORE_RESOLVE_REDIRECT = 'beforeResolveRedirect';
106
107
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
108
     * @event ResolveRedirectEvent The event that is triggered after Retour has attempted
109
     *        to resolve redirects. You may set [[ResolveRedirectEvent::redirectDestUrl]] to
110
     *        to the URL that it should redirect to, or null if no redirect should happen
111
     *
112
     * ```php
113
     * use nystudio107\retour\services\Redirects;
114
     * use nystudio107\retour\events\ResolveRedirectEvent;
115
     *
116
     * Event::on(Redirects::class,
117
     *     Redirects::EVENT_AFTER_RESOLVE_REDIRECT,
118
     *     function(ResolveRedirectEvent $event) {
119
     *         // potentially set $event->redirectDestUrl;
120
     *     }
121
     * );
122
     * ```
123
     */
124
    const EVENT_AFTER_RESOLVE_REDIRECT = 'afterResolveRedirect';
125
126
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
127
     * @event RedirectResolvedEvent The event that is triggered once Retour has resolved
128
     *        a redirect. [[RedirectResolvedEvent::redirect]] will be set to the redirect
129
     *        that was resolved. You may set [[RedirectResolvedEvent::redirectDestUrl]] to
130
     *        to a different URL that it should redirect to, or leave it null if the
131
     *        redirect should happen as resolved.
132
     *
133
     * ```php
134
     * use nystudio107\retour\services\Redirects;
135
     * use nystudio107\retour\events\RedirectResolvedEvent;
136
     *
137
     * Event::on(Redirects::class,
138
     *     Redirects::EVENT_REDIRECT_RESOLVED,
139
     *     function(RedirectResolvedEvent $event) {
140
     *         // potentially set $event->redirectDestUrl;
141
     *     }
142
     * );
143
     * ```
144
     */
145
    const EVENT_REDIRECT_RESOLVED = 'redirectResolved';
146
147
    // Protected Properties
148
    // =========================================================================
149
150
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
151
     * @var null|array
152
     */
153
    protected $cachedStaticRedirects;
154
155
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
156
     * @var null|array
157
     */
158
    protected $cachedRegExRedirects;
159
160
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
161
     * @var null|array
162
     */
163
    protected $cachedExactMatchRedirects;
164
165
    // Public Methods
166
    // =========================================================================
167
168
    /**
169
     * Handle 404s by looking for redirects
170
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
171
    public function handle404()
172
    {
173
        Craft::info(
174
            Craft::t(
175
                'retour',
176
                'A 404 exception occurred'
177
            ),
178
            __METHOD__
179
        );
180
        $request = Craft::$app->getRequest();
181
        // We only want site requests that are not live preview or console requests
182
        if ($request->getIsSiteRequest() && !$this->isPreview($request) && !$request->getIsConsoleRequest()) {
183
            // See if we should redirect
184
            try {
185
                $fullUrl = urldecode($request->getAbsoluteUrl());
186
                $pathOnly = urldecode($request->getUrl());
187
            } catch (InvalidConfigException $e) {
188
                Craft::error(
189
                    $e->getMessage(),
190
                    __METHOD__
191
                );
192
                $pathOnly = '';
193
                $fullUrl = '';
194
            }
195
            // Strip the query string if `alwaysStripQueryString` is set
196
            if (Retour::$settings->alwaysStripQueryString) {
197
                $fullUrl = UrlHelper::stripQueryString($fullUrl);
198
                $pathOnly = UrlHelper::stripQueryString($pathOnly);
199
            }
200
            // Stash the $pathOnly for use when incrementing the statistics
201
            $originalPathOnly = $pathOnly;
202
            Craft::info(
203
                Craft::t(
204
                    'retour',
205
                    '404 full URL: {fullUrl}, 404 path only: {pathOnly}',
206
                    ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
207
                ),
208
                __METHOD__
209
            );
210
            if (!$this->excludeUri($pathOnly)) {
211
                // Redirect if we find a match, otherwise let Craft handle it
212
                $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
213
                if (!$this->doRedirect($fullUrl, $pathOnly, $redirect) && !Retour::$settings->alwaysStripQueryString) {
214
                    // Try it again without the query string
215
                    $fullUrl = UrlHelper::stripQueryString($fullUrl);
216
                    $pathOnly = UrlHelper::stripQueryString($pathOnly);
217
                    $redirect = $this->findRedirectMatch($fullUrl, $pathOnly);
218
                    $this->doRedirect($fullUrl, $pathOnly, $redirect);
219
                }
220
                // Increment the stats
221
                Retour::$plugin->statistics->incrementStatistics($originalPathOnly, false);
222
            }
223
        }
224
    }
225
226
    /**
227
     * Do the redirect
228
     *
229
     * @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...
230
     * @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...
231
     * @param null|array $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
232
     *
233
     * @return bool false if not redirected
234
     */
235
    public function doRedirect(string $fullUrl, string $pathOnly, $redirect): bool
236
    {
237
        $response = Craft::$app->getResponse();
238
        if ($redirect !== null) {
239
            // Figure out what type of source matching was done
240
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
241
            switch ($redirectSrcMatch) {
242
                case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
243
                    $url = $pathOnly;
244
                    break;
245
                case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
246
                    $url = $fullUrl;
247
                    break;
248
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
249
                    $url = $pathOnly;
250
                    break;
251
            }
252
            $dest = $redirect['redirectDestUrl'];
253
            // If this isn't a full URL, make it one based on the appropriate site
254
            if (!UrlHelper::isFullUrl($dest)) {
255
                try {
256
                    $siteId = $redirect['siteId'] ?? null;
257
                    if ($siteId !== null) {
258
                        $siteId = (int)$siteId;
259
                    }
260
                    $dest = UrlHelper::siteUrl($dest, null, null, $siteId);
261
                } 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...
262
                }
263
            }
264
            if (Retour::$settings->preserveQueryString) {
265
                $request = Craft::$app->getRequest();
266
                $queryString = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $queryString is dead and can be removed.
Loading history...
267
                try {
268
                    $queryString = UrlHelper::combineQueryStringsFromUrls($dest, $request->getUrl());
269
                } catch (InvalidConfigException $e) {
270
                    // That's ok
271
                }
272
                if (!empty($queryString)) {
273
                    $dest = strtok($dest, '?') . '?' . $queryString;
274
                }
275
            }
276
            $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
277
            // Parse reference tags for exact matches
278
            if ($redirectMatchType === 'exactmatch') {
279
                $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

279
                /** @scrutinizer ignore-call */ 
280
                $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...
280
            }
281
            $status = $redirect['redirectHttpCode'];
282
            Craft::info(
283
                Craft::t(
284
                    'retour',
285
                    'Redirecting {url} to {dest} with status {status}',
286
                    ['url' => $url, 'dest' => $dest, 'status' => $status]
287
                ),
288
                __METHOD__
289
            );
290
            // Increment the stats
291
            Retour::$plugin->statistics->incrementStatistics($url, true);
292
            // Handle a Retour return status > 400 to render the actual error template
293
            if ($status >= 400) {
294
                Retour::$currentException->statusCode = $status;
295
                $errorHandler = Craft::$app->getErrorHandler();
296
                $errorHandler->exception = Retour::$currentException;
297
                try {
298
                    $response = Craft::$app->runAction('templates/render-error');
299
                } catch (InvalidRouteException $e) {
300
                    Craft::error($e->getMessage(), __METHOD__);
301
                } catch (\yii\console\Exception $e) {
302
                    Craft::error($e->getMessage(), __METHOD__);
303
                }
304
            }
305
            // Sanitize the URL
306
            $dest = UrlHelper::sanitizeUrl($dest);
307
            // Add any additional headers (existing ones will be replaced)
308
            if (!empty(Retour::$settings->additionalHeaders)) {
309
                foreach (Retour::$settings->additionalHeaders as $additionalHeader) {
310
                    $response->headers->set($additionalHeader['name'], $additionalHeader['value']);
311
                }
312
            }
313
            // Redirect the request away;
314
            $response->redirect($dest, $status)->send();
315
            try {
316
                Craft::$app->end();
317
            } catch (ExitException $e) {
318
                Craft::error($e->getMessage(), __METHOD__);
319
            }
320
        }
321
322
        return false;
323
    }
324
325
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
326
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
327
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
328
     * @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...
329
     *
330
     * @return array|null
331
     */
332
    public function findRedirectMatch(string $fullUrl, string $pathOnly, $siteId = null)
333
    {
334
        // Get the current site
335
        if ($siteId === null) {
0 ignored issues
show
introduced by
The condition $siteId === null is always true.
Loading history...
336
            $currentSite = Craft::$app->getSites()->currentSite;
337
            if ($currentSite) {
338
                $siteId = $currentSite->id;
339
            } else {
340
                $primarySite = Craft::$app->getSites()->primarySite;
341
                if ($currentSite) {
342
                    $siteId = $primarySite->id;
343
                }
344
            }
345
        }
346
        // Try getting the full URL redirect from the cache
347
        $redirect = $this->getRedirectFromCache($fullUrl, $siteId);
348
        if ($redirect) {
349
            $this->incrementRedirectHitCount($redirect);
350
            $this->saveRedirectToCache($fullUrl, $redirect);
351
352
            return $redirect;
353
        }
354
        // Try getting the path only redirect from the cache
355
        $redirect = $this->getRedirectFromCache($pathOnly, $siteId);
356
        if ($redirect) {
357
            $this->incrementRedirectHitCount($redirect);
358
            $this->saveRedirectToCache($pathOnly, $redirect);
359
360
            return $redirect;
361
        }
362
363
        $redirect = $this->getStaticRedirect($fullUrl, $pathOnly, $siteId);
364
        if ($redirect) {
365
            $this->incrementRedirectHitCount($redirect);
366
            $this->saveRedirectToCache($pathOnly, $redirect);
367
368
            return $redirect;
369
        }
370
371
        // Resolve static redirects
372
        $redirects = $this->getAllRegExRedirects(null, $siteId);
373
        $redirect = $this->resolveRedirect($fullUrl, $pathOnly, $redirects, $siteId);
374
        if ($redirect) {
375
            return $redirect;
376
        }
377
378
        return null;
379
    }
380
381
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
382
     * @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...
383
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
384
     *
385
     * @return bool|array
386
     */
387
    public function getRedirectFromCache($url, int $siteId = 0)
388
    {
389
        $cache = Craft::$app->getCache();
390
        $cacheKey = $this::CACHE_KEY . md5($url) . $siteId;
391
        $redirect = $cache->get($cacheKey);
392
        Craft::info(
393
            Craft::t(
394
                'retour',
395
                'Cached redirect hit for {url}',
396
                ['url' => $url]
397
            ),
398
            __METHOD__
399
        );
400
401
        return $redirect;
402
    }
403
404
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
405
     * @param string $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
406
     * @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...
407
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
408
    public function saveRedirectToCache($url, $redirect)
409
    {
410
        $cache = Craft::$app->getCache();
411
        // Get the current site id
412
        $sites = Craft::$app->getSites();
413
        try {
414
            $siteId = $sites->getCurrentSite()->id;
415
        } catch (SiteNotFoundException $e) {
416
            $siteId = 1;
417
        }
418
        $cacheKey = $this::CACHE_KEY . md5($url) . $siteId;
419
        // Create the dependency tags
420
        $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...
421
            'tags' => [
422
                $this::GLOBAL_REDIRECTS_CACHE_TAG,
423
                $this::GLOBAL_REDIRECTS_CACHE_TAG . $siteId,
424
            ],
425
        ]);
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...
426
        $cache->set($cacheKey, $redirect, Retour::$cacheDuration, $dependency);
427
        Craft::info(
428
            Craft::t(
429
                'retour',
430
                'Cached redirect saved for {url}',
431
                ['url' => $url]
432
            ),
433
            __METHOD__
434
        );
435
    }
436
437
    /**
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...
438
     * @param string $fullUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
439
     * @param string $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
440
     * @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...
441
     *
442
     * @return array|null
443
     */
444
    public function resolveRedirect(string $fullUrl, string $pathOnly, array $redirects, $siteId)
445
    {
446
        $result = null;
447
        // Throw the Redirects::EVENT_BEFORE_RESOLVE_REDIRECT event
448
        $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...
449
            'fullUrl' => $fullUrl,
450
            'pathOnly' => $pathOnly,
451
            'redirectDestUrl' => null,
452
            'redirectHttpCode' => 301,
453
            'siteId' => $siteId,
454
        ]);
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...
455
        $this->trigger(self::EVENT_BEFORE_RESOLVE_REDIRECT, $event);
456
        if ($event->redirectDestUrl !== null) {
457
            return $this->resolveEventRedirect($event);
458
        }
459
        // Iterate through the redirects
460
        foreach ($redirects as $redirect) {
461
            // Figure out what type of source matching to do
462
            $redirectSrcMatch = $redirect['redirectSrcMatch'] ?? 'pathonly';
463
            $redirectEnabled = (bool)$redirect['enabled'];
464
            if ($redirectEnabled === true) {
465
                switch ($redirectSrcMatch) {
466
                    case 'pathonly':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
467
                        $url = $pathOnly;
468
                        break;
469
                    case 'fullurl':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
470
                        $url = $fullUrl;
471
                        break;
472
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
473
                        $url = $pathOnly;
474
                        break;
475
                }
476
                $redirectMatchType = $redirect['redirectMatchType'] ?? 'notfound';
477
                switch ($redirectMatchType) {
478
                    // Do a straight up match
479
                    case 'exactmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
480
                        if (strcasecmp($redirect['redirectSrcUrlParsed'], $url) === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
481
                            $this->incrementRedirectHitCount($redirect);
482
                            $this->saveRedirectToCache($url, $redirect);
483
484
                            // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
485
                            $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...
486
                                'fullUrl' => $fullUrl,
487
                                'pathOnly' => $pathOnly,
488
                                'redirectDestUrl' => null,
489
                                'redirectHttpCode' => 301,
490
                                'redirect' => $redirect,
491
                                'siteId' => $siteId,
492
                            ]);
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...
493
                            $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
494
                            if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
495
                                return $this->resolveEventRedirect($event, $url, $redirect);
496
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 28
Loading history...
497
498
                            return $redirect;
499
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
500
                        break;
501
502
                    // Do a regex match
503
                    case 'regexmatch':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
504
                        $matchRegEx = '`' . $redirect['redirectSrcUrlParsed'] . '`i';
505
                        try {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
506
                            if (preg_match($matchRegEx, $url) === 1) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
507
                                $this->incrementRedirectHitCount($redirect);
508
                                // If we're not associated with an EntryID, handle capture group replacement
509
                                if ((int)$redirect['associatedElementId'] === 0) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
510
                                    $redirect['redirectDestUrl'] = preg_replace(
511
                                        $matchRegEx,
512
                                        $redirect['redirectDestUrl'],
513
                                        $url
514
                                    );
515
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
516
                                $url = preg_replace('/([^:])(\/{2,})/', '$1/', $url);
517
                                $this->saveRedirectToCache($url, $redirect);
518
519
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
520
                                $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...
521
                                    'fullUrl' => $fullUrl,
522
                                    'pathOnly' => $pathOnly,
523
                                    'redirectDestUrl' => null,
524
                                    'redirectHttpCode' => 301,
525
                                    'redirect' => $redirect,
526
                                    'siteId' => $siteId,
527
                                ]);
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...
528
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
529
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
530
                                    return $this->resolveEventRedirect($event, $url, $redirect);
531
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
532
533
                                return $redirect;
534
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
535
                        } catch (\Exception $e) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
536
                            // That's fine
537
                            Craft::error('Invalid Redirect Regex: ' . $matchRegEx, __METHOD__);
538
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
539
540
                        break;
541
542
                    // Otherwise try to look up a plugin's method by and call it for the match
543
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
544
                        $plugin = $redirectMatchType ? Craft::$app->getPlugins()->getPlugin($redirectMatchType) : null;
545
                        if ($plugin && method_exists($plugin, 'retourMatch')) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
546
                            $args = [
547
                                [
548
                                    'redirect' => &$redirect,
549
                                ],
550
                            ];
551
                            $result = \call_user_func_array([$plugin, 'retourMatch'], $args);
552
                            if ($result) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
553
                                $this->incrementRedirectHitCount($redirect);
554
                                $this->saveRedirectToCache($url, $redirect);
555
556
                                // Throw the Redirects::EVENT_REDIRECT_RESOLVED event
557
                                $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...
558
                                    'fullUrl' => $fullUrl,
559
                                    'pathOnly' => $pathOnly,
560
                                    'redirectDestUrl' => null,
561
                                    'redirectHttpCode' => 301,
562
                                    'redirect' => $redirect,
563
                                    'siteId' => $siteId,
564
                                ]);
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...
565
                                $this->trigger(self::EVENT_REDIRECT_RESOLVED, $event);
566
                                if ($event->redirectDestUrl !== null) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
567
                                    return $this->resolveEventRedirect($event, $url, $redirect);
568
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 32
Loading history...
569
570
                                return $redirect;
571
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
572
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
573
                        break;
574
                }
575
            }
576
        }
577
        // Throw the Redirects::EVENT_AFTER_RESOLVE_REDIRECT event
578
        $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...
579
            'fullUrl' => $fullUrl,
580
            'pathOnly' => $pathOnly,
581
            'redirectDestUrl' => null,
582
            'redirectHttpCode' => 301,
583
            'siteId' => $siteId,
584
        ]);
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...
585
        $this->trigger(self::EVENT_AFTER_RESOLVE_REDIRECT, $event);
586
        if ($event->redirectDestUrl !== null) {
587
            return $this->resolveEventRedirect($event);
588
        }
589
        Craft::info(
590
            Craft::t(
591
                'retour',
592
                'Not handled-> full URL: {fullUrl}, path only: {pathOnly}',
593
                ['fullUrl' => $fullUrl, 'pathOnly' => $pathOnly]
594
            ),
595
            __METHOD__
596
        );
597
598
        return $result;
599
    }
600
601
    /**
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...
602
     * @param ResolveRedirectEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
603
     *
604
     * @return null|array
605
     */
606
    public function resolveEventRedirect(ResolveRedirectEvent $event, $url = null, $redirect = null)
607
    {
608
        $result = null;
609
610
        if ($event->redirectDestUrl !== null) {
611
            $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...
612
                'id' => self::EVENT_REDIRECT_ID,
613
                'redirectDestUrl' => $event->redirectDestUrl,
614
                'redirectHttpCode' => $event->redirectHttpCode,
615
            ]);
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...
616
            $result = $resolvedRedirect->toArray();
617
618
            if ($url !== null && $redirect !== null) {
619
                // Save the modified redirect to the cache
620
                $redirect['redirectDestUrl'] = $event->redirectDestUrl;
621
                $redirect['redirectHttpCode'] = $event->redirectHttpCode;
622
                $this->saveRedirectToCache($url, $redirect);
623
            }
624
        }
625
626
        return $result;
627
    }
628
629
    /**
630
     * Returns the list of matching schemes
631
     *
632
     * @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...
633
     */
634
    public function getMatchesList(): array
635
    {
636
        $result = [
637
            'exactmatch' => Craft::t('retour', 'Exact Match'),
638
            'regexmatch' => Craft::t('retour', 'RegEx Match'),
639
        ];
640
641
        // Add any plugins that offer the retourMatch() method
642
        foreach (Craft::$app->getPlugins()->getAllPlugins() as $plugin) {
643
            /** @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...
644
            if (method_exists($plugin, 'retourMatch')) {
645
                $result[$plugin->getHandle()] = $plugin->name . Craft::t('retour', ' Match');
646
            }
647
        }
648
649
        return $result;
650
    }
651
652
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
653
     * @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...
654
     * @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...
655
     * @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...
656
     * @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...
657
     */
658
    public function getStaticRedirect(string $fullUrl, string $pathOnly, $siteId)
659
    {
660
        $staticCondition = ['redirectMatchType' => 'exactmatch'];
661
        $siteCondition = [
662
            'or',
663
            ['siteId' => $siteId],
664
            ['siteId' => null]
665
        ];
666
        $pathCondition = [
667
            'or',
668
            ['and',
669
                ['redirectSrcMatch' => 'pathonly'],
670
                ['redirectSrcUrlParsed' => $pathOnly]
671
            ],
672
            ['and',
673
                ['redirectSrcMatch' => 'fullurl'],
674
                ['redirectSrcUrlParsed' => $fullUrl]
675
            ],
676
        ];
677
678
        $query = (new Query)
679
            ->from('{{%retour_static_redirects}}')
680
            ->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...
681
                $staticCondition,
682
                $pathCondition,
683
                $siteCondition
684
            ])
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...
685
            ->limit(1);
686
687
        return $query->one();
688
    }
689
690
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
691
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
692
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
693
     *
694
     * @return array All of the regex match redirects
695
     */
696
    public function getAllRegExRedirects(int $limit = null, int $siteId = null): array
697
    {
698
        // Cache it in our class; no need to fetch it more than once
699
        if ($this->cachedRegExRedirects !== null) {
700
            return $this->cachedRegExRedirects;
701
        }
702
703
        $redirects = $this->getRedirectsByMatchType($limit, $siteId, 'regexmatch');
704
705
        // Cache for future accesses
706
        $this->cachedRegExRedirects = $redirects;
707
708
        return $redirects;
709
    }
710
711
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
712
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
713
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
714
     *
715
     * @return array All of the regex match redirects
716
     */
717
    public function getAllExactMatchRedirects(int $limit = null, int $siteId = null): array
718
    {
719
        // Cache it in our class; no need to fetch it more than once
720
        if ($this->cachedExactMatchRedirects !== null) {
721
            return $this->cachedExactMatchRedirects;
722
        }
723
724
        $redirects = $this->getRedirectsByMatchType($limit, $siteId, 'exactmatch');
725
726
        // Cache for future accesses
727
        $this->cachedExactMatchRedirects = $redirects;
728
729
        return $redirects;
730
    }
731
732
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
733
     * @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...
734
     * @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...
735
     * @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...
736
     * @return array
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
737
     */
738
    protected function getRedirectsByMatchType(int $limit = null, int $siteId = null, string $type): array
739
    {
740
        // Query the db table
741
        $query = (new Query())
742
            ->from(['{{%retour_static_redirects}}'])
743
            ->orderBy('redirectMatchType ASC, redirectSrcMatch ASC, hitCount DESC');
744
745
        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...
746
            $query
747
                ->where(['siteId' => $siteId])
748
                ->orWhere(['siteId' => null]);
749
        }
750
751
        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...
752
            $query->limit($limit);
753
        }
754
755
        $query->andWhere(['redirectMatchType' => $type]);
756
757
        return $query->all();
758
    }
759
760
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
761
     * @param null|int $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
762
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
763
     *
764
     * @return array All of the statistics
765
     */
766
    public function getAllStaticRedirects($limit = null, int $siteId = null): array
767
    {
768
        // Cache it in our class; no need to fetch it more than once
769
        if ($this->cachedStaticRedirects !== null) {
770
            return $this->cachedStaticRedirects;
771
        }
772
        // Query the db table
773
        $query = (new Query())
774
            ->from(['{{%retour_static_redirects}}'])
775
            ->orderBy('redirectMatchType ASC, redirectSrcMatch ASC, hitCount DESC');
776
        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...
777
            $query
778
                ->where(['siteId' => $siteId])
779
                ->orWhere(['siteId' => null]);
780
        }
781
        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...
782
            $query->limit($limit);
783
        }
784
        $redirects = $query->all();
785
        // Cache for future accesses
786
        $this->cachedStaticRedirects = $redirects;
787
788
        return $redirects;
789
    }
790
791
    /**
792
     * Return a redirect by id
793
     *
794
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
795
     *
796
     * @return null|array The static redirect
797
     */
798
    public function getRedirectById(int $id)
799
    {
800
        // Query the db table
801
        $redirect = (new Query())
802
            ->from(['{{%retour_static_redirects}}'])
803
            ->where(['id' => $id])
804
            ->one();
805
806
        return $redirect;
807
    }
808
809
    /**
810
     * Return a redirect by redirectSrcUrl
811
     *
812
     * @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...
813
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
814
     *
815
     * @return null|array
816
     */
817
    public function getRedirectByRedirectSrcUrl(string $redirectSrcUrl, int $siteId = null)
818
    {
819
        // Query the db table
820
        $query = (new Query())
821
            ->from(['{{%retour_static_redirects}}'])
822
            ->where(['redirectSrcUrl' => $redirectSrcUrl]);
823
        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...
824
            $query
825
                ->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...
826
                    'siteId' => $siteId,
827
                ], [
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...
828
                    'siteId' => null,
829
                ]]);
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...
830
        }
831
        $redirect = $query->one();
832
833
        return $redirect;
834
    }
835
836
    /**
837
     * Return redirects for a given element.
838
     *
839
     * @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...
840
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
841
     *
842
     * @return null|array
843
     */
844
    public function getRedirectsByElementId(int $elementId, int $siteId = null)
845
    {
846
        // Query the db table
847
        $query = (new Query())
848
            ->from(['{{%retour_static_redirects}}'])
849
            ->where(['associatedElementId' => $elementId]);
850
        if ($siteId !== null) {
851
            $query->andWhere(['siteId' => $siteId]);
852
        }
853
854
        return $query->all();
855
    }
856
857
    /**
858
     * Delete a redirect by id
859
     *
860
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
861
     *
862
     * @return int The result
863
     */
864
    public function deleteRedirectById(int $id): int
865
    {
866
        $db = Craft::$app->getDb();
867
        // Delete a row from the db table
868
        try {
869
            $result = $db->createCommand()->delete(
870
                '{{%retour_static_redirects}}',
871
                [
872
                    'id' => $id,
873
                ]
874
            )->execute();
875
        } catch (Exception $e) {
876
            Craft::error($e->getMessage(), __METHOD__);
877
            $result = 0;
878
        }
879
880
        return $result;
881
    }
882
883
    /**
884
     * Increment the retour_static_redirects record
885
     *
886
     * @param $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
887
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
888
    public function incrementRedirectHitCount(&$redirectConfig)
889
    {
890
        if ($redirectConfig !== null) {
891
            $db = Craft::$app->getDb();
892
            $redirectConfig['hitCount']++;
893
            $redirectConfig['hitLastTime'] = Db::prepareDateForDb(new \DateTime());
894
            Craft::debug(
895
                Craft::t(
896
                    'retour',
897
                    'Incrementing statistics for: {redirect}',
898
                    ['redirect' => print_r($redirectConfig, true)]
899
                ),
900
                __METHOD__
901
            );
902
            // Update the existing record
903
            try {
904
                $rowsAffected = $db->createCommand()->update(
905
                    '{{%retour_static_redirects}}',
906
                    [
907
                        'hitCount' => $redirectConfig['hitCount'],
908
                        'hitLastTime' => $redirectConfig['hitLastTime'],
909
                    ],
910
                    [
911
                        'id' => $redirectConfig['id'],
912
                    ]
913
                )->execute();
914
                Craft::debug('Rows affected: ' . $rowsAffected, __METHOD__);
915
            } catch (\Exception $e) {
916
                Craft::error($e->getMessage(), __METHOD__);
917
            }
918
        }
919
    }
920
921
    /**
922
     * Save an element redirect.
923
     *
924
     * @param ElementInterface $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
925
     * @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...
926
     * @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...
927
     * @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...
928
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
929
    public function enableElementRedirect(ElementInterface $element, string $sourceUrl, string $redirectSrcMatch = 'pathonly', int $redirectHttpCode = 301)
930
    {
931
        $redirectConfig = [
932
            'redirectMatchType' => 'exactmatch',
933
            'redirectSrcUrl' => $sourceUrl,
934
            'siteId' => $redirectSrcMatch === 'pathonly' ? null : $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...
935
            'associatedElementId' => $element->getCanonicalId(),
936
            'enabled' => true,
937
            'redirectSrcMatch' => $redirectSrcMatch,
938
            'redirectDestUrl' => $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...
939
            'redirectHttpCode' => $redirectHttpCode,
940
        ];
941
942
        $this->saveRedirect($redirectConfig);
943
    }
944
945
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $allSites should have a doc-comment as per coding-style.
Loading history...
946
     * Delete an element redirect.
947
     *
948
     * @param ElementInterface $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
949
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
950
    public function removeElementRedirect(ElementInterface $element, bool $allSites = false)
951
    {
952
        $redirects = $this->getRedirectsByElementId($element->getCanonicalId(), $allSites ? null : $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...
953
954
        if (!empty($redirects)) {
955
            foreach ($redirects as $redirect) {
956
                $this->deleteRedirectById($redirect['id']);
957
            }
958
        }
959
    }
960
961
    /**
962
     * Delete a short link by its ID.
963
     *
964
     * @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...
965
     * @throws \Throwable
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
966
     * @throws \craft\errors\ElementNotFoundException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
967
     * @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...
968
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
969
    public function deleteShortlinkById(int $redirectId)
970
    {
971
        $redirect = $this->getRedirectById($redirectId);
972
        $elementId = $redirect['associatedElementId'];
973
        $siteId = $redirect['siteId'];
974
        $element = Craft::$app->getElements()->getElementById($elementId, null, $siteId);
975
976
        if ($element) {
977
            $layout = $element->getFieldLayout();
978
            $srcUrl = $redirect['redirectSrcUrl'];
979
            $site = $element->getSite();
980
            $urlLess = StringHelper::removeLeft($srcUrl, $site->getBaseUrl());
981
982
            $match = false;
983
            foreach ($element->getFieldValues() as $fieldHandle => $fieldValue) {
984
                if (!is_string($fieldValue)) {
985
                    continue;
986
                }
987
                // Compare field value with starting slashes dropped to the redirectSrcUrl value as well as one with site URL removed, just in case
988
                if (in_array(ltrim($fieldValue, '/'), [ltrim($srcUrl, '/'), ltrim($urlLess, '/')], true)) {
989
                   $field = $layout->getFieldByHandle($fieldHandle);
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 20 spaces, found 19
Loading history...
990
991
                   if ($field instanceof ShortLink) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 19
Loading history...
992
                       $element->setFieldValue($fieldHandle, null);
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 24 spaces, found 23
Loading history...
993
                       $match = true;
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 24 spaces, found 23
Loading history...
994
                   }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 19
Loading history...
995
                }
996
            }
997
998
            if ($match) {
999
                // This will also delete the redirect from the table
1000
                Craft::$app->getElements()->saveElement($element);
1001
            }
1002
        }
1003
    }
1004
1005
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
1006
     * @param array $redirectConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1007
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1008
    public function saveRedirect(array $redirectConfig)
1009
    {
1010
        // Handle URL encoded URLs by decoding them before saving them
1011
        if (isset($redirectConfig['redirectMatchType']) && $redirectConfig['redirectMatchType'] === 'exactmatch') {
1012
            $redirectConfig['redirectSrcUrl'] = urldecode($redirectConfig['redirectSrcUrl'] ?? '');
1013
            $redirectConfig['redirectSrcUrlParsed'] = urldecode($redirectConfig['redirectSrcUrlParsed'] ?? '');
1014
        }
1015
        // Validate the model before saving it to the db
1016
        $redirect = new StaticRedirectsModel($redirectConfig);
1017
        if ($redirect->validate() === false) {
1018
            Craft::error(
1019
                Craft::t(
1020
                    'retour',
1021
                    'Error validating redirect {id}: {errors}',
1022
                    ['id' => $redirect->id, 'errors' => print_r($redirect->getErrors(), true)]
1023
                ),
1024
                __METHOD__
1025
            );
1026
1027
            return;
1028
        }
1029
        // Get the validated model attributes and save them to the db
1030
        $redirectConfig = $redirect->getAttributes();
1031
        // 0 for a siteId needs to be converted to null
1032
        if (empty($redirectConfig['siteId']) || (int)$redirectConfig['siteId'] === 0) {
1033
            $redirectConfig['siteId'] = null;
1034
        }
1035
        // Throw an event to before saving the redirect
1036
        $db = Craft::$app->getDb();
1037
        // See if a redirect exists with this source URL already
1038
        if ((int)$redirectConfig['id'] === 0) {
1039
            // Query the db table
1040
            $redirect = (new Query())
1041
                ->from(['{{%retour_static_redirects}}'])
1042
                ->where(['redirectSrcUrlParsed' => $redirectConfig['redirectSrcUrlParsed']])
1043
                ->andWhere(['siteId' => $redirectConfig['siteId']])
1044
                ->one();
1045
            // If it exists, update it rather than having duplicates
1046
            if (!empty($redirect)) {
1047
                $redirectConfig['id'] = $redirect['id'];
1048
            }
1049
        }
1050
        // Trigger a 'beforeSaveRedirect' event
1051
        $isNew = (int)$redirectConfig['id'] === 0;
1052
        $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...
1053
            'isNew' => $isNew,
1054
            'legacyUrl' => $redirectConfig['redirectSrcUrlParsed'],
1055
            'destinationUrl' => $redirectConfig['redirectDestUrl'],
1056
            'matchType' => $redirectConfig['redirectSrcMatch'],
1057
            'redirectType' => $redirectConfig['redirectHttpCode'],
1058
            'siteId' => $redirectConfig['siteId'],
1059
        ]);
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...
1060
        $this->trigger(self::EVENT_BEFORE_SAVE_REDIRECT, $event);
1061
        if (!$event->isValid) {
1062
            return;
1063
        }
1064
        // See if this is an existing redirect
1065
        if (!$isNew) {
1066
            Craft::debug(
1067
                Craft::t(
1068
                    'retour',
1069
                    'Updating existing redirect: {redirect}',
1070
                    ['redirect' => print_r($redirectConfig, true)]
1071
                ),
1072
                __METHOD__
1073
            );
1074
            // Update the existing record
1075
            try {
1076
                $db->createCommand()->update(
1077
                    '{{%retour_static_redirects}}',
1078
                    $redirectConfig,
1079
                    [
1080
                        'id' => $redirectConfig['id'],
1081
                    ]
1082
                )->execute();
1083
            } catch (Exception $e) {
1084
                Craft::error($e->getMessage(), __METHOD__);
1085
            }
1086
        } else {
1087
            Craft::debug(
1088
                Craft::t(
1089
                    'retour',
1090
                    'Creating new redirect: {redirect}',
1091
                    ['redirect' => print_r($redirectConfig, true)]
1092
                ),
1093
                __METHOD__
1094
            );
1095
            unset($redirectConfig['id']);
1096
            // Create a new record
1097
            try {
1098
                $db->createCommand()->insert(
1099
                    '{{%retour_static_redirects}}',
1100
                    $redirectConfig
1101
                )->execute();
1102
            } catch (Exception $e) {
1103
                Craft::error($e->getMessage(), __METHOD__);
1104
            }
1105
        }
1106
        // To prevent redirect loops, see if any static redirects have our redirectDestUrl as their redirectSrcUrl
1107
        $testRedirectConfig = $this->getRedirectByRedirectSrcUrl(
1108
            $redirectConfig['redirectDestUrl'],
1109
            $redirectConfig['siteId']
1110
        );
1111
        if ($testRedirectConfig !== null) {
1112
            Craft::debug(
1113
                Craft::t(
1114
                    'retour',
1115
                    'Deleting redirect to prevent a loop: {redirect}',
1116
                    ['redirect' => print_r($testRedirectConfig, true)]
1117
                ),
1118
                __METHOD__
1119
            );
1120
            // Delete the redirect that has a redirectSrcUrl the same as this record's redirectDestUrl
1121
            try {
1122
                $db->createCommand()->delete(
1123
                    '{{%retour_static_redirects}}',
1124
                    ['id' => $testRedirectConfig['id']]
1125
                )->execute();
1126
            } catch (Exception $e) {
1127
                Craft::error($e->getMessage(), __METHOD__);
1128
            }
1129
        }
1130
        // Trigger a 'afterSaveRedirect' event
1131
        $this->trigger(self::EVENT_AFTER_SAVE_REDIRECT, $event);
1132
1133
        // Invalidate caches after saving a redirect
1134
        $this->invalidateCaches();
1135
    }
1136
1137
    /**
1138
     * Invalidate all of the redirects caches
1139
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1140
    public function invalidateCaches()
1141
    {
1142
        $cache = Craft::$app->getCache();
1143
        TagDependency::invalidate($cache, $this::GLOBAL_REDIRECTS_CACHE_TAG);
1144
        // If they are using Craft 3.3 or later, clear the GraphQL caches too
1145
        if (Retour::$craft33) {
1146
            $gql = Craft::$app->getGql();
1147
            if (method_exists($gql, 'invalidateCaches')) {
1148
                $gql->invalidateCaches();
1149
            }
1150
        }
1151
        Craft::info(
1152
            Craft::t(
1153
                'retour',
1154
                'All redirect caches cleared'
1155
            ),
1156
            __METHOD__
1157
        );
1158
    }
1159
1160
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
1161
     * @param $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1162
     *
1163
     * @return bool
1164
     */
1165
    public function excludeUri($uri): bool
1166
    {
1167
        $uri = '/' . ltrim($uri, '/');
1168
        if (!empty(Retour::$settings->excludePatterns)) {
1169
            foreach (Retour::$settings->excludePatterns as $excludePattern) {
1170
                $pattern = '`' . $excludePattern['pattern'] . '`i';
1171
                try {
1172
                    if (preg_match($pattern, $uri) === 1) {
1173
                        return true;
1174
                    }
1175
                } catch (\Exception $e) {
1176
                    // That's fine
1177
                    Craft::error('Invalid exclude URI Regex: ' . $pattern, __METHOD__);
1178
                }
1179
            }
1180
        }
1181
1182
        return false;
1183
    }
1184
1185
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $request should have a doc-comment as per coding-style.
Loading history...
1186
     * Return whether this is a preview request of any kind
1187
     *
1188
     * @return bool
1189
     */
1190
    public function isPreview($request): bool
1191
    {
1192
        $isPreview = false;
1193
        if (Retour::$craft32) {
1194
            $isPreview = $request->getIsPreview();
1195
        }
1196
        $isLivePreview = $request->getIsLivePreview();
1197
1198
        return ($isPreview || $isLivePreview);
1199
    }
1200
}
1201