Passed
Push — v3 ( d49e4d...c31cc7 )
by Andrew
17:27 queued 08:45
created

Seomatic::customFrontendRoutes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
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\fields\SeoSettings as SeoSettingsField;
15
use nystudio107\seomatic\fields\Seomatic_Meta as Seomatic_MetaField;
16
use nystudio107\seomatic\helpers\Environment as EnvironmentHelper;
17
use nystudio107\seomatic\helpers\MetaValue as MetaValueHelper;
18
use nystudio107\seomatic\listeners\GetCraftQLSchema;
19
use nystudio107\seomatic\models\MetaScriptContainer;
20
use nystudio107\seomatic\models\Settings;
21
use nystudio107\seomatic\services\FrontendTemplates as FrontendTemplatesService;
22
use nystudio107\seomatic\services\Helper as HelperService;
23
use nystudio107\seomatic\services\JsonLd as JsonLdService;
24
use nystudio107\seomatic\services\Link as LinkService;
25
use nystudio107\seomatic\services\MetaBundles as MetaBundlesService;
26
use nystudio107\seomatic\services\MetaContainers as MetaContainersService;
27
use nystudio107\seomatic\services\Script as ScriptService;
28
use nystudio107\seomatic\services\SeoElements as SeoElementsService;
29
use nystudio107\seomatic\services\Sitemaps as SitemapsService;
30
use nystudio107\seomatic\services\Tag as TagService;
31
use nystudio107\seomatic\services\Title as TitleService;
32
use nystudio107\seomatic\twigextensions\SeomaticTwigExtension;
33
use nystudio107\seomatic\variables\SeomaticVariable;
34
35
use nystudio107\fastcgicachebust\FastcgiCacheBust;
0 ignored issues
show
Bug introduced by
The type nystudio107\fastcgicachebust\FastcgiCacheBust was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
37
use Craft;
38
use craft\base\Element;
39
use craft\base\ElementInterface;
40
use craft\base\Plugin;
41
use craft\elements\User;
42
use craft\elements\Entry;
43
use craft\errors\SiteNotFoundException;
44
use craft\events\ElementEvent;
45
use craft\events\DeleteTemplateCachesEvent;
46
use craft\events\PluginEvent;
47
use craft\events\RegisterCacheOptionsEvent;
48
use craft\events\RegisterComponentTypesEvent;
49
use craft\events\RegisterPreviewTargetsEvent;
50
use craft\events\RegisterUrlRulesEvent;
51
use craft\events\RegisterUserPermissionsEvent;
52
use craft\helpers\StringHelper;
53
use craft\services\Elements;
54
use craft\services\Fields;
55
use craft\services\Plugins;
56
use craft\services\TemplateCaches;
57
use craft\services\UserPermissions;
58
use craft\helpers\UrlHelper;
59
use craft\utilities\ClearCaches;
60
use craft\web\UrlManager;
61
use craft\web\View;
62
63
use markhuot\CraftQL\Builders\Schema;
0 ignored issues
show
Bug introduced by
The type markhuot\CraftQL\Builders\Schema was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
64
use markhuot\CraftQL\CraftQL;
0 ignored issues
show
Bug introduced by
The type markhuot\CraftQL\CraftQL was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
65
use markhuot\CraftQL\Events\AlterSchemaFields;
0 ignored issues
show
Bug introduced by
The type markhuot\CraftQL\Events\AlterSchemaFields was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
66
67
use yii\base\Event;
68
69
/** @noinspection MissingPropertyAnnotationsInspection */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
70
71
/**
72
 * Class Seomatic
73
 *
74
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
75
 * @package   Seomatic
76
 * @since     3.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
77
 *
78
 * @property  FrontendTemplatesService $frontendTemplates
79
 * @property  HelperService            $helper
80
 * @property  JsonLdService            $jsonLd
81
 * @property  LinkService              $link
82
 * @property  MetaBundlesService       $metaBundles
83
 * @property  MetaContainersService    $metaContainers
84
 * @property  ScriptService            $script
85
 * @property  SeoElementsService       $seoElements
86
 * @property  SitemapsService          $sitemaps
87
 * @property  TagService               $tag
88
 * @property  TitleService             $title
89
 */
90
class Seomatic extends Plugin
91
{
92
    // Constants
93
    // =========================================================================
94
95
    const SEOMATIC_HANDLE = 'Seomatic';
96
97
    const DEVMODE_CACHE_DURATION = 30;
98
99
    const FRONTEND_SEO_FILE_LINK = 'seomatic/seo-file-link/<url:[^\/]+>/<robots:[^\/]+>/<canonical:[^\/]+>/<inline:\d+>/<fileName:[-\w\.*]+>';
100
101
    const FRONTEND_PREVIEW_PATH = 'seomatic/preview-social-media';
102
103
    const SEOMATIC_PREVIEW_AUTHORIZATION_KEY = 'seomaticPreviewAuthorizationKey';
104
105
    // Static Properties
106
    // =========================================================================
107
108
    /**
109
     * @var Seomatic
110
     */
111
    public static $plugin;
112
113
    /**
114
     * @var SeomaticVariable
115
     */
116
    public static $seomaticVariable;
117
118
    /**
119
     * @var Settings
120
     */
121
    public static $settings;
122
123
    /**
124
     * @var ElementInterface
125
     */
126
    public static $matchedElement;
127
128
    /**
129
     * @var bool
130
     */
131
    public static $devMode;
132
133
    /**
134
     * @var View
135
     */
136
    public static $view;
137
138
    /**
139
     * @var string
140
     */
141
    public static $language;
142
143
    /**
144
     * @var string
145
     */
146
    public static $environment;
147
148
    /**
149
     * @var int
150
     */
151
    public static $cacheDuration;
152
153
    /**
154
     * @var bool
155
     */
156
    public static $previewingMetaContainers = false;
157
158
    /**
159
     * @var bool
160
     */
161
    public static $loadingContainers = false;
162
163
    /**
164
     * @var bool
165
     */
166
    public static $savingSettings = false;
167
168
    /**
169
     * @var bool
170
     */
171
    public static $craft31 = false;
172
173
    /**
174
     * @var bool
175
     */
176
    public static $craft32 = false;
177
178
    // Static Methods
179
    // =========================================================================
180
181
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $element should have a doc-comment as per coding-style.
Loading history...
182
     * Set the matched element
183
     *
184
     * @param $element null|ElementInterface
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
185
     */
186
    public static function setMatchedElement($element)
187
    {
188
        self::$matchedElement = $element;
189
        /** @var  $element Element */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
190
        if ($element) {
0 ignored issues
show
introduced by
$element is of type craft\base\Element, thus it always evaluated to true.
Loading history...
191
            self::$language = MetaValueHelper::getSiteLanguage($element->siteId);
192
        } else {
193
            self::$language = MetaValueHelper::getSiteLanguage(null);
194
        }
195
        MetaValueHelper::cache();
196
    }
197
198
    // Public Properties
199
    // =========================================================================
200
201
    /**
202
     * @var string
203
     */
204
    public $schemaVersion = '3.0.8';
205
206
    // Public Methods
207
    // =========================================================================
208
209
    /**
210
     * @inheritdoc
211
     */
212
    public function init()
213
    {
214
        parent::init();
215
        self::$plugin = $this;
216
        // Handle any console commands
217
        $request = Craft::$app->getRequest();
218
        if ($request->getIsConsoleRequest()) {
219
            $this->controllerNamespace = 'nystudio107\seomatic\console\controllers';
220
        }
221
        // Initialize properties
222
        self::$settings = self::$plugin->getSettings();
223
        self::$devMode = Craft::$app->getConfig()->getGeneral()->devMode;
224
        self::$view = Craft::$app->getView();
225
        self::$cacheDuration = self::$devMode
226
            ? $this::DEVMODE_CACHE_DURATION
227
            : null;
228
        self::$environment = EnvironmentHelper::determineEnvironment();
229
        MetaValueHelper::cache();
230
        // Version helpers
231
        self::$craft31 = version_compare(Craft::$app->getVersion(), '3.1', '>=');
232
        self::$craft32 = version_compare(Craft::$app->getVersion(), '3.2', '>=');
233
        $this->name = self::$settings->pluginName;
234
        // Install our event listeners
235
        $this->installEventListeners();
236
        // We're loaded
237
        Craft::info(
238
            Craft::t(
239
                'seomatic',
240
                '{name} plugin loaded',
241
                ['name' => $this->name]
242
            ),
243
            __METHOD__
244
        );
245
    }
246
247
    /**
248
     * @inheritdoc
249
     */
250
    public function getSettings()
251
    {
252
        // For all the emojis
253
        $settingsModel = parent::getSettings();
254
        if ($settingsModel !== null && !self::$savingSettings) {
255
            $attributes = $settingsModel->attributes();
256
            if ($attributes !== null) {
0 ignored issues
show
introduced by
The condition $attributes !== null is always true.
Loading history...
257
                foreach ($attributes as $attribute) {
258
                    if (is_string($settingsModel->$attribute)) {
259
                        $settingsModel->$attribute = html_entity_decode(
260
                            $settingsModel->$attribute,
261
                            ENT_NOQUOTES,
262
                            'UTF-8'
263
                        );
264
                    }
265
                }
266
            }
267
            self::$savingSettings = false;
268
        }
269
270
        return $settingsModel;
271
    }
272
273
    /**
274
     * @inheritdoc
275
     */
276
    public function getSettingsResponse()
277
    {
278
        // Just redirect to the plugin settings page
279
        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('seomatic/plugin'));
280
    }
281
282
    /**
283
     * @inheritdoc
284
     */
285
    public function getCpNavItem()
286
    {
287
        $subNavs = [];
288
        $navItem = parent::getCpNavItem();
289
        /** @var User $currentUser */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
290
        $currentUser = Craft::$app->getUser()->getIdentity();
291
        // Only show sub-navs the user has permission to view
292
        if ($currentUser->can('seomatic:dashboard')) {
293
            $subNavs['dashboard'] = [
294
                'label' => 'Dashboard',
295
                'url' => 'seomatic/dashboard',
296
            ];
297
        }
298
        if ($currentUser->can('seomatic:global-meta')) {
299
            $subNavs['global'] = [
300
                'label' => 'Global SEO',
301
                'url' => 'seomatic/global',
302
            ];
303
        }
304
        if ($currentUser->can('seomatic:content-meta')) {
305
            $subNavs['content'] = [
306
                'label' => 'Content SEO',
307
                'url' => 'seomatic/content',
308
            ];
309
        }
310
        if ($currentUser->can('seomatic:site-settings')) {
311
            $subNavs['site'] = [
312
                'label' => 'Site Settings',
313
                'url' => 'seomatic/site',
314
            ];
315
        }
316
        if ($currentUser->can('seomatic:tracking-scripts')) {
317
            $subNavs['tracking'] = [
318
                'label' => 'Tracking Scripts',
319
                'url' => 'seomatic/tracking',
320
            ];
321
        }
322
        $editableSettings = true;
323
        $general = Craft::$app->getConfig()->getGeneral();
324
        if (self::$craft31 && !$general->allowAdminChanges) {
325
            $editableSettings = false;
326
        }
327
        if ($currentUser->can('seomatic:plugin-settings') && $editableSettings) {
328
            $subNavs['plugin'] = [
329
                'label' => 'Plugin Settings',
330
                'url' => 'seomatic/plugin',
331
            ];
332
        }
333
        $navItem = array_merge($navItem, [
334
            'subnav' => $subNavs,
335
        ]);
336
337
        return $navItem;
338
    }
339
340
    /**
341
     * Clear all the caches!
342
     */
343
    public function clearAllCaches()
344
    {
345
        // Clear all of SEOmatic's caches
346
        self::$plugin->frontendTemplates->invalidateCaches();
347
        self::$plugin->metaContainers->invalidateCaches();
348
        self::$plugin->sitemaps->invalidateCaches();
349
        // If the FastCGI Cache Bust plugin is installed, clear its caches too
350
        $plugin = Craft::$app->getPlugins()->getPlugin('fastcgi-cache-bust');
351
        if ($plugin !== null) {
352
            FastcgiCacheBust::$plugin->cache->clearAll();
353
        }
354
    }
355
356
    // Protected Methods
357
    // =========================================================================
358
359
    /**
360
     * Determine whether our table schema exists or not; this is needed because
361
     * migrations such as the install migration and base_install migration may
362
     * not have been run by the time our init() method has been called
363
     *
364
     * @return bool
365
     */
366
    protected function tableSchemaExists(): bool
367
    {
368
        return (Craft::$app->db->schema->getTableSchema('{{%seomatic_metabundles}}') !== null);
369
    }
370
371
    /**
372
     * Install our event listeners.
373
     */
374
    protected function installEventListeners()
375
    {
376
        // Install our event listeners only if our table schema exists
377
        if ($this->tableSchemaExists()) {
378
            // Add in our Twig extensions
379
            self::$view->registerTwigExtension(new SeomaticTwigExtension);
380
            $request = Craft::$app->getRequest();
381
            // Add in our event listeners that are needed for every request
382
            $this->installGlobalEventListeners();
383
            // Install only for non-console site requests
384
            if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
385
                $this->installSiteEventListeners();
386
            }
387
            // Install only for non-console Control Panel requests
388
            if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
389
                $this->installCpEventListeners();
390
            }
391
        }
392
        // Handler: EVENT_AFTER_INSTALL_PLUGIN
393
        Event::on(
394
            Plugins::class,
395
            Plugins::EVENT_AFTER_INSTALL_PLUGIN,
396
            function (PluginEvent $event) {
397
                if ($event->plugin === $this) {
398
                    // Invalidate our caches after we've been installed
399
                    $this->clearAllCaches();
400
                    // Send them to our welcome screen
401
                    $request = Craft::$app->getRequest();
402
                    if ($request->isCpRequest) {
403
                        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl(
404
                            'seomatic/dashboard',
405
                            [
406
                                'showWelcome' => true,
407
                            ]
408
                        ))->send();
409
                    }
410
                }
411
            }
412
        );
413
        // Handler: ClearCaches::EVENT_REGISTER_CACHE_OPTIONS
414
        Event::on(
415
            ClearCaches::class,
416
            ClearCaches::EVENT_REGISTER_CACHE_OPTIONS,
417
            function (RegisterCacheOptionsEvent $event) {
418
                Craft::debug(
419
                    'ClearCaches::EVENT_REGISTER_CACHE_OPTIONS',
420
                    __METHOD__
421
                );
422
                // Register our Cache Options
423
                $event->options = array_merge(
424
                    $event->options,
425
                    $this->customAdminCpCacheOptions()
426
                );
427
            }
428
        );
429
        // Handler: EVENT_BEFORE_SAVE_PLUGIN_SETTINGS
430
        Event::on(
431
            Plugins::class,
432
            Plugins::EVENT_BEFORE_SAVE_PLUGIN_SETTINGS,
433
            function (PluginEvent $event) {
434
                if ($event->plugin === $this && !Craft::$app->getDb()->getSupportsMb4()) {
435
                    // For all the emojis
436
                    $settingsModel = $this->getSettings();
437
                    self::$savingSettings = true;
438
                    if ($settingsModel !== null) {
439
                        $attributes = $settingsModel->attributes();
440
                        if ($attributes !== null) {
441
                            foreach ($attributes as $attribute) {
442
                                if (is_string($settingsModel->$attribute)) {
443
                                    $settingsModel->$attribute =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
444
                                        StringHelper::encodeMb4($settingsModel->$attribute);
445
                                }
446
                            }
447
                        }
448
                    }
449
                }
450
            }
451
        );
452
    }
453
454
    /**
455
     * Install global event listeners for all request types
456
     */
457
    protected function installGlobalEventListeners()
458
    {
459
        // Handler: Plugins::EVENT_AFTER_LOAD_PLUGINS
460
        Event::on(
461
            Plugins::class,
462
            Plugins::EVENT_AFTER_LOAD_PLUGINS,
463
            function () {
464
                // Install these only after all other plugins have loaded
465
                $request = Craft::$app->getRequest();
466
                // Allow the SeoElements to register their own event handlers
467
                self::$plugin->seoElements->getAllSeoElementTypes();
468
                // Only respond to non-console site requests
469
                if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
470
                    $this->handleSiteRequest();
471
                }
472
                // Respond to Control Panel requests
473
                if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
474
                    $this->handleAdminCpRequest();
475
                }
476
            }
477
        );
478
        // Handler: Fields::EVENT_REGISTER_FIELD_TYPES
479
        Event::on(
480
            Fields::class,
481
            Fields::EVENT_REGISTER_FIELD_TYPES,
482
            function (RegisterComponentTypesEvent $event) {
483
                $event->types[] = SeoSettingsField::class;
484
                $event->types[] = Seomatic_MetaField::class;
485
            }
486
        );
487
        // Handler: TemplateCaches::EVENT_AFTER_DELETE_CACHES
488
        Event::on(
489
            TemplateCaches::class,
490
            TemplateCaches::EVENT_AFTER_DELETE_CACHES,
491
            function (DeleteTemplateCachesEvent $event) {
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

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

491
            function (/** @scrutinizer ignore-unused */ DeleteTemplateCachesEvent $event) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
492
                self::$plugin->metaContainers->invalidateCaches();
493
            }
494
        );
495
        // Handler: Elements::EVENT_AFTER_SAVE_ELEMENT
496
        Event::on(
497
            Elements::class,
498
            Elements::EVENT_AFTER_SAVE_ELEMENT,
499
            function (ElementEvent $event) {
500
                Craft::debug(
501
                    'Elements::EVENT_AFTER_SAVE_ELEMENT',
502
                    __METHOD__
503
                );
504
                /** @var  $element Element */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
505
                $element = $event->element;
506
                self::$plugin->metaBundles->invalidateMetaBundleByElement(
507
                    $element,
508
                    $event->isNew
509
                );
510
                if ($event->isNew) {
511
                    self::$plugin->sitemaps->submitSitemapForElement($element);
512
                }
513
            }
514
        );
515
        // Handler: Elements::EVENT_AFTER_DELETE_ELEMENT
516
        Event::on(
517
            Elements::class,
518
            Elements::EVENT_AFTER_DELETE_ELEMENT,
519
            function (ElementEvent $event) {
520
                Craft::debug(
521
                    'Elements::EVENT_AFTER_DELETE_ELEMENT',
522
                    __METHOD__
523
                );
524
                /** @var  $element Element */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
525
                $element = $event->element;
526
                self::$plugin->metaBundles->invalidateMetaBundleByElement(
527
                    $element,
528
                    false
529
                );
530
            }
531
        );
532
        if (self::$craft32 && Seomatic::$settings->socialMediaPreviewTarget) {
533
            // Handler: Entry::EVENT_REGISTER_PREVIEW_TARGETS
534
            Event::on(
535
                Entry::class,
536
                Entry::EVENT_REGISTER_PREVIEW_TARGETS,
537
                function (RegisterPreviewTargetsEvent $e) {
538
                    /** @var Element $element */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
539
                    $element = $e->sender;
540
                    $e->previewTargets[] = [
541
                        'label' => '📣 '.Craft::t('seomatic', 'Social Media Preview'),
542
                        'url' => UrlHelper::siteUrl(self::FRONTEND_PREVIEW_PATH, [
543
                            'elementId' => $element->id,
544
                            'siteId' => $element->siteId,
545
                        ]),
546
                    ];
547
                    // Don't allow the preview to be accessed publicly
548
                    Craft::$app->getSession()->authorize(self::SEOMATIC_PREVIEW_AUTHORIZATION_KEY . $element->id);
549
                }
550
            );
551
        }
552
        // CraftQL Support
553
        if (class_exists(CraftQL::class)) {
554
            Event::on(
555
                Schema::class,
556
                AlterSchemaFields::EVENT,
557
                [GetCraftQLSchema::class, 'handle']
558
            );
559
        }
560
    }
561
562
    /**
563
     * Install site event listeners for site requests only
564
     */
565
    protected function installSiteEventListeners()
566
    {
567
        // Load the sitemap containers
568
        self::$plugin->sitemaps->loadSitemapContainers();
569
        // Load the frontend template containers
570
        self::$plugin->frontendTemplates->loadFrontendTemplateContainers();
571
        // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES
572
        Event::on(
573
            UrlManager::class,
574
            UrlManager::EVENT_REGISTER_SITE_URL_RULES,
575
            function (RegisterUrlRulesEvent $event) {
576
                Craft::debug(
577
                    'UrlManager::EVENT_REGISTER_SITE_URL_RULES',
578
                    __METHOD__
579
                );
580
                // FileController
581
                $route = self::$plugin->handle.'/file/seo-file-link';
582
                $event->rules[self::FRONTEND_SEO_FILE_LINK] = ['route' => $route];
583
                // PreviewController
584
                $route = self::$plugin->handle.'/preview/social-media';
585
                $event->rules[self::FRONTEND_PREVIEW_PATH] = ['route' => $route];
586
                // Register our Control Panel routes
587
                $event->rules = array_merge(
588
                    $event->rules,
589
                    $this->customFrontendRoutes()
590
                );
591
            }
592
        );
593
    }
594
595
    /**
596
     * Install site event listeners for Control Panel requests only
597
     */
598
    protected function installCpEventListeners()
599
    {
600
        // Handler: UrlManager::EVENT_REGISTER_CP_URL_RULES
601
        Event::on(
602
            UrlManager::class,
603
            UrlManager::EVENT_REGISTER_CP_URL_RULES,
604
            function (RegisterUrlRulesEvent $event) {
605
                Craft::debug(
606
                    'UrlManager::EVENT_REGISTER_CP_URL_RULES',
607
                    __METHOD__
608
                );
609
                // Register our Control Panel routes
610
                $event->rules = array_merge(
611
                    $event->rules,
612
                    $this->customAdminCpRoutes()
613
                );
614
            }
615
        );
616
        // Handler: UserPermissions::EVENT_REGISTER_PERMISSIONS
617
        Event::on(
618
            UserPermissions::class,
619
            UserPermissions::EVENT_REGISTER_PERMISSIONS,
620
            function (RegisterUserPermissionsEvent $event) {
621
                Craft::debug(
622
                    'UserPermissions::EVENT_REGISTER_PERMISSIONS',
623
                    __METHOD__
624
                );
625
                // Register our custom permissions
626
                $event->permissions[Craft::t('seomatic', 'SEOmatic')] = $this->customAdminCpPermissions();
627
            }
628
        );
629
    }
630
631
    /**
632
     * Handle site requests.  We do it only after we receive the event
633
     * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run
634
     * before our event listeners kick in
635
     */
636
    protected function handleSiteRequest()
637
    {
638
        // Handler: View::EVENT_BEGIN_BODY
639
        Event::on(
640
            View::class,
641
            View::EVENT_BEGIN_BODY,
642
            function () {
643
                Craft::debug(
644
                    'View::EVENT_BEGIN_BODY',
645
                    __METHOD__
646
                );
647
                // The <body> placeholder tag has just rendered, include any script HTML
648
                if (self::$settings->renderEnabled && self::$seomaticVariable) {
649
                    self::$plugin->metaContainers->includeScriptBodyHtml(View::POS_BEGIN);
650
                }
651
            }
652
        );
653
        // Handler: View::EVENT_END_BODY
654
        Event::on(
655
            View::class,
656
            View::EVENT_END_BODY,
657
            function () {
658
                Craft::debug(
659
                    'View::EVENT_END_BODY',
660
                    __METHOD__
661
                );
662
                // The </body> placeholder tag is about to be rendered, include any script HTML
663
                if (self::$settings->renderEnabled && self::$seomaticVariable) {
664
                    self::$plugin->metaContainers->includeScriptBodyHtml(View::POS_END);
665
                }
666
            }
667
        );
668
        // Handler: View::EVENT_END_PAGE
669
        Event::on(
670
            View::class,
671
            View::EVENT_END_PAGE,
672
            function () {
673
                Craft::debug(
674
                    'View::EVENT_END_PAGE',
675
                    __METHOD__
676
                );
677
                // The page is done rendering, include our meta containers
678
                if (self::$settings->renderEnabled && self::$seomaticVariable) {
679
                    self::$plugin->metaContainers->includeMetaContainers();
680
                }
681
            }
682
        );
683
    }
684
685
    /**
686
     * Handle Control Panel requests. We do it only after we receive the event
687
     * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run
688
     * before our event listeners kick in
689
     */
690
    protected function handleAdminCpRequest()
691
    {
692
        // Don't cache Control Panel requests
693
        self::$cacheDuration = 1;
694
        // Prefix the Control Panel title
695
        self::$view->hook('cp.layouts.base', function (&$context) {
696
            if (self::$devMode) {
697
                $context['docTitle'] = self::$settings->devModeCpTitlePrefix.$context['docTitle'];
698
            } else {
699
                $context['docTitle'] = self::$settings->cpTitlePrefix.$context['docTitle'];
700
            }
701
        });
702
    }
703
704
    /**
705
     * @inheritdoc
706
     */
707
    protected function createSettingsModel()
708
    {
709
        return new Settings();
710
    }
711
712
    /**
713
     * Return the custom Control Panel routes
714
     *
715
     * @return array
716
     */
717
    protected function customAdminCpRoutes(): array
718
    {
719
        return [
720
            'seomatic' =>
721
                'seomatic/settings/dashboard',
722
            'seomatic/dashboard' =>
723
                'seomatic/settings/dashboard',
724
            'seomatic/dashboard/<siteHandle:{handle}>' =>
725
                'seomatic/settings/dashboard',
726
727
            'seomatic/global' => [
728
                'route' => 'seomatic/settings/global',
729
                'defaults' => ['subSection' => 'general'],
730
            ],
731
            'seomatic/global/<subSection:{handle}>' =>
732
                'seomatic/settings/global',
733
            'seomatic/global/<subSection:{handle}>/<siteHandle:{handle}>' =>
734
                'seomatic/settings/global',
735
736
            'seomatic/content' =>
737
                'seomatic/settings/content',
738
            'seomatic/content/<siteHandle:{handle}>' =>
739
                'seomatic/settings/content',
740
741
            'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{handle}>' =>
742
                'seomatic/settings/edit-content',
743
            'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{handle}>/<siteHandle:{handle}>' =>
744
                'seomatic/settings/edit-content',
745
746
            'seomatic/site' => [
747
                'route' => 'seomatic/settings/site',
748
                'defaults' => ['subSection' => 'identity'],
749
            ],
750
            'seomatic/site/<subSection:{handle}>' =>
751
                'seomatic/settings/site',
752
            'seomatic/site/<subSection:{handle}>/<siteHandle:{handle}>' =>
753
                'seomatic/settings/site',
754
755
            'seomatic/tracking' => [
756
                'route' => 'seomatic/settings/tracking',
757
                'defaults' => ['subSection' => 'googleAnalytics'],
758
            ],
759
            'seomatic/tracking/<subSection:{handle}>' =>
760
                'seomatic/settings/tracking',
761
            'seomatic/tracking/<subSection:{handle}>/<siteHandle:{handle}>' =>
762
                'seomatic/settings/tracking',
763
764
            'seomatic/plugin' =>
765
                'seomatic/settings/plugin',
766
        ];
767
    }
768
769
    /**
770
     * Return the custom frontend routes
771
     *
772
     * @return array
773
     */
774
    protected function customFrontendRoutes(): array
775
    {
776
        return [
777
            // Tables
778
            '/seomatic/content-seo/meta-bundles' => 'seomatic/content-seo/meta-bundles',
779
        ];
780
    }
781
782
    /**
783
     * Returns the custom Control Panel cache options.
784
     *
785
     * @return array
786
     */
787
    protected function customAdminCpCacheOptions(): array
788
    {
789
        return [
790
            // Frontend template caches
791
            [
792
                'key' => 'seomatic-frontendtemplate-caches',
793
                'label' => Craft::t('seomatic', 'SEOmatic frontend template caches'),
794
                'action' => [self::$plugin->frontendTemplates, 'invalidateCaches'],
795
            ],
796
            // Meta bundle caches
797
            [
798
                'key' => 'seomatic-metabundle-caches',
799
                'label' => Craft::t('seomatic', 'SEOmatic metadata caches'),
800
                'action' => [self::$plugin->metaContainers, 'invalidateCaches'],
801
            ],
802
            // Sitemap caches
803
            [
804
                'key' => 'seomatic-sitemap-caches',
805
                'label' => Craft::t('seomatic', 'SEOmatic sitemap caches'),
806
                'action' => [self::$plugin->sitemaps, 'invalidateCaches'],
807
            ],
808
        ];
809
    }
810
811
    /**
812
     * Returns the custom Control Panel user permissions.
813
     *
814
     * @return array
815
     */
816
    protected function customAdminCpPermissions(): array
817
    {
818
        // The script meta containers for the global meta bundle
819
        try {
820
            $currentSiteId = Craft::$app->getSites()->getCurrentSite()->id ?? 1;
821
        } catch (SiteNotFoundException $e) {
822
            $currentSiteId = 1;
823
        }
824
        // Dynamic permissions for the scripts
825
        $metaBundle = self::$plugin->metaBundles->getGlobalMetaBundle($currentSiteId);
826
        $scriptsPerms = [];
827
        if ($metaBundle !== null) {
828
            $scripts = self::$plugin->metaBundles->getContainerDataFromBundle(
829
                $metaBundle,
830
                MetaScriptContainer::CONTAINER_TYPE
831
            );
832
            foreach ($scripts as $scriptHandle => $scriptData) {
833
                $scriptsPerms["seomatic:tracking-scripts:${scriptHandle}"] = [
834
                    'label' => Craft::t('seomatic', $scriptData->name),
835
                ];
836
            }
837
        }
838
839
        return [
840
            'seomatic:dashboard' => [
841
                'label' => Craft::t('seomatic', 'Dashboard'),
842
            ],
843
            'seomatic:global-meta' => [
844
                'label' => Craft::t('seomatic', 'Edit Global Meta'),
845
                'nested' => [
846
                    'seomatic:global-meta:general' => [
847
                        'label' => Craft::t('seomatic', 'General'),
848
                    ],
849
                    'seomatic:global-meta:twitter' => [
850
                        'label' => Craft::t('seomatic', 'Twitter'),
851
                    ],
852
                    'seomatic:global-meta:facebook' => [
853
                        'label' => Craft::t('seomatic', 'Facebook'),
854
                    ],
855
                    'seomatic:global-meta:robots' => [
856
                        'label' => Craft::t('seomatic', 'Robots'),
857
                    ],
858
                    'seomatic:global-meta:humans' => [
859
                        'label' => Craft::t('seomatic', 'Humans'),
860
                    ],
861
                    'seomatic:global-meta:ads' => [
862
                        'label' => Craft::t('seomatic', 'Ads'),
863
                    ],
864
                ],
865
            ],
866
            'seomatic:content-meta' => [
867
                'label' => Craft::t('seomatic', 'Edit Content SEO'),
868
                'nested' => [
869
                    'seomatic:content-meta:general' => [
870
                        'label' => Craft::t('seomatic', 'General'),
871
                    ],
872
                    'seomatic:content-meta:twitter' => [
873
                        'label' => Craft::t('seomatic', 'Twitter'),
874
                    ],
875
                    'seomatic:content-meta:facebook' => [
876
                        'label' => Craft::t('seomatic', 'Facebook'),
877
                    ],
878
                    'seomatic:content-meta:sitemap' => [
879
                        'label' => Craft::t('seomatic', 'Sitemap'),
880
                    ],
881
                ],
882
            ],
883
            'seomatic:site-settings' => [
884
                'label' => Craft::t('seomatic', 'Edit Site Settings'),
885
                'nested' => [
886
                    'seomatic:site-settings:identity' => [
887
                        'label' => Craft::t('seomatic', 'Identity'),
888
                    ],
889
                    'seomatic:site-settings:creator' => [
890
                        'label' => Craft::t('seomatic', 'Creator'),
891
                    ],
892
                    'seomatic:site-settings:social' => [
893
                        'label' => Craft::t('seomatic', 'Social Media'),
894
                    ],
895
                    'seomatic:site-settings:sitemap' => [
896
                        'label' => Craft::t('seomatic', 'Sitemap'),
897
                    ],
898
                    'seomatic:site-settings:miscellaneous' => [
899
                        'label' => Craft::t('seomatic', 'Miscellaneous'),
900
                    ],
901
                ],
902
            ],
903
            'seomatic:tracking-scripts' => [
904
                'label' => Craft::t('seomatic', 'Edit Tracking Scripts'),
905
                'nested' => $scriptsPerms,
906
            ],
907
            'seomatic:plugin-settings' => [
908
                'label' => Craft::t('seomatic', 'Edit Plugin Settings'),
909
            ],
910
        ];
911
    }
912
}
913