Passed
Push — develop ( 0b0eed...aa8cda )
by Andrew
07:05
created

Retour::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 8
ccs 0
cts 3
cp 0
rs 10
cc 1
nc 1
nop 3
crap 2
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;
13
14
use nystudio107\retour\assetbundles\retour\RetourAsset;
15
use nystudio107\retour\gql\interfaces\RetourInterface;
16
use nystudio107\retour\gql\queries\RetourQuery;
17
use nystudio107\retour\listeners\GetCraftQLSchema;
18
use nystudio107\retour\models\Settings;
19
use nystudio107\retour\services\Redirects;
20
use nystudio107\retour\services\Statistics;
21
use nystudio107\retour\variables\RetourVariable;
22
use nystudio107\retour\widgets\RetourWidget;
23
24
use nystudio107\pluginmanifest\services\ManifestService;
25
26
use Craft;
27
use craft\base\Element;
28
use craft\base\Plugin;
29
use craft\events\ElementEvent;
30
use craft\events\ExceptionEvent;
31
use craft\events\PluginEvent;
32
use craft\events\RegisterCacheOptionsEvent;
33
use craft\events\RegisterComponentTypesEvent;
34
use craft\events\RegisterGqlQueriesEvent;
35
use craft\events\RegisterGqlTypesEvent;
36
use craft\events\RegisterUrlRulesEvent;
37
use craft\events\RegisterUserPermissionsEvent;
38
use craft\helpers\ElementHelper;
39
use craft\helpers\UrlHelper;
40
use craft\services\Elements;
41
use craft\services\Dashboard;
42
use craft\services\Gql;
43
use craft\services\Plugins;
44
use craft\services\UserPermissions;
45
use craft\utilities\ClearCaches;
46
use craft\web\ErrorHandler;
47
use craft\web\twig\variables\CraftVariable;
48
use craft\web\UrlManager;
49
50
use yii\base\Event;
51
use yii\base\Exception;
52
use yii\web\HttpException;
53
54
use markhuot\CraftQL\Builders\Schema;
0 ignored issues
show
Bug introduced by
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...
55
use markhuot\CraftQL\CraftQL;
0 ignored issues
show
Bug introduced by
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...
56
use markhuot\CraftQL\Events\AlterSchemaFields;
0 ignored issues
show
Bug introduced by
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...
57
58
/** @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
The close comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
59
60
/**
61
 * Class Retour
62
 *
63
 * @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...
64
 * @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...
65
 * @since     3.0.0
0 ignored issues
show
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
66
 *
67
 * @property Redirects          $redirects
68
 * @property Statistics         $statistics
69
 * @property ManifestService    $manifest
70
 */
0 ignored issues
show
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
Coding Style introduced by
Missing @category tag in class comment
Loading history...
71
class Retour extends Plugin
72
{
73
    // Constants
74
    // =========================================================================
75
76
    const DEVMODE_CACHE_DURATION = 30;
77
78
    // Static Properties
79
    // =========================================================================
80
81
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
82
     * @var Retour
83
     */
84
    public static $plugin;
85
86
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
87
     * @var Settings
88
     */
89
    public static $settings;
90
91
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
92
     * @var int
93
     */
94
    public static $cacheDuration;
95
96
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
97
     * @var HttpException
98
     */
99
    public static $currentException;
100
101
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
102
     * @var bool
103
     */
104
    public static $craft31 = false;
105
106
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
107
     * @var bool
108
     */
109
    public static $craft32 = false;
110
111
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
112
     * @var bool
113
     */
114
    public static $craft33 = false;
115
116
    // Static Methods
117
    // =========================================================================
118
119
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $config should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $id should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $parent should have a doc-comment as per coding-style.
Loading history...
120
     * @inheritdoc
121
     */
122
    public function __construct($id, $parent = null, array $config = [])
123
    {
124
        $config['components'] = [
125
            'redirects' => Redirects::class,
126
            'statistics' => Statistics::class,
127
        ];
128
129
        parent::__construct($id, $parent, $config);
130
    }
131
132
    // Public Properties
133
    // =========================================================================
134
135
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
136
     * @var string
137
     */
138
    public $schemaVersion = '3.0.9';
139
140
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
141
     * @var bool
142
     */
143
    public $hasCpSection = true;
144
145
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
146
     * @var bool
147
     */
148
    public $hasCpSettings = true;
149
150
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
151
     * @var array The URIs for the element before it was saved
152
     */
153
    public $oldElementUris = [];
154
155
    // Public Methods
156
    // =========================================================================
157
158
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
159
     * @inheritdoc
160
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
161
    public function init()
162
    {
163
        parent::init();
164
        self::$plugin = $this;
165
        // Initialize properties
166
        self::$settings = $this->getSettings();
167
        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...
168
        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...
169
        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...
170
        $this->name = self::$settings->pluginName;
171
        self::$cacheDuration = Craft::$app->getConfig()->getGeneral()->devMode
172
            ? $this::DEVMODE_CACHE_DURATION
173
            : null;
174
        // Handle any console commands
175
        $request = Craft::$app->getRequest();
176
        if ($request->getIsConsoleRequest()) {
177
            $this->controllerNamespace = 'nystudio107\retour\console\controllers';
178
        }
179
        // Install our event listeners
180
        $this->installEventListeners();
181
        // Log that Retour has been loaded
182
        Craft::info(
183
            Craft::t(
184
                'retour',
185
                '{name} plugin loaded',
186
                ['name' => $this->name]
187
            ),
188
            __METHOD__
189
        );
190
    }
191
192
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
193
     * @inheritdoc
194
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
195
    public function getSettingsResponse()
196
    {
197
        // Just redirect to the plugin settings page
198
        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('retour/settings'));
199
    }
200
201
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
202
     * @inheritdoc
203
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
204
    public function getCpNavItem()
205
    {
206
        $subNavs = [];
207
        $navItem = parent::getCpNavItem();
208
        $currentUser = Craft::$app->getUser()->getIdentity();
209
        // Only show sub-navs the user has permission to view
210
        if ($currentUser->can('retour:dashboard')) {
0 ignored issues
show
Bug introduced by
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

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

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