Seomatic::installGlobalEventListeners()   C
last analyzed

Complexity

Conditions 14
Paths 4

Size

Total Lines 152
Code Lines 91

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 2
Bugs 2 Features 0
Metric Value
eloc 91
dl 0
loc 152
ccs 0
cts 91
cp 0
rs 5.2096
c 2
b 2
f 0
cc 14
nc 4
nop 0
crap 210

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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) {
0 ignored issues
show
introduced by
$element is of type craft\base\Element, thus it always evaluated to true.
Loading history...
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
0 ignored issues
show
Documentation Bug introduced by
It seems like self::devMode ? self::DE...taCacheDuration ?? null can also be of type string. However, the property $cacheDuration is declared as type integer. 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...
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', '>=');
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...
294
        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...
295
        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...
296
        self::$craft34 = version_compare(Craft::$app->getVersion(), '3.4', '>=');
0 ignored issues
show
Documentation Bug introduced by
It seems like version_compare(Craft::a...Version(), '3.4', '>=') can also be of type integer. However, the property $craft34 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...
297
        self::$craft35 = version_compare(Craft::$app->getVersion(), '3.5', '>=');
0 ignored issues
show
Documentation Bug introduced by
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 $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...
298
        self::$craft37 = version_compare(Craft::$app->getVersion(), '3.7', '>=');
0 ignored issues
show
Documentation Bug introduced by
It seems like version_compare(Craft::a...Version(), '3.7', '>=') can also be of type integer. However, the property $craft37 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...
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) {
0 ignored issues
show
introduced by
The condition $attributes !== null is always true.
Loading history...
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);
0 ignored issues
show
Bug introduced by
The method registerTwigExtension() does not exist on null. ( Ignorable by Annotation )

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

483
            self::$sandboxView->/** @scrutinizer ignore-call */ 
484
                                registerTwigExtension($seomaticTwigExtension);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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) {
0 ignored issues
show
introduced by
$debugModule is of type yii\debug\Module, thus it always evaluated to true.
Loading history...
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