Passed
Push — v3 ( 9618fe...01dae8 )
by Andrew
47:57 queued 20:05
created

MetaBundles::getContentMetaBundleForElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 6
cp 0
crap 6
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\services;
13
14
use nystudio107\seomatic\Seomatic;
15
use nystudio107\seomatic\base\SeoElementInterface;
16
use nystudio107\seomatic\fields\SeoSettings;
17
use nystudio107\seomatic\helpers\ArrayHelper;
18
use nystudio107\seomatic\helpers\Config as ConfigHelper;
19
use nystudio107\seomatic\helpers\MetaValue as MetaValueHelper;
20
use nystudio107\seomatic\helpers\Migration as MigrationHelper;
21
use nystudio107\seomatic\models\MetaBundle;
22
use nystudio107\seomatic\models\MetaScriptContainer;
23
use nystudio107\seomatic\models\MetaTagContainer;
24
use nystudio107\seomatic\records\MetaBundle as MetaBundleRecord;
25
use nystudio107\seomatic\services\Tag as TagService;
26
27
use Craft;
28
use craft\base\Component;
29
use craft\base\Element;
30
use craft\base\Model;
31
use craft\db\Query;
32
use craft\models\Section_SiteSettings;
33
use craft\models\CategoryGroup;
34
use craft\models\Section;
35
use craft\models\Site;
36
37
use craft\commerce\models\ProductType;
0 ignored issues
show
Bug introduced by
The type craft\commerce\models\ProductType 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...
38
39
/**
40
 * @author    nystudio107Meta bundle failed validation
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...
41
 * @package   Seomatic
42
 * @since     3.0.0
43
 */
44
class MetaBundles extends Component
45
{
46
    // Constants
47
    // =========================================================================
48
49
    const GLOBAL_META_BUNDLE = '__GLOBAL_BUNDLE__';
50
    const FIELD_META_BUNDLE = 'field';
51
52
    const IGNORE_DB_ATTRIBUTES = [
53
        'id',
54
        'dateCreated',
55
        'dateUpdated',
56
        'uid',
57
    ];
58
59
    const ALWAYS_INCLUDED_SEO_SETTINGS_FIELDS = [
60
        'twitterTitle',
61
        'twitterDescription',
62
        'twitterImage',
63
        'twitterImageDescription',
64
65
        'ogTitle',
66
        'ogDescription',
67
        'ogImage',
68
        'ogImageDescription',
69
    ];
70
71
    const COMPOSITE_INHERITANCE_CHILDREN = [
72
        'seoImage' => [
73
            'metaBundleSettings.seoImageTransformMode',
74
            'metaBundleSettings.seoImageTransform',
75
            'metaBundleSettings.seoImageSource',
76
            'metaBundleSettings.seoImageField',
77
            'metaBundleSettings.seoImageIds',
78
        ],
79
        'ogImage' => [
80
            'metaBundleSettings.ogImageTransformMode',
81
            'metaBundleSettings.ogImageTransform',
82
            'metaBundleSettings.ogImageSource',
83
            'metaBundleSettings.ogImageField',
84
            'metaBundleSettings.ogImageIds',
85
        ],
86
        'twitterImage' => [
87
            'metaBundleSettings.twitterImageTransformMode',
88
            'metaBundleSettings.twitterImageTransform',
89
            'metaBundleSettings.twitterImageSource',
90
            'metaBundleSettings.twitterImageField',
91
            'metaBundleSettings.twitterImageIds',
92
        ],
93
    ];
94
95
    // Protected Properties
96
    // =========================================================================
97
98
    /**
99
     * @var MetaBundle[] indexed by [id]
100
     */
101
    protected $metaBundles = [];
102
103
    /**
104
     * @var array indexed by [sourceId][sourceSiteId] = id
105
     */
106
    protected $metaBundlesBySourceId = [];
107
108
    /**
109
     * @var array indexed by [sourceHandle][sourceSiteId] = id
110
     */
111
    protected $metaBundlesBySourceHandle = [];
112
113
    /**
114
     * @var array indexed by [sourceSiteId] = id
115
     */
116
    protected $globalMetaBundles = [];
117
118
    /**
119
     * @var array parent meta bundles for elements
120
     */
121
    protected $elementContentMetaBundles = [];
122
123
    // Public Methods
124
    // =========================================================================
125
126
    /**
127
     * Get the global meta bundle for the site
128
     *
129
     * @param int  $sourceSiteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
130
     * @param bool $parse Whether the resulting metabundle should be parsed
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 1 found
Loading history...
131
     *
132
     * @return null|MetaBundle
133
     */
134
    public function getGlobalMetaBundle(int $sourceSiteId, $parse = true)
135
    {
136
        $metaBundle = null;
137
        // See if we have the meta bundle cached
138
        if (!empty($this->globalMetaBundles[$sourceSiteId])) {
139
            return $this->globalMetaBundles[$sourceSiteId];
140
        }
141
        $metaBundleArray = (new Query())
142
            ->from(['{{%seomatic_metabundles}}'])
143
            ->where([
144
                'sourceBundleType' => self::GLOBAL_META_BUNDLE,
145
                'sourceSiteId' => $sourceSiteId,
146
            ])
147
            ->one();
148
        if (!empty($metaBundleArray)) {
149
            // Get the attributes from the db
150
            $metaBundleArray = array_diff_key($metaBundleArray, array_flip(self::IGNORE_DB_ATTRIBUTES));
151
            $metaBundle = MetaBundle::create($metaBundleArray, $parse);
152
            if ($parse) {
153
                $this->syncBundleWithConfig($metaBundle);
154
            }
155
        } else {
156
            // If it doesn't exist, create it
157
            $metaBundle = $this->createGlobalMetaBundleForSite($sourceSiteId);
158
        }
159
        if ($parse) {
160
            // Cache it for future accesses
161
            $this->globalMetaBundles[$sourceSiteId] = $metaBundle;
162
        }
163
164
        return $metaBundle;
165
    }
166
167
    /**
168
     * @param MetaBundle $metaBundle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
169
     * @param int        $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
170
     */
171
    public function updateMetaBundle(MetaBundle $metaBundle, int $siteId)
172
    {
173
        $metaBundle->sourceName = (string)$metaBundle->sourceName;
174
        $metaBundle->sourceTemplate = (string)$metaBundle->sourceTemplate;
175
        // Make sure it validates
176
        if ($metaBundle->validate(null, true)) {
177
            // Save it out to a record
178
            $params = [
179
                'sourceBundleType' => $metaBundle->sourceBundleType,
180
                'sourceId' => $metaBundle->sourceId,
181
                'sourceSiteId' => $siteId,
182
            ];
183
            if ($metaBundle->typeId !== null) {
184
                $metaBundle->typeId = (int)$metaBundle->typeId;
185
            }
186
            if (!empty($metaBundle->typeId)) {
187
                $params['typeId'] = $metaBundle->typeId;
188
            } else {
189
                $metaBundle->typeId = null;
190
            }
191
            $metaBundleRecord = MetaBundleRecord::findOne($params);
192
193
            if (!$metaBundleRecord) {
0 ignored issues
show
introduced by
$metaBundleRecord is of type yii\db\ActiveRecord, thus it always evaluated to true.
Loading history...
194
                $metaBundleRecord = new MetaBundleRecord();
195
            }
196
197
            // @TODO remove this hack that doesn't allow environment-transformed settings to be saved in a meta bundle with a proper system to address it
198
            // The issue was that the containers were getting saved to the db with a hard-coded setting in them, because they'd
199
            // been set that way by the environment, whereas to be changeable via the GUI, it needs to be set to {seomatic.meta.robots}
200
            $robotsTag = $metaBundle->metaContainers[MetaTagContainer::CONTAINER_TYPE.TagService::GENERAL_HANDLE]->data['robots'] ?? null;
201
            if (!empty($robotsTag)) {
202
                $robotsTag->content = $robotsTag->environment['live']['content'] ?? '{seomatic.meta.robots}';
0 ignored issues
show
Bug Best Practice introduced by
The property content does not exist on nystudio107\seomatic\base\MetaItem. Since you implemented __set, consider adding a @property annotation.
Loading history...
203
            }
204
205
            $metaBundleRecord->setAttributes($metaBundle->getAttributes(), false);
206
207
            if ($metaBundleRecord->save()) {
208
                Craft::info(
209
                    'Meta bundle updated: '
210
                    .$metaBundle->sourceBundleType
211
                    .' id: '
212
                    .$metaBundle->sourceId
213
                    .' from siteId: '
214
                    .$metaBundle->sourceSiteId,
215
                    __METHOD__
216
                );
217
            }
218
        } else {
219
            Craft::error(
220
                'Meta bundle failed validation: '
221
                .print_r($metaBundle->getErrors(), true)
0 ignored issues
show
Bug introduced by
Are you sure print_r($metaBundle->getErrors(), true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

221
                ./** @scrutinizer ignore-type */ print_r($metaBundle->getErrors(), true)
Loading history...
222
                .' type: '
223
                .$metaBundle->sourceType
224
                .' id: '
225
                .$metaBundle->sourceId
226
                .' from siteId: '
227
                .$metaBundle->sourceSiteId,
228
                __METHOD__
229
            );
230
        }
231
    }
232
233
    /**
234
     * @param string   $sourceBundleType
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
235
     * @param int      $sourceId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
236
     * @param int|null $sourceSiteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
237
     * @param int|null $typeId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
238
     *
239
     * @return null|MetaBundle
240
     */
241
    public function getMetaBundleBySourceId(string $sourceBundleType, int $sourceId, int $sourceSiteId, $typeId = null)
242
    {
243
        $metaBundle = null;
244
        $typeId = (int)$typeId;
245
        // See if we have the meta bundle cached
246
        if (!empty($this->metaBundlesBySourceId[$sourceBundleType][$sourceId][$sourceSiteId][$typeId])) {
247
            $id = $this->metaBundlesBySourceId[$sourceBundleType][$sourceId][$sourceSiteId][$typeId];
248
            if (!empty($this->metaBundles[$id])) {
249
                return $this->metaBundles[$id];
250
            }
251
        }
252
        // Look for a matching meta bundle in the db
253
        $query = (new Query())
254
            ->from(['{{%seomatic_metabundles}}'])
255
            ->where([
256
                'sourceBundleType' => $sourceBundleType,
257
                'sourceId' => $sourceId,
258
                'sourceSiteId' => $sourceSiteId,
259
            ])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
260
            ;
261
        if (!empty($typeId)) {
262
            $query
263
                ->andWhere([
264
                    'typeId' => $typeId,
265
                ]);
266
        }
267
        $metaBundleArray = $query
268
            ->one();
269
        // If the specific query with a `typeId` returned nothing, try a more general query without `typeId`
270
        if (empty($metaBundleArray)) {
271
            $metaBundleArray = (new Query())
272
                ->from(['{{%seomatic_metabundles}}'])
273
                ->where([
274
                    'sourceBundleType' => $sourceBundleType,
275
                    'sourceId' => $sourceId,
276
                    'sourceSiteId' => $sourceSiteId,
277
                ])
278
                ->one();
279
        }
280
        if (!empty($metaBundleArray)) {
281
            // Get the attributes from the db
282
            $metaBundleArray = array_diff_key($metaBundleArray, array_flip(self::IGNORE_DB_ATTRIBUTES));
283
            $metaBundle = MetaBundle::create($metaBundleArray);
284
            $this->syncBundleWithConfig($metaBundle);
285
            $id = count($this->metaBundles);
286
            $this->metaBundles[$id] = $metaBundle;
287
            $this->metaBundlesBySourceId[$sourceBundleType][$sourceId][$sourceSiteId][$typeId] = $id;
288
        } else {
289
            // If it doesn't exist, create it
290
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
291
            if ($seoElement !== null) {
292
                $sourceModel = $seoElement::sourceModelFromId($sourceId);
293
                if ($sourceModel) {
294
                    $metaBundle = $this->createMetaBundleFromSeoElement($seoElement, $sourceModel, $sourceSiteId);
295
                }
296
            }
297
        }
298
299
        return $metaBundle;
300
    }
301
302
    /**
303
     * @param string $sourceBundleType
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
304
     * @param string $sourceHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
305
     * @param int    $sourceSiteId
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter type; 4 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
306
     * @param int|null $typeId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
307
     *
308
     * @return null|MetaBundle
309
     */
310
    public function getMetaBundleBySourceHandle(string $sourceBundleType, string $sourceHandle, int $sourceSiteId, $typeId = null)
311
    {
312
        $metaBundle = null;
313
        $typeId = (int)$typeId;
314
        // See if we have the meta bundle cached
315
        if (!empty($this->metaBundlesBySourceHandle[$sourceBundleType][$sourceHandle][$sourceSiteId][$typeId])) {
316
            $id = $this->metaBundlesBySourceHandle[$sourceBundleType][$sourceHandle][$sourceSiteId][$typeId];
317
            if (!empty($this->metaBundles[$id])) {
318
                return $this->metaBundles[$id];
319
            }
320
        }
321
        // Look for a matching meta bundle in the db
322
        $query = (new Query())
323
            ->from(['{{%seomatic_metabundles}}'])
324
            ->where([
325
                'sourceBundleType' => $sourceBundleType,
326
                'sourceHandle' => $sourceHandle,
327
                'sourceSiteId' => $sourceSiteId,
328
            ])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
329
            ;
330
        if (!empty($typeId)) {
331
            $query
332
                ->andWhere([
333
                    'typeId' => $typeId,
334
                ]);
335
        }
336
        $metaBundleArray = $query
337
            ->one();
338
        // If the specific query with a `typeId` returned nothing, try a more general query without `typeId`
339
        if (empty($metaBundleArray)) {
340
            $metaBundleArray = (new Query())
341
                ->from(['{{%seomatic_metabundles}}'])
342
                ->where([
343
                    'sourceBundleType' => $sourceBundleType,
344
                    'sourceHandle' => $sourceHandle,
345
                    'sourceSiteId' => $sourceSiteId,
346
                ])
347
                ->one();
348
        }
349
        if (!empty($metaBundleArray)) {
350
            $metaBundleArray = array_diff_key($metaBundleArray, array_flip(self::IGNORE_DB_ATTRIBUTES));
351
            $metaBundle = MetaBundle::create($metaBundleArray);
352
            $id = count($this->metaBundles);
353
            $this->metaBundles[$id] = $metaBundle;
354
            $this->metaBundlesBySourceHandle[$sourceBundleType][$sourceHandle][$sourceSiteId][$typeId] = $id;
355
        } else {
356
            // If it doesn't exist, create it
357
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
358
            if ($seoElement !== null) {
359
                $sourceModel = $seoElement::sourceModelFromHandle($sourceHandle);
360
                if ($sourceModel) {
361
                    $metaBundle = $this->createMetaBundleFromSeoElement($seoElement, $sourceModel, $sourceSiteId);
362
                }
363
            }
364
        }
365
366
        return $metaBundle;
367
    }
368
369
    /**
370
     * Invalidate the caches and data structures associated with this MetaBundle
371
     *
372
     * @param string   $sourceBundleType
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
373
     * @param int|null $sourceId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
374
     * @param bool     $isNew
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
375
     */
376
    public function invalidateMetaBundleById(string $sourceBundleType, int $sourceId, bool $isNew = false)
377
    {
378
        $metaBundleInvalidated = false;
379
        $sites = Craft::$app->getSites()->getAllSites();
380
        foreach ($sites as $site) {
381
            // See if this is a section we are tracking
382
            $metaBundle = $this->getMetaBundleBySourceId($sourceBundleType, $sourceId, $site->id);
383
            if ($metaBundle) {
384
                Craft::info(
385
                    'Invalidating meta bundle: '
386
                    .$metaBundle->sourceHandle
387
                    .' from siteId: '
388
                    .$site->id,
389
                    __METHOD__
390
                );
391
                // Is this a new source?
392
                if (!$isNew) {
393
                    $metaBundleInvalidated = true;
394
                    // Handle syncing up the sourceHandle
395
                    if ($sourceBundleType !== self::GLOBAL_META_BUNDLE) {
396
                        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
397
                        if ($seoElement !== null) {
398
                            /** @var Section|CategoryGroup|ProductType $sourceModel */
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...
399
                            $sourceModel = $seoElement::sourceModelFromId($sourceId);
400
                            if ($sourceModel !== null) {
401
                                $metaBundle->sourceName = (string)$sourceModel->name;
402
                                $metaBundle->sourceHandle = $sourceModel->handle;
403
                            }
404
                        }
405
                    }
406
                    // Invalidate caches after an existing section is saved
407
                    Seomatic::$plugin->metaContainers->invalidateContainerCacheById(
408
                        $sourceId,
409
                        $sourceBundleType,
410
                        $metaBundle->sourceSiteId
411
                    );
412
                    if (Seomatic::$settings->regenerateSitemapsAutomatically) {
413
                        Seomatic::$plugin->sitemaps->invalidateSitemapCache(
414
                            $metaBundle->sourceHandle,
415
                            $metaBundle->sourceSiteId,
416
                            $metaBundle->sourceBundleType
417
                        );
418
                    }
419
                    // Update the meta bundle data
420
                    $this->updateMetaBundle($metaBundle, $site->id);
421
                }
422
            }
423
        }
424
        // If we've invalidated a meta bundle, we need to invalidate the sitemap index, too
425
        if ($metaBundleInvalidated) {
0 ignored issues
show
introduced by
The condition $metaBundleInvalidated is always false.
Loading history...
426
            Seomatic::$plugin->sitemaps->invalidateSitemapIndexCache();
427
        }
428
    }
429
430
    /**
431
     * Invalidate the caches and data structures associated with this MetaBundle
432
     *
433
     * @param Element $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
434
     * @param bool    $isNew
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
435
     */
436
    public function invalidateMetaBundleByElement($element, bool $isNew = false)
0 ignored issues
show
Unused Code introduced by
The parameter $isNew 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

436
    public function invalidateMetaBundleByElement($element, /** @scrutinizer ignore-unused */ bool $isNew = false)

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...
437
    {
438
        $metaBundleInvalidated = false;
439
        $invalidateMetaBundle = true;
440
        if (Seomatic::$craft32) {
441
            if ($element->getIsDraft() || $element->getIsRevision()) {
442
                $invalidateMetaBundle = false;
443
            }
444
        }
445
        if ($element && $invalidateMetaBundle) {
446
            $uri = $element->uri ?? '';
447
            // Normalize the incoming URI to account for `__home__`
448
            if ($element->slug) {
449
                $uri = ($element->slug === '__home__') ? '' : $uri;
450
            }
451
            // Invalidate sitemap caches after an existing element is saved
452
            list($sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId)
453
                = $this->getMetaSourceFromElement($element);
454
            if ($sourceId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
455
                Craft::info(
456
                    'Invalidating meta bundle: '
457
                    .$uri
458
                    .'/'
459
                    .$sourceSiteId,
460
                    __METHOD__
461
                );
462
                $metaBundleInvalidated = true;
463
                Seomatic::$plugin->metaContainers->invalidateContainerCacheByPath($uri, $sourceSiteId);
464
                // Invalidate the sitemap cache
465
                $metaBundle = $this->getMetaBundleBySourceId($sourceBundleType, $sourceId, $sourceSiteId);
466
                if ($metaBundle) {
467
                    if ($element) {
0 ignored issues
show
introduced by
$element is of type craft\base\Element, thus it always evaluated to true.
Loading history...
468
                        $dateUpdated = $element->dateUpdated ?? $element->dateCreated;
469
                    } else {
470
                        try {
471
                            $dateUpdated = new \DateTime();
472
                        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
473
                        }
474
                    }
475
                    $metaBundle->sourceDateUpdated = $dateUpdated;
476
                    // Update the meta bundle data
477
                    $this->updateMetaBundle($metaBundle, $sourceSiteId);
478
                    if ($metaBundle
479
                        && $element->scenario !== Element::SCENARIO_ESSENTIALS
480
                        && Seomatic::$settings->regenerateSitemapsAutomatically) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
481
                        Seomatic::$plugin->sitemaps->invalidateSitemapCache(
482
                            $metaBundle->sourceHandle,
483
                            $metaBundle->sourceSiteId,
484
                            $metaBundle->sourceBundleType
485
                        );
486
                    }
487
                }
488
            }
489
            // If we've invalidated a meta bundle, we need to invalidate the sitemap index, too
490
            if ($metaBundleInvalidated && $element->scenario !== Element::SCENARIO_ESSENTIALS) {
491
                Seomatic::$plugin->sitemaps->invalidateSitemapIndexCache();
492
            }
493
        }
494
    }
495
496
    /**
497
     * Delete a meta bundle by $sourceId
498
     *
499
     * @param string $sourceBundleType
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
500
     * @param int    $sourceId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
501
     * @param null   $siteId
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $siteId is correct as it would always require null to be passed?
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
502
     */
503
    public function deleteMetaBundleBySourceId(string $sourceBundleType, int $sourceId, $siteId = null)
504
    {
505
        $sites = [];
506
        if ($siteId === null) {
0 ignored issues
show
introduced by
The condition $siteId === null is always true.
Loading history...
507
            $sites = Craft::$app->getSites()->getAllSites();
508
        } else {
509
            $sites[] = Craft::$app->getSites()->getSiteById($siteId);
510
        }
511
        /** @var  $site Site */
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...
512
        foreach ($sites as $site) {
513
            // Look for a matching meta bundle in the db
514
            $metaBundleRecord = MetaBundleRecord::findOne([
515
                'sourceBundleType' => $sourceBundleType,
516
                'sourceId' => $sourceId,
517
                'sourceSiteId' => $site->id,
518
            ]);
519
520
            if ($metaBundleRecord) {
521
                try {
522
                    $metaBundleRecord->delete();
523
                } catch (\Throwable $e) {
524
                    Craft::error($e->getMessage(), __METHOD__);
525
                }
526
                Craft::info(
527
                    'Meta bundle deleted: '
528
                    .$sourceId
529
                    .' from siteId: '
530
                    .$site->id,
531
                    __METHOD__
532
                );
533
            }
534
        }
535
    }
536
537
    /**
538
     * @param Element $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
539
     *
540
     * @return array
541
     */
542
    public function getMetaSourceFromElement(Element $element): array
543
    {
544
        $sourceId = 0;
545
        $typeId = null;
546
        $sourceSiteId = 0;
547
        $sourceHandle = '';
548
        // See if this is a section we are tracking
549
        $sourceBundleType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
550
        if ($sourceBundleType) {
551
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
552
            if ($seoElement) {
553
                $sourceId = $seoElement::sourceIdFromElement($element);
554
                $typeId = $seoElement::typeIdFromElement($element);
555
                $sourceHandle = $seoElement::sourceHandleFromElement($element);
556
                $sourceSiteId = $element->siteId;
557
            }
558
        } else {
559
            $sourceBundleType = '';
560
        }
561
562
        return [$sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId];
563
    }
564
565
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filter should have a doc-comment as per coding-style.
Loading history...
566
     * Get all of the meta bundles for a given $sourceSiteId
567
     *
568
     * @param int|null $sourceSiteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
569
     *
570
     * @return array
571
     */
572
    public function getContentMetaBundlesForSiteId($sourceSiteId, $filter = ''): array
573
    {
574
        $metaBundles = [];
575
        $bundles = [];
576
        // Since sectionIds, CategoryIds, etc. are not unique, we need to do separate queries and combine them
577
        $seoElements = Seomatic::$plugin->seoElements->getAllSeoElementTypes();
578
        foreach ($seoElements as $seoElement) {
579
580
            $subQuery = (new Query())
581
                ->from(['{{%seomatic_metabundles}}'])
582
                ->where(['=', 'sourceBundleType', $seoElement::META_BUNDLE_TYPE]);
0 ignored issues
show
Bug introduced by
The constant nystudio107\seomatic\bas...rface::META_BUNDLE_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
583
584
            if ((int)$sourceSiteId !== 0) {
585
                $subQuery->andWhere(['sourceSiteId' => $sourceSiteId]);
586
            }
587
            if ($filter !== '') {
588
                $subQuery->andWhere(['like', 'sourceName', $filter]);
589
            }
590
            $bundleQuery = (new Query())
591
                ->select(['mb.*'])
592
                ->from(['mb' => $subQuery])
593
                ->leftJoin(['mb2' => $subQuery], [
594
                    'and',
595
                    '[[mb.sourceId]] = [[mb2.sourceId]]',
596
                    '[[mb.id]] < [[mb2.id]]'
597
                ])
598
                ->where(['mb2.id' => null])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
599
            ;
600
            $bundles = array_merge($bundles, $bundleQuery->all());
601
        }
602
        foreach ($bundles as $bundle) {
603
            $bundle = array_diff_key($bundle, array_flip(self::IGNORE_DB_ATTRIBUTES));
604
            $metaBundle = MetaBundle::create($bundle);
605
            if ($metaBundle) {
606
                $metaBundles[] = $metaBundle;
607
            }
608
        }
609
610
        return $metaBundles;
611
    }
612
613
    /**
614
     * Get the parent content meta bundle for a given element.
615
     *
616
     * @param Element $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
617
     * @return mixed|MetaBundle|null
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
618
     */
619
    public function getContentMetaBundleForElement(Element $element)
620
    {
621
        $source = $this->getMetaSourceFromElement($element);
622
        $key = implode(".", $source) . '.' . $element->siteId;
623
624
        if (empty($this->elementContentMetaBundles[$key])) {
625
            $this->elementContentMetaBundles[$key] = $this->getMetaBundleBySourceId($source[1], $source[0], $element->siteId, $source[4]);
0 ignored issues
show
Bug introduced by
It seems like $source[0] can also be of type null; however, parameter $sourceId of nystudio107\seomatic\ser...tMetaBundleBySourceId() does only seem to accept integer, 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

625
            $this->elementContentMetaBundles[$key] = $this->getMetaBundleBySourceId($source[1], /** @scrutinizer ignore-type */ $source[0], $element->siteId, $source[4]);
Loading history...
Bug introduced by
It seems like $element->siteId can also be of type null; however, parameter $sourceSiteId of nystudio107\seomatic\ser...tMetaBundleBySourceId() does only seem to accept integer, 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

625
            $this->elementContentMetaBundles[$key] = $this->getMetaBundleBySourceId($source[1], $source[0], /** @scrutinizer ignore-type */ $element->siteId, $source[4]);
Loading history...
626
        }
627
628
        return $this->elementContentMetaBundles[$key];
629
    }
630
631
    /**
632
     * Set fields the user is unable to edit to an empty string, so they are
633
     * filtered out when meta containers are combined
634
     *
635
     * @param MetaBundle $metaBundle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
636
     * @param string     $fieldHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
637
     */
638
    public function pruneFieldMetaBundleSettings(MetaBundle $metaBundle, string $fieldHandle)
639
    {
640
        /** @var SeoSettings $seoSettingsField */
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...
641
        $seoSettingsField = Craft::$app->getFields()->getFieldByHandle($fieldHandle);
642
        if ($seoSettingsField) {
0 ignored issues
show
introduced by
$seoSettingsField is of type nystudio107\seomatic\fields\SeoSettings, thus it always evaluated to true.
Loading history...
643
            $seoSettingsEnabledFields = array_flip(array_merge(
644
                $seoSettingsField->generalEnabledFields,
645
                $seoSettingsField->twitterEnabledFields,
646
                $seoSettingsField->facebookEnabledFields,
647
                $seoSettingsField->sitemapEnabledFields
648
            ));
649
            // Always include some fields, as they are calculated even if not explicitly included
650
            $seoSettingsEnabledFields = array_merge(
651
                $seoSettingsEnabledFields,
652
                array_flip(self::ALWAYS_INCLUDED_SEO_SETTINGS_FIELDS)
653
            );
654
            // metaGlobalVars
655
            $attributes = $metaBundle->metaGlobalVars->getAttributes();
656
657
            // Get a list of explicitly inherited values
658
            $inherited = array_keys(ArrayHelper::remove($attributes, 'inherited', []));
0 ignored issues
show
Bug introduced by
It seems like nystudio107\seomatic\hel..., 'inherited', array()) can also be of type null; however, parameter $array of array_keys() does only seem to accept array, 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

658
            $inherited = array_keys(/** @scrutinizer ignore-type */ ArrayHelper::remove($attributes, 'inherited', []));
Loading history...
659
            $emptyValues = array_fill_keys(array_keys(array_diff_key($attributes, $seoSettingsEnabledFields)), '');
660
661
            // Nullify the inherited values
662
            $emptyValues = array_merge($emptyValues, array_fill_keys($inherited, ''));
663
            foreach ($inherited as $inheritedAttribute) {
664
                foreach (self::COMPOSITE_INHERITANCE_CHILDREN[$inheritedAttribute] ?? [] as $child) {
665
                    list ($model, $attribute) = explode('.', $child);
666
                    $metaBundle->{$model}->$attribute = '';
667
                }
668
            }
669
670
            $attributes = array_merge($attributes, $emptyValues);
671
            $metaBundle->metaGlobalVars->setAttributes($attributes, false);
672
673
674
            // Handle the mainEntityOfPage
675
            if (!\in_array('mainEntityOfPage', $seoSettingsField->generalEnabledFields, false)) {
676
                $metaBundle->metaGlobalVars->mainEntityOfPage = '';
677
            }
678
            // metaSiteVars
679
            $attributes = $metaBundle->metaSiteVars->getAttributes();
680
            $emptyValues = array_fill_keys(array_keys(array_diff_key($attributes, $seoSettingsEnabledFields)), '');
681
            $attributes = array_merge($attributes, $emptyValues);
682
            $metaBundle->metaSiteVars->setAttributes($attributes, false);
683
            // metaSitemapVars
684
            $attributes = $metaBundle->metaSitemapVars->getAttributes();
685
686
            // Get a list of explicitly inherited values
687
            $inherited = array_keys(ArrayHelper::remove($attributes, 'inherited', []));
688
            $emptyValues = array_fill_keys(array_keys(array_diff_key($attributes, $seoSettingsEnabledFields)), '');
689
690
            // Nullify the inherited values
691
            $emptyValues = array_merge($emptyValues, array_fill_keys($inherited, ''));
692
693
            $attributes = array_merge($attributes, $emptyValues);
694
            $metaBundle->metaSitemapVars->setAttributes($attributes, false);
695
        }
696
    }
697
698
    /**
699
     * Remove any meta bundles from the $metaBundles array that no longer
700
     * correspond with an SeoElement
701
     *
702
     * @param array $metaBundles
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
703
     */
704
    public function pruneVestigialMetaBundles(array &$metaBundles)
705
    {
706
        foreach ($metaBundles as $key => $metaBundle) {
707
            $prune = $this->pruneVestigialMetaBundle($metaBundle);
708
            /** @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...
709
            if ($prune) {
710
                unset($metaBundles[$key]);
711
            }
712
        }
713
        ArrayHelper::multisort($metaBundles, 'sourceName');
714
    }
715
716
    /**
717
     * Delete any meta bundles from the $metaBundles array that no longer
718
     * correspond with an SeoElement
719
     *
720
     * @param array $metaBundles
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
721
     */
722
    public function deleteVestigialMetaBundles(array $metaBundles)
723
    {
724
        foreach ($metaBundles as $key => $metaBundle) {
725
            $prune = $this->pruneVestigialMetaBundle($metaBundle);
726
            /** @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...
727
            if ($prune) {
728
                $this->deleteMetaBundleBySourceId(
729
                    $metaBundle->sourceBundleType,
730
                    $metaBundle->sourceId,
731
                    $metaBundle->sourceSiteId
732
                );
733
            }
734
        }
735
    }
736
737
    /**
738
     * Determine whether a given MetaBundle is vestigial or not
739
     *
740
     * @param $metaBundle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
741
     *
742
     * @return bool
743
     */
744
    public function pruneVestigialMetaBundle($metaBundle): bool
745
    {
746
        $prune = false;
747
        $sourceBundleType = $metaBundle->sourceBundleType;
748
        if ($sourceBundleType !== self::GLOBAL_META_BUNDLE) {
749
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
750
            if ($seoElement) {
751
                $sourceModel = $seoElement::sourceModelFromHandle($metaBundle->sourceHandle);
752
                /** @var Section|CategoryGroup|ProductType $sourceModel */
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...
753
                if ($sourceModel === null) {
754
                    $prune = true;
755
                } else {
756
                    $prune = true;
757
                    $siteSettings = $sourceModel->getSiteSettings();
758
                    if (!empty($siteSettings)) {
759
                        /** @var Section_SiteSettings $siteSetting */
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...
760
                        foreach ($siteSettings as $siteSetting) {
761
                            if ($siteSetting->siteId == $metaBundle->sourceSiteId && $siteSetting->hasUrls) {
762
                                $prune = false;
763
                            }
764
                        }
765
                    }
766
                }
767
            } else {
768
                $prune = true;
769
            }
770
        }
771
772
        return $prune;
773
    }
774
775
    /**
776
     * Get all of the data from $bundle in containers of $type
777
     *
778
     * @param MetaBundle $bundle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
779
     * @param string     $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
780
     *
781
     * @return array
782
     */
783
    public function getContainerDataFromBundle(MetaBundle $bundle, string $type): array
784
    {
785
        $containerData = [];
786
        foreach ($bundle->metaContainers as $metaContainer) {
787
            if ($metaContainer::CONTAINER_TYPE === $type) {
788
                foreach ($metaContainer->data as $dataHandle => $data) {
789
                    $containerData[$dataHandle] = $data;
790
                }
791
            }
792
        }
793
794
        return $containerData;
795
    }
796
797
    /**
798
     * Create all of the content meta bundles
799
     */
800
    public function createContentMetaBundles()
801
    {
802
        $seoElements = Seomatic::$plugin->seoElements->getAllSeoElementTypes();
803
        foreach ($seoElements as $seoElement) {
804
            /** @var SeoElementInterface $seoElement */
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...
805
            $seoElement::createAllContentMetaBundles();
806
        }
807
    }
808
809
    /**
810
     * Create the default global meta bundles
811
     */
812
    public function createGlobalMetaBundles()
813
    {
814
        $sites = Craft::$app->getSites()->getAllSites();
815
        foreach ($sites as $site) {
816
            $this->createGlobalMetaBundleForSite($site->id);
817
        }
818
    }
819
820
    /**
821
     * Synchronize the passed in metaBundle with the seomatic-config files if
822
     * there is a newer version of the MetaBundle bundleVersion in the config
823
     * file
824
     *
825
     * @param MetaBundle $metaBundle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
826
     * @param bool       $forceUpdate
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
827
     */
828
    public function syncBundleWithConfig(MetaBundle &$metaBundle, bool $forceUpdate = false)
829
    {
830
        $prevMetaBundle = $metaBundle;
831
        $config = [];
832
        $sourceBundleType = $metaBundle->sourceBundleType;
833
        if ($sourceBundleType === self::GLOBAL_META_BUNDLE) {
834
            $config = ConfigHelper::getConfigFromFile('globalmeta/Bundle');
835
        }
836
        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
837
        if ($seoElement) {
838
            $configPath = $seoElement::configFilePath();
839
            $config = ConfigHelper::getConfigFromFile($configPath);
840
        }
841
        // If the config file has a newer version than the $metaBundleArray, merge them
842
        $shouldUpdate = !empty($config) && version_compare($config['bundleVersion'], $metaBundle->bundleVersion, '>');
843
        if ($shouldUpdate || $forceUpdate) {
844
            // Create a new meta bundle
845
            if ($sourceBundleType === self::GLOBAL_META_BUNDLE) {
846
                $metaBundle = $this->createGlobalMetaBundleForSite(
847
                    $metaBundle->sourceSiteId,
848
                    $metaBundle
849
                );
850
            } else {
851
                $sourceModel = $seoElement::sourceModelFromId($metaBundle->sourceId);
852
                if ($sourceModel) {
853
                    $metaBundle = $this->createMetaBundleFromSeoElement(
854
                        $seoElement,
855
                        $sourceModel,
856
                        $metaBundle->sourceSiteId,
857
                        $metaBundle
858
                    );
859
                }
860
            }
861
        }
862
863
        // If for some reason we were unable to sync this meta bundle, return the old one
864
        if ($metaBundle === null) {
865
            $metaBundle = $prevMetaBundle;
866
        }
867
    }
868
869
    /**
870
     * @param SeoElementInterface $seoElement
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
871
     * @param Model               $sourceModel
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
872
     * @param int                 $sourceSiteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
873
     * @param MetaBundle|null     $baseConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
874
     *
875
     * @return MetaBundle|null
876
     */
877
    public function createMetaBundleFromSeoElement(
878
        $seoElement,
879
        $sourceModel,
880
        int $sourceSiteId,
881
        $baseConfig = null
882
    ) {
883
        $metaBundle = null;
884
        // Get the site settings and turn them into arrays
885
        /** @var Section|CategoryGroup|ProductType $sourceModel */
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...
886
        $siteSettings = $sourceModel->getSiteSettings();
887
        if (!empty($siteSettings[$sourceSiteId])) {
888
            $siteSettingsArray = [];
889
            /** @var Section_SiteSettings $siteSetting  */
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...
890
            foreach ($siteSettings as $siteSetting) {
891
                if ($siteSetting->hasUrls) {
892
                    $siteSettingArray = $siteSetting->toArray();
893
                    // Get the site language
894
                    $siteSettingArray['language'] = MetaValueHelper::getSiteLanguage($siteSetting->siteId);
895
                    $siteSettingsArray[] = $siteSettingArray;
896
                }
897
            }
898
            $siteSettingsArray = ArrayHelper::index($siteSettingsArray, 'siteId');
899
            // Create a MetaBundle for this site
900
            $siteSetting = $siteSettings[$sourceSiteId];
901
            if ($siteSetting->hasUrls) {
902
                // Get the most recent dateUpdated
903
                $element = $seoElement::mostRecentElement($sourceModel, $sourceSiteId);
904
                /** @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...
905
                if ($element) {
0 ignored issues
show
introduced by
$element is of type craft\base\Element, thus it always evaluated to true.
Loading history...
906
                    $dateUpdated = $element->dateUpdated ?? $element->dateCreated;
907
                } else {
908
                    try {
909
                        $dateUpdated = new \DateTime();
910
                    } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
911
                    }
912
                }
913
                // Create a new meta bundle with propagated defaults
914
                $metaBundleDefaults = ArrayHelper::merge(
915
                    $seoElement::metaBundleConfig($sourceModel),
916
                    [
917
                        'sourceTemplate' => (string)$siteSetting->template,
918
                        'sourceSiteId' => $siteSetting->siteId,
919
                        'sourceAltSiteSettings' => $siteSettingsArray,
920
                        'sourceDateUpdated' => $dateUpdated,
921
                    ]
922
                );
923
                // The mainEntityOfPage computedType must be set before creating the bundle
924
                if ($baseConfig !== null && !empty($baseConfig->metaGlobalVars->mainEntityOfPage)) {
925
                    $metaBundleDefaults['metaGlobalVars']['mainEntityOfPage'] =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
926
                        $baseConfig->metaGlobalVars->mainEntityOfPage;
927
                }
928
                // Merge in any migrated settings from an old Seomatic_Meta Field
929
                if ($element !== null) {
930
                    /** @var Element $elementFromSite */
0 ignored issues
show
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
931
                    $elementFromSite = Craft::$app->getElements()->getElementById($element->id, null, $sourceSiteId);
932
                    if ($element instanceof Element) {
0 ignored issues
show
introduced by
$element is always a sub-type of craft\base\Element.
Loading history...
933
                        $config = MigrationHelper::configFromSeomaticMeta(
934
                            $elementFromSite,
935
                            MigrationHelper::SECTION_MIGRATION_CONTEXT
936
                        );
937
                        $metaBundleDefaults = ArrayHelper::merge(
938
                            $metaBundleDefaults,
939
                            $config
940
                        );
941
                    }
942
                }
943
                $metaBundle = MetaBundle::create($metaBundleDefaults);
944
                if ($baseConfig !== null) {
945
                    $this->mergeMetaBundleSettings($metaBundle, $baseConfig);
946
                }
947
                $this->updateMetaBundle($metaBundle, $sourceSiteId);
948
            }
949
        }
950
951
        return $metaBundle;
952
    }
953
954
    /**
955
     * @param int             $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
956
     * @param MetaBundle|null $baseConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
957
     *
958
     * @return MetaBundle
959
     */
960
    public function createGlobalMetaBundleForSite(int $siteId, $baseConfig = null): MetaBundle
961
    {
962
        // Create a new meta bundle with propagated defaults
963
        $metaBundleDefaults = ArrayHelper::merge(
964
            ConfigHelper::getConfigFromFile('globalmeta/Bundle'),
965
            [
966
                'sourceSiteId' => $siteId,
967
            ]
968
        );
969
        // The computedType must be set before creating the bundle
970
        if ($baseConfig !== null) {
971
            $metaBundleDefaults['metaGlobalVars']['mainEntityOfPage'] = $baseConfig->metaGlobalVars->mainEntityOfPage;
972
            $metaBundleDefaults['metaSiteVars']['identity']['computedType'] =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
973
                $baseConfig->metaSiteVars->identity->computedType;
974
            $metaBundleDefaults['metaSiteVars']['creator']['computedType'] =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
975
                $baseConfig->metaSiteVars->creator->computedType;
976
        }
977
        $metaBundle = MetaBundle::create($metaBundleDefaults);
978
        if ($metaBundle !== null) {
979
            if ($baseConfig !== null) {
980
                $this->mergeMetaBundleSettings($metaBundle, $baseConfig);
981
            }
982
            $this->updateMetaBundle($metaBundle, $siteId);
983
        }
984
985
        return $metaBundle;
986
    }
987
988
    // Protected Methods
989
    // =========================================================================
990
991
    /**
992
     * Preserve user settings from the meta bundle when updating it from the
993
     * config
994
     *
995
     * @param MetaBundle $metaBundle The new meta bundle
996
     * @param MetaBundle $baseConfig The existing meta bundle to preserve
997
     *                               settings from
998
     */
999
    protected function mergeMetaBundleSettings(MetaBundle $metaBundle, MetaBundle $baseConfig)
1000
    {
1001
        // Preserve the metaGlobalVars
1002
        $attributes = $baseConfig->metaGlobalVars->getAttributes();
1003
        $metaBundle->metaGlobalVars->setAttributes($attributes);
1004
        // Preserve the metaSiteVars
1005
        if ($baseConfig->metaSiteVars !== null) {
1006
            $attributes = $baseConfig->metaSiteVars->getAttributes();
1007
            $metaBundle->metaSiteVars->setAttributes($attributes);
1008
            if ($baseConfig->metaSiteVars->identity !== null) {
1009
                $attributes = $baseConfig->metaSiteVars->identity->getAttributes();
1010
                $metaBundle->metaSiteVars->identity->setAttributes($attributes);
1011
            }
1012
            if ($baseConfig->metaSiteVars->creator !== null) {
1013
                $attributes = $baseConfig->metaSiteVars->creator->getAttributes();
1014
                $metaBundle->metaSiteVars->creator->setAttributes($attributes);
1015
            }
1016
        }
1017
        // Preserve the Frontend Templates, but add in any new containers
1018
        foreach ($baseConfig->frontendTemplatesContainer->data as $key => $value) {
1019
            $metaBundle->frontendTemplatesContainer->data[$key] = $value;
1020
        }
1021
        // Preserve the metaSitemapVars
1022
        $attributes = $baseConfig->metaSitemapVars->getAttributes();
1023
        $metaBundle->metaSitemapVars->setAttributes($attributes);
1024
        // Preserve the metaBundleSettings
1025
        $attributes = $baseConfig->metaBundleSettings->getAttributes();
1026
        $metaBundle->metaBundleSettings->setAttributes($attributes);
1027
        // Preserve the Script containers, but add in any new containers
1028
        foreach ($baseConfig->metaContainers as $baseMetaContainerName => $baseMetaContainer) {
1029
            if ($baseMetaContainer::CONTAINER_TYPE === MetaScriptContainer::CONTAINER_TYPE) {
1030
                foreach ($baseMetaContainer->data as $key => $value) {
1031
                    if (!empty($metaBundle->metaContainers[$baseMetaContainerName])) {
1032
                        $metaBundle->metaContainers[$baseMetaContainerName]->data[$key] = $value;
1033
                    }
1034
                }
1035
            }
1036
        }
1037
    }
1038
}
1039