Issues (1834)

src/Retour.php (2 issues)

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/
9
 * @copyright Copyright (c) 2018 nystudio107
10
 */
11
12
namespace nystudio107\retour;
13
14
use Craft;
15
use craft\base\Element;
16
use craft\base\Plugin;
17
use craft\events\ElementEvent;
18
use craft\events\ExceptionEvent;
19
use craft\events\PluginEvent;
20
use craft\events\RegisterCacheOptionsEvent;
21
use craft\events\RegisterComponentTypesEvent;
22
use craft\events\RegisterGqlQueriesEvent;
23
use craft\events\RegisterGqlSchemaComponentsEvent;
24
use craft\events\RegisterGqlTypesEvent;
25
use craft\events\RegisterUrlRulesEvent;
26
use craft\events\RegisterUserPermissionsEvent;
27
use craft\helpers\ElementHelper;
28
use craft\helpers\UrlHelper;
29
use craft\services\Dashboard;
30
use craft\services\Elements;
31
use craft\services\Fields;
32
use craft\services\Gql;
33
use craft\services\Plugins;
34
use craft\services\UserPermissions;
35
use craft\utilities\ClearCaches;
36
use craft\web\ErrorHandler;
37
use craft\web\twig\variables\CraftVariable;
38
use craft\web\UrlManager;
39
use craft\web\User;
40
use markhuot\CraftQL\Builders\Schema;
41
use markhuot\CraftQL\CraftQL;
42
use markhuot\CraftQL\Events\AlterSchemaFields;
43
use nystudio107\retour\fields\ShortLink as ShortLinkField;
44
use nystudio107\retour\gql\interfaces\RetourInterface;
45
use nystudio107\retour\gql\queries\RetourQuery;
46
use nystudio107\retour\listeners\GetCraftQLSchema;
47
use nystudio107\retour\models\Settings;
48
use nystudio107\retour\services\ServicesTrait;
49
use nystudio107\retour\variables\RetourVariable;
50
use nystudio107\retour\widgets\RetourWidget;
51
use Twig\Error\RuntimeError;
52
use yii\base\Event;
53
use yii\web\HttpException;
54
55
/** @noinspection MissingPropertyAnnotationsInspection */
56
57
/**
58
 * Class Retour
59
 *
60
 * @author    nystudio107
61
 * @package   Retour
62
 * @since     3.0.0
63
 * @method Settings getSettings()
64
 */
65
class Retour extends Plugin
66
{
67
    // Traits
68
    // =========================================================================
69
70
    use ServicesTrait;
71
72
    // Constants
73
    // =========================================================================
74
75
    const DEVMODE_CACHE_DURATION = 30;
76
77
    // Static Properties
78
    // =========================================================================
79
80
    /**
81
     * @var Retour
82
     */
83
    public static $plugin;
84
85
    /**
86
     * @var Settings
87
     */
88
    public static $settings;
89
90
    /**
91
     * @var int
92
     */
93
    public static $cacheDuration;
94
95
    /**
96
     * @var HttpException
97
     */
98
    public static $currentException;
99
100
    /**
101
     * @var bool
102
     */
103
    public static $craft31 = false;
104
105
    /**
106
     * @var bool
107
     */
108
    public static $craft32 = false;
109
110
    /**
111
     * @var bool
112
     */
113
    public static $craft33 = false;
114
115
    /**
116
     * @var bool
117
     */
118
    public static $craft35 = false;
119
120
    // Public Properties
121
    // =========================================================================
122
123
    /**
124
     * @var string
125
     */
126
    public $schemaVersion = '3.0.12';
127
128
    /**
129
     * @var bool
130
     */
131
    public $hasCpSection = true;
132
133
    /**
134
     * @var bool
135
     */
136
    public $hasCpSettings = true;
137
138
    // Public Methods
139
    // =========================================================================
140
141
    /**
142
     * @inheritdoc
143
     */
144
    public function init()
145
    {
146
        parent::init();
147
        self::$plugin = $this;
148
        // Initialize properties
149
        self::$settings = $this->getSettings();
150
        self::$craft31 = version_compare(Craft::$app->getVersion(), '3.1', '>=');
151
        self::$craft32 = version_compare(Craft::$app->getVersion(), '3.2', '>=');
152
        self::$craft33 = version_compare(Craft::$app->getVersion(), '3.3', '>=');
153
        self::$craft35 = version_compare(Craft::$app->getVersion(), '3.5', '>=');
154
        $this->name = self::$settings->pluginName;
155
        self::$cacheDuration = Craft::$app->getConfig()->getGeneral()->devMode
156
            ? $this::DEVMODE_CACHE_DURATION
157
            : null;
158
        // Handle any console commands
159
        $request = Craft::$app->getRequest();
160
        if ($request->getIsConsoleRequest()) {
161
            $this->controllerNamespace = 'nystudio107\retour\console\controllers';
162
        }
163
        // Install our event listeners
164
        $this->installEventListeners();
165
        // Log that Retour has been loaded
166
        Craft::info(
167
            Craft::t(
168
                'retour',
169
                '{name} plugin loaded',
170
                ['name' => $this->name]
171
            ),
172
            __METHOD__
173
        );
174
    }
175
176
    /**
177
     * @inheritdoc
178
     */
179
    public function getSettingsResponse()
180
    {
181
        // Just redirect to the plugin settings page
182
        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('retour/settings'));
183
    }
184
185
    /**
186
     * @inheritdoc
187
     */
188
    public function getCpNavItem()
189
    {
190
        $subNavs = [];
191
        $navItem = parent::getCpNavItem();
192
        /** @var User $user */
193
        $user = Craft::$app->getUser();
194
        if ($currentUser = $user->getIdentity()) {
195
            // Only show sub-navs the user has permission to view
196
            if ($currentUser->can('retour:dashboard')) {
197
                $subNavs['dashboard'] = [
198
                    'label' => 'Dashboard',
199
                    'url' => 'retour/dashboard',
200
                ];
201
            }
202
            if ($currentUser->can('retour:redirects')) {
203
                $subNavs['redirects'] = [
204
                    'label' => 'Redirects',
205
                    'url' => 'retour/redirects',
206
                ];
207
            }
208
            if ($currentUser->can('retour:shortlinks')) {
209
                $subNavs['shortlinks'] = [
210
                    'label' => 'Short Links',
211
                    'url' => 'retour/shortlinks',
212
                ];
213
            }
214
            $editableSettings = true;
215
            $general = Craft::$app->getConfig()->getGeneral();
216
            if (self::$craft31 && !$general->allowAdminChanges) {
217
                $editableSettings = false;
218
            }
219
            if ($currentUser->can('retour:settings') && $editableSettings) {
220
                $subNavs['settings'] = [
221
                    'label' => 'Settings',
222
                    'url' => 'retour/settings',
223
                ];
224
            }
225
        }
226
        // Retour doesn't really have an index page, so if the user can't access any sub nav items, we probably shouldn't show the main sub nav item either
227
        if (empty($subNavs)) {
228
            return null;
229
        }
230
        // A single sub nav item is redundant
231
        if (count($subNavs) === 1) {
232
            $subNavs = [];
233
        }
234
        $navItem = array_merge($navItem, [
235
            'subnav' => $subNavs,
236
        ]);
237
238
        return $navItem;
239
    }
240
241
    /**
242
     * Clear all the caches!
243
     */
244
    public function clearAllCaches()
245
    {
246
        // Clear all of Retour's caches
247
        self::$plugin->redirects->invalidateCaches();
248
    }
249
250
    // Protected Methods
251
    // =========================================================================
252
253
    /**
254
     * Determine whether our table schema exists or not; this is needed because
255
     * migrations such as the install migration and base_install migration may
256
     * not have been run by the time our init() method has been called
257
     *
258
     * @return bool
259
     */
260
    protected function tableSchemaExists(): bool
261
    {
262
        return (Craft::$app->db->schema->getTableSchema('{{%retour_redirects}}') !== null);
263
    }
264
265
    /**
266
     * Install our event listeners.
267
     */
268
    protected function installEventListeners()
269
    {
270
        // Install our event listeners only if our table schema exists
271
        if ($this->tableSchemaExists()) {
272
            $request = Craft::$app->getRequest();
273
            // Add in our event listeners that are needed for every request
274
            $this->installGlobalEventListeners();
275
            // Install only for non-console site requests
276
            if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
277
                $this->installSiteEventListeners();
278
            }
279
            // Install only for non-console Control Panel requests
280
            if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
281
                $this->installCpEventListeners();
282
            }
283
        }
284
        // Handler: ClearCaches::EVENT_REGISTER_CACHE_OPTIONS
285
        Event::on(
286
            ClearCaches::class,
287
            ClearCaches::EVENT_REGISTER_CACHE_OPTIONS,
288
            function(RegisterCacheOptionsEvent $event) {
289
                Craft::debug(
290
                    'ClearCaches::EVENT_REGISTER_CACHE_OPTIONS',
291
                    __METHOD__
292
                );
293
                // Register our Control Panel routes
294
                $event->options = array_merge(
295
                    $event->options,
296
                    $this->customAdminCpCacheOptions()
297
                );
298
            }
299
        );
300
        // Handler: EVENT_AFTER_INSTALL_PLUGIN
301
        Event::on(
302
            Plugins::class,
303
            Plugins::EVENT_AFTER_INSTALL_PLUGIN,
304
            function(PluginEvent $event) {
305
                if ($event->plugin === $this) {
306
                    // Invalidate our caches after we've been installed
307
                    $this->clearAllCaches();
308
                    // Send them to our welcome screen
309
                    $request = Craft::$app->getRequest();
310
                    if ($request->isCpRequest) {
311
                        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl(
312
                            'retour/dashboard',
313
                            [
314
                                'showWelcome' => true,
315
                            ]
316
                        ))->send();
317
                    }
318
                }
319
            }
320
        );
321
    }
322
323
    /**
324
     * Install global event listeners for all request types
325
     */
326
    protected function installGlobalEventListeners()
327
    {
328
        Event::on(
329
            CraftVariable::class,
330
            CraftVariable::EVENT_INIT,
331
            function(Event $event) {
332
                /** @var CraftVariable $variable */
333
                $variable = $event->sender;
334
                $variable->set('retour', [
335
                    'class' => RetourVariable::class,
336
                    'viteService' => $this->vite,
337
                ]);
338
            }
339
        );
340
341
        $prepareRedirectOnElementChange = function(ElementEvent $event) {
342
            /** @var Element $element */
343
            $element = $event->element;
344
            if ($element !== null && !$element->propagating && !$event->isNew && $element->getUrl() !== null) {
345
                $checkElementSlug = true;
346
                // Make sure the element is enabled
347
                if (!$element->enabled || !$element->getEnabledForSite()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $element->getEnabledForSite() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
348
                    $checkElementSlug = false;
349
                }
350
                // If we're running Craft 3.2 or later, also check that isn't not a draft or revision
351
                if (Retour::$craft32 && (
352
                    ElementHelper::isDraftOrRevision($element)
353
                    )) {
354
                    $checkElementSlug = false;
355
                }
356
                // Only do this for elements that aren't new, pass $checkElementSlug, and the user
357
                // has turned on the setting
358
                if (self::$settings->createUriChangeRedirects && $checkElementSlug) {
359
                    // Make sure this isn't a transitioning temporary draft/revision and that it's
360
                    // not propagating to other sites
361
                    if ($element->uri && !str_contains($element->uri, '__temp_')) {
362
                        Retour::$plugin->events->stashElementUris($element);
363
                    }
364
                }
365
            }
366
        };
367
368
        $insertRedirectOnElementChange = function(ElementEvent $event) {
369
            /** @var Element $element */
370
            $element = $event->element;
371
            if ($element !== null && !$event->isNew && $element->getUrl() !== null) {
372
                $checkElementSlug = true;
373
                // Make sure the element is enabled
374
                if (!$element->enabled || !$element->getEnabledForSite()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $element->getEnabledForSite() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
375
                    $checkElementSlug = false;
376
                }
377
                if (Retour::$craft32 && ElementHelper::isDraftOrRevision($element)) {
378
                    $checkElementSlug = false;
379
                }
380
                if (self::$settings->createUriChangeRedirects && $checkElementSlug) {
381
                    Retour::$plugin->events->handleElementUriChange($element);
382
                }
383
            }
384
        };
385
386
        // Handler: Elements::EVENT_BEFORE_SAVE_ELEMENT
387
        Event::on(
388
            Elements::class,
389
            Elements::EVENT_BEFORE_SAVE_ELEMENT,
390
            function(ElementEvent $event) use ($prepareRedirectOnElementChange) {
391
                Craft::debug(
392
                    'Elements::EVENT_BEFORE_SAVE_ELEMENT',
393
                    __METHOD__
394
                );
395
                $prepareRedirectOnElementChange($event);
396
            }
397
        );
398
        // Handler: Elements::EVENT_AFTER_SAVE_ELEMENT
399
        Event::on(
400
            Elements::class,
401
            Elements::EVENT_AFTER_SAVE_ELEMENT,
402
            function(ElementEvent $event) use ($insertRedirectOnElementChange) {
403
                Craft::debug(
404
                    'Elements::EVENT_AFTER_SAVE_ELEMENT',
405
                    __METHOD__
406
                );
407
                $insertRedirectOnElementChange($event);
408
            }
409
        );
410
        // Handler: Elements::EVENT_BEFORE_UPDATE_SLUG_AND_URI
411
        Event::on(
412
            Elements::class,
413
            Elements::EVENT_BEFORE_UPDATE_SLUG_AND_URI,
414
            function(ElementEvent $event) use ($prepareRedirectOnElementChange) {
415
                Craft::debug(
416
                    'Elements::EVENT_BEFORE_UPDATE_SLUG_AND_URI',
417
                    __METHOD__
418
                );
419
                $prepareRedirectOnElementChange($event);
420
            }
421
        );
422
        // Handler: Elements::EVENT_AFTER_UPDATE_SLUG_AND_URI
423
        Event::on(
424
            Elements::class,
425
            Elements::EVENT_AFTER_UPDATE_SLUG_AND_URI,
426
            function(ElementEvent $event) use ($insertRedirectOnElementChange) {
427
                Craft::debug(
428
                    'Elements::EVENT_AFTER_UPDATE_SLUG_AND_URI',
429
                    __METHOD__
430
                );
431
                $insertRedirectOnElementChange($event);
432
            }
433
        );
434
435
        // Handler: Plugins::EVENT_AFTER_LOAD_PLUGINS
436
        Event::on(
437
            Plugins::class,
438
            Plugins::EVENT_AFTER_LOAD_PLUGINS,
439
            function() {
440
                // Install these only after all other plugins have loaded
441
                $request = Craft::$app->getRequest();
442
                // Only respond to non-console site requests
443
                if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
444
                    $this->handleSiteRequest();
445
                }
446
                // Respond to Control Panel requests
447
                if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
448
                    $this->handleAdminCpRequest();
449
                }
450
            }
451
        );
452
        // Handler: Fields::EVENT_REGISTER_FIELD_TYPES
453
        Event::on(
454
            Fields::class,
455
            Fields::EVENT_REGISTER_FIELD_TYPES,
456
            function(RegisterComponentTypesEvent $event) {
457
                $event->types[] = ShortLinkField::class;
458
            }
459
        );
460
        if (self::$craft33) {
461
            // Handler: Gql::EVENT_REGISTER_GQL_TYPES
462
            Event::on(
463
                Gql::class,
464
                Gql::EVENT_REGISTER_GQL_TYPES,
465
                function(RegisterGqlTypesEvent $event) {
466
                    Craft::debug(
467
                        'Gql::EVENT_REGISTER_GQL_TYPES',
468
                        __METHOD__
469
                    );
470
                    $event->types[] = RetourInterface::class;
471
                }
472
            );
473
            // Handler: Gql::EVENT_REGISTER_GQL_QUERIES
474
            Event::on(
475
                Gql::class,
476
                Gql::EVENT_REGISTER_GQL_QUERIES,
477
                function(RegisterGqlQueriesEvent $event) {
478
                    Craft::debug(
479
                        'Gql::EVENT_REGISTER_GQL_QUERIES',
480
                        __METHOD__
481
                    );
482
                    $queries = RetourQuery::getQueries();
483
                    foreach ($queries as $key => $value) {
484
                        $event->queries[$key] = $value;
485
                    }
486
                }
487
            );
488
            if (self::$craft35) {
489
                // Handler: Gql::EVENT_REGISTER_SCHEMA_COMPONENTS
490
                Event::on(
491
                    Gql::class,
492
                    Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS,
493
                    function(RegisterGqlSchemaComponentsEvent $event) {
494
                        Craft::debug(
495
                            'Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS',
496
                            __METHOD__
497
                        );
498
                        $label = Craft::t('retour', 'Retour');
499
                        $event->queries[$label]['retour.all:read'] = ['label' => Craft::t('retour', 'Query Retour data')];
500
                    }
501
                );
502
            }
503
        }
504
        // CraftQL Support
505
        if (class_exists(CraftQL::class)) {
506
            Event::on(
507
                Schema::class,
508
                AlterSchemaFields::EVENT,
509
                [GetCraftQLSchema::class, 'handle']
510
            );
511
        }
512
    }
513
514
    /**
515
     * Install site event listeners for site requests only
516
     */
517
    protected function installSiteEventListeners()
518
    {
519
        // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES
520
        Event::on(
521
            UrlManager::class,
522
            UrlManager::EVENT_REGISTER_SITE_URL_RULES,
523
            function(RegisterUrlRulesEvent $event) {
524
                Craft::debug(
525
                    'UrlManager::EVENT_REGISTER_SITE_URL_RULES',
526
                    __METHOD__
527
                );
528
                // Register our Control Panel routes
529
                $event->rules = array_merge(
530
                    $event->rules,
531
                    $this->customFrontendRoutes()
532
                );
533
            }
534
        );
535
    }
536
537
    /**
538
     * Install site event listeners for Control Panel requests only
539
     */
540
    protected function installCpEventListeners()
541
    {
542
        // Handler: Dashboard::EVENT_REGISTER_WIDGET_TYPES
543
        Event::on(
544
            Dashboard::class,
545
            Dashboard::EVENT_REGISTER_WIDGET_TYPES,
546
            function(RegisterComponentTypesEvent $event) {
547
                /** @var User $user */
548
                $user = Craft::$app->getUser();
549
                if ($currentUser = $user->getIdentity()) {
550
                    if ($currentUser->can('accessPlugin-retour')) {
551
                        $event->types[] = RetourWidget::class;
552
                    }
553
                }
554
            }
555
        );
556
        // Handler: UrlManager::EVENT_REGISTER_CP_URL_RULES
557
        Event::on(
558
            UrlManager::class,
559
            UrlManager::EVENT_REGISTER_CP_URL_RULES,
560
            function(RegisterUrlRulesEvent $event) {
561
                Craft::debug(
562
                    'UrlManager::EVENT_REGISTER_CP_URL_RULES',
563
                    __METHOD__
564
                );
565
                // Register our Control Panel routes
566
                $event->rules = array_merge(
567
                    $event->rules,
568
                    $this->customAdminCpRoutes()
569
                );
570
            }
571
        );
572
        // Handler: UserPermissions::EVENT_REGISTER_PERMISSIONS
573
        Event::on(
574
            UserPermissions::class,
575
            UserPermissions::EVENT_REGISTER_PERMISSIONS,
576
            function(RegisterUserPermissionsEvent $event) {
577
                Craft::debug(
578
                    'UserPermissions::EVENT_REGISTER_PERMISSIONS',
579
                    __METHOD__
580
                );
581
                // Register our custom permissions
582
                $event->permissions[Craft::t('retour', 'Retour')] = $this->customAdminCpPermissions();
583
            }
584
        );
585
    }
586
587
    /**
588
     * Handle site requests.  We do it only after we receive the event
589
     * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run
590
     * before our event listeners kick in
591
     */
592
    protected function handleSiteRequest()
593
    {
594
        // Handler: ErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTION
595
        Event::on(
596
            ErrorHandler::class,
597
            ErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTION,
598
            function(ExceptionEvent $event) {
599
                Craft::debug(
600
                    'ErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTION',
601
                    __METHOD__
602
                );
603
                $exception = $event->exception;
604
                // If this is a Twig Runtime exception, use the previous one instead
605
                if ($exception instanceof RuntimeError &&
606
                    ($previousException = $exception->getPrevious()) !== null) {
607
                    $exception = $previousException;
608
                }
609
                // If this is a 404 error, see if we can handle it
610
                if ($exception instanceof HttpException && $exception->statusCode === 404) {
611
                    self::$currentException = $exception;
612
                    Retour::$plugin->redirects->handle404();
613
                }
614
            }
615
        );
616
    }
617
618
    /**
619
     * Handle Control Panel requests. We do it only after we receive the event
620
     * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run
621
     * before our event listeners kick in
622
     */
623
    protected function handleAdminCpRequest()
624
    {
625
    }
626
627
    /**
628
     * @inheritdoc
629
     */
630
    protected function createSettingsModel()
631
    {
632
        return new Settings();
633
    }
634
635
    /**
636
     * Return the custom Control Panel routes
637
     *
638
     * @return array
639
     */
640
    protected function customAdminCpRoutes(): array
641
    {
642
        return [
643
            'retour' => '',
644
645
            'retour/redirects' => 'retour/redirects/redirects',
646
            'retour/redirects/<siteHandle:{handle}>' => 'retour/redirects/redirects',
647
648
            'retour/edit-redirect/<redirectId:\d+>' => 'retour/redirects/edit-redirect',
649
650
            'retour/add-redirect' => 'retour/redirects/edit-redirect',
651
            'retour/add-redirect/<siteId:\d+>' => 'retour/redirects/edit-redirect',
652
653
            'retour/dashboard' => 'retour/statistics/dashboard',
654
            'retour/dashboard/<siteHandle:{handle}>' => 'retour/statistics/dashboard',
655
656
            'retour/shortlinks' => 'retour/redirects/shortlinks',
657
            'retour/shortlinks/<siteHandle:{handle}>' => 'retour/redirects/shortlinks',
658
659
            'retour/settings' => 'retour/settings/plugin-settings',
660
        ];
661
    }
662
663
    /**
664
     * Return the custom frontend routes
665
     *
666
     * @return array
667
     */
668
    protected function customFrontendRoutes(): array
669
    {
670
        return [
671
        ];
672
    }
673
674
    /**
675
     * Returns the custom Control Panel cache options.
676
     *
677
     * @return array
678
     */
679
    protected function customAdminCpCacheOptions(): array
680
    {
681
        return [
682
            [
683
                'key' => 'retour-redirect-caches',
684
                'label' => Craft::t('retour', 'Retour redirect caches'),
685
                'action' => [self::$plugin->redirects, 'invalidateCaches'],
686
            ],
687
        ];
688
    }
689
690
    /**
691
     * Returns the custom Control Panel user permissions.
692
     *
693
     * @return array
694
     */
695
    protected function customAdminCpPermissions(): array
696
    {
697
        return [
698
            'retour:dashboard' => [
699
                'label' => Craft::t('retour', 'Dashboard'),
700
            ],
701
            'retour:redirects' => [
702
                'label' => Craft::t('retour', 'Redirects'),
703
            ],
704
            'retour:shortlinks' => [
705
                'label' => Craft::t('retour', 'Short Links'),
706
            ],
707
            'retour:settings' => [
708
                'label' => Craft::t('retour', 'Settings'),
709
            ],
710
        ];
711
    }
712
}
713