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', '>='); |
||
0 ignored issues
–
show
|
|||
151 | self::$craft32 = version_compare(Craft::$app->getVersion(), '3.2', '>='); |
||
0 ignored issues
–
show
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 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;
}
![]() |
|||
152 | self::$craft33 = version_compare(Craft::$app->getVersion(), '3.3', '>='); |
||
0 ignored issues
–
show
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 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;
}
![]() |
|||
153 | self::$craft35 = version_compare(Craft::$app->getVersion(), '3.5', '>='); |
||
0 ignored issues
–
show
It seems like
version_compare(Craft::a...Version(), '3.5', '>=') can also be of type integer . However, the property $craft35 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 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;
}
![]() |
|||
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()) { |
||
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()) { |
||
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 |
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 theid
property of an instance of theAccount
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.