Passed
Push — v3 ( 01a338...22e12d )
by Andrew
53:06 queued 24:15
created

src/Seomatic.php (1 issue)

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