Passed
Push — v3 ( 2b006a...43f239 )
by Andrew
33:34 queued 16:32
created

src/Retour.php (82 issues)

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
The tag in position 1 should be the @copyright tag
Loading history...
9
 * @copyright Copyright (c) 2018 nystudio107
0 ignored issues
show
@copyright tag must contain a year and the name of the copyright holder
Loading history...
10
 */
0 ignored issues
show
PHP version not specified
Loading history...
Missing @category tag in file comment
Loading history...
Missing @package tag in file comment
Loading history...
Missing @author tag in file comment
Loading history...
Missing @license tag in file comment
Loading history...
11
12
namespace nystudio107\retour;
13
14
use craft\events\RegisterGqlSchemaComponentsEvent;
15
use nystudio107\retour\assetbundles\retour\RetourAsset;
16
use nystudio107\retour\gql\interfaces\RetourInterface;
17
use nystudio107\retour\gql\queries\RetourQuery;
18
use nystudio107\retour\listeners\GetCraftQLSchema;
19
use nystudio107\retour\models\Settings;
20
use nystudio107\retour\services\Redirects;
21
use nystudio107\retour\services\Statistics;
22
use nystudio107\retour\variables\RetourVariable;
23
use nystudio107\retour\widgets\RetourWidget;
24
25
use nystudio107\pluginmanifest\services\ManifestService;
26
27
use Craft;
28
use craft\base\Element;
29
use craft\base\Plugin;
30
use craft\events\ElementEvent;
31
use craft\events\ExceptionEvent;
32
use craft\events\PluginEvent;
33
use craft\events\RegisterCacheOptionsEvent;
34
use craft\events\RegisterComponentTypesEvent;
35
use craft\events\RegisterGqlQueriesEvent;
36
use craft\events\RegisterGqlTypesEvent;
37
use craft\events\RegisterUrlRulesEvent;
38
use craft\events\RegisterUserPermissionsEvent;
39
use craft\helpers\ElementHelper;
40
use craft\helpers\UrlHelper;
41
use craft\services\Elements;
42
use craft\services\Dashboard;
43
use craft\services\Gql;
44
use craft\services\Plugins;
45
use craft\services\UserPermissions;
46
use craft\utilities\ClearCaches;
47
use craft\web\ErrorHandler;
48
use craft\web\twig\variables\CraftVariable;
49
use craft\web\UrlManager;
50
51
use yii\base\Event;
52
use yii\base\Exception;
53
use yii\web\HttpException;
54
55
use markhuot\CraftQL\Builders\Schema;
0 ignored issues
show
The type markhuot\CraftQL\Builders\Schema was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
56
use markhuot\CraftQL\CraftQL;
0 ignored issues
show
The type markhuot\CraftQL\CraftQL was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
57
use markhuot\CraftQL\Events\AlterSchemaFields;
0 ignored issues
show
The type markhuot\CraftQL\Events\AlterSchemaFields was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
58
59
/** @noinspection MissingPropertyAnnotationsInspection */
0 ignored issues
show
The close comment tag must be the only content on the line
Loading history...
The open comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
60
61
/**
62
 * Class Retour
63
 *
64
 * @author    nystudio107
0 ignored issues
show
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
The tag in position 1 should be the @package tag
Loading history...
65
 * @package   Retour
0 ignored issues
show
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
66
 * @since     3.0.0
0 ignored issues
show
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
The tag in position 3 should be the @author tag
Loading history...
67
 *
68
 * @property Redirects          $redirects
69
 * @property Statistics         $statistics
70
 * @property ManifestService    $manifest
71
 */
0 ignored issues
show
Missing @link tag in class comment
Loading history...
Missing @license tag in class comment
Loading history...
Missing @category tag in class comment
Loading history...
72
class Retour extends Plugin
73
{
74
    // Constants
75
    // =========================================================================
76
77
    const DEVMODE_CACHE_DURATION = 30;
78
79
    // Static Properties
80
    // =========================================================================
81
82
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
83
     * @var Retour
84
     */
85
    public static $plugin;
86
87
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
88
     * @var Settings
89
     */
90
    public static $settings;
91
92
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
93
     * @var int
94
     */
95
    public static $cacheDuration;
96
97
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
98
     * @var HttpException
99
     */
100
    public static $currentException;
101
102
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
103
     * @var bool
104
     */
105
    public static $craft31 = false;
106
107
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
108
     * @var bool
109
     */
110
    public static $craft32 = false;
111
112
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
113
     * @var bool
114
     */
115
    public static $craft33 = false;
116
117
    // Static Methods
118
    // =========================================================================
119
120
    /**
0 ignored issues
show
Parameter $id should have a doc-comment as per coding-style.
Loading history...
Parameter $config should have a doc-comment as per coding-style.
Loading history...
Parameter $parent should have a doc-comment as per coding-style.
Loading history...
Missing short description in doc comment
Loading history...
121
     * @inheritdoc
122
     */
123
    public function __construct($id, $parent = null, array $config = [])
124
    {
125
        $config['components'] = [
126
            'redirects' => Redirects::class,
127
            'statistics' => Statistics::class,
128
            // Register the manifest service
129
            'manifest' => [
130
                'class' => ManifestService::class,
131
                'assetClass' => RetourAsset::class,
132
                'devServerManifestPath' => 'http://craft-retour-buildchain:8080/',
133
                'devServerPublicPath' => 'http://craft-retour-buildchain:8080/',
134
            ]
135
        ];
136
137
        parent::__construct($id, $parent, $config);
138
    }
139
140
    // Public Properties
141
    // =========================================================================
142
143
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
144
     * @var string
145
     */
146
    public $schemaVersion = '3.0.10';
147
148
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
149
     * @var bool
150
     */
151
    public $hasCpSection = true;
152
153
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
154
     * @var bool
155
     */
156
    public $hasCpSettings = true;
157
158
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
159
     * @var array The URIs for the element before it was saved
160
     */
161
    public $oldElementUris = [];
162
163
    // Public Methods
164
    // =========================================================================
165
166
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
167
     * @inheritdoc
168
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
169
    public function init()
170
    {
171
        parent::init();
172
        self::$plugin = $this;
173
        // Initialize properties
174
        self::$settings = $this->getSettings();
175
        self::$craft31 = version_compare(Craft::$app->getVersion(), '3.1', '>=');
0 ignored issues
show
Documentation Bug introduced by
It seems like version_compare(Craft::a...Version(), '3.1', '>=') can also be of type integer. However, the property $craft31 is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
176
        self::$craft32 = version_compare(Craft::$app->getVersion(), '3.2', '>=');
0 ignored issues
show
Documentation Bug introduced by
It seems like version_compare(Craft::a...Version(), '3.2', '>=') can also be of type integer. However, the property $craft32 is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
177
        self::$craft33 = version_compare(Craft::$app->getVersion(), '3.3', '>=');
0 ignored issues
show
Documentation Bug introduced by
It seems like version_compare(Craft::a...Version(), '3.3', '>=') can also be of type integer. However, the property $craft33 is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
178
        $this->name = self::$settings->pluginName;
179
        self::$cacheDuration = Craft::$app->getConfig()->getGeneral()->devMode
180
            ? $this::DEVMODE_CACHE_DURATION
181
            : null;
182
        // Handle any console commands
183
        $request = Craft::$app->getRequest();
184
        if ($request->getIsConsoleRequest()) {
185
            $this->controllerNamespace = 'nystudio107\retour\console\controllers';
186
        }
187
        // Install our event listeners
188
        $this->installEventListeners();
189
        // Log that Retour has been loaded
190
        Craft::info(
191
            Craft::t(
192
                'retour',
193
                '{name} plugin loaded',
194
                ['name' => $this->name]
195
            ),
196
            __METHOD__
197
        );
198
    }
199
200
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
201
     * @inheritdoc
202
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
203
    public function getSettingsResponse()
204
    {
205
        // Just redirect to the plugin settings page
206
        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('retour/settings'));
207
    }
208
209
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
210
     * @inheritdoc
211
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
212
    public function getCpNavItem()
213
    {
214
        $subNavs = [];
215
        $navItem = parent::getCpNavItem();
216
        $currentUser = Craft::$app->getUser()->getIdentity();
217
        // Only show sub-navs the user has permission to view
218
        if ($currentUser->can('retour:dashboard')) {
0 ignored issues
show
The method can() does not exist on yii\web\IdentityInterface. It seems like you code against a sub-type of yii\web\IdentityInterface such as craft\elements\User. ( Ignorable by Annotation )

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

218
        if ($currentUser->/** @scrutinizer ignore-call */ can('retour:dashboard')) {
Loading history...
219
            $subNavs['dashboard'] = [
220
                'label' => 'Dashboard',
221
                'url' => 'retour/dashboard',
222
            ];
223
        }
224
        if ($currentUser->can('retour:redirects')) {
225
            $subNavs['redirects'] = [
226
                'label' => 'Redirects',
227
                'url' => 'retour/redirects',
228
            ];
229
        }
230
        $editableSettings = true;
231
        $general = Craft::$app->getConfig()->getGeneral();
232
        if (self::$craft31 && !$general->allowAdminChanges) {
233
            $editableSettings = false;
234
        }
235
        if ($currentUser->can('retour:settings') && $editableSettings) {
236
            $subNavs['settings'] = [
237
                'label' => 'Settings',
238
                'url' => 'retour/settings',
239
            ];
240
        }
241
        // 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
242
        if (empty($subNavs)) {
243
            return null;
244
        }
245
        // A single sub nav item is redundant
246
        if (count($subNavs) === 1) {
247
            $subNavs = [];
248
        }
249
        $navItem = array_merge($navItem, [
0 ignored issues
show
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
250
            'subnav' => $subNavs,
251
        ]);
0 ignored issues
show
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...
252
253
        return $navItem;
254
    }
255
256
    /**
257
     * Clear all the caches!
258
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
259
    public function clearAllCaches()
260
    {
261
        // Clear all of Retour's caches
262
        self::$plugin->redirects->invalidateCaches();
263
    }
264
265
    // Protected Methods
266
    // =========================================================================
267
268
    /**
269
     * Determine whether our table schema exists or not; this is needed because
270
     * migrations such as the install migration and base_install migration may
271
     * not have been run by the time our init() method has been called
272
     *
273
     * @return bool
274
     */
275
    protected function tableSchemaExists(): bool
276
    {
277
        return (Craft::$app->db->schema->getTableSchema('{{%retour_redirects}}') !== null);
278
    }
279
280
    /**
281
     * Install our event listeners.
282
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
283
    protected function installEventListeners()
284
    {
285
        // Install our event listeners only if our table schema exists
286
        if ($this->tableSchemaExists()) {
287
            $request = Craft::$app->getRequest();
288
            // Add in our event listeners that are needed for every request
289
            $this->installGlobalEventListeners();
290
            // Install only for non-console site requests
291
            if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
292
                $this->installSiteEventListeners();
293
            }
294
            // Install only for non-console Control Panel requests
295
            if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
296
                $this->installCpEventListeners();
297
            }
298
        }
299
        // Handler: ClearCaches::EVENT_REGISTER_CACHE_OPTIONS
300
        Event::on(
301
            ClearCaches::class,
302
            ClearCaches::EVENT_REGISTER_CACHE_OPTIONS,
303
            function (RegisterCacheOptionsEvent $event) {
304
                Craft::debug(
305
                    'ClearCaches::EVENT_REGISTER_CACHE_OPTIONS',
306
                    __METHOD__
307
                );
308
                // Register our Control Panel routes
309
                $event->options = array_merge(
310
                    $event->options,
311
                    $this->customAdminCpCacheOptions()
312
                );
313
            }
314
        );
315
        // Handler: EVENT_AFTER_INSTALL_PLUGIN
316
        Event::on(
317
            Plugins::class,
318
            Plugins::EVENT_AFTER_INSTALL_PLUGIN,
319
            function (PluginEvent $event) {
320
                if ($event->plugin === $this) {
321
                    // Invalidate our caches after we've been installed
322
                    $this->clearAllCaches();
323
                    // Send them to our welcome screen
324
                    $request = Craft::$app->getRequest();
325
                    if ($request->isCpRequest) {
326
                        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl(
0 ignored issues
show
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
327
                            'retour/dashboard',
328
                            [
329
                                'showWelcome' => true,
330
                            ]
331
                        ))->send();
0 ignored issues
show
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...
332
                    }
333
                }
334
            }
335
        );
336
    }
337
338
    /**
339
     * Install global event listeners for all request types
340
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
341
    protected function installGlobalEventListeners()
342
    {
343
        Event::on(
344
            CraftVariable::class,
345
            CraftVariable::EVENT_INIT,
346
            function (Event $event) {
347
                /** @var CraftVariable $variable */
0 ignored issues
show
The close comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
The open comment tag must be the only content on the line
Loading history...
348
                $variable = $event->sender;
349
                $variable->set('retour', [
0 ignored issues
show
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
350
                    'class' => RetourVariable::class,
351
                    'manifestService' => $this->manifest,
352
                ]);
0 ignored issues
show
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...
353
            }
354
        );
355
        // Handler: Elements::EVENT_BEFORE_SAVE_ELEMENT
356
        Event::on(
357
            Elements::class,
358
            Elements::EVENT_BEFORE_SAVE_ELEMENT,
359
            function (ElementEvent $event) {
360
                Craft::debug(
361
                    'Elements::EVENT_BEFORE_SAVE_ELEMENT',
362
                    __METHOD__
363
                );
364
                /** @var Element $element */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
The close comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
365
                $element = $event->element;
366
                if ($element !== null && !$event->isNew && $element->getUrl() !== null && !$element->propagating) {
367
                    $checkElementSlug = true;
368
                    // If we're running Craft 3.2 or later, also check that isn't not a draft or revision
369
                    if (Retour::$craft32 && (
370
                            ElementHelper::isDraftOrRevision($element)
0 ignored issues
show
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
Multi-line IF statement not indented correctly; expected 24 spaces but found 28
Loading history...
371
                        )) {
0 ignored issues
show
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
372
                        $checkElementSlug = false;
373
                    }
374
                    // Only do this for elements that aren't new, pass $checkElementSlug, and the user
375
                    // has turned on the setting
376
                    if (self::$settings->createUriChangeRedirects && $checkElementSlug) {
377
                        // Make sure this isn't a transitioning temporary draft/revision and that it's
378
                        // not propagating to other sites
379
                        if (strpos($element->uri, '__temp_') === false && !$element->propagating) {
0 ignored issues
show
It seems like $element->uri can also be of type null; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

379
                        if (strpos(/** @scrutinizer ignore-type */ $element->uri, '__temp_') === false && !$element->propagating) {
Loading history...
380
                            // Stash the old URLs by element id, and do so only once,
381
                            // in case we are called more than once per request
382
                            if (empty($this->oldElementUris[$element->id])) {
383
                                $this->oldElementUris[$element->id] = $this->getAllElementUris($element);
384
                            }
385
                        }
386
                    }
387
                }
388
            }
389
        );
390
        // Handler: Elements::EVENT_AFTER_SAVE_ELEMENT
391
        Event::on(
392
            Elements::class,
393
            Elements::EVENT_AFTER_SAVE_ELEMENT,
394
            function (ElementEvent $event) {
395
                Craft::debug(
396
                    'Elements::EVENT_AFTER_SAVE_ELEMENT',
397
                    __METHOD__
398
                );
399
                /** @var Element $element */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
The close comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
400
                $element = $event->element;
401
                if ($element !== null && !$event->isNew && $element->getUrl() !== null) {
402
                    $checkElementSlug = true;
403
                    if (Retour::$craft32 && ElementHelper::isDraftOrRevision($element)) {
404
                        $checkElementSlug = false;
405
                    }
406
                    if (self::$settings->createUriChangeRedirects && $checkElementSlug) {
407
                        $this->handleElementUriChange($element);
408
                    }
409
                }
410
            }
411
        );
412
        // Handler: Plugins::EVENT_AFTER_LOAD_PLUGINS
413
        Event::on(
414
            Plugins::class,
415
            Plugins::EVENT_AFTER_LOAD_PLUGINS,
416
            function () {
417
                // Install these only after all other plugins have loaded
418
                $request = Craft::$app->getRequest();
419
                // Only respond to non-console site requests
420
                if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
421
                    $this->handleSiteRequest();
422
                }
423
                // Respond to Control Panel requests
424
                if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
425
                    $this->handleAdminCpRequest();
426
                }
427
            }
428
        );
429
        if (self::$craft33) {
430
            // Handler: Gql::EVENT_REGISTER_GQL_TYPES
431
            Event::on(
432
                Gql::class,
433
                Gql::EVENT_REGISTER_GQL_TYPES,
434
                function (RegisterGqlTypesEvent $event) {
435
                    Craft::debug(
436
                        'Gql::EVENT_REGISTER_GQL_TYPES',
437
                        __METHOD__
438
                    );
439
                    $event->types[] = RetourInterface::class;
440
                }
441
            );
442
            // Handler: Gql::EVENT_REGISTER_GQL_QUERIES
443
            Event::on(
444
                Gql::class,
445
                Gql::EVENT_REGISTER_GQL_QUERIES,
446
                function (RegisterGqlQueriesEvent $event) {
447
                    Craft::debug(
448
                        'Gql::EVENT_REGISTER_GQL_QUERIES',
449
                        __METHOD__
450
                    );
451
                    $queries = RetourQuery::getQueries();
452
                    foreach ($queries as $key => $value) {
453
                        $event->queries[$key] = $value;
454
                    }
455
                }
456
            );
457
            // Handler: Gql::EVENT_REGISTER_SCHEMA_COMPONENTS
458
            Event::on(
459
                Gql::class,
460
                Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS,
461
                function (RegisterGqlSchemaComponentsEvent $event) {
462
                    Craft::debug(
463
                        'Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS',
464
                        __METHOD__
465
                    );
466
                    $label = Craft::t('retour', 'Retour');
467
                    $event->queries[$label]['retour.all:read'] = ['label' => Craft::t('retour', 'Query Retour data')];
468
                }
469
            );
470
        }
471
        // CraftQL Support
472
        if (class_exists(CraftQL::class)) {
473
            Event::on(
474
                Schema::class,
475
                AlterSchemaFields::EVENT,
476
                [GetCraftQLSchema::class, 'handle']
477
            );
478
        }
479
    }
480
481
    /**
482
     * Install site event listeners for site requests only
483
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
484
    protected function installSiteEventListeners()
485
    {
486
        // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES
487
        Event::on(
488
            UrlManager::class,
489
            UrlManager::EVENT_REGISTER_SITE_URL_RULES,
490
            function (RegisterUrlRulesEvent $event) {
491
                Craft::debug(
492
                    'UrlManager::EVENT_REGISTER_SITE_URL_RULES',
493
                    __METHOD__
494
                );
495
                // Register our Control Panel routes
496
                $event->rules = array_merge(
497
                    $event->rules,
498
                    $this->customFrontendRoutes()
499
                );
500
            }
501
        );
502
    }
503
504
    /**
505
     * Install site event listeners for Control Panel requests only
506
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
507
    protected function installCpEventListeners()
508
    {
509
        // Handler: Dashboard::EVENT_REGISTER_WIDGET_TYPES
510
        Event::on(
511
            Dashboard::class,
512
            Dashboard::EVENT_REGISTER_WIDGET_TYPES,
513
            function (RegisterComponentTypesEvent $event) {
514
                $event->types[] = RetourWidget::class;
515
            }
516
        );
517
        // Handler: UrlManager::EVENT_REGISTER_CP_URL_RULES
518
        Event::on(
519
            UrlManager::class,
520
            UrlManager::EVENT_REGISTER_CP_URL_RULES,
521
            function (RegisterUrlRulesEvent $event) {
522
                Craft::debug(
523
                    'UrlManager::EVENT_REGISTER_CP_URL_RULES',
524
                    __METHOD__
525
                );
526
                // Register our Control Panel routes
527
                $event->rules = array_merge(
528
                    $event->rules,
529
                    $this->customAdminCpRoutes()
530
                );
531
            }
532
        );
533
        // Handler: UserPermissions::EVENT_REGISTER_PERMISSIONS
534
        Event::on(
535
            UserPermissions::class,
536
            UserPermissions::EVENT_REGISTER_PERMISSIONS,
537
            function (RegisterUserPermissionsEvent $event) {
538
                Craft::debug(
539
                    'UserPermissions::EVENT_REGISTER_PERMISSIONS',
540
                    __METHOD__
541
                );
542
                // Register our custom permissions
543
                $event->permissions[Craft::t('retour', 'Retour')] = $this->customAdminCpPermissions();
544
            }
545
        );
546
    }
547
548
    /**
549
     * Handle site requests.  We do it only after we receive the event
550
     * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run
551
     * before our event listeners kick in
552
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
553
    protected function handleSiteRequest()
554
    {
555
        // Handler: ErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTION
556
        Event::on(
557
            ErrorHandler::class,
558
            ErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTION,
559
            function (ExceptionEvent $event) {
560
                Craft::debug(
561
                    'ErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTION',
562
                    __METHOD__
563
                );
564
                $exception = $event->exception;
565
                // If this is a Twig Runtime exception, use the previous one instead
566
                if ($exception instanceof \Twig\Error\RuntimeError &&
567
                    ($previousException = $exception->getPrevious()) !== null) {
0 ignored issues
show
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
568
                    $exception = $previousException;
569
                }
570
                // If this is a 404 error, see if we can handle it
571
                if ($exception instanceof HttpException && $exception->statusCode === 404) {
572
                    self::$currentException = $exception;
573
                    Retour::$plugin->redirects->handle404();
574
                }
575
            }
576
        );
577
    }
578
579
    /**
580
     * Handle Control Panel requests. We do it only after we receive the event
581
     * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run
582
     * before our event listeners kick in
583
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
584
    protected function handleAdminCpRequest()
585
    {
586
    }
587
588
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
589
     * @inheritdoc
590
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
591
    protected function createSettingsModel()
592
    {
593
        return new Settings();
594
    }
595
596
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
597
     * @param Element $element
0 ignored issues
show
Missing parameter comment
Loading history...
598
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
599
    protected function handleElementUriChange(Element $element)
600
    {
601
        $uris = $this->getAllElementUris($element);
602
        if (!empty($this->oldElementUris[$element->id])) {
603
            $oldElementUris = $this->oldElementUris[$element->id];
604
            foreach ($uris as $siteId => $newUri) {
605
                if (!empty($oldElementUris[$siteId])) {
606
                    $oldUri = $oldElementUris[$siteId];
607
                    Craft::debug(
608
                        Craft::t(
609
                            'retour',
610
                            'Comparing old: {oldUri} to new: {newUri}',
611
                            ['oldUri' => print_r($oldUri, true), 'newUri' => print_r($newUri, true)]
612
                        ),
613
                        __METHOD__
614
                    );
615
                    // Handle the siteId
616
                    $redirectSiteId = null;
617
                    if (Craft::$app->getIsMultiSite()) {
618
                        $redirectSiteId = $siteId;
619
                    }
620
                    // Make sure the URIs are not the same
621
                    if (strcmp($oldUri, $newUri) !== 0) {
622
                        // Handle trailing slash config setting
623
                        if (Craft::$app->config->general->addTrailingSlashesToUrls) {
624
                            $oldUri = rtrim($oldUri, '/') . '/';
625
                            $newUri = rtrim($newUri, '/') . '/';
626
                        }
627
                        // Handle the URL match type
628
                        if (self::$settings->uriChangeRedirectSrcMatch === 'fullurl') {
629
                            try {
630
                                if ($redirectSiteId !== null) {
631
                                    $redirectSiteId = (int)$redirectSiteId;
632
                                }
633
                                $oldUri = UrlHelper::siteUrl($oldUri, null, null, $redirectSiteId);
634
                                $newUri = UrlHelper::siteUrl($newUri, null, null, $redirectSiteId);
635
                            } catch (Exception $e) {
636
                                // That's fine
637
                            }
638
                        }
639
                        $redirectConfig = [
640
                            'id' => 0,
641
                            'redirectMatchType' => 'exactmatch',
642
                            'redirectHttpCode' => 301,
643
                            'redirectSrcMatch' => self::$settings->uriChangeRedirectSrcMatch,
644
                            'redirectSrcUrl' => $oldUri,
645
                            'redirectDestUrl' => $newUri,
646
                            'siteId' => $redirectSiteId,
647
                        ];
648
                        Retour::$plugin->redirects->saveRedirect($redirectConfig);
649
                    }
650
                }
651
            }
652
        }
653
    }
654
655
    /**
656
     * Get the URIs for each site for the element
657
     *
658
     * @param Element $element
0 ignored issues
show
Missing parameter comment
Loading history...
659
     *
660
     * @return array
661
     */
662
    protected function getAllElementUris(Element $element): array
663
    {
664
        $uris = [];
665
        if (!self::$craft32 || !ElementHelper::isDraftOrRevision($element)) {
666
            $sites = Craft::$app->getSites()->getAllSites();
667
            foreach ($sites as $site) {
668
                $uri = Craft::$app->getElements()->getElementUriForSite($element->id, $site->id);
669
                if ($uri !== null) {
670
                    $uris[$site->id] = $uri;
671
                }
672
            }
673
        }
674
675
        Craft::debug(
676
            Craft::t(
677
                'retour',
678
                'Getting Element URIs: {uris}',
679
                ['uris' => print_r($uris, true)]
680
            ),
681
            __METHOD__
682
        );
683
684
        return $uris;
685
    }
686
687
    /**
688
     * Return the custom Control Panel routes
689
     *
690
     * @return array
691
     */
692
    protected function customAdminCpRoutes(): array
693
    {
694
        return [
695
            'retour' => '',
696
697
            'retour/redirects' => 'retour/redirects/redirects',
698
            'retour/redirects/<siteHandle:{handle}>' => 'retour/redirects/redirects',
699
700
            'retour/edit-redirect/<redirectId:\d+>' => 'retour/redirects/edit-redirect',
701
702
            'retour/add-redirect' => 'retour/redirects/edit-redirect',
703
            'retour/add-redirect/<siteId:\d+>' => 'retour/redirects/edit-redirect',
704
705
            'retour/dashboard' => 'retour/statistics/dashboard',
706
            'retour/dashboard/<siteHandle:{handle}>' => 'retour/statistics/dashboard',
707
708
            'retour/settings' => 'retour/settings/plugin-settings',
709
        ];
710
    }
711
712
    /**
713
     * Return the custom frontend routes
714
     *
715
     * @return array
716
     */
717
    protected function customFrontendRoutes(): array
718
    {
719
        return [
720
        ];
721
    }
722
723
    /**
724
     * Returns the custom Control Panel cache options.
725
     *
726
     * @return array
727
     */
728
    protected function customAdminCpCacheOptions(): array
729
    {
730
        return [
731
            [
732
                'key' => 'retour-redirect-caches',
733
                'label' => Craft::t('retour', 'Retour redirect caches'),
734
                'action' => [self::$plugin->redirects, 'invalidateCaches'],
735
            ],
736
        ];
737
    }
738
739
    /**
740
     * Returns the custom Control Panel user permissions.
741
     *
742
     * @return array
743
     */
744
    protected function customAdminCpPermissions(): array
745
    {
746
        return [
747
            'retour:dashboard' => [
748
                'label' => Craft::t('retour', 'Dashboard'),
749
            ],
750
            'retour:redirects' => [
751
                'label' => Craft::t('retour', 'Redirects'),
752
            ],
753
            'retour:settings' => [
754
                'label' => Craft::t('retour', 'Settings'),
755
            ],
756
        ];
757
    }
758
}
759