MetaBundles::mergeMetaBundleSettings()   C
last analyzed

Complexity

Conditions 12
Paths 120

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
eloc 26
c 0
b 0
f 0
dl 0
loc 40
ccs 0
cts 27
cp 0
rs 6.8
cc 12
nc 120
nop 2
crap 156

How to fix   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 Craft;
15
use craft\base\Component;
16
use craft\base\Element;
17
use craft\base\Model;
18
use craft\commerce\models\ProductType;
19
use craft\db\Query;
20
use craft\models\CategoryGroup;
21
use craft\models\Section;
22
use craft\models\Section_SiteSettings;
23
use craft\models\Site;
24
use DateTime;
25
use nystudio107\seomatic\base\SeoElementInterface;
26
use nystudio107\seomatic\fields\SeoSettings;
27
use nystudio107\seomatic\helpers\ArrayHelper;
28
use nystudio107\seomatic\helpers\Config as ConfigHelper;
29
use nystudio107\seomatic\helpers\MetaValue as MetaValueHelper;
30
use nystudio107\seomatic\helpers\Migration as MigrationHelper;
31
use nystudio107\seomatic\helpers\SiteHelper;
32
use nystudio107\seomatic\models\MetaBundle;
33
use nystudio107\seomatic\models\MetaScriptContainer;
34
use nystudio107\seomatic\models\metatag\RobotsTag;
35
use nystudio107\seomatic\models\MetaTagContainer;
36
use nystudio107\seomatic\records\MetaBundle as MetaBundleRecord;
37
use nystudio107\seomatic\Seomatic;
38
use nystudio107\seomatic\services\Tag as TagService;
39
use Throwable;
40
use yii\base\InvalidConfigException;
41
use function in_array;
42
43
/**
44
 * Meta bundle functions for SEOmatic
45
 * An instance of the service is available via [[`Seomatic::$plugin->metaBundles`|`seomatic.bundles`]]
46
 *
47
 * @author    nystudio107Meta bundle failed validation
48
 * @package   Seomatic
49
 * @since     3.0.0
50
 */
51
class MetaBundles extends Component
52
{
53
    // Constants
54
    // =========================================================================
55
56
    const GLOBAL_META_BUNDLE = '__GLOBAL_BUNDLE__';
57
    const FIELD_META_BUNDLE = 'field';
58
59
    const IGNORE_DB_ATTRIBUTES = [
60
        'id',
61
        'dateCreated',
62
        'dateUpdated',
63
        'uid',
64
    ];
65
66
    const ALWAYS_INCLUDED_SEO_SETTINGS_FIELDS = [
67
        'twitterTitle',
68
        'twitterDescription',
69
        'twitterImage',
70
        'twitterImageDescription',
71
72
        'ogTitle',
73
        'ogDescription',
74
        'ogImage',
75
        'ogImageDescription',
76
    ];
77
78
    const COMPOSITE_INHERITANCE_CHILDREN = [
79
        'seoImage' => [
80
            'metaBundleSettings.seoImageTransformMode',
81
            'metaBundleSettings.seoImageTransform',
82
            'metaBundleSettings.seoImageSource',
83
            'metaBundleSettings.seoImageField',
84
            'metaBundleSettings.seoImageIds',
85
        ],
86
        'ogImage' => [
87
            'metaBundleSettings.ogImageTransformMode',
88
            'metaBundleSettings.ogImageTransform',
89
            'metaBundleSettings.ogImageSource',
90
            'metaBundleSettings.ogImageField',
91
            'metaBundleSettings.ogImageIds',
92
        ],
93
        'twitterImage' => [
94
            'metaBundleSettings.twitterImageTransformMode',
95
            'metaBundleSettings.twitterImageTransform',
96
            'metaBundleSettings.twitterImageSource',
97
            'metaBundleSettings.twitterImageField',
98
            'metaBundleSettings.twitterImageIds',
99
        ],
100
    ];
101
102
    const PRESERVE_SCRIPT_SETTINGS = [
103
        'include',
104
        'tagAttrs',
105
        'templateString',
106
        'position',
107
        'bodyTemplateString',
108
        'bodyPosition',
109
        'vars',
110
    ];
111
112
    const PRESERVE_FRONTEND_TEMPLATE_SETTINGS = [
113
        'include',
114
        'templateString',
115
    ];
116
117
    // Protected Properties
118
    // =========================================================================
119
120
    /**
121
     * @var MetaBundle[] indexed by [id]
122
     */
123
    protected $metaBundles = [];
124
125
    /**
126
     * @var array indexed by [sourceId][sourceSiteId] = id
127
     */
128
    protected $metaBundlesBySourceId = [];
129
130
    /**
131
     * @var array indexed by [sourceHandle][sourceSiteId] = id
132
     */
133
    protected $metaBundlesBySourceHandle = [];
134
135
    /**
136
     * @var array indexed by [sourceSiteId] = id
137
     */
138
    protected $globalMetaBundles = [];
139
140
    /**
141
     * @var array parent meta bundles for elements
142
     */
143
    protected $elementContentMetaBundles = [];
144
145
    // Public Methods
146
    // =========================================================================
147
148
    /**
149
     * Get the global meta bundle for the site
150
     *
151
     * @param int $sourceSiteId
152
     * @param bool $parse Whether the resulting metabundle should be parsed
153
     *
154
     * @return null|MetaBundle
155
     */
156
    public function getGlobalMetaBundle(int $sourceSiteId, $parse = true)
157
    {
158
        $metaBundle = null;
159
        // See if we have the meta bundle cached
160
        if (!empty($this->globalMetaBundles[$sourceSiteId])) {
161
            return $this->globalMetaBundles[$sourceSiteId];
162
        }
163
        $metaBundleArray = (new Query())
164
            ->from(['{{%seomatic_metabundles}}'])
165
            ->where([
166
                'sourceBundleType' => self::GLOBAL_META_BUNDLE,
167
                'sourceSiteId' => $sourceSiteId,
168
            ])
169
            ->one();
170
        if (!empty($metaBundleArray)) {
171
            // Get the attributes from the db
172
            $metaBundleArray = array_diff_key($metaBundleArray, array_flip(self::IGNORE_DB_ATTRIBUTES));
173
            $metaBundle = MetaBundle::create($metaBundleArray, $parse);
174
            if ($parse) {
175
                $this->syncBundleWithConfig($metaBundle);
176
            }
177
        } else {
178
            // If it doesn't exist, create it
179
            $metaBundle = $this->createGlobalMetaBundleForSite($sourceSiteId);
180
        }
181
        if ($parse) {
182
            // Cache it for future accesses
183
            $this->globalMetaBundles[$sourceSiteId] = $metaBundle;
184
        }
185
186
        return $metaBundle;
187
    }
188
189
    /**
190
     * Synchronize the passed in metaBundle with the seomatic-config files if
191
     * there is a newer version of the MetaBundle bundleVersion in the config
192
     * file
193
     *
194
     * @param MetaBundle $metaBundle
195
     * @param bool $forceUpdate
196
     */
197
    public function syncBundleWithConfig(MetaBundle &$metaBundle, bool $forceUpdate = false)
198
    {
199
        $prevMetaBundle = $metaBundle;
200
        $config = [];
201
        $sourceBundleType = $metaBundle->sourceBundleType;
202
        if ($sourceBundleType === self::GLOBAL_META_BUNDLE) {
203
            $config = ConfigHelper::getConfigFromFile('globalmeta/Bundle');
204
        }
205
        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
206
        if ($seoElement) {
207
            $configPath = $seoElement::configFilePath();
208
            $config = ConfigHelper::getConfigFromFile($configPath);
209
        }
210
        // If the config file has a newer version than the $metaBundleArray, merge them
211
        $shouldUpdate = !empty($config) && version_compare($config['bundleVersion'], $metaBundle->bundleVersion, '>');
212
        if ($shouldUpdate || $forceUpdate) {
213
            // Create a new meta bundle
214
            if ($sourceBundleType === self::GLOBAL_META_BUNDLE) {
215
                $metaBundle = $this->createGlobalMetaBundleForSite(
216
                    $metaBundle->sourceSiteId,
217
                    $metaBundle
218
                );
219
            } else {
220
                $sourceModel = $seoElement::sourceModelFromId($metaBundle->sourceId);
221
                if ($sourceModel) {
222
                    $metaBundle = $this->createMetaBundleFromSeoElement(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $metaBundle is correct as $this->createMetaBundleF...rceSiteId, $metaBundle) targeting nystudio107\seomatic\ser...aBundleFromSeoElement() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
223
                        $seoElement,
224
                        $sourceModel,
225
                        $metaBundle->sourceSiteId,
226
                        $metaBundle
227
                    );
228
                }
229
            }
230
        }
231
232
        // If for some reason we were unable to sync this meta bundle, return the old one
233
        if ($metaBundle === null) {
234
            $metaBundle = $prevMetaBundle;
235
        }
236
    }
237
238
    /**
239
     * @param int $siteId
240
     * @param MetaBundle|null $baseConfig
241
     *
242
     * @return MetaBundle
243
     */
244
    public function createGlobalMetaBundleForSite(int $siteId, $baseConfig = null): MetaBundle
245
    {
246
        // Create a new meta bundle with propagated defaults
247
        $metaBundleDefaults = ArrayHelper::merge(
248
            ConfigHelper::getConfigFromFile('globalmeta/Bundle'),
249
            [
250
                'sourceSiteId' => $siteId,
251
            ]
252
        );
253
        // The computedType must be set before creating the bundle
254
        if ($baseConfig !== null) {
255
            $metaBundleDefaults['metaGlobalVars']['mainEntityOfPage'] = $baseConfig->metaGlobalVars->mainEntityOfPage;
256
            $metaBundleDefaults['metaSiteVars']['identity']['computedType'] =
257
                $baseConfig->metaSiteVars->identity->computedType;
258
            $metaBundleDefaults['metaSiteVars']['creator']['computedType'] =
259
                $baseConfig->metaSiteVars->creator->computedType;
260
        }
261
        $metaBundle = MetaBundle::create($metaBundleDefaults);
262
        if ($metaBundle !== null) {
263
            if ($baseConfig !== null) {
264
                $this->mergeMetaBundleSettings($metaBundle, $baseConfig);
265
            }
266
            $this->updateMetaBundle($metaBundle, $siteId);
267
        }
268
269
        return $metaBundle;
270
    }
271
272
    /**
273
     * @param MetaBundle $metaBundle
274
     * @param int $siteId
275
     */
276
    public function updateMetaBundle(MetaBundle $metaBundle, int $siteId)
277
    {
278
        $metaBundle->sourceName = (string)$metaBundle->sourceName;
279
        $metaBundle->sourceTemplate = (string)$metaBundle->sourceTemplate;
280
        // Make sure it validates
281
        if ($metaBundle->validate(null, true)) {
282
            // Save it out to a record
283
            $params = [
284
                'sourceBundleType' => $metaBundle->sourceBundleType,
285
                'sourceId' => $metaBundle->sourceId,
286
                'sourceSiteId' => $siteId,
287
            ];
288
            if ($metaBundle->typeId !== null) {
289
                $metaBundle->typeId = (int)$metaBundle->typeId;
290
            }
291
            if (!empty($metaBundle->typeId)) {
292
                $params['typeId'] = $metaBundle->typeId;
293
            } else {
294
                $metaBundle->typeId = null;
295
            }
296
            $metaBundleRecord = MetaBundleRecord::findOne($params);
297
298
            if (!$metaBundleRecord) {
0 ignored issues
show
introduced by
$metaBundleRecord is of type yii\db\ActiveRecord, thus it always evaluated to true.
Loading history...
299
                $metaBundleRecord = new MetaBundleRecord();
300
            }
301
302
            // @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
303
            // The issue was that the containers were getting saved to the db with a hard-coded setting in them, because they'd
304
            // been set that way by the environment, whereas to be changeable via the GUI, it needs to be set to {seomatic.meta.robots}
305
            /** @var RobotsTag|null $robotsTag */
306
            $robotsTag = $metaBundle->metaContainers[MetaTagContainer::CONTAINER_TYPE . TagService::GENERAL_HANDLE]->data['robots'] ?? null;
307
            if (!empty($robotsTag)) {
308
                $robotsTag->content = $robotsTag->environment['live']['content'] ?? '{seomatic.meta.robots}';
309
            }
310
311
            $metaBundleRecord->setAttributes($metaBundle->getAttributes(), false);
312
313
            if ($metaBundleRecord->save()) {
314
                Craft::info(
315
                    'Meta bundle updated: '
316
                    . $metaBundle->sourceBundleType
317
                    . ' id: '
318
                    . $metaBundle->sourceId
319
                    . ' from siteId: '
320
                    . $metaBundle->sourceSiteId,
321
                    __METHOD__
322
                );
323
            }
324
        } else {
325
            Craft::error(
326
                'Meta bundle failed validation: '
327
                . 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

327
                . /** @scrutinizer ignore-type */ print_r($metaBundle->getErrors(), true)
Loading history...
328
                . ' type: '
329
                . $metaBundle->sourceType
330
                . ' id: '
331
                . $metaBundle->sourceId
332
                . ' from siteId: '
333
                . $metaBundle->sourceSiteId,
334
                __METHOD__
335
            );
336
        }
337
    }
338
339
    /**
340
     * @param class-string<SeoElementInterface> $seoElement
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<SeoElementInterface> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<SeoElementInterface>.
Loading history...
341
     * @param Model $sourceModel
342
     * @param int $sourceSiteId
343
     * @param ?MetaBundle $baseConfig
344
     * @param bool $syncConfig
345
     * @return MetaBundle|null
346
     * @throws InvalidConfigException
347
     */
348
    public function createMetaBundleFromSeoElement(
349
        $seoElement,
350
        $sourceModel,
351
        int $sourceSiteId,
352
        $baseConfig = null,
353
        $syncConfig = false
354
    ) {
355
        $metaBundle = null;
356
        // Get the site settings and turn them into arrays
357
        /** @var Section|CategoryGroup|ProductType $sourceModel */
358
        $siteSettings = $sourceModel->getSiteSettings();
359
        if (!empty($siteSettings[$sourceSiteId])) {
360
            $siteSettingsArray = [];
361
            /** @var Section_SiteSettings $siteSetting */
362
            foreach ($siteSettings as $siteSetting) {
363
                if ($siteSetting->hasUrls && SiteHelper::siteEnabledWithUrls($sourceSiteId)) {
364
                    $siteSettingArray = $siteSetting->toArray();
365
                    // Get the site language
366
                    $siteSettingArray['language'] = MetaValueHelper::getSiteLanguage($siteSetting->siteId);
367
                    $siteSettingsArray[] = $siteSettingArray;
368
                }
369
            }
370
            $siteSettingsArray = ArrayHelper::index($siteSettingsArray, 'siteId');
371
            // Create a MetaBundle for this site
372
            $siteSetting = $siteSettings[$sourceSiteId];
373
            if ($siteSetting->hasUrls && SiteHelper::siteEnabledWithUrls($sourceSiteId)) {
374
                if ($syncConfig) {
375
                    // Get the most recent dateUpdated
376
                    $element = $seoElement::mostRecentElement($sourceModel, $sourceSiteId);
377
                    /** @var Element|null $element */
378
                    if ($element) {
379
                        $dateUpdated = $element->dateUpdated ?? $element->dateCreated;
380
                    } else {
381
                        $dateUpdated = new DateTime();
382
                    }
383
                    // Create a new meta bundle with propagated defaults
384
                    $metaBundleDefaults = ArrayHelper::merge(
385
                        $seoElement::metaBundleConfig($sourceModel),
386
                        [
387
                            'sourceTemplate' => (string)$siteSetting->template,
388
                            'sourceSiteId' => $siteSetting->siteId,
389
                            'sourceAltSiteSettings' => $siteSettingsArray,
390
                            'sourceDateUpdated' => $dateUpdated,
391
                        ]
392
                    );
393
                    // The mainEntityOfPage computedType must be set before creating the bundle
394
                    if ($baseConfig !== null && !empty($baseConfig->metaGlobalVars->mainEntityOfPage)) {
395
                        $metaBundleDefaults['metaGlobalVars']['mainEntityOfPage'] =
396
                            $baseConfig->metaGlobalVars->mainEntityOfPage;
397
                    }
398
                    // Merge in any migrated settings from an old Seomatic_Meta Field
399
                    if ($element !== null) {
400
                        /** @var Element $elementFromSite */
401
                        $elementFromSite = Craft::$app->getElements()->getElementById($element->id, null, $sourceSiteId);
402
                        if ($element instanceof Element) {
0 ignored issues
show
introduced by
$element is always a sub-type of craft\base\Element.
Loading history...
403
                            $config = MigrationHelper::configFromSeomaticMeta(
404
                                $elementFromSite,
405
                                MigrationHelper::SECTION_MIGRATION_CONTEXT
406
                            );
407
                            $metaBundleDefaults = ArrayHelper::merge(
408
                                $metaBundleDefaults,
409
                                $config
410
                            );
411
                        }
412
                    }
413
                    $metaBundle = MetaBundle::create($metaBundleDefaults);
414
                    if ($baseConfig !== null) {
415
                        $this->mergeMetaBundleSettings($metaBundle, $baseConfig);
416
                    }
417
                    $this->updateMetaBundle($metaBundle, $sourceSiteId);
418
                }
419
            }
420
        }
421
422
        return $metaBundle;
423
    }
424
425
    /**
426
     * @param string $sourceBundleType
427
     * @param string $sourceHandle
428
     * @param int $sourceSiteId
429
     * @param int|null $typeId
430
     *
431
     * @return null|MetaBundle
432
     */
433
    public function getMetaBundleBySourceHandle(string $sourceBundleType, string $sourceHandle, int $sourceSiteId, $typeId = null)
434
    {
435
        $metaBundle = null;
436
        $typeId = (int)$typeId;
437
        // See if we have the meta bundle cached
438
        if (!empty($this->metaBundlesBySourceHandle[$sourceBundleType][$sourceHandle][$sourceSiteId][$typeId])) {
439
            $id = $this->metaBundlesBySourceHandle[$sourceBundleType][$sourceHandle][$sourceSiteId][$typeId];
440
            if (!empty($this->metaBundles[$id])) {
441
                return $this->metaBundles[$id];
442
            }
443
        }
444
        // Look for a matching meta bundle in the db
445
        $query = (new Query())
446
            ->from(['{{%seomatic_metabundles}}'])
447
            ->where([
448
                'sourceBundleType' => $sourceBundleType,
449
                'sourceHandle' => $sourceHandle,
450
                'sourceSiteId' => $sourceSiteId,
451
            ]);
452
        if (!empty($typeId)) {
453
            $query
454
                ->andWhere([
455
                    'typeId' => $typeId,
456
                ]);
457
        }
458
        $metaBundleArray = $query
459
            ->one();
460
        // If the specific query with a `typeId` returned nothing, try a more general query without `typeId`
461
        if (empty($metaBundleArray)) {
462
            $metaBundleArray = (new Query())
463
                ->from(['{{%seomatic_metabundles}}'])
464
                ->where([
465
                    'sourceBundleType' => $sourceBundleType,
466
                    'sourceHandle' => $sourceHandle,
467
                    'sourceSiteId' => $sourceSiteId,
468
                ])
469
                ->one();
470
        }
471
        if (!empty($metaBundleArray)) {
472
            $metaBundleArray = array_diff_key($metaBundleArray, array_flip(self::IGNORE_DB_ATTRIBUTES));
473
            $metaBundle = MetaBundle::create($metaBundleArray);
474
            $id = count($this->metaBundles);
475
            $this->metaBundles[$id] = $metaBundle;
476
            $this->metaBundlesBySourceHandle[$sourceBundleType][$sourceHandle][$sourceSiteId][$typeId] = $id;
477
        } else {
478
            // If it doesn't exist, create it
479
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
480
            if ($seoElement !== null) {
481
                $sourceModel = $seoElement::sourceModelFromHandle($sourceHandle);
482
                if ($sourceModel) {
483
                    $metaBundle = $this->createMetaBundleFromSeoElement($seoElement, $sourceModel, $sourceSiteId, null, true);
484
                }
485
            }
486
        }
487
488
        return $metaBundle;
489
    }
490
491
    /**
492
     * Invalidate the caches and data structures associated with this MetaBundle
493
     *
494
     * @param string $sourceBundleType
495
     * @param int|null $sourceId
496
     * @param bool $isNew
497
     */
498
    public function invalidateMetaBundleById(string $sourceBundleType, ?int $sourceId, bool $isNew = false)
499
    {
500
        $metaBundleInvalidated = false;
501
        $sites = Craft::$app->getSites()->getAllSites();
502
        foreach ($sites as $site) {
503
            // See if this is a section we are tracking
504
            $metaBundle = $this->getMetaBundleBySourceId($sourceBundleType, $sourceId, $site->id);
0 ignored issues
show
Bug introduced by
It seems like $sourceId 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

504
            $metaBundle = $this->getMetaBundleBySourceId($sourceBundleType, /** @scrutinizer ignore-type */ $sourceId, $site->id);
Loading history...
505
            if ($metaBundle) {
506
                Craft::info(
507
                    'Invalidating meta bundle: '
508
                    . $metaBundle->sourceHandle
509
                    . ' from siteId: '
510
                    . $site->id,
511
                    __METHOD__
512
                );
513
                // Is this a new source?
514
                if (!$isNew) {
515
                    $metaBundleInvalidated = true;
516
                    // Handle syncing up the sourceHandle
517
                    if ($sourceBundleType !== self::GLOBAL_META_BUNDLE) {
518
                        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
519
                        if ($seoElement !== null) {
520
                            /** @var Section|CategoryGroup|ProductType $sourceModel */
521
                            $sourceModel = $seoElement::sourceModelFromId($sourceId);
522
                            if ($sourceModel !== null) {
523
                                $metaBundle->sourceName = (string)$sourceModel->name;
524
                                $metaBundle->sourceHandle = $sourceModel->handle;
525
                            }
526
                        }
527
                    }
528
                    // Invalidate caches after an existing section is saved
529
                    Seomatic::$plugin->metaContainers->invalidateContainerCacheById(
530
                        $sourceId,
0 ignored issues
show
Bug introduced by
It seems like $sourceId can also be of type null; however, parameter $sourceId of nystudio107\seomatic\ser...ateContainerCacheById() 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

530
                        /** @scrutinizer ignore-type */ $sourceId,
Loading history...
531
                        $sourceBundleType,
532
                        $metaBundle->sourceSiteId
533
                    );
534
                    Seomatic::$plugin->sitemaps->invalidateSitemapCache(
535
                        $metaBundle->sourceHandle,
536
                        $metaBundle->sourceSiteId,
537
                        $metaBundle->sourceBundleType,
538
                        false
539
                    );
540
                    // Update the meta bundle data
541
                    $this->updateMetaBundle($metaBundle, $site->id);
542
                }
543
            }
544
        }
545
        // If we've invalidated a meta bundle, we need to invalidate the sitemap index, too
546
        if ($metaBundleInvalidated) {
0 ignored issues
show
introduced by
The condition $metaBundleInvalidated is always false.
Loading history...
547
            Seomatic::$plugin->sitemaps->invalidateSitemapIndexCache();
548
        }
549
    }
550
551
    /**
552
     * @param string $sourceBundleType
553
     * @param int $sourceId
554
     * @param int|null $sourceSiteId
555
     * @param int|null $typeId
556
     *
557
     * @return null|MetaBundle
558
     */
559
    public function getMetaBundleBySourceId(string $sourceBundleType, int $sourceId, ?int $sourceSiteId, $typeId = null)
560
    {
561
        $metaBundle = null;
562
        $typeId = (int)$typeId;
563
        // See if we have the meta bundle cached
564
        if (!empty($this->metaBundlesBySourceId[$sourceBundleType][$sourceId][$sourceSiteId][$typeId])) {
565
            $id = $this->metaBundlesBySourceId[$sourceBundleType][$sourceId][$sourceSiteId][$typeId];
566
            if (!empty($this->metaBundles[$id])) {
567
                return $this->metaBundles[$id];
568
            }
569
        }
570
        // Look for a matching meta bundle in the db
571
        $query = (new Query())
572
            ->from(['{{%seomatic_metabundles}}'])
573
            ->where([
574
                'sourceBundleType' => $sourceBundleType,
575
                'sourceId' => $sourceId,
576
                'sourceSiteId' => $sourceSiteId,
577
            ]);
578
        if (!empty($typeId)) {
579
            $query
580
                ->andWhere([
581
                    'typeId' => $typeId,
582
                ]);
583
        }
584
        $metaBundleArray = $query
585
            ->one();
586
        // If the specific query with a `typeId` returned nothing, try a more general query without `typeId`
587
        if (empty($metaBundleArray)) {
588
            $metaBundleArray = (new Query())
589
                ->from(['{{%seomatic_metabundles}}'])
590
                ->where([
591
                    'sourceBundleType' => $sourceBundleType,
592
                    'sourceId' => $sourceId,
593
                    'sourceSiteId' => $sourceSiteId,
594
                ])
595
                ->one();
596
        }
597
        if (!empty($metaBundleArray)) {
598
            // Get the attributes from the db
599
            $metaBundleArray = array_diff_key($metaBundleArray, array_flip(self::IGNORE_DB_ATTRIBUTES));
600
            $metaBundle = MetaBundle::create($metaBundleArray);
601
            $this->syncBundleWithConfig($metaBundle);
602
            $id = count($this->metaBundles);
603
            $this->metaBundles[$id] = $metaBundle;
604
            $this->metaBundlesBySourceId[$sourceBundleType][$sourceId][$sourceSiteId][$typeId] = $id;
605
        } else {
606
            // If it doesn't exist, create it
607
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
608
            if ($seoElement !== null) {
609
                $sourceModel = $seoElement::sourceModelFromId($sourceId);
610
                if ($sourceModel) {
611
                    $metaBundle = $this->createMetaBundleFromSeoElement($seoElement, $sourceModel, $sourceSiteId, null, true);
0 ignored issues
show
Bug introduced by
It seems like $sourceSiteId can also be of type null; however, parameter $sourceSiteId of nystudio107\seomatic\ser...aBundleFromSeoElement() 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

611
                    $metaBundle = $this->createMetaBundleFromSeoElement($seoElement, $sourceModel, /** @scrutinizer ignore-type */ $sourceSiteId, null, true);
Loading history...
612
                }
613
            }
614
        }
615
616
        return $metaBundle;
617
    }
618
619
    /**
620
     * Resave all the meta bundles of a given type.
621
     *
622
     * @param string $metaBundleType
623
     */
624
    public function resaveMetaBundles(string $metaBundleType)
625
    {
626
        // For all meta bundles of a given type
627
        $metaBundleRows = (new Query())
628
            ->from(['{{%seomatic_metabundles}}'])
629
            ->where(['sourceBundleType' => $metaBundleType])
630
            ->all();
631
632
        foreach ($metaBundleRows as $metaBundleRow) {
633
            // Create it from the DB data
634
            $metaBundleData = array_diff_key($metaBundleRow, array_flip(self::IGNORE_DB_ATTRIBUTES));
635
            $metaBundle = MetaBundle::create($metaBundleData);
636
            if (!$metaBundle) {
637
                continue;
638
            }
639
            // Sync it and update it.
640
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
641
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $metaBundle->sourceSiteId);
642
        }
643
    }
644
645
    /**
646
     * Invalidate the caches and data structures associated with this MetaBundle
647
     *
648
     * @param Element|null $element
649
     * @param bool $isNew
650
     */
651
    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

651
    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...
652
    {
653
        $metaBundleInvalidated = false;
654
        $invalidateMetaBundle = true;
655
        $sitemapInvalidated = false;
656
        if (Seomatic::$craft32) {
657
            if ($element->getIsDraft() || $element->getIsRevision()) {
0 ignored issues
show
Bug introduced by
The method getIsDraft() does not exist on null. ( Ignorable by Annotation )

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

657
            if ($element->/** @scrutinizer ignore-call */ getIsDraft() || $element->getIsRevision()) {

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

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

Loading history...
658
                $invalidateMetaBundle = false;
659
            }
660
        }
661
        if ($element && $invalidateMetaBundle) {
662
            $uri = $element->uri ?? '';
663
            // Normalize the incoming URI to account for `__home__`
664
            if ($element->uri) {
665
                $uri = ($element->uri === '__home__') ? '' : $uri;
666
            }
667
            // Invalidate sitemap caches after an existing element is saved
668
            list($sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId)
669
                = $this->getMetaSourceFromElement($element);
670
            if ($sourceId) {
671
                Craft::info(
672
                    'Invalidating meta bundle: '
673
                    . $uri
674
                    . '/'
675
                    . $sourceSiteId,
676
                    __METHOD__
677
                );
678
                $metaBundleInvalidated = true;
679
                Seomatic::$plugin->metaContainers->invalidateContainerCacheById($sourceId, $sourceBundleType);
680
                // Invalidate the sitemap cache
681
                $metaBundle = $this->getMetaBundleBySourceId($sourceBundleType, $sourceId, $sourceSiteId);
682
                if ($metaBundle) {
683
                    $dateUpdated = $element->dateUpdated ?? $element->dateCreated;
684
                    $metaBundle->sourceDateUpdated = $dateUpdated;
685
                    // Update the meta bundle data
686
                    $this->updateMetaBundle($metaBundle, $sourceSiteId);
687
                    if (
688
                        $metaBundle->metaSitemapVars->sitemapUrls
689
                        && !$element->resaving) {
690
                        $sitemapInvalidated = true;
691
                        Seomatic::$plugin->sitemaps->invalidateSitemapCache(
692
                            $metaBundle->sourceHandle,
693
                            $metaBundle->sourceSiteId,
694
                            $metaBundle->sourceBundleType,
695
                            false
696
                        );
697
                    }
698
                }
699
            }
700
            // If we've invalidated a meta bundle, we need to invalidate the sitemap index, too
701
            if ($metaBundleInvalidated
702
                && $sitemapInvalidated
703
                && !$element->resaving) {
704
                Seomatic::$plugin->sitemaps->invalidateSitemapIndexCache();
705
            }
706
        }
707
    }
708
709
    /**
710
     * @param Element $element
711
     *
712
     * @return array
713
     */
714
    public function getMetaSourceFromElement(Element $element): array
715
    {
716
        $sourceId = 0;
717
        $typeId = null;
718
        $sourceSiteId = 0;
719
        $sourceHandle = '';
720
        // See if this is a section we are tracking
721
        $sourceBundleType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
722
        if ($sourceBundleType) {
723
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
724
            if ($seoElement) {
725
                $sourceId = $seoElement::sourceIdFromElement($element);
726
                $typeId = $seoElement::typeIdFromElement($element);
727
                $sourceHandle = $seoElement::sourceHandleFromElement($element);
728
                $sourceSiteId = $element->siteId;
729
            }
730
        } else {
731
            $sourceBundleType = '';
732
        }
733
734
        return [$sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId];
735
    }
736
737
    /**
738
     * Get all of the meta bundles for a given $sourceSiteId
739
     *
740
     * @param int|null $sourceSiteId
741
     *
742
     * @return array
743
     */
744
    public function getContentMetaBundlesForSiteId($sourceSiteId, $filter = ''): array
745
    {
746
        $metaBundles = [];
747
        $bundles = [];
748
        // Since sectionIds, CategoryIds, etc. are not unique, we need to do separate queries and combine them
749
        $seoElements = Seomatic::$plugin->seoElements->getAllSeoElementTypes();
750
        foreach ($seoElements as $seoElement) {
751
            $subQuery = (new Query())
752
                ->from(['{{%seomatic_metabundles}}'])
753
                ->where(['=', 'sourceBundleType', $seoElement::META_BUNDLE_TYPE])
754
                ->andWhere(['typeId' => null]);
755
756
            if ((int)$sourceSiteId !== 0) {
757
                $subQuery->andWhere(['sourceSiteId' => $sourceSiteId]);
758
            }
759
            if ($filter !== '') {
760
                $subQuery->andWhere(['like', 'sourceName', $filter]);
761
            }
762
            $bundleQuery = (new Query())
763
                ->select(['mb.*'])
764
                ->from(['mb' => $subQuery])
765
                ->leftJoin(['mb2' => $subQuery], [
766
                    'and',
767
                    '[[mb.sourceId]] = [[mb2.sourceId]]',
768
                    '[[mb.id]] < [[mb2.id]]',
769
                ])
770
                ->where(['mb2.id' => null]);
771
            $bundles = array_merge($bundles, $bundleQuery->all());
772
        }
773
        foreach ($bundles as $bundle) {
774
            $bundle = array_diff_key($bundle, array_flip(self::IGNORE_DB_ATTRIBUTES));
775
            $metaBundle = MetaBundle::create($bundle);
776
            if ($metaBundle) {
777
                $metaBundles[] = $metaBundle;
778
            }
779
        }
780
781
        return $metaBundles;
782
    }
783
784
    /**
785
     * Get the parent content meta bundle for a given element.
786
     *
787
     * @param Element $element
788
     * @return mixed|MetaBundle|null
789
     */
790
    public function getContentMetaBundleForElement(Element $element)
791
    {
792
        $source = $this->getMetaSourceFromElement($element);
793
        $key = implode(".", $source) . '.' . $element->siteId;
794
795
        if (empty($this->elementContentMetaBundles[$key])) {
796
            $this->elementContentMetaBundles[$key] = $this->getMetaBundleBySourceId($source[1], $source[0], $element->siteId, $source[4]);
797
        }
798
799
        return $this->elementContentMetaBundles[$key];
800
    }
801
802
    /**
803
     * Set fields the user is unable to edit to an empty string, so they are
804
     * filtered out when meta containers are combined
805
     *
806
     * @param MetaBundle $metaBundle
807
     * @param string $fieldHandle
808
     */
809
    public function pruneFieldMetaBundleSettings(MetaBundle $metaBundle, string $fieldHandle)
810
    {
811
        /** @var SeoSettings|null $seoSettingsField */
812
        $seoSettingsField = Craft::$app->getFields()->getFieldByHandle($fieldHandle);
813
        if ($seoSettingsField) {
814
            $seoSettingsEnabledFields = array_flip(array_merge(
815
                $seoSettingsField->generalEnabledFields,
816
                $seoSettingsField->twitterEnabledFields,
817
                $seoSettingsField->facebookEnabledFields,
818
                $seoSettingsField->sitemapEnabledFields
819
            ));
820
            // Always include some fields, as they are calculated even if not explicitly included
821
            $seoSettingsEnabledFields = array_merge(
822
                $seoSettingsEnabledFields,
823
                array_flip(self::ALWAYS_INCLUDED_SEO_SETTINGS_FIELDS)
824
            );
825
            // metaGlobalVars
826
            $attributes = $metaBundle->metaGlobalVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

826
            /** @scrutinizer ignore-call */ 
827
            $attributes = $metaBundle->metaGlobalVars->getAttributes();

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

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

Loading history...
827
828
            // Get a list of explicitly inherited values
829
            $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

829
            $inherited = array_keys(/** @scrutinizer ignore-type */ ArrayHelper::remove($attributes, 'inherited', []));
Loading history...
830
            $emptyValues = array_fill_keys(array_keys(array_diff_key($attributes, $seoSettingsEnabledFields)), '');
831
832
            // Nullify the inherited values
833
            $emptyValues = array_merge($emptyValues, array_fill_keys($inherited, ''));
834
            foreach ($inherited as $inheritedAttribute) {
835
                foreach (self::COMPOSITE_INHERITANCE_CHILDREN[$inheritedAttribute] ?? [] as $child) {
836
                    list($model, $attribute) = explode('.', $child);
837
                    $metaBundle->{$model}->$attribute = '';
838
                }
839
            }
840
841
            $attributes = array_merge($attributes, $emptyValues);
842
            $metaBundle->metaGlobalVars->setAttributes($attributes, false);
843
844
845
            // Handle the mainEntityOfPage
846
            if (!in_array('mainEntityOfPage', $seoSettingsField->generalEnabledFields, false)) {
847
                $metaBundle->metaGlobalVars->mainEntityOfPage = '';
848
            }
849
            // metaSiteVars
850
            $attributes = $metaBundle->metaSiteVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

850
            /** @scrutinizer ignore-call */ 
851
            $attributes = $metaBundle->metaSiteVars->getAttributes();

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

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

Loading history...
851
            $emptyValues = array_fill_keys(array_keys(array_diff_key($attributes, $seoSettingsEnabledFields)), '');
852
            $attributes = array_merge($attributes, $emptyValues);
853
            $metaBundle->metaSiteVars->setAttributes($attributes, false);
854
            // metaSitemapVars
855
            $attributes = $metaBundle->metaSitemapVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

855
            /** @scrutinizer ignore-call */ 
856
            $attributes = $metaBundle->metaSitemapVars->getAttributes();

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

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

Loading history...
856
857
            // Get a list of explicitly inherited values
858
            $inherited = array_keys(ArrayHelper::remove($attributes, 'inherited', []));
859
            $emptyValues = array_fill_keys(array_keys(array_diff_key($attributes, $seoSettingsEnabledFields)), '');
860
861
            // Nullify the inherited values
862
            $emptyValues = array_merge($emptyValues, array_fill_keys($inherited, ''));
863
864
            $attributes = array_merge($attributes, $emptyValues);
865
            $metaBundle->metaSitemapVars->setAttributes($attributes, false);
866
        }
867
    }
868
869
    /**
870
     * Remove any meta bundles from the $metaBundles array that no longer
871
     * correspond with an SeoElement
872
     *
873
     * @param array $metaBundles
874
     */
875
    public function pruneVestigialMetaBundles(array &$metaBundles)
876
    {
877
        foreach ($metaBundles as $key => $metaBundle) {
878
            $prune = $this->pruneVestigialMetaBundle($metaBundle);
879
            /** @var MetaBundle $metaBundle */
880
            if ($prune) {
881
                unset($metaBundles[$key]);
882
            }
883
        }
884
        ArrayHelper::multisort($metaBundles, 'sourceName');
885
    }
886
887
    /**
888
     * Determine whether a given MetaBundle is vestigial or not
889
     *
890
     * @param $metaBundle
891
     *
892
     * @return bool
893
     */
894
    public function pruneVestigialMetaBundle($metaBundle): bool
895
    {
896
        $prune = false;
897
        $sourceBundleType = $metaBundle->sourceBundleType;
898
        if ($sourceBundleType && $sourceBundleType !== self::GLOBAL_META_BUNDLE) {
899
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
900
            if ($seoElement) {
901
                $sourceModel = $seoElement::sourceModelFromId($metaBundle->sourceId);
902
                /** @var Section|CategoryGroup|ProductType|null $sourceModel */
903
                if ($sourceModel === null) {
904
                    $prune = true;
905
                } else {
906
                    $prune = true;
907
                    $siteSettings = $sourceModel->getSiteSettings();
908
                    if (!empty($siteSettings)) {
909
                        /** @var Section_SiteSettings $siteSetting */
910
                        foreach ($siteSettings as $siteSetting) {
911
                            if ($siteSetting->siteId == $metaBundle->sourceSiteId && $siteSetting->hasUrls && SiteHelper::siteEnabledWithUrls($siteSetting->siteId)) {
912
                                $prune = false;
913
                            }
914
                        }
915
                    }
916
                }
917
            } else {
918
                $prune = true;
919
            }
920
        }
921
922
        return $prune;
923
    }
924
925
    /**
926
     * Delete any meta bundles from the $metaBundles array that no longer
927
     * correspond with an SeoElement
928
     *
929
     * @param array $metaBundles
930
     */
931
    public function deleteVestigialMetaBundles(array $metaBundles)
932
    {
933
        foreach ($metaBundles as $key => $metaBundle) {
934
            $prune = $this->pruneVestigialMetaBundle($metaBundle);
935
            /** @var MetaBundle $metaBundle */
936
            if ($prune) {
937
                $this->deleteMetaBundleBySourceId(
938
                    $metaBundle->sourceBundleType,
939
                    $metaBundle->sourceId,
940
                    $metaBundle->sourceSiteId
941
                );
942
            }
943
        }
944
    }
945
946
    /**
947
     * Delete a meta bundle by $sourceId
948
     *
949
     * @param string $sourceBundleType
950
     * @param int $sourceId
951
     * @param int|null $siteId
952
     */
953
    public function deleteMetaBundleBySourceId(string $sourceBundleType, int $sourceId, ?int $siteId = null)
954
    {
955
        $sites = [];
956
        if ($siteId === null) {
957
            $sites = Craft::$app->getSites()->getAllSites();
958
        } else {
959
            $sites[] = Craft::$app->getSites()->getSiteById($siteId);
960
        }
961
        /** @var Site $site */
962
        foreach ($sites as $site) {
963
            // Look for a matching meta bundle in the db
964
            $metaBundleRecord = MetaBundleRecord::findOne([
965
                'sourceBundleType' => $sourceBundleType,
966
                'sourceId' => $sourceId,
967
                'sourceSiteId' => $site->id,
968
            ]);
969
970
            if ($metaBundleRecord) {
971
                try {
972
                    $metaBundleRecord->delete();
973
                } catch (Throwable $e) {
974
                    Craft::error($e->getMessage(), __METHOD__);
975
                }
976
                Craft::info(
977
                    'Meta bundle deleted: '
978
                    . $sourceId
979
                    . ' from siteId: '
980
                    . $site->id,
981
                    __METHOD__
982
                );
983
            }
984
        }
985
    }
986
987
    /**
988
     * Get all of the data from $bundle in containers of $type
989
     *
990
     * @param MetaBundle $bundle
991
     * @param string $type
992
     *
993
     * @return array
994
     */
995
    public function getContainerDataFromBundle(MetaBundle $bundle, string $type): array
996
    {
997
        $containerData = [];
998
        foreach ($bundle->metaContainers as $metaContainer) {
999
            if ($metaContainer::CONTAINER_TYPE === $type) {
1000
                foreach ($metaContainer->data as $dataHandle => $data) {
1001
                    $containerData[$dataHandle] = $data;
1002
                }
1003
            }
1004
        }
1005
1006
        return $containerData;
1007
    }
1008
1009
    /**
1010
     * Create all of the content meta bundles
1011
     */
1012
    public function createContentMetaBundles()
1013
    {
1014
        $seoElements = Seomatic::$plugin->seoElements->getAllSeoElementTypes();
1015
        foreach ($seoElements as $seoElement) {
1016
            /** @var SeoElementInterface $seoElement */
1017
            $seoElement::createAllContentMetaBundles();
1018
        }
1019
    }
1020
1021
    /**
1022
     * Create the default global meta bundles
1023
     */
1024
    public function createGlobalMetaBundles()
1025
    {
1026
        $sites = Craft::$app->getSites()->getAllSites();
1027
        foreach ($sites as $site) {
1028
            $this->createGlobalMetaBundleForSite($site->id);
1029
        }
1030
    }
1031
1032
    // Protected Methods
1033
    // =========================================================================
1034
1035
    /**
1036
     * Preserve user settings from the meta bundle when updating it from the
1037
     * config
1038
     *
1039
     * @param MetaBundle $metaBundle The new meta bundle
1040
     * @param MetaBundle $baseConfig The existing meta bundle to preserve
1041
     *                               settings from
1042
     */
1043
    protected function mergeMetaBundleSettings(MetaBundle $metaBundle, MetaBundle $baseConfig)
1044
    {
1045
        // Preserve the metaGlobalVars
1046
        $attributes = $baseConfig->metaGlobalVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

1046
        /** @scrutinizer ignore-call */ 
1047
        $attributes = $baseConfig->metaGlobalVars->getAttributes();

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

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

Loading history...
1047
        $metaBundle->metaGlobalVars->setAttributes($attributes);
1048
        // Preserve the metaSiteVars
1049
        if ($baseConfig->metaSiteVars !== null) {
1050
            $attributes = $baseConfig->metaSiteVars->getAttributes();
1051
            $metaBundle->metaSiteVars->setAttributes($attributes);
1052
            if ($baseConfig->metaSiteVars->identity !== null) {
1053
                $attributes = $baseConfig->metaSiteVars->identity->getAttributes();
1054
                $metaBundle->metaSiteVars->identity->setAttributes($attributes);
1055
            }
1056
            if ($baseConfig->metaSiteVars->creator !== null) {
1057
                $attributes = $baseConfig->metaSiteVars->creator->getAttributes();
1058
                $metaBundle->metaSiteVars->creator->setAttributes($attributes);
1059
            }
1060
        }
1061
        // Preserve the Frontend Templates container user settings, but update everything else
1062
        foreach ($baseConfig->frontendTemplatesContainer->data as $baseMetaContainerName => $baseMetaContainer) {
1063
            $attributes = $baseMetaContainer->getAttributes();
1064
            if (!empty($metaBundle->frontendTemplatesContainer->data[$baseMetaContainerName])) {
1065
                foreach (self::PRESERVE_FRONTEND_TEMPLATE_SETTINGS as $frontendTemplateSetting) {
1066
                    $metaBundle->frontendTemplatesContainer->data[$baseMetaContainerName]->$frontendTemplateSetting = $attributes[$frontendTemplateSetting] ?? '';
1067
                }
1068
            }
1069
        }
1070
        // Preserve the metaSitemapVars
1071
        $attributes = $baseConfig->metaSitemapVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

1071
        /** @scrutinizer ignore-call */ 
1072
        $attributes = $baseConfig->metaSitemapVars->getAttributes();

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

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

Loading history...
1072
        $metaBundle->metaSitemapVars->setAttributes($attributes);
1073
        // Preserve the metaBundleSettings
1074
        $attributes = $baseConfig->metaBundleSettings->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

1074
        /** @scrutinizer ignore-call */ 
1075
        $attributes = $baseConfig->metaBundleSettings->getAttributes();

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

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

Loading history...
1075
        $metaBundle->metaBundleSettings->setAttributes($attributes);
0 ignored issues
show
Bug introduced by
The method setAttributes() does not exist on null. ( Ignorable by Annotation )

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

1075
        $metaBundle->metaBundleSettings->/** @scrutinizer ignore-call */ 
1076
                                         setAttributes($attributes);

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

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

Loading history...
1076
        // Preserve the Script container user settings, but update everything else
1077
        foreach ($baseConfig->metaContainers as $baseMetaContainerName => $baseMetaContainer) {
1078
            if ($baseMetaContainer::CONTAINER_TYPE === MetaScriptContainer::CONTAINER_TYPE) {
1079
                foreach ($baseMetaContainer->data as $key => $value) {
1080
                    if (!empty($metaBundle->metaContainers[$baseMetaContainerName])) {
1081
                        foreach (self::PRESERVE_SCRIPT_SETTINGS as $scriptSetting) {
1082
                            $metaBundle->metaContainers[$baseMetaContainerName]->data[$key][$scriptSetting] = $value[$scriptSetting] ?? '';
1083
                        }
1084
                    }
1085
                }
1086
            }
1087
        }
1088
    }
1089
}
1090