Passed
Push — develop ( 3bd85d...9ec411 )
by Andrew
08:37
created

MetaContainers::previewMetaContainers()   F

Complexity

Conditions 12
Paths 576

Size

Total Lines 58
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 12
eloc 34
nc 576
nop 4
dl 0
loc 58
ccs 0
cts 34
cp 0
crap 156
rs 3.3888
c 3
b 0
f 0

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 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\services;
13
14
use nystudio107\seomatic\helpers\ArrayHelper;
15
use nystudio107\seomatic\helpers\Json;
16
use nystudio107\seomatic\helpers\Localization as LocalizationHelper;
17
use nystudio107\seomatic\models\MetaJsonLd;
18
use nystudio107\seomatic\Seomatic;
19
use nystudio107\seomatic\base\MetaContainer;
20
use nystudio107\seomatic\base\MetaItem;
21
use nystudio107\seomatic\events\InvalidateContainerCachesEvent;
22
use nystudio107\seomatic\helpers\DynamicMeta as DynamicMetaHelper;
23
use nystudio107\seomatic\helpers\Field as FieldHelper;
24
use nystudio107\seomatic\helpers\MetaValue as MetaValueHelper;
25
use nystudio107\seomatic\helpers\UrlHelper;
26
use nystudio107\seomatic\models\MetaBundle;
27
use nystudio107\seomatic\models\MetaGlobalVars;
28
use nystudio107\seomatic\models\MetaSiteVars;
29
use nystudio107\seomatic\models\MetaSitemapVars;
30
use nystudio107\seomatic\models\MetaJsonLdContainer;
31
use nystudio107\seomatic\models\MetaLinkContainer;
32
use nystudio107\seomatic\models\MetaScriptContainer;
33
use nystudio107\seomatic\models\MetaScript;
34
use nystudio107\seomatic\models\MetaTagContainer;
35
use nystudio107\seomatic\models\MetaTitleContainer;
36
use nystudio107\seomatic\services\JsonLd as JsonLdService;
37
use nystudio107\seomatic\variables\SeomaticVariable;
38
39
use Craft;
40
use craft\base\Component;
41
use craft\base\Element;
42
use craft\console\Application as ConsoleApplication;
43
use craft\elements\GlobalSet;
44
45
use yii\base\Exception;
46
use yii\base\InvalidConfigException;
47
use yii\caching\TagDependency;
48
49
/**
50
 * @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...
51
 * @package   Seomatic
52
 * @since     3.0.0
53
 */
54
class MetaContainers extends Component
55
{
56
    // Constants
57
    // =========================================================================
58
59
    const GLOBAL_METACONTAINER_CACHE_TAG = 'seomatic_metacontainer';
60
    const METACONTAINER_CACHE_TAG = 'seomatic_metacontainer_';
61
62
    const CACHE_KEY = 'seomatic_metacontainer_';
63
    const INVALID_RESPONSE_CACHE_KEY = 'seomatic_invalid_response';
64
    const GLOBALS_CACHE_KEY = 'parsed_globals_';
65
    const SCRIPTS_CACHE_KEY = 'body_scripts_';
66
67
    /**
68
     * @event InvalidateContainerCachesEvent The event that is triggered when SEOmatic
69
     *        is about to clear its meta container caches
70
     *
71
     * ---
72
     * ```php
73
     * use nystudio107\seomatic\events\InvalidateContainerCachesEvent;
74
     * use nystudio107\seomatic\services\MetaContainers;
75
     * use yii\base\Event;
76
     * Event::on(MetaContainers::class, MetaContainers::EVENT_INVALIDATE_CONTAINER_CACHES, function(InvalidateContainerCachesEvent $e) {
77
     *     // Container caches are about to be cleared
78
     * });
79
     * ```
80
     */
81
    const EVENT_INVALIDATE_CONTAINER_CACHES = 'invalidateContainerCaches';
82
83
    // Public Properties
84
    // =========================================================================
85
86
    /**
87
     * @var MetaGlobalVars
88
     */
89
    public $metaGlobalVars;
90
91
    /**
92
     * @var MetaSiteVars
93
     */
94
    public $metaSiteVars;
95
96
    /**
97
     * @var MetaSitemapVars
98
     */
99
    public $metaSitemapVars;
100
101
    /**
102
     * @var string The current page number of paginated pages
103
     */
104
    public $paginationPage = '1';
105
106
    /**
107
     * @var null|string Cached nonce to be shared by all JSON-LD entities
108
     */
109
    public $cachedJsonLdNonce;
110
111
    // Protected Properties
112
    // =========================================================================
113
114
    /**
115
     * @var MetaContainer
116
     */
117
    protected $metaContainers = [];
118
119
    /**
120
     * @var null|MetaBundle
121
     */
122
    protected $matchedMetaBundle;
123
124
    /**
125
     * @var null|TagDependency
126
     */
127
    protected $containerDependency;
128
129
    /**
130
     * @var bool Whether or not the matched element should be included in the
131
     *      meta containers
132
     */
133
    protected $includeMatchedElement = true;
134
135
    // Public Methods
136
    // =========================================================================
137
138
    /**
139
     * @inheritdoc
140
     */
141 1
    public function init()
142
    {
143 1
        parent::init();
144
        // Get the page number of this request
145 1
        $request = Craft::$app->getRequest();
146 1
        if (!$request->isConsoleRequest) {
147
            $this->paginationPage = (string)$request->pageNum;
148
        }
149 1
    }
150
151
    /**
152
     * Load the meta containers
153
     *
154
     * @param string|null $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
155
     * @param int|null    $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
156
     */
157
    public function loadMetaContainers(string $uri = '', int $siteId = null)
158
    {
159
        Craft::beginProfile('MetaContainers::loadMetaContainers', __METHOD__);
160
        // Avoid recursion
161
        if (!Seomatic::$loadingMetaContainers) {
162
            Seomatic::$loadingMetaContainers = true;
163
            $this->setMatchedElement($uri, $siteId);
164
            // Get the cache tag for the matched meta bundle
165
            $metaBundle = $this->getMatchedMetaBundle();
166
            $metaBundleSourceId = '';
167
            $metaBundleSourceType = '';
168
            if ($metaBundle) {
169
                $metaBundleSourceId = $metaBundle->sourceId;
170
                $metaBundleSourceType = $metaBundle->sourceBundleType;
171
            }
172
            // We need an actual $siteId here for the cache key
173
            if ($siteId === null) {
174
                $siteId = Craft::$app->getSites()->currentSite->id
175
                    ?? Craft::$app->getSites()->primarySite->id
176
                    ?? 1;
177
            }
178
            // Handle pagination
179
            $paginationPage = 'page'.$this->paginationPage;
180
            // Get the path for the current request
181
            $request = Craft::$app->getRequest();
182
            $requestPath = '/';
183
            if (!$request->getIsConsoleRequest()) {
184
                try {
185
                    $requestPath = $request->getPathInfo();
186
                } catch (InvalidConfigException $e) {
187
                    Craft::error($e->getMessage(), __METHOD__);
188
                }
189
                // If this is any type of a preview, ensure that it's not cached
190
                if (Seomatic::$plugin->helper::isPreview()) {
191
                    Seomatic::$previewingMetaContainers = true;
192
                }
193
            }
194
            // Get our cache key
195
            $cacheKey = $uri.$siteId.$paginationPage.$requestPath;
196
            // For requests with a status code of >= 400, use one cache key
197
            if (!$request->isConsoleRequest) {
198
                $response = Craft::$app->getResponse();
199
                if ($response->statusCode >= 400) {
200
                    $cacheKey = $siteId.self::INVALID_RESPONSE_CACHE_KEY.$response->statusCode;
201
                }
202
            }
203
            // Load the meta containers
204
            $dependency = new TagDependency([
205
                'tags' => [
206
                    self::GLOBAL_METACONTAINER_CACHE_TAG,
207
                    self::METACONTAINER_CACHE_TAG.$metaBundleSourceId.$metaBundleSourceType.$siteId,
208
                    self::METACONTAINER_CACHE_TAG.$uri.$siteId,
209
                    self::METACONTAINER_CACHE_TAG.$cacheKey,
210
                ],
211
            ]);
212
            $this->containerDependency = $dependency;
213
            if (Seomatic::$previewingMetaContainers) {
214
                $this->loadGlobalMetaContainers($siteId);
215
                $this->loadContentMetaContainers();
216
                $this->loadFieldMetaContainers();
217
                DynamicMetaHelper::addDynamicMetaToContainers($uri, $siteId);
218
            } else {
219
                $cache = Craft::$app->getCache();
220
                list($this->metaGlobalVars, $this->metaSiteVars, $this->metaSitemapVars, $this->metaContainers) = $cache->getOrSet(
221
                    self::CACHE_KEY.$cacheKey,
222
                    function () use ($uri, $siteId) {
223
                        Craft::info(
224
                            'Meta container cache miss: '.$uri.'/'.$siteId,
225
                            __METHOD__
226
                        );
227
                        $this->loadGlobalMetaContainers($siteId);
228
                        $this->loadContentMetaContainers();
229
                        $this->loadFieldMetaContainers();
230
                        DynamicMetaHelper::addDynamicMetaToContainers($uri, $siteId);
231
232
                        return [$this->metaGlobalVars, $this->metaSiteVars, $this->metaSitemapVars, $this->metaContainers];
233
                    },
234
                    Seomatic::$cacheDuration,
235
                    $dependency
236
                );
237
            }
238
            Seomatic::$seomaticVariable->init();
239
            MetaValueHelper::cache();
240
            Seomatic::$loadingMetaContainers = false;
241
        }
242
        Craft::endProfile('MetaContainers::loadMetaContainers', __METHOD__);
243
    }
244
245
    /**
246
     * Include any script body HTML
247
     *
248
     * @param int $bodyPosition
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
249
     */
250
    public function includeScriptBodyHtml(int $bodyPosition)
251
    {
252
        Craft::beginProfile('MetaContainers::includeScriptBodyHtml', __METHOD__);
253
        $dependency = $this->containerDependency;
254
        $uniqueKey = $dependency->tags[3] ?? self::GLOBALS_CACHE_KEY;
255
        $uniqueKey .= $bodyPosition;
256
        $scriptData = Craft::$app->getCache()->getOrSet(
257
            self::GLOBALS_CACHE_KEY.$uniqueKey,
258
            function () use ($uniqueKey, $bodyPosition) {
259
                Craft::info(
260
                    self::SCRIPTS_CACHE_KEY.' cache miss: '.$uniqueKey,
261
                    __METHOD__
262
                );
263
                $scriptData = [];
264
                $scriptContainers = $this->getContainersOfType(MetaScriptContainer::CONTAINER_TYPE);
265
                foreach ($scriptContainers as $scriptContainer) {
266
                    /** @var MetaScriptContainer $scriptContainer */
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...
267
                    if ($scriptContainer->include) {
268
                        if ($scriptContainer->prepForInclusion()) {
269
                            foreach ($scriptContainer->data as $metaScript) {
270
                                /** @var MetaScript $metaScript */
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...
271
                                if (!empty($metaScript->bodyTemplatePath)
272
                                    && ((int)$metaScript->bodyPosition === $bodyPosition)) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
273
                                    $scriptData[] = $metaScript->renderBodyHtml();
274
                                }
275
                            }
276
                        }
277
                    }
278
                }
279
280
                return $scriptData;
281
            },
282
            Seomatic::$cacheDuration,
283
            $dependency
284
        );
285
        // Output the script HTML
286
        foreach ($scriptData as $script) {
287
            if (is_string($script) && !empty($script)) {
288
                echo $script;
289
            }
290
        }
291
        Craft::endProfile('MetaContainers::includeScriptBodyHtml', __METHOD__);
292
    }
293
294
    /**
295
     * Include the meta containers
296
     */
297
    public function includeMetaContainers()
298
    {
299
        Craft::beginProfile('MetaContainers::includeMetaContainers', __METHOD__);
300
        // If this page is paginated, we need to factor that into the cache key
301
        // We also need to re-add the hreflangs
302
        if ($this->paginationPage !== '1') {
303
            if (Seomatic::$settings->addHrefLang && Seomatic::$settings->addPaginatedHreflang) {
304
                DynamicMetaHelper::addMetaLinkHrefLang();
305
            }
306
        }
307
        // Add in our http headers
308
        DynamicMetaHelper::includeHttpHeaders();
309
        DynamicMetaHelper::addCspTags();
310
        $this->parseGlobalVars();
311
        foreach ($this->metaContainers as $metaContainer) {
312
            /** @var $metaContainer MetaContainer */
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...
313
            if ($metaContainer->include) {
314
                // Don't cache the rendered result if we're previewing meta containers
315
                if (Seomatic::$previewingMetaContainers) {
316
                    $metaContainer->clearCache = true;
317
                }
318
                $metaContainer->includeMetaData($this->containerDependency);
319
            }
320
        }
321
        Craft::endProfile('MetaContainers::includeMetaContainers', __METHOD__);
322
    }
323
324
    /**
325
     * Parse the global variables
326
     */
327
    public function parseGlobalVars()
328
    {
329
        $dependency = $this->containerDependency;
330
        $uniqueKey = $dependency->tags[3] ?? self::GLOBALS_CACHE_KEY;
331
        list($this->metaGlobalVars, $this->metaSiteVars) = Craft::$app->getCache()->getOrSet(
332
            self::GLOBALS_CACHE_KEY.$uniqueKey,
333
            function () use ($uniqueKey) {
334
                Craft::info(
335
                    self::GLOBALS_CACHE_KEY.' cache miss: '.$uniqueKey,
336
                    __METHOD__
337
                );
338
339
                if ($this->metaGlobalVars) {
340
                    $this->metaGlobalVars->parseProperties();
341
                }
342
                if ($this->metaSiteVars) {
343
                    $this->metaSiteVars->parseProperties();
344
                }
345
346
                return [$this->metaGlobalVars, $this->metaSiteVars];
347
            },
348
            Seomatic::$cacheDuration,
349
            $dependency
350
        );
351
    }
352
353
    /**
354
     * Prep all of the meta for preview purposes
355
     *
356
     * @param string   $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
357
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
358
     * @param bool     $parseVariables Whether or not the variables should be
359
     *                                 parsed as Twig
360
     * @param bool     $includeElement Whether or not the matched element
361
     *                                 should be factored into the preview
362
     */
363
    public function previewMetaContainers(
364
        string $uri = '',
365
        int $siteId = null,
366
        bool $parseVariables = false,
367
        bool $includeElement = true
368
    ) {
369
        // It's possible this won't exist at this point
370
        if (!Seomatic::$seomaticVariable) {
371
            // Create our variable and stash it in the plugin for global access
372
            Seomatic::$seomaticVariable = new SeomaticVariable();
373
        }
374
        Seomatic::$previewingMetaContainers = true;
375
        $this->includeMatchedElement = $includeElement;
376
        $this->loadMetaContainers($uri, $siteId);
377
        // Load in the right globals
378
        $twig = Craft::$app->getView()->getTwig();
0 ignored issues
show
Unused Code introduced by
The assignment to $twig is dead and can be removed.
Loading history...
379
        $globalSets = GlobalSet::findAll([
380
            'siteId' => $siteId,
381
        ]);
382
        MetaValueHelper::$templatePreviewVars = [];
383
        foreach ($globalSets as $globalSet) {
384
            MetaValueHelper::$templatePreviewVars[$globalSet->handle] = $globalSet;
385
        }
386
        // Parse the global vars
387
        if ($parseVariables) {
388
            $this->parseGlobalVars();
389
        }
390
        // Get the homeUrl and canonicalUrl
391
        $homeUrl = '/';
392
        $canonicalUrl = DynamicMetaHelper::sanitizeUrl($uri, false);
393
        // Special-case the global bundle
394
        if ($uri === MetaBundles::GLOBAL_META_BUNDLE || $uri === '__home__') {
395
            $canonicalUrl = '/';
396
        }
397
        try {
398
            $homeUrl = UrlHelper::siteUrl($homeUrl, null, null, $siteId);
399
            $canonicalUrl = UrlHelper::siteUrl($canonicalUrl, null, null, $siteId);
400
        } catch (Exception $e) {
401
            Craft::error($e->getMessage(), __METHOD__);
402
        }
403
        $canonical = Seomatic::$seomaticVariable->link->get('canonical');
404
        if ($canonical !== null) {
405
            $canonical->href = $canonicalUrl;
406
        }
407
        $home = Seomatic::$seomaticVariable->link->get('home');
408
        if ($home !== null) {
409
            $home->href = $homeUrl;
410
        }
411
        // The current language may _not_ match the current site, if we're headless
412
        $ogLocale = Seomatic::$seomaticVariable->tag->get('og:locale');
413
        if ($ogLocale !== null && $siteId !== null) {
414
            $site = Craft::$app->getSites()->getSiteById($siteId);
415
            if ($site !== null) {
416
                $ogLocale->content = LocalizationHelper::normalizeOgLocaleLanguage($site->language);
0 ignored issues
show
Bug introduced by
It seems like $site->language can also be of type null; however, parameter $language of nystudio107\seomatic\hel...alizeOgLocaleLanguage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

416
                $ogLocale->content = LocalizationHelper::normalizeOgLocaleLanguage(/** @scrutinizer ignore-type */ $site->language);
Loading history...
417
            }
418
        }
419
        // Update seomatic.meta.canonicalUrl when previewing meta containers
420
        $this->metaGlobalVars->canonicalUrl = $canonicalUrl;
421
    }
422
423
    /**
424
     * Add the passed in MetaItem to the MetaContainer indexed as $key
425
     *
426
     * @param $data MetaItem The MetaItem to add to the container
427
     * @param $key  string   The key to the container to add the data to
428
     */
429 1
    public function addToMetaContainer(MetaItem $data, string $key)
430
    {
431
        /** @var  $container MetaContainer */
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...
432 1
        $container = $this->getMetaContainer($key);
433
434 1
        if ($container !== null) {
435
            $container->addData($data, $data->key);
436
        }
437 1
    }
438
439
    /**
440
     * @param string $key
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
441
     *
442
     * @return mixed|null
443
     */
444 1
    public function getMetaContainer(string $key)
445
    {
446 1
        if (!$key || empty($this->metaContainers[$key])) {
447 1
            $error = Craft::t(
448 1
                'seomatic',
449 1
                'Meta container with key `{key}` does not exist.',
450 1
                ['key' => $key]
451
            );
452 1
            Craft::error($error, __METHOD__);
453
454 1
            return null;
455
        }
456
457
        return $this->metaContainers[$key];
458
    }
459
460
    /**
461
     * Create a MetaContainer of the given $type with the $key
462
     *
463
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
464
     * @param string $key
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
465
     *
466
     * @return null|MetaContainer
467
     */
468
    public function createMetaContainer(string $type, string $key): MetaContainer
469
    {
470
        /** @var MetaContainer $container */
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...
471
        $container = null;
472
        if (empty($this->metaContainers[$key])) {
473
            /** @var MetaContainer $className */
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...
474
            $className = null;
475
            // Create a new container based on the type passed in
476
            switch ($type) {
477
                case MetaTagContainer::CONTAINER_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
478
                    $className = MetaTagContainer::class;
479
                    break;
480
                case MetaLinkContainer::CONTAINER_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
481
                    $className = MetaLinkContainer::class;
482
                    break;
483
                case MetaScriptContainer::CONTAINER_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
484
                    $className = MetaScriptContainer::class;
485
                    break;
486
                case MetaJsonLdContainer::CONTAINER_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
487
                    $className = MetaJsonLdContainer::class;
488
                    break;
489
                case MetaTitleContainer::CONTAINER_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
490
                    $className = MetaTitleContainer::class;
491
                    break;
492
            }
493
            if ($className) {
494
                $container = $className::create();
495
                if ($container) {
0 ignored issues
show
introduced by
$container is of type nystudio107\seomatic\base\Container, thus it always evaluated to true.
Loading history...
496
                    $this->metaContainers[$key] = $container;
497
                }
498
            }
499
        }
500
501
        /** @var MetaContainer $className */
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...
502
        return $container;
503
    }
504
505
    /**
506
     * Return the containers of a specific type
507
     *
508
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
509
     *
510
     * @return array
511
     */
512
    public function getContainersOfType(string $type): array
513
    {
514
        $containers = [];
515
        /** @var  $metaContainer MetaContainer */
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...
516
        foreach ($this->metaContainers as $metaContainer) {
517
            if ($metaContainer::CONTAINER_TYPE === $type) {
518
                $containers[] = $metaContainer;
519
            }
520
        }
521
522
        return $containers;
523
    }
524
525
    /**
526
     * Render the HTML of all MetaContainers of a specific $type
527
     *
528
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
529
     *
530
     * @return string
531
     */
532
    public function renderContainersByType(string $type): string
533
    {
534
        $html = '';
535
        /** @var  $metaContainer MetaContainer */
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...
536
        foreach ($this->metaContainers as $metaContainer) {
537
            if ($metaContainer::CONTAINER_TYPE === $type && $metaContainer->include) {
538
                $result = $metaContainer->render([
539
                    'renderRaw'        => true,
540
                    'renderScriptTags' => true,
541
                    'array'            => true,
542
                ]);
543
                // Special case for script containers, because they can have body scripts too
544
                if ($metaContainer::CONTAINER_TYPE === MetaScriptContainer::CONTAINER_TYPE) {
545
                    $bodyScript = '';
546
                    /** @var MetaScriptContainer $metaContainer */
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...
547
                    if ($metaContainer->prepForInclusion()) {
548
                        foreach ($metaContainer->data as $metaScript) {
549
                            /** @var MetaScript $metaScript */
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...
550
                            if (!empty($metaScript->bodyTemplatePath)) {
551
                                $bodyScript .= $metaScript->renderBodyHtml();
552
                            }
553
                        }
554
                    }
555
556
                    $result = Json::encode([
557
                        'script' => $result,
558
                        'bodyScript' => $bodyScript,
559
                    ]);
560
                }
561
562
                $html .= $result;
563
            }
564
        }
565
        // Special-case for requests for the MetaSiteVars "container"
566
        if ($type === MetaSiteVars::CONTAINER_TYPE) {
567
            $result = Json::encode($this->metaSiteVars->toArray());
568
            $html .= $result;
569
        }
570
571
        return $html;
572
    }
573
574
    /**
575
     * Render the HTML of all MetaContainers of a specific $type as an array
576
     *
577
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
578
     *
579
     * @return array
580
     */
581
    public function renderContainersArrayByType(string $type): array
582
    {
583
        $htmlArray = [];
584
        /** @var  $metaContainer MetaContainer */
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...
585
        foreach ($this->metaContainers as $metaContainer) {
586
            if ($metaContainer::CONTAINER_TYPE === $type && $metaContainer->include) {
587
                /** @noinspection SlowArrayOperationsInLoopInspection */
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...
588
                $htmlArray = array_merge($htmlArray, $metaContainer->renderArray());
589
            }
590
        }
591
        // Special-case for requests for the MetaSiteVars "container"
592
        if ($type === MetaSiteVars::CONTAINER_TYPE) {
593
            $result = Json::encode($this->metaSiteVars->toArray());
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
594
            $htmlArray = array_merge($htmlArray, $this->metaSiteVars->toArray());
595
        }
596
597
        return $htmlArray;
598
    }
599
600
    /**
601
     * Return a MetaItem object by $key from container $type
602
     *
603
     * @param string $key
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
604
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
605
     *
606
     * @return null|MetaItem
607
     */
608
    public function getMetaItemByKey(string $key, string $type = '')
609
    {
610
        $metaItem = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $metaItem is dead and can be removed.
Loading history...
611
        /** @var  $metaContainer MetaContainer */
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...
612
        foreach ($this->metaContainers as $metaContainer) {
613
            if (($metaContainer::CONTAINER_TYPE === $type) || empty($type)) {
614
                /** @var  $metaTag MetaItem */
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...
615
                foreach ($metaContainer->data as $metaItem) {
616
                    if ($key === $metaItem->key) {
617
                        return $metaItem;
618
                    }
619
                }
620
            }
621
        }
622
623
        return null;
624
    }
625
626
    // Protected Methods
627
    // =========================================================================
628
629
    /**
630
     * Return the MetaBundle that corresponds with the Seomatic::$matchedElement
631
     *
632
     * @return null|MetaBundle
633
     */
634
    public function getMatchedMetaBundle()
635
    {
636
        $metaBundle = null;
637
        /** @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...
638
        $element = Seomatic::$matchedElement;
639
        if ($element) {
0 ignored issues
show
introduced by
$element is of type craft\base\Element, thus it always evaluated to true.
Loading history...
640
            $sourceType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
641
            if ($sourceType) {
642
                list($sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId)
643
                    = Seomatic::$plugin->metaBundles->getMetaSourceFromElement($element);
644
                $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceId(
645
                    $sourceType,
646
                    $sourceId,
647
                    $sourceSiteId,
648
                    $typeId
649
                );
650
            }
651
        }
652
        $this->matchedMetaBundle = $metaBundle;
653
654
        return $metaBundle;
655
    }
656
657
    /**
658
     * Add the meta bundle to our existing meta containers, overwriting meta
659
     * items with the same key
660
     *
661
     * @param MetaBundle $metaBundle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
662
     */
663
    public function addMetaBundleToContainers(MetaBundle $metaBundle)
664
    {
665
        // Ensure the variable is synced properly first
666
        Seomatic::$seomaticVariable->init();
667
        // Meta global vars
668
        $attributes = $metaBundle->metaGlobalVars->getAttributes();
669
        // Parse the meta values so we can filter out any blank or empty attributes
670
        // So that they can fall back on the parent container
671
        $parsedAttributes = $attributes;
672
        MetaValueHelper::parseArray($parsedAttributes);
673
        $parsedAttributes = array_filter(
674
            $parsedAttributes,
675
            [ArrayHelper::class, 'preserveBools']
676
        );
677
        $attributes = array_intersect_key($attributes, $parsedAttributes);
678
        // Add the attributes in
679
        $attributes = array_filter(
680
            $attributes,
681
            [ArrayHelper::class, 'preserveBools']
682
        );
683
        $this->metaGlobalVars->setAttributes($attributes, false);
684
        // Meta site vars
685
        /*
686
         * Don't merge in the Site vars, since they are only editable on
687
         * a global basis. Otherwise stale data will be unable to be edited
688
        $attributes = $metaBundle->metaSiteVars->getAttributes();
689
        $attributes = array_filter($attributes);
690
        $this->metaSiteVars->setAttributes($attributes, false);
691
        */
692
        // Meta sitemap vars
693
        $attributes = $metaBundle->metaSitemapVars->getAttributes();
694
        $attributes = array_filter(
695
            $attributes,
696
            [ArrayHelper::class, 'preserveBools']
697
        );
698
        $this->metaSitemapVars->setAttributes($attributes, false);
699
        // Language
700
        $this->metaGlobalVars->language = Seomatic::$language;
701
        // Meta containers
702
        foreach ($metaBundle->metaContainers as $key => $metaContainer) {
703
            foreach ($metaContainer->data as $metaTag) {
704
                $this->addToMetaContainer($metaTag, $key);
705
            }
706
        }
707
    }
708
709
    /**
710
     * Invalidate all of the meta container caches
711
     */
712
    public function invalidateCaches()
713
    {
714
        $cache = Craft::$app->getCache();
715
        TagDependency::invalidate($cache, self::GLOBAL_METACONTAINER_CACHE_TAG);
716
        Craft::info(
717
            'All meta container caches cleared',
718
            __METHOD__
719
        );
720
        // Trigger an event to let other plugins/modules know we've cleared our caches
721
        $event = new InvalidateContainerCachesEvent([
722
            'uri' => null,
723
            'siteId' => null,
724
            'sourceId' => null,
725
            'sourceType' => null,
726
        ]);
727
        if (!Craft::$app instanceof ConsoleApplication) {
728
            $this->trigger(self::EVENT_INVALIDATE_CONTAINER_CACHES, $event);
729
        }
730
    }
731
732
    /**
733
     * Invalidate a meta bundle cache
734
     *
735
     * @param int          $sourceId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 9 spaces after parameter type; 10 found
Loading history...
736
     * @param null|string  $sourceType
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter type; 2 found
Loading history...
737
     * @param null|int     $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 4 spaces after parameter type; 5 found
Loading history...
738
     */
739
    public function invalidateContainerCacheById(int $sourceId, $sourceType = null, $siteId = null)
740
    {
741
        $metaBundleSourceId = '';
742
        if ($sourceId) {
743
            $metaBundleSourceId = $sourceId;
744
        }
745
        $metaBundleSourceType = '';
746
        if ($sourceType) {
747
            $metaBundleSourceType = $sourceType;
748
        }
749
        if ($siteId === null) {
750
            $siteId = Craft::$app->getSites()->currentSite->id ?? 1;
751
        }
752
        $cache = Craft::$app->getCache();
753
        TagDependency::invalidate(
754
            $cache,
755
            self::METACONTAINER_CACHE_TAG.$metaBundleSourceId.$metaBundleSourceType.$siteId
756
        );
757
        Craft::info(
758
            'Meta bundle cache cleared: '.$metaBundleSourceId.' / '.$metaBundleSourceType.' / '.$siteId,
759
            __METHOD__
760
        );
761
        // Trigger an event to let other plugins/modules know we've cleared our caches
762
        $event = new InvalidateContainerCachesEvent([
763
            'uri' => null,
764
            'siteId' => $siteId,
765
            'sourceId' => $sourceId,
766
            'sourceType' => $metaBundleSourceType,
767
        ]);
768
        if (!Craft::$app instanceof ConsoleApplication) {
769
            $this->trigger(self::EVENT_INVALIDATE_CONTAINER_CACHES, $event);
770
        }
771
    }
772
773
    /**
774
     * Invalidate a meta bundle cache
775
     *
776
     * @param string   $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
777
     * @param null|int $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
778
     */
779
    public function invalidateContainerCacheByPath(string $uri, $siteId = null)
780
    {
781
        $cache = Craft::$app->getCache();
782
        if ($siteId === null) {
783
            $siteId = Craft::$app->getSites()->currentSite->id ?? 1;
784
        }
785
        TagDependency::invalidate($cache, self::METACONTAINER_CACHE_TAG.$uri.$siteId);
786
        Craft::info(
787
            'Meta container cache cleared: '.$uri.' / '.$siteId,
788
            __METHOD__
789
        );
790
        // Trigger an event to let other plugins/modules know we've cleared our caches
791
        $event = new InvalidateContainerCachesEvent([
792
            'uri' => $uri,
793
            'siteId' => $siteId,
794
            'sourceId' => null,
795
            'sourceType' => null,
796
        ]);
797
        if (!Craft::$app instanceof ConsoleApplication) {
798
            $this->trigger(self::EVENT_INVALIDATE_CONTAINER_CACHES, $event);
799
        }
800
    }
801
802
    // Protected Methods
803
    // =========================================================================
804
805
    /**
806
     * Load the global site meta containers
807
     *
808
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
809
     */
810
    protected function loadGlobalMetaContainers(int $siteId = null)
811
    {
812
        Craft::beginProfile('MetaContainers::loadGlobalMetaContainers', __METHOD__);
813
        if ($siteId === null) {
814
            $siteId = Craft::$app->getSites()->currentSite->id ?? 1;
815
        }
816
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteId);
817
        if ($metaBundle) {
818
            // Meta global vars
819
            $this->metaGlobalVars = $metaBundle->metaGlobalVars;
820
            // Meta site vars
821
            $this->metaSiteVars = $metaBundle->metaSiteVars;
822
            // Meta sitemap vars
823
            $this->metaSitemapVars = $metaBundle->metaSitemapVars;
824
            // Language
825
            $this->metaGlobalVars->language = Seomatic::$language;
826
            // Meta containers
827
            foreach ($metaBundle->metaContainers as $key => $metaContainer) {
828
                $this->metaContainers[$key] = $metaContainer;
829
            }
830
        }
831
        Craft::beginProfile('MetaContainers::loadGlobalMetaContainers', __METHOD__);
832
    }
833
834
    /**
835
     * Load the meta containers specific to the matched meta bundle
836
     */
837
    protected function loadContentMetaContainers()
838
    {
839
        Craft::beginProfile('MetaContainers::loadContentMetaContainers', __METHOD__);
840
        $metaBundle = $this->getMatchedMetaBundle();
841
        if ($metaBundle) {
842
            $this->addMetaBundleToContainers($metaBundle);
843
        }
844
        Craft::beginProfile('MetaContainers::loadContentMetaContainers', __METHOD__);
845
    }
846
847
    /**
848
     * Load any meta containers in the current element
849
     */
850
    protected function loadFieldMetaContainers()
851
    {
852
        Craft::beginProfile('MetaContainers::loadFieldMetaContainers', __METHOD__);
853
        $element = Seomatic::$matchedElement;
854
        if ($element && $this->includeMatchedElement) {
855
            /** @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...
856
            $fieldHandles = FieldHelper::fieldsOfTypeFromElement($element, FieldHelper::SEO_SETTINGS_CLASS_KEY, true);
857
            foreach ($fieldHandles as $fieldHandle) {
858
                if (!empty($element->$fieldHandle)) {
859
                    /** @var MetaBundle $metaBundle */
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...
860
                    $metaBundle = $element->$fieldHandle;
861
                    Seomatic::$plugin->metaBundles->pruneFieldMetaBundleSettings($metaBundle, $fieldHandle);
862
                    // Handle re-creating the `mainEntityOfPage` so that the model injected into the
863
                    // templates has the appropriate attributes
864
                    $generalContainerKey = MetaJsonLdContainer::CONTAINER_TYPE.JsonLdService::GENERAL_HANDLE;
865
                    $generalContainer = $this->metaContainers[$generalContainerKey];
866
                    if (($generalContainer !== null) && !empty($generalContainer->data['mainEntityOfPage'])) {
867
                        /** @var MetaJsonLd $jsonLdModel */
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...
868
                        $jsonLdModel = $generalContainer->data['mainEntityOfPage'];
869
                        $config = $jsonLdModel->getAttributes();
870
                        $schemaType = $metaBundle->metaGlobalVars->mainEntityOfPage ?? $config['type'] ?? null;
871
                        // If the schemaType is '' we should fall back on whatever the mainEntityOfPage already is
872
                        if (empty($schemaType)) {
873
                            $schemaType = null;
874
                        }
875
                        if ($schemaType !== null) {
876
                            $config['key'] = 'mainEntityOfPage';
877
                            $schemaType = MetaValueHelper::parseString($schemaType);
878
                            $generalContainer->data['mainEntityOfPage'] = MetaJsonLd::create($schemaType, $config);
879
                        }
880
                    }
881
                    $this->addMetaBundleToContainers($metaBundle);
882
                }
883
            }
884
        }
885
        Craft::beginProfile('MetaContainers::loadFieldMetaContainers', __METHOD__);
886
    }
887
888
    /**
889
     * Set the element that matches the $uri
890
     *
891
     * @param string   $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
892
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
893
     */
894
    protected function setMatchedElement(string $uri, int $siteId = null)
895
    {
896
        if ($siteId === null) {
897
            $siteId = Craft::$app->getSites()->currentSite->id
898
                ?? Craft::$app->getSites()->primarySite->id
899
                ?? 1;
900
        }
901
        $uri = trim($uri, '/');
902
        /** @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...
903
        $enabledOnly = !Seomatic::$previewingMetaContainers;
904
        $element = Craft::$app->getElements()->getElementByUri($uri, $siteId, $enabledOnly);
905
        if ($element && ($element->uri !== null)) {
0 ignored issues
show
Bug introduced by
Accessing uri on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
906
            Seomatic::setMatchedElement($element);
907
        }
908
    }
909
910
    /**
911
     * Generate an md5 hash from an object or array
912
     *
913
     * @param string|array|MetaItem $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
914
     *
915
     * @return string
916
     */
917
    protected function getHash($data): string
918
    {
919
        if (\is_object($data)) {
920
            $data = $data->toArray();
921
        }
922
        if (\is_array($data)) {
923
            $data = serialize($data);
924
        }
925
926
        return md5($data);
927
    }
928
929
    // Private Methods
930
    // =========================================================================
931
}
932