1 | <?php |
||
2 | /** |
||
3 | * SEOmatic plugin for Craft CMS |
||
4 | * |
||
5 | * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful, |
||
6 | * and flexible |
||
7 | * |
||
8 | * @link https://nystudio107.com |
||
9 | * @copyright Copyright (c) 2022 nystudio107 |
||
10 | */ |
||
11 | |||
12 | namespace nystudio107\seomatic; |
||
13 | |||
14 | use Craft; |
||
15 | use craft\base\Element; |
||
16 | use craft\base\ElementInterface; |
||
17 | use craft\base\Plugin; |
||
18 | use craft\elements\Entry; |
||
19 | use craft\elements\User; |
||
20 | use craft\errors\SiteNotFoundException; |
||
21 | use craft\events\DefineGqlTypeFieldsEvent; |
||
22 | use craft\events\ElementEvent; |
||
23 | use craft\events\ModelEvent; |
||
24 | use craft\events\PluginEvent; |
||
25 | use craft\events\RegisterCacheOptionsEvent; |
||
26 | use craft\events\RegisterComponentTypesEvent; |
||
27 | use craft\events\RegisterGqlQueriesEvent; |
||
28 | use craft\events\RegisterGqlSchemaComponentsEvent; |
||
29 | use craft\events\RegisterGqlTypesEvent; |
||
30 | use craft\events\RegisterPreviewTargetsEvent; |
||
31 | use craft\events\RegisterUrlRulesEvent; |
||
32 | use craft\events\RegisterUserPermissionsEvent; |
||
33 | use craft\feedme\events\RegisterFeedMeFieldsEvent; |
||
34 | use craft\feedme\Plugin as FeedMe; |
||
35 | use craft\feedme\services\Fields as FeedMeFields; |
||
36 | use craft\gql\TypeManager; |
||
37 | use craft\helpers\StringHelper; |
||
38 | use craft\services\Elements; |
||
39 | use craft\services\Fields; |
||
40 | use craft\services\Gql; |
||
41 | use craft\services\Plugins; |
||
42 | use craft\services\Sites as SitesService; |
||
43 | use craft\services\UserPermissions; |
||
44 | use craft\utilities\ClearCaches; |
||
45 | use craft\web\Application; |
||
46 | use craft\web\UrlManager; |
||
47 | use craft\web\View; |
||
48 | use markhuot\CraftQL\Builders\Schema; |
||
49 | use markhuot\CraftQL\CraftQL; |
||
50 | use markhuot\CraftQL\Events\AlterSchemaFields; |
||
51 | use nystudio107\codeeditor\autocompletes\EnvironmentVariableAutocomplete; |
||
52 | use nystudio107\codeeditor\events\RegisterCodeEditorAutocompletesEvent; |
||
53 | use nystudio107\codeeditor\events\RegisterTwigValidatorVariablesEvent; |
||
54 | use nystudio107\codeeditor\services\AutocompleteService; |
||
55 | use nystudio107\codeeditor\validators\TwigTemplateValidator; |
||
56 | use nystudio107\crafttwigsandbox\helpers\SecurityPolicy; |
||
57 | use nystudio107\crafttwigsandbox\web\SandboxView; |
||
58 | use nystudio107\fastcgicachebust\FastcgiCacheBust; |
||
59 | use nystudio107\seomatic\autocompletes\TrackingVarsAutocomplete; |
||
60 | use nystudio107\seomatic\debug\panels\SeomaticPanel; |
||
61 | use nystudio107\seomatic\fields\Seomatic_Meta as Seomatic_MetaField; |
||
62 | use nystudio107\seomatic\fields\SeoSettings as SeoSettingsField; |
||
63 | use nystudio107\seomatic\gql\arguments\SeomaticArguments; |
||
64 | use nystudio107\seomatic\gql\interfaces\SeomaticInterface; |
||
65 | use nystudio107\seomatic\gql\queries\SeomaticQuery; |
||
66 | use nystudio107\seomatic\gql\resolvers\SeomaticResolver; |
||
67 | use nystudio107\seomatic\gql\types\SeomaticEnvironmentType; |
||
68 | use nystudio107\seomatic\helpers\Environment as EnvironmentHelper; |
||
69 | use nystudio107\seomatic\helpers\Gql as GqlHelper; |
||
70 | use nystudio107\seomatic\helpers\MetaValue as MetaValueHelper; |
||
71 | use nystudio107\seomatic\helpers\Schema as SchemaHelper; |
||
72 | use nystudio107\seomatic\helpers\UrlHelper; |
||
73 | use nystudio107\seomatic\integrations\feedme\SeoSettings as SeoSettingsFeedMe; |
||
74 | use nystudio107\seomatic\listeners\GetCraftQLSchema; |
||
75 | use nystudio107\seomatic\models\MetaScriptContainer; |
||
76 | use nystudio107\seomatic\models\Settings; |
||
77 | use nystudio107\seomatic\services\ServicesTrait; |
||
78 | use nystudio107\seomatic\twigextensions\SeomaticTwigExtension; |
||
79 | use nystudio107\seomatic\variables\SeomaticVariable; |
||
80 | use yii\base\Application as BaseApplication; |
||
81 | use yii\base\Event; |
||
82 | use yii\debug\Module; |
||
83 | |||
84 | /** @noinspection MissingPropertyAnnotationsInspection */ |
||
85 | |||
86 | /** |
||
87 | * Class Seomatic |
||
88 | * |
||
89 | * @author nystudio107 |
||
90 | * @package Seomatic |
||
91 | * @since 3.0.0 |
||
92 | */ |
||
93 | class Seomatic extends Plugin |
||
94 | { |
||
95 | // Traits |
||
96 | // ========================================================================= |
||
97 | |||
98 | use ServicesTrait; |
||
99 | |||
100 | // Constants |
||
101 | // ========================================================================= |
||
102 | |||
103 | const SEOMATIC_HANDLE = 'Seomatic'; |
||
104 | |||
105 | const DEVMODE_CACHE_DURATION = 30; |
||
106 | |||
107 | const FRONTEND_SEO_FILE_LINK = 'seomatic/seo-file-link/<url:[^\/]+>/<robots:[^\/]+>/<canonical:[^\/]+>/<inline:\d+>/<fileName:[-\w\.*]+>'; |
||
108 | |||
109 | const FRONTEND_PREVIEW_PATH = 'seomatic/preview-social-media'; |
||
110 | |||
111 | const SEOMATIC_PREVIEW_AUTHORIZATION_KEY = 'seomaticPreviewAuthorizationKey'; |
||
112 | |||
113 | const GQL_ELEMENT_INTERFACES = [ |
||
114 | 'EntryInterface', |
||
115 | 'CategoryInterface', |
||
116 | 'ProductInterface', |
||
117 | ]; |
||
118 | |||
119 | const SEOMATIC_EXPRESSION_FIELD_TYPE = 'SeomaticExpressionField'; |
||
120 | const SEOMATIC_TRACKING_FIELD_TYPE = 'SeomaticTrackingField'; |
||
121 | |||
122 | // Static Properties |
||
123 | // ========================================================================= |
||
124 | |||
125 | /** |
||
126 | * @var Seomatic |
||
127 | */ |
||
128 | public static $plugin; |
||
129 | |||
130 | /** |
||
131 | * @var SeomaticVariable|null |
||
132 | */ |
||
133 | public static $seomaticVariable; |
||
134 | |||
135 | /** |
||
136 | * @var Settings |
||
137 | */ |
||
138 | public static $settings; |
||
139 | |||
140 | /** |
||
141 | * @var ElementInterface|null |
||
142 | */ |
||
143 | public static $matchedElement; |
||
144 | |||
145 | /** |
||
146 | * @var bool |
||
147 | */ |
||
148 | public static $devMode; |
||
149 | |||
150 | /** |
||
151 | * @var View |
||
152 | */ |
||
153 | public static $view; |
||
154 | |||
155 | /** |
||
156 | * @var null|View |
||
157 | */ |
||
158 | public static $sandboxView; |
||
159 | |||
160 | /** |
||
161 | * @var string |
||
162 | */ |
||
163 | public static $language; |
||
164 | |||
165 | /** |
||
166 | * @var string |
||
167 | */ |
||
168 | public static $environment; |
||
169 | |||
170 | /** |
||
171 | * @var int |
||
172 | */ |
||
173 | public static $cacheDuration; |
||
174 | |||
175 | /** |
||
176 | * @var bool |
||
177 | */ |
||
178 | public static $previewingMetaContainers = false; |
||
179 | |||
180 | /** |
||
181 | * @var bool |
||
182 | */ |
||
183 | public static $loadingMetaContainers = false; |
||
184 | |||
185 | /** |
||
186 | * @var bool |
||
187 | */ |
||
188 | public static $savingSettings = false; |
||
189 | |||
190 | /** |
||
191 | * @var bool |
||
192 | */ |
||
193 | public static $headlessRequest = false; |
||
194 | |||
195 | /** |
||
196 | * @var bool |
||
197 | */ |
||
198 | public static $craft31 = false; |
||
199 | |||
200 | /** |
||
201 | * @var bool |
||
202 | */ |
||
203 | public static $craft32 = false; |
||
204 | |||
205 | /** |
||
206 | * @var bool |
||
207 | */ |
||
208 | public static $craft33 = false; |
||
209 | |||
210 | /** |
||
211 | * @var bool |
||
212 | */ |
||
213 | public static $craft34 = false; |
||
214 | |||
215 | /** |
||
216 | * @var bool |
||
217 | */ |
||
218 | public static $craft35 = false; |
||
219 | |||
220 | /** |
||
221 | * @var bool |
||
222 | */ |
||
223 | public static $craft37 = false; |
||
224 | |||
225 | /** |
||
226 | * @var string |
||
227 | */ |
||
228 | public $schemaVersion = '3.0.12'; |
||
229 | /** |
||
230 | * @var bool |
||
231 | */ |
||
232 | public $hasCpSection = true; |
||
233 | |||
234 | // Public Properties |
||
235 | // ========================================================================= |
||
236 | |||
237 | /** |
||
238 | * @var bool |
||
239 | */ |
||
240 | public $hasCpSettings = true; |
||
241 | |||
242 | // Static Methods |
||
243 | // ========================================================================= |
||
244 | |||
245 | /** |
||
246 | * Set the matched element |
||
247 | * |
||
248 | * @param $element null|ElementInterface |
||
249 | */ |
||
250 | public static function setMatchedElement($element) |
||
251 | { |
||
252 | self::$matchedElement = $element; |
||
253 | /** @var $element Element */ |
||
254 | if ($element) { |
||
255 | self::$language = MetaValueHelper::getSiteLanguage($element->siteId); |
||
256 | } else { |
||
257 | self::$language = MetaValueHelper::getSiteLanguage(null); |
||
258 | } |
||
259 | MetaValueHelper::cache(); |
||
260 | } |
||
261 | |||
262 | // Public Methods |
||
263 | // ========================================================================= |
||
264 | |||
265 | /** |
||
266 | * @inheritdoc |
||
267 | */ |
||
268 | public function init() |
||
269 | { |
||
270 | parent::init(); |
||
271 | self::$plugin = $this; |
||
272 | // Handle any console commands |
||
273 | $request = Craft::$app->getRequest(); |
||
274 | if ($request->getIsConsoleRequest()) { |
||
275 | $this->controllerNamespace = 'nystudio107\seomatic\console\controllers'; |
||
276 | } |
||
277 | // Initialize properties |
||
278 | /** @var Settings $settings */ |
||
279 | $settings = self::$plugin->getSettings(); |
||
280 | self::$settings = $settings; |
||
281 | self::$devMode = Craft::$app->getConfig()->getGeneral()->devMode; |
||
282 | self::$view = Craft::$app->getView(); |
||
283 | // Use a Twig sandbox for SEOmatic rendering |
||
284 | $securityPolicy = SecurityPolicy::createFromFile('seomatic-sandbox', '@nystudio107/seomatic'); |
||
285 | self::$sandboxView = new SandboxView(['securityPolicy' => $securityPolicy]); |
||
286 | self::$cacheDuration = self::$devMode |
||
287 | ? self::DEVMODE_CACHE_DURATION |
||
288 | : self::$settings->metaCacheDuration ?? null; |
||
289 | self::$cacheDuration = self::$cacheDuration === null ? null : (int)self::$cacheDuration; |
||
290 | self::$environment = EnvironmentHelper::determineEnvironment(); |
||
291 | MetaValueHelper::cache(); |
||
292 | // Version helpers |
||
293 | self::$craft31 = version_compare(Craft::$app->getVersion(), '3.1', '>='); |
||
294 | self::$craft32 = version_compare(Craft::$app->getVersion(), '3.2', '>='); |
||
295 | self::$craft33 = version_compare(Craft::$app->getVersion(), '3.3', '>='); |
||
296 | self::$craft34 = version_compare(Craft::$app->getVersion(), '3.4', '>='); |
||
0 ignored issues
–
show
|
|||
297 | 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;
}
![]() |
|||
298 | self::$craft37 = version_compare(Craft::$app->getVersion(), '3.7', '>='); |
||
299 | $this->name = self::$settings->pluginName; |
||
300 | // Install our event listeners |
||
301 | $this->installEventListeners(); |
||
302 | // We're loaded |
||
303 | Craft::info( |
||
304 | Craft::t( |
||
305 | 'seomatic', |
||
306 | '{name} plugin loaded', |
||
307 | ['name' => $this->name] |
||
308 | ), |
||
309 | __METHOD__ |
||
310 | ); |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * @inheritdoc |
||
315 | */ |
||
316 | public function getSettings() |
||
317 | { |
||
318 | // For all the emojis |
||
319 | $settingsModel = parent::getSettings(); |
||
320 | if ($settingsModel !== null && !self::$savingSettings) { |
||
321 | $attributes = $settingsModel->attributes(); |
||
322 | if ($attributes !== null) { |
||
323 | foreach ($attributes as $attribute) { |
||
324 | if (is_string($settingsModel->$attribute)) { |
||
325 | $settingsModel->$attribute = html_entity_decode( |
||
326 | $settingsModel->$attribute, |
||
327 | ENT_NOQUOTES, |
||
328 | 'UTF-8' |
||
329 | ); |
||
330 | } |
||
331 | } |
||
332 | } |
||
333 | self::$savingSettings = false; |
||
334 | } |
||
335 | |||
336 | /* @var Settings $settingsModel */ |
||
337 | return $settingsModel; |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Determine whether our table schema exists or not; this is needed because |
||
342 | * migrations such as the install migration and base_install migration may |
||
343 | * not have been run by the time our init() method has been called |
||
344 | * |
||
345 | * @return bool |
||
346 | */ |
||
347 | public function migrationsAndSchemaReady(): bool |
||
348 | { |
||
349 | $pluginsService = Craft::$app->getPlugins(); |
||
350 | if ($pluginsService->doesPluginRequireDatabaseUpdate(self::$plugin)) { |
||
351 | return false; |
||
352 | } |
||
353 | if (Craft::$app->db->schema->getTableSchema('{{%seomatic_metabundles}}') === null) { |
||
354 | return false; |
||
355 | } |
||
356 | |||
357 | return true; |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Clear all the caches! |
||
362 | */ |
||
363 | public function clearAllCaches() |
||
364 | { |
||
365 | // Clear all of SEOmatic's caches |
||
366 | self::$plugin->frontendTemplates->invalidateCaches(); |
||
367 | self::$plugin->metaContainers->invalidateCaches(); |
||
368 | self::$plugin->sitemaps->invalidateCaches(); |
||
369 | SchemaHelper::invalidateCaches(); |
||
370 | // If they are using Craft 3.3 or later, clear the GraphQL caches too |
||
371 | if (self::$craft33) { |
||
372 | $gql = Craft::$app->getGql(); |
||
373 | if (method_exists($gql, 'invalidateCaches')) { |
||
374 | $gql->invalidateCaches(); |
||
375 | } |
||
376 | } |
||
377 | // If the FastCGI Cache Bust plugin is installed, clear its caches too |
||
378 | /** @var ?FastcgiCacheBust $plugin */ |
||
379 | $plugin = Craft::$app->getPlugins()->getPlugin('fastcgi-cache-bust'); |
||
380 | if ($plugin !== null) { |
||
381 | $plugin->cache->clearAll(); |
||
382 | } |
||
383 | } |
||
384 | |||
385 | // Protected Methods |
||
386 | // ========================================================================= |
||
387 | |||
388 | /** |
||
389 | * @inheritdoc |
||
390 | */ |
||
391 | public function getSettingsResponse() |
||
392 | { |
||
393 | // Just redirect to the plugin settings page |
||
394 | Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('seomatic/plugin')); |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * @inheritdoc |
||
399 | */ |
||
400 | public function getCpNavItem() |
||
401 | { |
||
402 | $subNavs = []; |
||
403 | $navItem = parent::getCpNavItem(); |
||
404 | $request = Craft::$app->getRequest(); |
||
405 | $siteSuffix = ''; |
||
406 | if ($request->getSegment(1) === 'seomatic') { |
||
407 | $segments = $request->getSegments(); |
||
408 | $lastSegment = end($segments); |
||
409 | $site = Craft::$app->getSites()->getSiteByHandle($lastSegment); |
||
410 | if ($site !== null) { |
||
411 | $siteSuffix = '/' . $lastSegment; |
||
412 | } |
||
413 | } |
||
414 | /** @var User $currentUser */ |
||
415 | $currentUser = Craft::$app->getUser()->getIdentity(); |
||
416 | // Only show sub-navs the user has permission to view |
||
417 | if ($currentUser->can('seomatic:dashboard')) { |
||
418 | $subNavs['dashboard'] = [ |
||
419 | 'label' => Craft::t('seomatic', 'Dashboard'), |
||
420 | 'url' => 'seomatic/dashboard' . $siteSuffix, |
||
421 | ]; |
||
422 | } |
||
423 | if ($currentUser->can('seomatic:global-meta')) { |
||
424 | $subNavs['global'] = [ |
||
425 | 'label' => Craft::t('seomatic', 'Global SEO'), |
||
426 | 'url' => 'seomatic/global/general' . $siteSuffix, |
||
427 | ]; |
||
428 | } |
||
429 | if ($currentUser->can('seomatic:content-meta')) { |
||
430 | $subNavs['content'] = [ |
||
431 | 'label' => Craft::t('seomatic', 'Content SEO'), |
||
432 | 'url' => 'seomatic/content' . $siteSuffix, |
||
433 | ]; |
||
434 | } |
||
435 | if ($currentUser->can('seomatic:site-settings')) { |
||
436 | $subNavs['site'] = [ |
||
437 | 'label' => Craft::t('seomatic', 'Site Settings'), |
||
438 | 'url' => 'seomatic/site/identity' . $siteSuffix, |
||
439 | ]; |
||
440 | } |
||
441 | if ($currentUser->can('seomatic:tracking-scripts')) { |
||
442 | $subNavs['tracking'] = [ |
||
443 | 'label' => Craft::t('seomatic', 'Tracking Scripts'), |
||
444 | 'url' => 'seomatic/tracking/gtag' . $siteSuffix, |
||
445 | ]; |
||
446 | } |
||
447 | $editableSettings = true; |
||
448 | $general = Craft::$app->getConfig()->getGeneral(); |
||
449 | if (self::$craft31 && !$general->allowAdminChanges) { |
||
450 | $editableSettings = false; |
||
451 | } |
||
452 | if ($currentUser->can('seomatic:plugin-settings') && $editableSettings) { |
||
453 | $subNavs['plugin'] = [ |
||
454 | 'label' => Craft::t('seomatic', 'Plugin Settings'), |
||
455 | 'url' => 'seomatic/plugin', |
||
456 | ]; |
||
457 | } |
||
458 | // SEOmatic 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 |
||
459 | if (empty($subNavs)) { |
||
460 | return null; |
||
461 | } |
||
462 | // A single sub nav item is redundant |
||
463 | if (count($subNavs) === 1) { |
||
464 | $subNavs = []; |
||
465 | } |
||
466 | $navItem = array_merge($navItem, [ |
||
467 | 'subnav' => $subNavs, |
||
468 | ]); |
||
469 | |||
470 | return $navItem; |
||
471 | } |
||
472 | |||
473 | /** |
||
474 | * Install our event listeners. |
||
475 | */ |
||
476 | protected function installEventListeners() |
||
477 | { |
||
478 | // Install our event listeners only if our table schema exists |
||
479 | if ($this->migrationsAndSchemaReady()) { |
||
480 | // Add in our Twig extensions |
||
481 | $seomaticTwigExtension = new SeomaticTwigExtension(); |
||
482 | self::$view->registerTwigExtension($seomaticTwigExtension); |
||
483 | self::$sandboxView->registerTwigExtension($seomaticTwigExtension); |
||
484 | // Register the additional TwigExtension classes |
||
485 | foreach (Seomatic::$settings->twigExtensionClasses as $className) { |
||
486 | if (class_exists($className)) { |
||
487 | self::$sandboxView->registerTwigExtension(new $className()); |
||
488 | } |
||
489 | } |
||
490 | $request = Craft::$app->getRequest(); |
||
491 | // Add in our event listeners that are needed for every request |
||
492 | $this->installGlobalEventListeners(); |
||
493 | // Install only for non-console site requests |
||
494 | if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) { |
||
495 | $this->installSiteEventListeners(); |
||
496 | } |
||
497 | // Install only for non-console Control Panel requests |
||
498 | if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) { |
||
499 | $this->installCpEventListeners(); |
||
500 | } |
||
501 | } |
||
502 | // Handler: EVENT_AFTER_INSTALL_PLUGIN |
||
503 | Event::on( |
||
504 | Plugins::class, |
||
505 | Plugins::EVENT_AFTER_INSTALL_PLUGIN, |
||
506 | function(PluginEvent $event) { |
||
507 | if ($event->plugin === $this) { |
||
508 | // Invalidate our caches after we've been installed |
||
509 | $this->clearAllCaches(); |
||
510 | // Send them to our welcome screen |
||
511 | $request = Craft::$app->getRequest(); |
||
512 | if ($request->isCpRequest) { |
||
513 | Craft::$app->getResponse()->redirect(UrlHelper::cpUrl( |
||
514 | 'seomatic/dashboard', |
||
515 | [ |
||
516 | 'showWelcome' => true, |
||
517 | ] |
||
518 | ))->send(); |
||
519 | } |
||
520 | } |
||
521 | } |
||
522 | ); |
||
523 | // Handler: ClearCaches::EVENT_REGISTER_CACHE_OPTIONS |
||
524 | Event::on( |
||
525 | ClearCaches::class, |
||
526 | ClearCaches::EVENT_REGISTER_CACHE_OPTIONS, |
||
527 | function(RegisterCacheOptionsEvent $event) { |
||
528 | Craft::debug( |
||
529 | 'ClearCaches::EVENT_REGISTER_CACHE_OPTIONS', |
||
530 | __METHOD__ |
||
531 | ); |
||
532 | // Register our Cache Options |
||
533 | $event->options = array_merge( |
||
534 | $event->options, |
||
535 | $this->customAdminCpCacheOptions() |
||
536 | ); |
||
537 | } |
||
538 | ); |
||
539 | // Handler: EVENT_BEFORE_SAVE_PLUGIN_SETTINGS |
||
540 | Event::on( |
||
541 | Plugins::class, |
||
542 | Plugins::EVENT_BEFORE_SAVE_PLUGIN_SETTINGS, |
||
543 | function(PluginEvent $event) { |
||
544 | if ($event->plugin === $this && !Craft::$app->getDb()->getSupportsMb4()) { |
||
545 | // For all the emojis |
||
546 | $settingsModel = $this->getSettings(); |
||
547 | self::$savingSettings = true; |
||
548 | if ($settingsModel !== null) { |
||
549 | $attributes = $settingsModel->attributes(); |
||
550 | if ($attributes !== null) { |
||
551 | foreach ($attributes as $attribute) { |
||
552 | if (is_string($settingsModel->$attribute)) { |
||
553 | $settingsModel->$attribute = |
||
554 | StringHelper::encodeMb4($settingsModel->$attribute); |
||
555 | } |
||
556 | } |
||
557 | } |
||
558 | } |
||
559 | } |
||
560 | } |
||
561 | ); |
||
562 | } |
||
563 | |||
564 | /** |
||
565 | * Install global event listeners for all request types |
||
566 | */ |
||
567 | protected function installGlobalEventListeners() |
||
568 | { |
||
569 | // Handler: Plugins::EVENT_AFTER_LOAD_PLUGINS |
||
570 | Event::on( |
||
571 | Plugins::class, |
||
572 | Plugins::EVENT_AFTER_LOAD_PLUGINS, |
||
573 | function() { |
||
574 | // Delay registering SEO Elements to give other plugins a chance to load first |
||
575 | $this->seoElements->getAllSeoElementTypes(false); |
||
576 | // Delay installing GQL handlers to give other plugins a chance to register their own first |
||
577 | $this->installGqlHandlers(); |
||
578 | // Install these only after all other plugins have loaded |
||
579 | $request = Craft::$app->getRequest(); |
||
580 | // Only respond to non-console site requests |
||
581 | if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) { |
||
582 | $this->handleSiteRequest(); |
||
583 | } |
||
584 | // Respond to Control Panel requests |
||
585 | if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) { |
||
586 | $this->handleAdminCpRequest(); |
||
587 | } |
||
588 | } |
||
589 | ); |
||
590 | // Handler: Fields::EVENT_REGISTER_FIELD_TYPES |
||
591 | Event::on( |
||
592 | Fields::class, |
||
593 | Fields::EVENT_REGISTER_FIELD_TYPES, |
||
594 | function(RegisterComponentTypesEvent $event) { |
||
595 | $event->types[] = SeoSettingsField::class; |
||
596 | $event->types[] = Seomatic_MetaField::class; |
||
597 | } |
||
598 | ); |
||
599 | // Handler: Element::EVENT_AFTER_PROPAGATE |
||
600 | Event::on( |
||
601 | Element::class, |
||
602 | Element::EVENT_AFTER_PROPAGATE, |
||
603 | static function(ModelEvent $event) { |
||
604 | Craft::debug( |
||
605 | 'Element::EVENT_AFTER_PROPAGATE', |
||
606 | __METHOD__ |
||
607 | ); |
||
608 | /** @var Element $element */ |
||
609 | $element = $event->sender; |
||
610 | self::$plugin->metaBundles->invalidateMetaBundleByElement( |
||
611 | $element, |
||
612 | $event->isNew |
||
613 | ); |
||
614 | if ($event->isNew) { |
||
615 | self::$plugin->sitemaps->submitSitemapForElement($element); |
||
616 | } |
||
617 | } |
||
618 | ); |
||
619 | // Handler: Elements::EVENT_AFTER_DELETE_ELEMENT |
||
620 | Event::on( |
||
621 | Elements::class, |
||
622 | Elements::EVENT_AFTER_DELETE_ELEMENT, |
||
623 | function(ElementEvent $event) { |
||
624 | Craft::debug( |
||
625 | 'Elements::EVENT_AFTER_DELETE_ELEMENT', |
||
626 | __METHOD__ |
||
627 | ); |
||
628 | /** @var Element $element */ |
||
629 | $element = $event->element; |
||
630 | self::$plugin->metaBundles->invalidateMetaBundleByElement( |
||
631 | $element, |
||
632 | false |
||
633 | ); |
||
634 | } |
||
635 | ); |
||
636 | // Add social media preview targets on Craft 3.2 or later |
||
637 | if (self::$craft32 && Seomatic::$settings->socialMediaPreviewTarget) { |
||
638 | // Handler: Entry::EVENT_REGISTER_PREVIEW_TARGETS |
||
639 | Event::on( |
||
640 | Entry::class, |
||
641 | Entry::EVENT_REGISTER_PREVIEW_TARGETS, |
||
642 | function(RegisterPreviewTargetsEvent $e) { |
||
643 | /** @var Element $element */ |
||
644 | $element = $e->sender; |
||
645 | if ($element->uri !== null) { |
||
646 | $e->previewTargets[] = [ |
||
647 | 'label' => '📣 ' . Craft::t('seomatic', 'Social Media Preview'), |
||
648 | 'url' => UrlHelper::siteUrl(self::FRONTEND_PREVIEW_PATH, [ |
||
649 | 'elementId' => $element->id, |
||
650 | 'siteId' => $element->siteId, |
||
651 | ]), |
||
652 | ]; |
||
653 | // Don't allow the preview to be accessed publicly |
||
654 | Craft::$app->getSession()->authorize(self::SEOMATIC_PREVIEW_AUTHORIZATION_KEY . $element->id); |
||
655 | } |
||
656 | } |
||
657 | ); |
||
658 | } |
||
659 | // Yii2 Debug Toolbar support |
||
660 | Event::on( |
||
661 | Application::class, |
||
662 | BaseApplication::EVENT_BEFORE_REQUEST, |
||
663 | static function() { |
||
664 | /** @var Module|null $debugModule */ |
||
665 | $debugModule = Seomatic::$settings->enableDebugToolbarPanel ? Craft::$app->getModule('debug') : null; |
||
666 | |||
667 | if ($debugModule) { |
||
668 | $debugModule->panels['seomatic'] = new SeomaticPanel([ |
||
669 | 'id' => 'seomatic', |
||
670 | 'module' => $debugModule, |
||
671 | ]); |
||
672 | } |
||
673 | } |
||
674 | ); |
||
675 | // FeedMe Support |
||
676 | if (class_exists(FeedMe::class)) { |
||
677 | Event::on( |
||
678 | FeedMeFields::class, |
||
679 | FeedMeFields::EVENT_REGISTER_FEED_ME_FIELDS, |
||
680 | function(RegisterFeedMeFieldsEvent $e) { |
||
681 | Craft::debug( |
||
682 | 'FeedMeFields::EVENT_REGISTER_FEED_ME_FIELDS', |
||
683 | __METHOD__ |
||
684 | ); |
||
685 | $e->fields[] = SeoSettingsFeedMe::class; |
||
686 | } |
||
687 | ); |
||
688 | } |
||
689 | $updateMetaBundles = function($message) { |
||
690 | Craft::debug( |
||
691 | $message, |
||
692 | __METHOD__ |
||
693 | ); |
||
694 | $seoElementTypes = Seomatic::$plugin->seoElements->getAllSeoElementTypes(); |
||
695 | foreach ($seoElementTypes as $seoElementType) { |
||
696 | $metaBundleType = $seoElementType::META_BUNDLE_TYPE ?? ''; |
||
697 | |||
698 | if ($metaBundleType) { |
||
699 | Seomatic::$plugin->metaBundles->resaveMetaBundles($metaBundleType); |
||
700 | } |
||
701 | } |
||
702 | }; |
||
703 | |||
704 | // Handler: Elements::EVENT_AFTER_SAVE_SITE |
||
705 | Event::on( |
||
706 | SitesService::class, |
||
707 | SitesService::EVENT_AFTER_SAVE_SITE, |
||
708 | function() use ($updateMetaBundles) { |
||
709 | $updateMetaBundles('SitesService::EVENT_AFTER_SAVE_SITE'); |
||
710 | } |
||
711 | ); |
||
712 | |||
713 | // Handler: Elements::EVENT_AFTER_DELETE_SITE |
||
714 | Event::on( |
||
715 | SitesService::class, |
||
716 | SitesService::EVENT_AFTER_DELETE_SITE, |
||
717 | function() use ($updateMetaBundles) { |
||
718 | $updateMetaBundles('SitesService::EVENT_AFTER_DELETE_SITE'); |
||
719 | } |
||
720 | ); |
||
721 | } |
||
722 | |||
723 | /** |
||
724 | * Register our GraphQL handlers |
||
725 | * |
||
726 | * @return void |
||
727 | */ |
||
728 | protected function installGqlHandlers() |
||
729 | { |
||
730 | // Add native GraphQL support on Craft 3.3 or later |
||
731 | if (self::$craft33) { |
||
732 | // Handler: Gql::EVENT_REGISTER_GQL_TYPES |
||
733 | Event::on( |
||
734 | Gql::class, |
||
735 | Gql::EVENT_REGISTER_GQL_TYPES, |
||
736 | function(RegisterGqlTypesEvent $event) { |
||
737 | Craft::debug( |
||
738 | 'Gql::EVENT_REGISTER_GQL_TYPES', |
||
739 | __METHOD__ |
||
740 | ); |
||
741 | $event->types[] = SeomaticInterface::class; |
||
742 | $event->types[] = SeomaticEnvironmentType::class; |
||
743 | } |
||
744 | ); |
||
745 | // Handler: Gql::EVENT_REGISTER_GQL_QUERIES |
||
746 | Event::on( |
||
747 | Gql::class, |
||
748 | Gql::EVENT_REGISTER_GQL_QUERIES, |
||
749 | function(RegisterGqlQueriesEvent $event) { |
||
750 | Craft::debug( |
||
751 | 'Gql::EVENT_REGISTER_GQL_QUERIES', |
||
752 | __METHOD__ |
||
753 | ); |
||
754 | $queries = SeomaticQuery::getQueries(); |
||
755 | foreach ($queries as $key => $value) { |
||
756 | $event->queries[$key] = $value; |
||
757 | } |
||
758 | } |
||
759 | ); |
||
760 | if (self::$craft35) { |
||
761 | // Handler: Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS |
||
762 | Event::on( |
||
763 | Gql::class, |
||
764 | Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS, |
||
765 | function(RegisterGqlSchemaComponentsEvent $event) { |
||
766 | Craft::debug( |
||
767 | 'Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS', |
||
768 | __METHOD__ |
||
769 | ); |
||
770 | $label = Craft::t('seomatic', 'Seomatic'); |
||
771 | $event->queries[$label]['seomatic.all:read'] = ['label' => Craft::t('seomatic', 'Query Seomatic data')]; |
||
772 | } |
||
773 | ); |
||
774 | } |
||
775 | } |
||
776 | // Add support for querying for SEOmatic metadata inside of element queries |
||
777 | if (self::$craft34) { |
||
778 | // Handler: TypeManager::EVENT_DEFINE_GQL_TYPE_FIELDS |
||
779 | $knownInterfaceNames = self::$plugin->seoElements->getAllSeoElementGqlInterfaceNames(); |
||
780 | Event::on( |
||
781 | TypeManager::class, |
||
782 | TypeManager::EVENT_DEFINE_GQL_TYPE_FIELDS, |
||
783 | function(DefineGqlTypeFieldsEvent $event) use ($knownInterfaceNames) { |
||
784 | if (in_array($event->typeName, $knownInterfaceNames, true)) { |
||
785 | Craft::debug( |
||
786 | 'TypeManager::EVENT_DEFINE_GQL_TYPE_FIELDS', |
||
787 | __METHOD__ |
||
788 | ); |
||
789 | |||
790 | if (GqlHelper::canQuerySeo()) { |
||
791 | // Make Seomatic tags available to all entries. |
||
792 | $event->fields['seomatic'] = [ |
||
793 | 'name' => 'seomatic', |
||
794 | 'type' => SeomaticInterface::getType(), |
||
795 | 'args' => SeomaticArguments::getArguments(), |
||
796 | 'resolve' => SeomaticResolver::class . '::resolve', |
||
797 | 'description' => Craft::t('seomatic', 'This query is used to query for SEOmatic meta data.'), |
||
798 | ]; |
||
799 | } |
||
800 | } |
||
801 | }); |
||
802 | } |
||
803 | // CraftQL Support |
||
804 | if (class_exists(CraftQL::class)) { |
||
805 | Event::on( |
||
806 | Schema::class, |
||
807 | AlterSchemaFields::EVENT, |
||
808 | [GetCraftQLSchema::class, 'handle'] |
||
809 | ); |
||
810 | } |
||
811 | } |
||
812 | |||
813 | /** |
||
814 | * Handle site requests. We do it only after we receive the event |
||
815 | * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run |
||
816 | * before our event listeners kick in |
||
817 | */ |
||
818 | protected function handleSiteRequest() |
||
819 | { |
||
820 | // Handler: View::EVENT_END_PAGE |
||
821 | Event::on( |
||
822 | View::class, |
||
823 | View::EVENT_END_PAGE, |
||
824 | function() { |
||
825 | Craft::debug( |
||
826 | 'View::EVENT_END_PAGE', |
||
827 | __METHOD__ |
||
828 | ); |
||
829 | // The page is done rendering, include our meta containers |
||
830 | if (self::$settings->renderEnabled && self::$seomaticVariable) { |
||
831 | self::$plugin->metaContainers->includeMetaContainers(); |
||
832 | } |
||
833 | } |
||
834 | ); |
||
835 | } |
||
836 | |||
837 | /** |
||
838 | * Handle Control Panel requests. We do it only after we receive the event |
||
839 | * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run |
||
840 | * before our event listeners kick in |
||
841 | */ |
||
842 | protected function handleAdminCpRequest() |
||
843 | { |
||
844 | // Don't cache Control Panel requests |
||
845 | self::$cacheDuration = 1; |
||
846 | // Prefix the Control Panel title |
||
847 | self::$view->hook('cp.layouts.base', function(&$context) { |
||
848 | if (self::$devMode) { |
||
849 | $context['docTitle'] = self::$settings->devModeCpTitlePrefix . $context['docTitle']; |
||
850 | } else { |
||
851 | $context['docTitle'] = self::$settings->cpTitlePrefix . $context['docTitle']; |
||
852 | } |
||
853 | }); |
||
854 | } |
||
855 | |||
856 | /** |
||
857 | * Install site event listeners for site requests only |
||
858 | */ |
||
859 | protected function installSiteEventListeners() |
||
860 | { |
||
861 | // Load the sitemap containers |
||
862 | self::$plugin->sitemaps->loadSitemapContainers(); |
||
863 | // Load the frontend template containers |
||
864 | self::$plugin->frontendTemplates->loadFrontendTemplateContainers(); |
||
865 | // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES |
||
866 | Event::on( |
||
867 | UrlManager::class, |
||
868 | UrlManager::EVENT_REGISTER_SITE_URL_RULES, |
||
869 | function(RegisterUrlRulesEvent $event) { |
||
870 | Craft::debug( |
||
871 | 'UrlManager::EVENT_REGISTER_SITE_URL_RULES', |
||
872 | __METHOD__ |
||
873 | ); |
||
874 | // FileController |
||
875 | $route = self::$plugin->handle . '/file/seo-file-link'; |
||
876 | $event->rules[self::FRONTEND_SEO_FILE_LINK] = ['route' => $route]; |
||
877 | // PreviewController |
||
878 | $route = self::$plugin->handle . '/preview/social-media'; |
||
879 | $event->rules[self::FRONTEND_PREVIEW_PATH] = ['route' => $route]; |
||
880 | // Register our Control Panel routes |
||
881 | $event->rules = array_merge( |
||
882 | $event->rules, |
||
883 | $this->customFrontendRoutes() |
||
884 | ); |
||
885 | } |
||
886 | ); |
||
887 | } |
||
888 | |||
889 | /** |
||
890 | * Return the custom frontend routes |
||
891 | * |
||
892 | * @return array |
||
893 | */ |
||
894 | protected function customFrontendRoutes(): array |
||
895 | { |
||
896 | return [ |
||
897 | ]; |
||
898 | } |
||
899 | |||
900 | /** |
||
901 | * Install site event listeners for Control Panel requests only |
||
902 | */ |
||
903 | protected function installCpEventListeners() |
||
904 | { |
||
905 | // Load the frontend template containers |
||
906 | self::$plugin->frontendTemplates->loadFrontendTemplateContainers(); |
||
907 | // Handler: UrlManager::EVENT_REGISTER_CP_URL_RULES |
||
908 | Event::on( |
||
909 | UrlManager::class, |
||
910 | UrlManager::EVENT_REGISTER_CP_URL_RULES, |
||
911 | function(RegisterUrlRulesEvent $event) { |
||
912 | Craft::debug( |
||
913 | 'UrlManager::EVENT_REGISTER_CP_URL_RULES', |
||
914 | __METHOD__ |
||
915 | ); |
||
916 | // Register our Control Panel routes |
||
917 | $event->rules = array_merge( |
||
918 | $event->rules, |
||
919 | $this->customAdminCpRoutes() |
||
920 | ); |
||
921 | } |
||
922 | ); |
||
923 | // Handler: UserPermissions::EVENT_REGISTER_PERMISSIONS |
||
924 | Event::on( |
||
925 | UserPermissions::class, |
||
926 | UserPermissions::EVENT_REGISTER_PERMISSIONS, |
||
927 | function(RegisterUserPermissionsEvent $event) { |
||
928 | Craft::debug( |
||
929 | 'UserPermissions::EVENT_REGISTER_PERMISSIONS', |
||
930 | __METHOD__ |
||
931 | ); |
||
932 | // Register our custom permissions |
||
933 | $event->permissions[Craft::t('seomatic', 'SEOmatic')] = $this->customAdminCpPermissions(); |
||
934 | } |
||
935 | ); |
||
936 | // Handler: AutocompleteService::EVENT_REGISTER_CODEEDITOR_AUTOCOMPLETES |
||
937 | Event::on(AutocompleteService::class, AutocompleteService::EVENT_REGISTER_CODEEDITOR_AUTOCOMPLETES, |
||
938 | function(RegisterCodeEditorAutocompletesEvent $event) { |
||
939 | if ($event->fieldType === self::SEOMATIC_EXPRESSION_FIELD_TYPE) { |
||
940 | $event->types[] = EnvironmentVariableAutocomplete::class; |
||
941 | } |
||
942 | if ($event->fieldType === self::SEOMATIC_TRACKING_FIELD_TYPE) { |
||
943 | $event->types[] = TrackingVarsAutocomplete::class; |
||
944 | } |
||
945 | } |
||
946 | ); |
||
947 | // Handler: TwigTemplateValidator::EVENT_REGISTER_TWIG_VALIDATOR_VARIABLES |
||
948 | Event::on(TwigTemplateValidator::class, |
||
949 | TwigTemplateValidator::EVENT_REGISTER_TWIG_VALIDATOR_VARIABLES, |
||
950 | function(RegisterTwigValidatorVariablesEvent $event) { |
||
951 | if (Seomatic::$seomaticVariable === null) { |
||
952 | Seomatic::$seomaticVariable = new SeomaticVariable(); |
||
953 | Seomatic::$plugin->metaContainers->loadGlobalMetaContainers(); |
||
954 | Seomatic::$seomaticVariable->init(); |
||
955 | } |
||
956 | $event->variables['seomatic'] = Seomatic::$seomaticVariable; |
||
957 | } |
||
958 | ); |
||
959 | } |
||
960 | |||
961 | /** |
||
962 | * Return the custom Control Panel routes |
||
963 | * |
||
964 | * @return array |
||
965 | */ |
||
966 | protected function customAdminCpRoutes(): array |
||
967 | { |
||
968 | return [ |
||
969 | 'seomatic' => |
||
970 | '', |
||
971 | 'seomatic/dashboard' => |
||
972 | 'seomatic/settings/dashboard', |
||
973 | 'seomatic/dashboard/<siteHandle:{handle}>' => |
||
974 | 'seomatic/settings/dashboard', |
||
975 | |||
976 | 'seomatic/global' => [ |
||
977 | 'route' => 'seomatic/settings/global', |
||
978 | 'defaults' => ['subSection' => 'general'], |
||
979 | ], |
||
980 | 'seomatic/global/<subSection:{handle}>' => |
||
981 | 'seomatic/settings/global', |
||
982 | 'seomatic/global/<subSection:{handle}>/<siteHandle:{handle}>' => |
||
983 | 'seomatic/settings/global', |
||
984 | |||
985 | 'seomatic/content' => |
||
986 | 'seomatic/settings/content', |
||
987 | 'seomatic/content/<siteHandle:{handle}>' => |
||
988 | 'seomatic/settings/content', |
||
989 | |||
990 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{handle}>' => |
||
991 | 'seomatic/settings/edit-content', |
||
992 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{handle}>/<siteHandle:{handle}>' => |
||
993 | 'seomatic/settings/edit-content', |
||
994 | |||
995 | // Seemingly duplicate route needed to handle Solspace Calendar, which allows characters like -'s |
||
996 | // in their handles |
||
997 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{slug}>' => |
||
998 | 'seomatic/settings/edit-content', |
||
999 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{slug}>/<siteHandle:{handle}>' => |
||
1000 | 'seomatic/settings/edit-content', |
||
1001 | |||
1002 | 'seomatic/site' => [ |
||
1003 | 'route' => 'seomatic/settings/site', |
||
1004 | 'defaults' => ['subSection' => 'identity'], |
||
1005 | ], |
||
1006 | 'seomatic/site/<subSection:{handle}>' => |
||
1007 | 'seomatic/settings/site', |
||
1008 | 'seomatic/site/<subSection:{handle}>/<siteHandle:{handle}>' => |
||
1009 | 'seomatic/settings/site', |
||
1010 | |||
1011 | 'seomatic/tracking' => [ |
||
1012 | 'route' => 'seomatic/settings/tracking', |
||
1013 | 'defaults' => ['subSection' => 'googleAnalytics'], |
||
1014 | ], |
||
1015 | 'seomatic/tracking/<subSection:{handle}>' => |
||
1016 | 'seomatic/settings/tracking', |
||
1017 | 'seomatic/tracking/<subSection:{handle}>/<siteHandle:{handle}>' => |
||
1018 | 'seomatic/settings/tracking', |
||
1019 | |||
1020 | 'seomatic/plugin' => |
||
1021 | 'seomatic/settings/plugin', |
||
1022 | ]; |
||
1023 | } |
||
1024 | |||
1025 | /** |
||
1026 | * Returns the custom Control Panel user permissions. |
||
1027 | * |
||
1028 | * @return array |
||
1029 | */ |
||
1030 | protected function customAdminCpPermissions(): array |
||
1031 | { |
||
1032 | // The script meta containers for the global meta bundle |
||
1033 | try { |
||
1034 | $currentSiteId = Craft::$app->getSites()->getCurrentSite()->id ?? 1; |
||
1035 | } catch (SiteNotFoundException $e) { |
||
1036 | $currentSiteId = 1; |
||
1037 | } |
||
1038 | // Dynamic permissions for the scripts |
||
1039 | $metaBundle = self::$plugin->metaBundles->getGlobalMetaBundle($currentSiteId); |
||
1040 | $scriptsPerms = []; |
||
1041 | if ($metaBundle !== null) { |
||
1042 | $scripts = self::$plugin->metaBundles->getContainerDataFromBundle( |
||
1043 | $metaBundle, |
||
1044 | MetaScriptContainer::CONTAINER_TYPE |
||
1045 | ); |
||
1046 | foreach ($scripts as $scriptHandle => $scriptData) { |
||
1047 | $scriptsPerms["seomatic:tracking-scripts:${scriptHandle}"] = [ |
||
1048 | 'label' => Craft::t('seomatic', $scriptData->name), |
||
1049 | ]; |
||
1050 | } |
||
1051 | } |
||
1052 | |||
1053 | return [ |
||
1054 | 'seomatic:dashboard' => [ |
||
1055 | 'label' => Craft::t('seomatic', 'Dashboard'), |
||
1056 | ], |
||
1057 | 'seomatic:global-meta' => [ |
||
1058 | 'label' => Craft::t('seomatic', 'Edit Global Meta'), |
||
1059 | 'nested' => [ |
||
1060 | 'seomatic:global-meta:general' => [ |
||
1061 | 'label' => Craft::t('seomatic', 'General'), |
||
1062 | ], |
||
1063 | 'seomatic:global-meta:twitter' => [ |
||
1064 | 'label' => Craft::t('seomatic', 'Twitter'), |
||
1065 | ], |
||
1066 | 'seomatic:global-meta:facebook' => [ |
||
1067 | 'label' => Craft::t('seomatic', 'Facebook'), |
||
1068 | ], |
||
1069 | 'seomatic:global-meta:robots' => [ |
||
1070 | 'label' => Craft::t('seomatic', 'Robots'), |
||
1071 | ], |
||
1072 | 'seomatic:global-meta:humans' => [ |
||
1073 | 'label' => Craft::t('seomatic', 'Humans'), |
||
1074 | ], |
||
1075 | 'seomatic:global-meta:ads' => [ |
||
1076 | 'label' => Craft::t('seomatic', 'Ads'), |
||
1077 | ], |
||
1078 | 'seomatic:global-meta:security' => [ |
||
1079 | 'label' => Craft::t('seomatic', 'Security'), |
||
1080 | ], |
||
1081 | ], |
||
1082 | ], |
||
1083 | 'seomatic:content-meta' => [ |
||
1084 | 'label' => Craft::t('seomatic', 'Edit Content SEO'), |
||
1085 | 'nested' => [ |
||
1086 | 'seomatic:content-meta:general' => [ |
||
1087 | 'label' => Craft::t('seomatic', 'General'), |
||
1088 | ], |
||
1089 | 'seomatic:content-meta:twitter' => [ |
||
1090 | 'label' => Craft::t('seomatic', 'Twitter'), |
||
1091 | ], |
||
1092 | 'seomatic:content-meta:facebook' => [ |
||
1093 | 'label' => Craft::t('seomatic', 'Facebook'), |
||
1094 | ], |
||
1095 | 'seomatic:content-meta:sitemap' => [ |
||
1096 | 'label' => Craft::t('seomatic', 'Sitemap'), |
||
1097 | ], |
||
1098 | ], |
||
1099 | ], |
||
1100 | 'seomatic:site-settings' => [ |
||
1101 | 'label' => Craft::t('seomatic', 'Edit Site Settings'), |
||
1102 | 'nested' => [ |
||
1103 | 'seomatic:site-settings:identity' => [ |
||
1104 | 'label' => Craft::t('seomatic', 'Identity'), |
||
1105 | ], |
||
1106 | 'seomatic:site-settings:creator' => [ |
||
1107 | 'label' => Craft::t('seomatic', 'Creator'), |
||
1108 | ], |
||
1109 | 'seomatic:site-settings:social' => [ |
||
1110 | 'label' => Craft::t('seomatic', 'Social Media'), |
||
1111 | ], |
||
1112 | 'seomatic:site-settings:sitemap' => [ |
||
1113 | 'label' => Craft::t('seomatic', 'Sitemap'), |
||
1114 | ], |
||
1115 | 'seomatic:site-settings:miscellaneous' => [ |
||
1116 | 'label' => Craft::t('seomatic', 'Miscellaneous'), |
||
1117 | ], |
||
1118 | ], |
||
1119 | ], |
||
1120 | 'seomatic:tracking-scripts' => [ |
||
1121 | 'label' => Craft::t('seomatic', 'Edit Tracking Scripts'), |
||
1122 | 'nested' => $scriptsPerms, |
||
1123 | ], |
||
1124 | 'seomatic:plugin-settings' => [ |
||
1125 | 'label' => Craft::t('seomatic', 'Edit Plugin Settings'), |
||
1126 | ], |
||
1127 | ]; |
||
1128 | } |
||
1129 | |||
1130 | /** |
||
1131 | * Returns the custom Control Panel cache options. |
||
1132 | * |
||
1133 | * @return array |
||
1134 | */ |
||
1135 | protected function customAdminCpCacheOptions(): array |
||
1136 | { |
||
1137 | return [ |
||
1138 | // Frontend template caches |
||
1139 | [ |
||
1140 | 'key' => 'seomatic-frontendtemplate-caches', |
||
1141 | 'label' => Craft::t('seomatic', 'SEOmatic frontend template caches'), |
||
1142 | 'action' => [self::$plugin->frontendTemplates, 'invalidateCaches'], |
||
1143 | ], |
||
1144 | // Meta bundle caches |
||
1145 | [ |
||
1146 | 'key' => 'seomatic-metabundle-caches', |
||
1147 | 'label' => Craft::t('seomatic', 'SEOmatic metadata caches'), |
||
1148 | 'action' => [self::$plugin->metaContainers, 'invalidateCaches'], |
||
1149 | ], |
||
1150 | // Sitemap caches |
||
1151 | [ |
||
1152 | 'key' => 'seomatic-sitemap-caches', |
||
1153 | 'label' => Craft::t('seomatic', 'SEOmatic sitemap caches'), |
||
1154 | 'action' => [self::$plugin->sitemaps, 'invalidateCaches'], |
||
1155 | ], |
||
1156 | // Schema caches |
||
1157 | [ |
||
1158 | 'key' => 'seomatic-schema-caches', |
||
1159 | 'label' => Craft::t('seomatic', 'SEOmatic schema caches'), |
||
1160 | 'action' => [SchemaHelper::class, 'invalidateCaches'], |
||
1161 | ], |
||
1162 | ]; |
||
1163 | } |
||
1164 | |||
1165 | /** |
||
1166 | * @inheritdoc |
||
1167 | */ |
||
1168 | protected function createSettingsModel() |
||
1169 | { |
||
1170 | return new Settings(); |
||
1171 | } |
||
1172 | } |
||
1173 |
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.