SettingsController   F
last analyzed

Complexity

Total Complexity 134

Size/Duplication

Total Lines 1316
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 6
Bugs 3 Features 0
Metric Value
wmc 134
eloc 727
c 6
b 3
f 0
dl 0
loc 1316
ccs 0
cts 767
cp 0
rs 1.873

20 Methods

Rating   Name   Duplication   Size   Complexity  
C actionGlobal() 0 113 9
A uriFromSourceBundle() 0 18 5
A cullDisabledSites() 0 11 4
F actionSaveGlobal() 0 88 21
A actionContent() 0 44 3
B actionSaveContent() 0 59 9
A prepEntitySettings() 0 18 4
C actionSaveTracking() 0 56 12
B actionSite() 0 78 6
C actionDashboard() 0 112 11
B setMultiSiteVariables() 0 43 9
A actionSavePluginSettings() 0 38 5
A getSiteIdFromHandle() 0 14 3
A setContentFieldSourceVariables() 0 41 1
B actionSaveSite() 0 47 8
A actionPlugin() 0 48 4
F actionEditContent() 0 140 11
B actionTracking() 0 75 6
A setGlobalFieldSourceVariables() 0 14 1
A getCpSiteHandle() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like SettingsController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SettingsController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS
4
 *
5
 * @link      https://nystudio107.com/
6
 * @copyright Copyright (c) 2017 nystudio107
7
 * @license   https://nystudio107.com/license
8
 */
9
10
namespace nystudio107\seomatic\controllers;
11
12
use Craft;
13
use craft\elements\Asset;
14
use craft\errors\MissingComponentException;
15
use craft\helpers\Cp;
16
use craft\helpers\UrlHelper;
17
use craft\web\Controller;
18
use craft\web\UrlManager;
19
use DateTime;
20
use nystudio107\seomatic\assetbundles\seomatic\SeomaticAsset;
21
use nystudio107\seomatic\autocompletes\TrackingVarsAutocomplete;
22
use nystudio107\seomatic\helpers\ArrayHelper;
23
use nystudio107\seomatic\helpers\AssetHelper;
24
use nystudio107\seomatic\helpers\DynamicMeta as DynamicMetaHelper;
25
use nystudio107\seomatic\helpers\Field as FieldHelper;
26
use nystudio107\seomatic\helpers\ImageTransform as ImageTransformHelper;
27
use nystudio107\seomatic\helpers\PullField as PullFieldHelper;
28
use nystudio107\seomatic\helpers\Schema as SchemaHelper;
29
use nystudio107\seomatic\models\MetaBundle;
30
use nystudio107\seomatic\models\MetaScript;
31
use nystudio107\seomatic\models\MetaScriptContainer;
32
use nystudio107\seomatic\Seomatic;
33
use nystudio107\seomatic\services\FrontendTemplates;
34
use nystudio107\seomatic\services\MetaBundles;
35
use yii\base\InvalidConfigException;
36
use yii\web\BadRequestHttpException;
37
use yii\web\ForbiddenHttpException;
38
use yii\web\NotFoundHttpException;
39
use yii\web\Response;
40
use function count;
41
use function in_array;
42
use function is_array;
43
44
/**
45
 * @author    nystudio107
46
 * @package   Seomatic
47
 * @since     3.0.0
48
 */
49
class SettingsController extends Controller
50
{
51
    // Constants
52
    // =========================================================================
53
54
    public const DOCUMENTATION_URL = 'https://github.com/nystudio107/craft-seomatic';
55
56
    public const SETUP_GRADES = [
57
        ['id' => 'data1', 'name' => 'A', 'color' => '#008002'],
58
        ['id' => 'data2', 'name' => 'B', 'color' => '#9ACD31'],
59
        ['id' => 'data4', 'name' => 'C', 'color' => '#FFA500'],
60
        ['id' => 'data5', 'name' => 'D', 'color' => '#8B0100'],
61
    ];
62
63
    public const SEO_SETUP_FIELDS = [
64
        'mainEntityOfPage' => 'Main Entity of Page',
65
        'seoTitle' => 'SEO Title',
66
        'seoDescription' => 'SEO Description',
67
        'seoKeywords' => 'SEO Keywords',
68
        'seoImage' => 'SEO Image',
69
        'seoImageDescription' => 'SEO Image Description',
70
    ];
71
72
    public const SITE_SETUP_FIELDS = [
73
        'siteName' => 'Site Name',
74
        'twitterHandle' => 'Twitter Handle',
75
        'facebookProfileId' => 'Facebook Profile ID',
76
    ];
77
78
    public const IDENTITY_SETUP_FIELDS = [
79
        'computedType' => 'Identity Entity Type',
80
        'genericName' => 'Identity Entity Name',
81
        'genericDescription' => 'Identity Entity Description',
82
        'genericUrl' => 'Identity Entity URL',
83
        'genericImage' => 'Identity Entity Brand',
84
    ];
85
86
    // Protected Properties
87
    // =========================================================================
88
89
    /**
90
     * @inheritdoc
91
     */
92
    protected array|bool|int $allowAnonymous = [
93
    ];
94
95
    // Public Methods
96
    // =========================================================================
97
98
    /**
99
     * Dashboard display
100
     *
101
     * @param string|null $siteHandle
102
     * @param bool $showWelcome
103
     *
104
     * @return Response The rendered result
105
     * @throws NotFoundHttpException
106
     * @throws ForbiddenHttpException
107
     */
108
    public function actionDashboard(string $siteHandle = null, bool $showWelcome = false): Response
109
    {
110
        $variables = [];
111
        // Get the site to edit
112
        $siteHandle = $this->getCpSiteHandle($siteHandle);
113
        $siteId = $this->getSiteIdFromHandle($siteHandle);
114
        $pluginName = Seomatic::$settings->pluginName;
115
        $templateTitle = Craft::t('seomatic', 'Dashboard');
116
        // Asset bundle
117
        try {
118
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
0 ignored issues
show
Bug introduced by
The method registerAssetBundle() 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

118
            Seomatic::$view->/** @scrutinizer ignore-call */ 
119
                             registerAssetBundle(SeomaticAsset::class);

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...
119
        } catch (InvalidConfigException $e) {
120
            Craft::error($e->getMessage(), __METHOD__);
121
        }
122
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
123
            '@nystudio107/seomatic/web/assets/dist',
124
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

124
        /** @scrutinizer ignore-call */ 
125
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
125
        );
126
        // Enabled sites
127
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
128
        $variables['controllerHandle'] = 'dashboard';
129
130
        // Basic variables
131
        $variables['fullPageForm'] = false;
132
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
133
        $variables['pluginName'] = Seomatic::$settings->pluginName;
134
        $variables['title'] = $templateTitle;
135
        $variables['docTitle'] = "{$pluginName} - {$templateTitle}";
136
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
137
        $variables['crumbs'] = [
138
            [
139
                'label' => $pluginName,
140
                'url' => UrlHelper::cpUrl('seomatic'),
141
            ],
142
            [
143
                'label' => $templateTitle,
144
                'url' => UrlHelper::cpUrl('seomatic/dashboard' . $siteHandleUri),
145
            ],
146
        ];
147
        $variables['selectedSubnavItem'] = 'dashboard';
148
        $variables['showWelcome'] = $showWelcome;
149
        // Calulate the setup grades
150
        $variables['contentSetupStats'] = [];
151
        $variables['setupGrades'] = self::SETUP_GRADES;
152
        $numFields = count(self::SEO_SETUP_FIELDS);
153
        $numGrades = count(self::SETUP_GRADES);
154
        while ($numGrades--) {
155
            $variables['contentSetupStats'][] = 0;
156
        }
157
        $numGrades = count(self::SETUP_GRADES);
158
        // Content SEO grades
159
        $variables['metaBundles'] = Seomatic::$plugin->metaBundles->getContentMetaBundlesForSiteId($siteId);
160
        $variables['contentSetupChecklistCutoff'] = floor(count($variables['metaBundles']) / 2);
161
        $variables['contentSetupChecklist'] = [];
162
        Seomatic::$plugin->metaBundles->pruneVestigialMetaBundles($variables['metaBundles']);
163
        /** @var MetaBundle $metaBundle */
164
        foreach ($variables['metaBundles'] as $metaBundle) {
165
            $stat = 0;
166
            foreach (self::SEO_SETUP_FIELDS as $setupField => $setupLabel) {
167
                $stat += (int)!empty($metaBundle->metaGlobalVars[$setupField]);
168
                $value = $variables['contentSetupChecklist'][$setupField]['value'] ?? 0;
169
                $variables['contentSetupChecklist'][$setupField] = [
170
                    'label' => $setupLabel,
171
                    'value' => $value + (int)!empty($metaBundle->metaGlobalVars[$setupField]),
172
                ];
173
            }
174
            $stat = round($numGrades - (($stat * $numGrades) / $numFields));
175
            if ($stat >= $numGrades) {
176
                $stat = $numGrades - 1;
177
            }
178
            $variables['contentSetupStats'][$stat]++;
179
        }
180
        // Global SEO grades
181
        Seomatic::$previewingMetaContainers = true;
182
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle((int)$siteId);
183
        Seomatic::$previewingMetaContainers = false;
184
        if ($metaBundle !== null) {
185
            $stat = 0;
186
            $variables['globalSetupChecklist'] = [];
187
            foreach (self::SEO_SETUP_FIELDS as $setupField => $setupLabel) {
188
                $stat += (int)!empty($metaBundle->metaGlobalVars[$setupField]);
189
                $variables['globalSetupChecklist'][$setupField] = [
190
                    'label' => $setupLabel,
191
                    'value' => (int)!empty($metaBundle->metaGlobalVars[$setupField]),
192
                ];
193
            }
194
            $stat = round(($stat / $numFields) * 100);
195
            $variables['globalSetupStat'] = $stat;
196
            // Site Settings grades
197
            $numFields = count(self::SITE_SETUP_FIELDS) + count(self::IDENTITY_SETUP_FIELDS);
198
            $stat = 0;
199
            $variables['siteSetupChecklist'] = [];
200
            foreach (self::SITE_SETUP_FIELDS as $setupField => $setupLabel) {
201
                $stat += (int)!empty($metaBundle->metaSiteVars[$setupField]);
202
                $variables['siteSetupChecklist'][$setupField] = [
203
                    'label' => $setupLabel,
204
                    'value' => (int)!empty($metaBundle->metaSiteVars[$setupField]),
205
                ];
206
            }
207
            foreach (self::IDENTITY_SETUP_FIELDS as $setupField => $setupLabel) {
208
                $stat += (int)!empty($metaBundle->metaSiteVars->identity[$setupField]);
209
                $variables['siteSetupChecklist'][$setupField] = [
210
                    'label' => $setupLabel,
211
                    'value' => (int)!empty($metaBundle->metaSiteVars->identity[$setupField]),
212
                ];
213
            }
214
            $stat = round(($stat / $numFields) * 100);
215
            $variables['siteSetupStat'] = $stat;
216
        }
217
218
        // Render the template
219
        return $this->renderTemplate('seomatic/dashboard/index', $variables);
220
    }
221
222
    /**
223
     * Global settings
224
     *
225
     * @param string $subSection
226
     * @param string|null $siteHandle
227
     * @param string|null $loadFromSiteHandle
228
     *
229
     * @return Response The rendered result
230
     * @throws NotFoundHttpException
231
     * @throws ForbiddenHttpException
232
     */
233
    public function actionGlobal(string $subSection = 'general', string $siteHandle = null, $loadFromSiteHandle = null, $editedMetaBundle = null): Response
234
    {
235
        $variables = [];
236
        $siteHandle = $this->getCpSiteHandle($siteHandle);
237
        $siteId = $this->getSiteIdFromHandle($siteHandle);
238
239
        $pluginName = Seomatic::$settings->pluginName;
240
        $templateTitle = Craft::t('seomatic', 'Global SEO');
241
        $subSectionTitle = Craft::t('seomatic', ucfirst($subSection));
242
        // Asset bundle
243
        try {
244
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
245
        } catch (InvalidConfigException $e) {
246
            Craft::error($e->getMessage(), __METHOD__);
247
        }
248
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
249
            '@nystudio107/seomatic/web/assets/dist',
250
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

250
        /** @scrutinizer ignore-call */ 
251
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
251
        );
252
        // Basic variables
253
        $variables['fullPageForm'] = true;
254
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
255
        $variables['pluginName'] = Seomatic::$settings->pluginName;
256
        $variables['title'] = $templateTitle;
257
        $variables['subSectionTitle'] = $subSectionTitle;
258
        $variables['docTitle'] = "{$pluginName} - {$templateTitle} - {$subSectionTitle}";
259
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
260
        $variables['crumbs'] = [
261
            [
262
                'label' => $pluginName,
263
                'url' => UrlHelper::cpUrl('seomatic'),
264
            ],
265
            [
266
                'label' => $templateTitle,
267
                'url' => UrlHelper::cpUrl('seomatic/global/general' . $siteHandleUri),
268
            ],
269
            [
270
                'label' => $subSectionTitle,
271
                'url' => UrlHelper::cpUrl('seomatic/global/' . $subSection . $siteHandleUri),
272
            ],
273
        ];
274
        $variables['selectedSubnavItem'] = 'global';
275
        // Pass in the pull fields
276
        $this->setGlobalFieldSourceVariables($variables);
277
        // Enabled sites
278
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
279
        $variables['controllerHandle'] = 'global' . '/' . $subSection;
280
        $variables['currentSubSection'] = $subSection;
281
        // Meta bundle settings
282
        Seomatic::$previewingMetaContainers = true;
283
        // Get the site to copy the settings from, if any
284
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
285
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
286
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
287
        // Load the metabundle
288
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteIdToLoad);
0 ignored issues
show
Bug introduced by
It seems like $siteIdToLoad can also be of type null; however, parameter $sourceSiteId of nystudio107\seomatic\ser...::getGlobalMetaBundle() 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

288
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $siteIdToLoad);
Loading history...
289
        if ($editedMetaBundle) {
290
            $metaBundle = $editedMetaBundle;
291
        }
292
        Seomatic::$previewingMetaContainers = false;
293
        if ($metaBundle !== null) {
294
            $variables['metaGlobalVars'] = clone $metaBundle->metaGlobalVars;
295
            $variables['metaSitemapVars'] = $metaBundle->metaSitemapVars;
296
            $variables['metaBundleSettings'] = $metaBundle->metaBundleSettings;
297
            // Template container settings
298
            $templateContainers = $metaBundle->frontendTemplatesContainer->data;
299
            $variables['robotsTemplate'] = $templateContainers[FrontendTemplates::ROBOTS_TXT_HANDLE];
300
            $variables['humansTemplate'] = $templateContainers[FrontendTemplates::HUMANS_TXT_HANDLE];
301
            // Handle an edge-case where a migration didn't work properly to add ADS_TXT_HANDLE
302
            if (!isset($templateContainers[FrontendTemplates::ADS_TXT_HANDLE])) {
303
                $globalMetaBundle = Seomatic::$plugin->metaBundles->createGlobalMetaBundleForSite($siteId, $metaBundle);
304
                $templateContainers[FrontendTemplates::ADS_TXT_HANDLE] =
305
                    $globalMetaBundle->frontendTemplatesContainer->data[FrontendTemplates::ADS_TXT_HANDLE];
306
            }
307
            // Handle an edge-case where a migration didn't work properly to add ADS_TXT_HANDLE
308
            if (!isset($templateContainers[FrontendTemplates::ADS_TXT_HANDLE])) {
309
                $globalMetaBundle = Seomatic::$plugin->metaBundles->createGlobalMetaBundleForSite($siteId, $metaBundle);
310
                $templateContainers[FrontendTemplates::ADS_TXT_HANDLE] =
311
                    $globalMetaBundle->frontendTemplatesContainer->data[FrontendTemplates::ADS_TXT_HANDLE];
312
            }
313
            $variables['adsTemplate'] = $templateContainers[FrontendTemplates::ADS_TXT_HANDLE];
314
            // Handle an edge-case where a migration didn't work properly to add SECURITY_TXT_HANDLE
315
            if (!isset($templateContainers[FrontendTemplates::SECURITY_TXT_HANDLE])) {
316
                $globalMetaBundle = Seomatic::$plugin->metaBundles->createGlobalMetaBundleForSite($siteId, $metaBundle);
317
                $templateContainers[FrontendTemplates::SECURITY_TXT_HANDLE] =
318
                    $globalMetaBundle->frontendTemplatesContainer->data[FrontendTemplates::SECURITY_TXT_HANDLE];
319
            }
320
            $variables['securityTemplate'] = $templateContainers[FrontendTemplates::SECURITY_TXT_HANDLE];
321
            // Image selectors
322
            $bundleSettings = $metaBundle->metaBundleSettings;
323
            $variables['elementType'] = Asset::class;
324
            $variables['assetVolumeSources'] = AssetHelper::getAssetInputSources();
325
            $variables['seoImageElements'] = ImageTransformHelper::assetElementsFromIds(
326
                $bundleSettings->seoImageIds,
327
                $siteId
328
            );
329
            $variables['twitterImageElements'] = ImageTransformHelper::assetElementsFromIds(
330
                $bundleSettings->twitterImageIds,
331
                $siteId
332
            );
333
            $variables['ogImageElements'] = ImageTransformHelper::assetElementsFromIds(
334
                $bundleSettings->ogImageIds,
335
                $siteId
336
            );
337
        }
338
        // Preview the meta containers
339
        Seomatic::$plugin->metaContainers->previewMetaContainers(
340
            MetaBundles::GLOBAL_META_BUNDLE,
341
            (int)$variables['currentSiteId']
342
        );
343
344
        // Render the template
345
        return $this->renderTemplate('seomatic/settings/global/' . $subSection, $variables);
346
    }
347
348
    /**
349
     * @return Response|null
350
     * @throws BadRequestHttpException
351
     * @throws MissingComponentException
352
     */
353
    public function actionSaveGlobal()
354
    {
355
        $this->requirePostRequest();
356
        $request = Craft::$app->getRequest();
357
        $siteId = $request->getParam('siteId');
358
        $globalsSettings = $request->getParam('metaGlobalVars');
359
        $bundleSettings = $request->getParam('metaBundleSettings');
360
        $robotsTemplate = $request->getParam('robotsTemplate');
361
        $humansTemplate = $request->getParam('humansTemplate');
362
        $adsTemplate = $request->getParam('adsTemplate');
363
        $securityTemplate = $request->getParam('securityTemplate');
364
        if (is_array($securityTemplate)) {
365
            if (!str_ends_with($securityTemplate['templateString'], "\n")) {
366
                $securityTemplate['templateString'] .= "\n";
367
            }
368
        }
369
        // Set the element type in the template
370
        $elementName = '';
371
372
        $hasErrors = false;
373
        // The site settings for the appropriate meta bundle
374
        Seomatic::$previewingMetaContainers = true;
375
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteId);
376
        Seomatic::$previewingMetaContainers = false;
377
        if ($metaBundle !== null) {
378
            if (is_array($globalsSettings) && is_array($bundleSettings)) {
379
                PullFieldHelper::parseTextSources($elementName, $globalsSettings, $bundleSettings);
380
                PullFieldHelper::parseImageSources($elementName, $globalsSettings, $bundleSettings, $siteId);
381
                if (!empty($bundleSettings['siteType'])) {
382
                    $globalsSettings['mainEntityOfPage'] = SchemaHelper::getSpecificEntityType($bundleSettings);
383
                }
384
                $metaBundle->metaGlobalVars->setAttributes($globalsSettings);
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

384
                $metaBundle->metaGlobalVars->/** @scrutinizer ignore-call */ 
385
                                             setAttributes($globalsSettings);

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...
385
                $metaBundle->metaBundleSettings->setAttributes($bundleSettings);
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

385
                $metaBundle->metaBundleSettings->/** @scrutinizer ignore-call */ 
386
                                                 setAttributes($bundleSettings);

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...
386
            }
387
            $templateContainers = $metaBundle->frontendTemplatesContainer->data;
388
            $robotsContainer = $templateContainers[FrontendTemplates::ROBOTS_TXT_HANDLE];
389
            if ($robotsContainer !== null && is_array($robotsTemplate)) {
390
                $robotsContainer->setAttributes($robotsTemplate);
391
                if (!$robotsContainer->validate()) {
392
                    $hasErrors = true;
393
                }
394
            }
395
            $humansContainer = $templateContainers[FrontendTemplates::HUMANS_TXT_HANDLE];
396
            if ($humansContainer !== null && is_array($humansTemplate)) {
397
                $humansContainer->setAttributes($humansTemplate);
398
                if (!$humansContainer->validate()) {
399
                    $hasErrors = true;
400
                }
401
            }
402
            $adsContainer = $templateContainers[FrontendTemplates::ADS_TXT_HANDLE];
403
            if ($adsContainer !== null && is_array($adsTemplate)) {
404
                $adsContainer->setAttributes($adsTemplate);
405
                if (!$adsContainer->validate()) {
406
                    $hasErrors = true;
407
                }
408
            }
409
            $securityContainer = $templateContainers[FrontendTemplates::SECURITY_TXT_HANDLE];
410
            if ($securityContainer !== null && is_array($securityTemplate)) {
411
                $securityContainer->setAttributes($securityTemplate);
412
                if (!$securityContainer->validate()) {
413
                    $hasErrors = true;
414
                }
415
            }
416
            if (!$metaBundle->metaGlobalVars->validate()) {
417
                $hasErrors = true;
418
            }
419
420
            if ($hasErrors) {
421
                Craft::error(print_r($metaBundle->metaGlobalVars->getErrors(), true), __METHOD__);
0 ignored issues
show
Bug introduced by
It seems like print_r($metaBundle->met...ars->getErrors(), true) can also be of type true; however, parameter $message of yii\BaseYii::error() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

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

421
                Craft::error(/** @scrutinizer ignore-type */ print_r($metaBundle->metaGlobalVars->getErrors(), true), __METHOD__);
Loading history...
422
                Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save settings due to a Twig error."));
423
                // Send the redirect back to the template
424
                /** @var UrlManager $urlManager */
425
                $urlManager = Craft::$app->getUrlManager();
426
                $urlManager->setRouteParams([
427
                    'editedMetaBundle' => $metaBundle,
428
                ]);
429
430
                return null;
431
            }
432
433
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
434
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
435
436
            Seomatic::$plugin->clearAllCaches();
0 ignored issues
show
Bug introduced by
The method clearAllCaches() 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

436
            Seomatic::$plugin->/** @scrutinizer ignore-call */ 
437
                               clearAllCaches();

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...
437
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic global settings saved.'));
438
        }
439
440
        return $this->redirectToPostedUrl();
441
    }
442
443
    /**
444
     * Content settings
445
     *
446
     * @param string|null $siteHandle
447
     *
448
     * @return Response The rendered result
449
     * @throws NotFoundHttpException
450
     * @throws ForbiddenHttpException
451
     */
452
    public function actionContent(string $siteHandle = null): Response
453
    {
454
        $variables = [];
455
        // Get the site to edit
456
        $siteHandle = $this->getCpSiteHandle($siteHandle);
457
        $siteId = $this->getSiteIdFromHandle($siteHandle);
458
459
        $pluginName = Seomatic::$settings->pluginName;
460
        $templateTitle = Craft::t('seomatic', 'Content SEO');
461
        // Asset bundle
462
        try {
463
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
464
        } catch (InvalidConfigException $e) {
465
            Craft::error($e->getMessage(), __METHOD__);
466
        }
467
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
468
            '@nystudio107/seomatic/web/assets/dist',
469
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

469
        /** @scrutinizer ignore-call */ 
470
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
470
        );
471
        // Basic variables
472
        $variables['fullPageForm'] = false;
473
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
474
        $variables['pluginName'] = Seomatic::$settings->pluginName;
475
        $variables['title'] = $templateTitle;
476
        $variables['docTitle'] = "{$pluginName} - {$templateTitle}";
477
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
478
        $variables['crumbs'] = [
479
            [
480
                'label' => $pluginName,
481
                'url' => UrlHelper::cpUrl('seomatic'),
482
            ],
483
            [
484
                'label' => $templateTitle,
485
                'url' => UrlHelper::cpUrl('seomatic/content' . $siteHandleUri),
486
            ],
487
        ];
488
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
489
        $variables['controllerHandle'] = 'content';
490
        $variables['selectedSubnavItem'] = 'content';
491
        $metaBundles = Seomatic::$plugin->metaBundles->getContentMetaBundlesForSiteId($siteId);
492
        Seomatic::$plugin->metaBundles->deleteVestigialMetaBundles($metaBundles);
493
494
        // Render the template
495
        return $this->renderTemplate('seomatic/settings/content/index', $variables);
496
    }
497
498
    /**
499
     * Global settings
500
     *
501
     * @param string $subSection
502
     * @param string $sourceBundleType
503
     * @param string $sourceHandle
504
     * @param string|null $siteHandle
505
     * @param string|int|null $typeId
506
     * @param string|null $loadFromSiteHandle
507
     *
508
     * @return Response The rendered result
509
     * @throws NotFoundHttpException
510
     * @throws ForbiddenHttpException
511
     */
512
    public function actionEditContent(
513
        string $subSection,
514
        string $sourceBundleType,
515
        string $sourceHandle,
516
        string $siteHandle = null,
517
               $typeId = null,
518
               $loadFromSiteHandle = null,
519
    ): Response {
520
        $variables = [];
521
        // @TODO: Let people choose an entry/categorygroup/product as the preview
522
        // Get the site to edit
523
        $siteHandle = $this->getCpSiteHandle($siteHandle);
524
        $siteId = $this->getSiteIdFromHandle($siteHandle);
525
        if (is_string($typeId)) {
526
            $typeId = (int)$typeId;
527
        }
528
        if (empty($typeId)) {
529
            $typeId = null;
530
        }
531
        // Get the (entry) type menu
532
        $typeMenu = [];
533
        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
534
        if ($seoElement !== null) {
535
            $typeMenu = $seoElement::typeMenuFromHandle($sourceHandle);
536
        }
537
        $variables['typeMenu'] = $typeMenu;
538
        $variables['currentTypeId'] = null;
539
        $variables['specificTypeId'] = null;
540
        if (count($typeMenu) > 1) {
541
            $currentType = reset($typeMenu);
542
            $variables['currentType'] = $typeMenu[$typeId] ?? $currentType;
543
            $variables['currentTypeId'] = $typeId ?? key($typeMenu);
544
            $typeId = (int)$variables['currentTypeId'];
545
        }
546
        // If there's only one EntryType, don't bother displaying the menu
547
        if (count($typeMenu) === 1) {
548
            $variables['typeMenu'] = [];
549
            $variables['specificTypeId'] = $typeId ?? key($typeMenu);
550
        }
551
        // If this is the sitemap section, there are no per-entry type settings
552
        if ($subSection === 'sitemap') {
553
            $variables['typeMenu'] = [];
554
            $variables['specificTypeId'] = $typeId ?? key($typeMenu);
555
            $typeId = 0;
556
        }
557
        $pluginName = Seomatic::$settings->pluginName;
558
        // Asset bundle
559
        try {
560
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
561
        } catch (InvalidConfigException $e) {
562
            Craft::error($e->getMessage(), __METHOD__);
563
        }
564
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
565
            '@nystudio107/seomatic/web/assets/dist',
566
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

566
        /** @scrutinizer ignore-call */ 
567
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
567
        );
568
        // Enabled sites
569
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
570
        $this->cullDisabledSites($sourceBundleType, $sourceHandle, $variables);
571
        // Meta Bundle settings
572
        Seomatic::$previewingMetaContainers = true;
573
        // Get the site to copy the settings from, if any
574
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
575
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
576
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
577
        // Load the metabundle
578
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
579
            $sourceBundleType,
580
            $sourceHandle,
581
            $siteIdToLoad,
0 ignored issues
show
Bug introduced by
It seems like $siteIdToLoad can also be of type null; however, parameter $sourceSiteId of nystudio107\seomatic\ser...aBundleBySourceHandle() 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

581
            /** @scrutinizer ignore-type */ $siteIdToLoad,
Loading history...
582
            $typeId
0 ignored issues
show
Bug introduced by
It seems like $typeId can also be of type string; however, parameter $typeId of nystudio107\seomatic\ser...aBundleBySourceHandle() does only seem to accept integer|null, 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

582
            /** @scrutinizer ignore-type */ $typeId
Loading history...
583
        );
584
        Seomatic::$previewingMetaContainers = false;
585
        $templateTitle = '';
586
        if ($metaBundle !== null) {
587
            $variables['metaGlobalVars'] = clone $metaBundle->metaGlobalVars;
588
            $variables['metaSitemapVars'] = $metaBundle->metaSitemapVars;
589
            $variables['metaBundleSettings'] = $metaBundle->metaBundleSettings;
590
            $variables['currentSourceHandle'] = $metaBundle->sourceHandle;
591
            $variables['currentSourceBundleType'] = $metaBundle->sourceBundleType;
592
            $templateTitle = $metaBundle->sourceName;
593
        }
594
        // Basic variables
595
        $subSectionTitle = Craft::t('seomatic', ucfirst($subSection));
596
        $variables['fullPageForm'] = true;
597
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
598
        $variables['pluginName'] = Seomatic::$settings->pluginName;
599
        $variables['title'] = $templateTitle;
600
        $variables['subSectionTitle'] = $subSectionTitle;
601
        $variables['docTitle'] = "{$pluginName} - Content SEO - {$templateTitle} - {$subSectionTitle}";
602
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
603
        $variables['siteHandleUri'] = $siteHandleUri;
604
        $variables['crumbs'] = [
605
            [
606
                'label' => $pluginName,
607
                'url' => UrlHelper::cpUrl('seomatic'),
608
            ],
609
            [
610
                'label' => 'Content SEO',
611
                'url' => UrlHelper::cpUrl('seomatic/content' . $siteHandleUri),
612
            ],
613
            [
614
                'label' => $metaBundle->sourceName . ' · ' . $subSectionTitle,
615
                'url' => UrlHelper::cpUrl("seomatic/edit-content/${subSection}/${sourceBundleType}/${sourceHandle}"),
616
            ],
617
        ];
618
        $variables['selectedSubnavItem'] = 'content';
619
        $variables['controllerHandle'] = "edit-content/${subSection}/${sourceBundleType}/${sourceHandle}";
620
        // Image selectors
621
        $variables['currentSubSection'] = $subSection;
622
        $bundleSettings = $metaBundle->metaBundleSettings;
623
        $variables['elementType'] = Asset::class;
624
        $variables['assetVolumeSources'] = AssetHelper::getAssetInputSources();
625
        $variables['seoImageElements'] = ImageTransformHelper::assetElementsFromIds(
626
            $bundleSettings->seoImageIds,
627
            $siteId
628
        );
629
        $variables['twitterImageElements'] = ImageTransformHelper::assetElementsFromIds(
630
            $bundleSettings->twitterImageIds,
631
            $siteId
632
        );
633
        $variables['ogImageElements'] = ImageTransformHelper::assetElementsFromIds(
634
            $bundleSettings->ogImageIds,
635
            $siteId
636
        );
637
        $variables['sourceType'] = $metaBundle->sourceType;
638
        // Pass in the pull fields
639
        $groupName = ucfirst($metaBundle->sourceType);
640
        $this->setContentFieldSourceVariables($sourceBundleType, $sourceHandle, $groupName, $variables, $typeId);
641
        $uri = $this->uriFromSourceBundle($sourceBundleType, $sourceHandle, $siteId, $typeId);
642
        // Preview the meta containers
643
        Seomatic::$plugin->metaContainers->previewMetaContainers(
644
            $uri,
645
            (int)$variables['currentSiteId'],
646
            false,
647
            false
648
        );
649
650
        // Render the template
651
        return $this->renderTemplate('seomatic/settings/content/' . $subSection, $variables);
652
    }
653
654
    /**
655
     * @return Response
656
     * @throws BadRequestHttpException
657
     * @throws MissingComponentException
658
     */
659
    public function actionSaveContent(): Response
660
    {
661
        $this->requirePostRequest();
662
        $request = Craft::$app->getRequest();
663
        $sourceBundleType = $request->getParam('sourceBundleType');
664
        $sourceHandle = $request->getParam('sourceHandle');
665
        $siteId = $request->getParam('siteId');
666
        $typeId = $request->getBodyParam('typeId') ?? null;
667
        $specificTypeId = $request->getParam('specificTypeId') ?? null;
668
        $globalsSettings = $request->getParam('metaGlobalVars');
669
        $bundleSettings = $request->getParam('metaBundleSettings');
670
        $sitemapSettings = $request->getParam('metaSitemapVars');
671
        if (is_string($typeId)) {
672
            $typeId = (int)$typeId;
673
        }
674
        // Set the element type in the template
675
        $elementName = '';
676
        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
677
        if ($seoElement !== null) {
678
            $elementName = $seoElement::getElementRefHandle();
679
        }
680
        // The site settings for the appropriate meta bundle
681
        Seomatic::$previewingMetaContainers = true;
682
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
683
            $sourceBundleType,
684
            $sourceHandle,
685
            $siteId,
686
            $typeId
687
        );
688
        Seomatic::$previewingMetaContainers = false;
689
        if ($metaBundle) {
690
            if (is_array($globalsSettings) && is_array($bundleSettings)) {
691
                PullFieldHelper::parseTextSources($elementName, $globalsSettings, $bundleSettings);
692
                PullFieldHelper::parseImageSources($elementName, $globalsSettings, $bundleSettings, $siteId);
693
                if (!empty($bundleSettings['siteType'])) {
694
                    $globalsSettings['mainEntityOfPage'] = SchemaHelper::getSpecificEntityType($bundleSettings);
695
                }
696
                $metaBundle->metaGlobalVars->setAttributes($globalsSettings);
697
                $metaBundle->metaBundleSettings->setAttributes($bundleSettings);
698
            }
699
            if (is_array($sitemapSettings)) {
700
                $metaBundle->metaSitemapVars->setAttributes($sitemapSettings);
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

700
                $metaBundle->metaSitemapVars->/** @scrutinizer ignore-call */ 
701
                                              setAttributes($sitemapSettings);

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...
701
            }
702
703
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
704
            $metaBundle->typeId = $typeId;
705
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
706
            // If there's also a specific typeId associated with this section, save the same
707
            // metabundle there too, to fix: https://github.com/nystudio107/craft-seomatic/issues/1557
708
            if (!empty($specificTypeId)) {
709
                $metaBundle->typeId = $specificTypeId;
710
                Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
711
            }
712
713
            Seomatic::$plugin->clearAllCaches();
714
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic content settings saved.'));
715
        }
716
717
        return $this->redirectToPostedUrl();
718
    }
719
720
    /**
721
     * Site settings
722
     *
723
     * @param string $subSection
724
     * @param string|null $siteHandle
725
     * @param string|null $loadFromSiteHandle
726
     *
727
     * @return Response The rendered result
728
     * @throws NotFoundHttpException
729
     * @throws ForbiddenHttpException
730
     */
731
    public function actionSite(string $subSection = 'identity', string $siteHandle = null, $loadFromSiteHandle = null): Response
732
    {
733
        $variables = [];
734
        // Get the site to edit
735
        $siteHandle = $this->getCpSiteHandle($siteHandle);
736
        $siteId = $this->getSiteIdFromHandle($siteHandle);
737
738
        $pluginName = Seomatic::$settings->pluginName;
739
        $templateTitle = Craft::t('seomatic', 'Site Settings');
740
        $subSectionSuffix = '';
741
        if ($subSection === 'social') {
742
            $subSectionSuffix = ' Media';
743
        }
744
        $subSectionTitle = Craft::t('seomatic', ucfirst($subSection) . $subSectionSuffix);
745
        // Asset bundle
746
        try {
747
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
748
        } catch (InvalidConfigException $e) {
749
            Craft::error($e->getMessage(), __METHOD__);
750
        }
751
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
752
            '@nystudio107/seomatic/web/assets/dist',
753
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

753
        /** @scrutinizer ignore-call */ 
754
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
754
        );
755
        // Basic variables
756
        $variables['fullPageForm'] = true;
757
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
758
        $variables['pluginName'] = Seomatic::$settings->pluginName;
759
        $variables['title'] = $templateTitle;
760
        $variables['subSectionTitle'] = $subSectionTitle;
761
        $variables['docTitle'] = "{$pluginName} - {$templateTitle} - {$subSectionTitle}";
762
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
763
        $variables['crumbs'] = [
764
            [
765
                'label' => $pluginName,
766
                'url' => UrlHelper::cpUrl('seomatic'),
767
            ],
768
            [
769
                'label' => $templateTitle,
770
                'url' => UrlHelper::cpUrl('seomatic/site/identity' . $siteHandleUri),
771
            ],
772
            [
773
                'label' => $subSectionTitle,
774
                'url' => UrlHelper::cpUrl('seomatic/site/' . $subSection . $siteHandleUri),
775
            ],
776
        ];
777
        $variables['selectedSubnavItem'] = 'site';
778
        $variables['currentSubSection'] = $subSection;
779
780
        // Enabled sites
781
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
782
        $variables['controllerHandle'] = 'site' . '/' . $subSection;
783
784
        // The site settings for the appropriate meta bundle
785
        Seomatic::$previewingMetaContainers = true;
786
        // Get the site to copy the settings from, if any
787
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
788
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
789
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
790
        // Load the metabundle
791
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteIdToLoad);
0 ignored issues
show
Bug introduced by
It seems like $siteIdToLoad can also be of type null; however, parameter $sourceSiteId of nystudio107\seomatic\ser...::getGlobalMetaBundle() 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

791
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $siteIdToLoad);
Loading history...
792
        Seomatic::$previewingMetaContainers = false;
793
        if ($metaBundle !== null) {
794
            $variables['site'] = $metaBundle->metaSiteVars;
795
            $variables['identityImageElements'] = ImageTransformHelper::assetElementsFromIds(
796
                $variables['site']->identity->genericImageIds,
797
                $siteId
798
            );
799
            $variables['creatorImageElements'] = ImageTransformHelper::assetElementsFromIds(
800
                $variables['site']->creator->genericImageIds,
801
                $siteId
802
            );
803
        }
804
        $variables['elementType'] = Asset::class;
805
        $variables['assetVolumeSources'] = AssetHelper::getAssetInputSources();
806
807
        // Render the template
808
        return $this->renderTemplate('seomatic/settings/site/' . $subSection, $variables);
809
    }
810
811
    /**
812
     * @return Response
813
     * @throws BadRequestHttpException
814
     * @throws MissingComponentException
815
     */
816
    public function actionSaveSite(): Response
817
    {
818
        $this->requirePostRequest();
819
        $request = Craft::$app->getRequest();
820
        $siteId = $request->getParam('siteId');
821
        $siteSettings = $request->getParam('seomaticSite');
822
823
        // Make sure the twitter handle isn't prefixed with an @
824
        if (!empty($siteSettings['twitterHandle'])) {
825
            $siteSettings['twitterHandle'] = ltrim($siteSettings['twitterHandle'], '@');
826
        }
827
        // Make sure the sameAsLinks are indexed by the handle
828
        if (!empty($siteSettings['sameAsLinks'])) {
829
            $siteSettings['sameAsLinks'] = ArrayHelper::index($siteSettings['sameAsLinks'], 'handle');
830
        }
831
        // The site settings for the appropriate meta bundle
832
        Seomatic::$previewingMetaContainers = true;
833
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteId);
834
        Seomatic::$previewingMetaContainers = false;
835
        if ($metaBundle) {
836
            if (is_array($siteSettings)) {
837
                if (!empty($siteSettings['identity'])) {
838
                    $settings = $siteSettings['identity'];
839
                    $this->prepEntitySettings($settings);
840
                    $metaBundle->metaSiteVars->identity->setAttributes($settings);
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

840
                    $metaBundle->metaSiteVars->identity->/** @scrutinizer ignore-call */ 
841
                                                         setAttributes($settings);

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...
841
                    $siteSettings['identity'] = $metaBundle->metaSiteVars->identity;
842
                }
843
                if (!empty($siteSettings['creator'])) {
844
                    $settings = $siteSettings['creator'];
845
                    $this->prepEntitySettings($settings);
846
                    $metaBundle->metaSiteVars->creator->setAttributes($settings);
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

846
                    $metaBundle->metaSiteVars->creator->/** @scrutinizer ignore-call */ 
847
                                                        setAttributes($settings);

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...
847
                    $siteSettings['creator'] = $metaBundle->metaSiteVars->creator;
848
                }
849
                if (!empty($siteSettings['additionalSitemapUrls'])) {
850
                    $siteSettings['additionalSitemapUrlsDateUpdated'] = new DateTime();
851
                    Seomatic::$plugin->sitemaps->submitCustomSitemap($siteId);
852
                }
853
                $metaBundle->metaSiteVars->setAttributes($siteSettings);
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

853
                $metaBundle->metaSiteVars->/** @scrutinizer ignore-call */ 
854
                                           setAttributes($siteSettings);

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...
854
            }
855
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
856
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
857
858
            Seomatic::$plugin->clearAllCaches();
859
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic site settings saved.'));
860
        }
861
862
        return $this->redirectToPostedUrl();
863
    }
864
865
    /**
866
     * Plugin settings
867
     *
868
     * @return Response The rendered result
869
     * @throws ForbiddenHttpException
870
     */
871
    public function actionPlugin(): Response
872
    {
873
        // Ensure they have permission to edit the plugin settings
874
        $currentUser = Craft::$app->getUser()->getIdentity();
875
        if (!$currentUser->can('seomatic:plugin-settings')) {
0 ignored issues
show
Bug introduced by
The method can() does not exist on yii\web\IdentityInterface. It seems like you code against a sub-type of yii\web\IdentityInterface such as craft\elements\User. ( Ignorable by Annotation )

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

875
        if (!$currentUser->/** @scrutinizer ignore-call */ can('seomatic:plugin-settings')) {
Loading history...
876
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
877
        }
878
        $general = Craft::$app->getConfig()->getGeneral();
879
        if (!$general->allowAdminChanges) {
880
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
881
        }
882
        // Edit the plugin settings
883
        $variables = [];
884
        $pluginName = Seomatic::$settings->pluginName;
885
        $templateTitle = Craft::t('seomatic', 'Plugin Settings');
886
        // Asset bundle
887
        try {
888
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
889
        } catch (InvalidConfigException $e) {
890
            Craft::error($e->getMessage(), __METHOD__);
891
        }
892
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
893
            '@nystudio107/seomatic/web/assets/dist',
894
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

894
        /** @scrutinizer ignore-call */ 
895
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
895
        );
896
        // Basic variables
897
        $variables['fullPageForm'] = true;
898
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
899
        $variables['pluginName'] = Seomatic::$settings->pluginName;
900
        $variables['title'] = $templateTitle;
901
        $variables['docTitle'] = "{$pluginName} - {$templateTitle}";
902
        $variables['crumbs'] = [
903
            [
904
                'label' => $pluginName,
905
                'url' => UrlHelper::cpUrl('seomatic'),
906
            ],
907
            [
908
                'label' => $templateTitle,
909
                'url' => UrlHelper::cpUrl('seomatic/plugin'),
910
            ],
911
        ];
912
        $variables['selectedSubnavItem'] = 'plugin';
913
        $variables['settings'] = Seomatic::$settings;
914
        $sites = ArrayHelper::map(Craft::$app->getSites()->getAllSites(), 'id', 'name');
915
        $variables['sites'] = $sites;
916
917
        // Render the template
918
        return $this->renderTemplate('seomatic/settings/plugin/_edit', $variables);
919
    }
920
921
    /**
922
     * Tracking settings
923
     *
924
     * @param string $subSection
925
     * @param string|null $siteHandle
926
     * @param string|null $loadFromSiteHandle
927
     *
928
     * @return Response The rendered result
929
     * @throws NotFoundHttpException
930
     * @throws ForbiddenHttpException
931
     */
932
    public function actionTracking(string $subSection = 'gtag', string $siteHandle = null, $loadFromSiteHandle = null, $editedMetaBundle = null): Response
933
    {
934
        $variables = [];
935
        // Get the site to edit
936
        $siteHandle = $this->getCpSiteHandle($siteHandle);
937
        $siteId = $this->getSiteIdFromHandle($siteHandle);
938
        // Enabled sites
939
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
940
        $variables['controllerHandle'] = 'tracking' . '/' . $subSection;
941
        $variables['currentSubSection'] = $subSection;
942
943
        // The script meta containers for the global meta bundle
944
        Seomatic::$previewingMetaContainers = true;
945
        // Get the site to copy the settings from, if any
946
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
947
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
948
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
949
        // Load the metabundle
950
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteIdToLoad);
0 ignored issues
show
Bug introduced by
It seems like $siteIdToLoad can also be of type null; however, parameter $sourceSiteId of nystudio107\seomatic\ser...::getGlobalMetaBundle() 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

950
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $siteIdToLoad);
Loading history...
951
        if ($editedMetaBundle) {
952
            $metaBundle = $editedMetaBundle;
953
        }
954
        Seomatic::$previewingMetaContainers = false;
955
        if ($metaBundle !== null) {
956
            $variables['scripts'] = Seomatic::$plugin->metaBundles->getContainerDataFromBundle(
957
                $metaBundle,
958
                MetaScriptContainer::CONTAINER_TYPE
959
            );
960
        }
961
        // Add in the variables to the autocomplete cache so they can be accessed across requests
962
        $subSectionSettings = $variables['scripts'][$subSection];
963
        $variables['codeEditorOptions'] = [
964
            TrackingVarsAutocomplete::OPTIONS_DATA_KEY => $subSectionSettings->vars,
965
        ];
966
        // Plugin and section settings
967
        $pluginName = Seomatic::$settings->pluginName;
968
        $templateTitle = Craft::t('seomatic', 'Tracking Scripts');
969
        $subSectionTitle = $variables['scripts'][$subSection]->name;
970
        $subSectionTitle = Craft::t('seomatic', $subSectionTitle);
971
        // Asset bundle
972
        try {
973
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
974
        } catch (InvalidConfigException $e) {
975
            Craft::error($e->getMessage(), __METHOD__);
976
        }
977
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
978
            '@nystudio107/seomatic/web/assets/dist',
979
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

979
        /** @scrutinizer ignore-call */ 
980
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
980
        );
981
        // Basic variables
982
        $variables['fullPageForm'] = true;
983
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
984
        $variables['pluginName'] = Seomatic::$settings->pluginName;
985
        $variables['title'] = $templateTitle;
986
        $variables['subSectionTitle'] = $subSectionTitle;
987
        $variables['docTitle'] = "{$pluginName} - {$templateTitle} - {$subSectionTitle}";
988
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
989
        $variables['crumbs'] = [
990
            [
991
                'label' => $pluginName,
992
                'url' => UrlHelper::cpUrl('seomatic'),
993
            ],
994
            [
995
                'label' => $templateTitle,
996
                'url' => UrlHelper::cpUrl('seomatic/tracking'),
997
            ],
998
            [
999
                'label' => $subSectionTitle,
1000
                'url' => UrlHelper::cpUrl('seomatic/tracking/' . $subSection . $siteHandleUri),
1001
            ],
1002
        ];
1003
        $variables['selectedSubnavItem'] = 'tracking';
1004
1005
        // Render the template
1006
        return $this->renderTemplate('seomatic/settings/tracking/_edit', $variables);
1007
    }
1008
1009
    /**
1010
     * @return Response|null
1011
     * @throws BadRequestHttpException
1012
     * @throws MissingComponentException
1013
     */
1014
    public function actionSaveTracking()
1015
    {
1016
        $this->requirePostRequest();
1017
        $request = Craft::$app->getRequest();
1018
        $siteId = $request->getParam('siteId');
1019
        $scriptSettings = $request->getParam('scripts');
1020
1021
        // The site settings for the appropriate meta bundle
1022
        Seomatic::$previewingMetaContainers = true;
1023
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteId);
1024
        Seomatic::$previewingMetaContainers = false;
1025
        $hasErrors = false;
1026
        if ($metaBundle) {
1027
            /** @var array $scriptSettings */
1028
            foreach ($scriptSettings as $scriptHandle => $scriptData) {
1029
                foreach ($metaBundle->metaContainers as $metaContainer) {
1030
                    if ($metaContainer::CONTAINER_TYPE === MetaScriptContainer::CONTAINER_TYPE) {
1031
                        $data = $metaContainer->getData($scriptHandle);
1032
                        /** @var MetaScript|null $data */
1033
                        if ($data) {
1034
                            /** @var array $scriptData */
1035
                            foreach ($scriptData as $key => $value) {
1036
                                if (is_array($value) && $key !== 'tagAttrs') {
1037
                                    foreach ($value as $varsKey => $varsValue) {
1038
                                        $data->$key[$varsKey]['value'] = $varsValue;
1039
                                    }
1040
                                } else {
1041
                                    $data->$key = $value;
1042
                                }
1043
                            }
1044
                            if (!$data->validate()) {
1045
                                $hasErrors = true;
1046
                            }
1047
                        }
1048
                    }
1049
                }
1050
            }
1051
            if ($hasErrors) {
1052
                Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save tracking settings due to a Twig error."));
1053
                // Send the redirect back to the template
1054
                /** @var UrlManager $urlManager */
1055
                $urlManager = Craft::$app->getUrlManager();
1056
                $urlManager->setRouteParams([
1057
                    'editedMetaBundle' => $metaBundle,
1058
                ]);
1059
1060
                return null;
1061
            }
1062
1063
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
1064
1065
            Seomatic::$plugin->clearAllCaches();
1066
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic site settings saved.'));
1067
        }
1068
1069
        return $this->redirectToPostedUrl();
1070
    }
1071
1072
    /**
1073
     * Saves a plugin’s settings.
1074
     *
1075
     * @return Response|null
1076
     * @throws NotFoundHttpException if the requested plugin cannot be found
1077
     * @throws BadRequestHttpException
1078
     * @throws MissingComponentException
1079
     */
1080
    public function actionSavePluginSettings()
1081
    {
1082
        // Ensure they have permission to edit the plugin settings
1083
        $currentUser = Craft::$app->getUser()->getIdentity();
1084
        if (!$currentUser->can('seomatic:plugin-settings')) {
1085
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
1086
        }
1087
        $general = Craft::$app->getConfig()->getGeneral();
1088
        if (!$general->allowAdminChanges) {
1089
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
1090
        }
1091
        // Save the plugin settings
1092
        $this->requirePostRequest();
1093
        $pluginHandle = Craft::$app->getRequest()->getRequiredBodyParam('pluginHandle');
1094
        $settings = Craft::$app->getRequest()->getBodyParam('settings', []);
1095
        $plugin = Craft::$app->getPlugins()->getPlugin($pluginHandle);
1096
1097
        if ($plugin === null) {
1098
            throw new NotFoundHttpException('Plugin not found');
1099
        }
1100
1101
        if (!Craft::$app->getPlugins()->savePluginSettings($plugin, $settings)) {
1102
            Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save plugin settings."));
1103
1104
            // Send the redirect back to the template
1105
            /** @var UrlManager $urlManager */
1106
            $urlManager = Craft::$app->getUrlManager();
1107
            $urlManager->setRouteParams([
1108
                'plugin' => $plugin,
1109
            ]);
1110
1111
            return null;
1112
        }
1113
1114
        Seomatic::$plugin->clearAllCaches();
1115
        Craft::$app->getSession()->setNotice(Craft::t('app', 'Plugin settings saved.'));
1116
1117
        return $this->redirectToPostedUrl();
1118
    }
1119
1120
    // Protected Methods
1121
    // =========================================================================
1122
1123
    /**
1124
     * @param $siteHandle
1125
     * @return string|null
1126
     */
1127
    protected function getCpSiteHandle($siteHandle)
1128
    {
1129
        // As of Craft 4, the site query parameter is appended to CP urls to indicate the current
1130
        // site that is being edited, so respect it
1131
        $cpSite = Cp::requestedSite();
1132
        if ($cpSite) {
1133
            return $cpSite->handle;
1134
        }
1135
1136
        return $siteHandle;
1137
    }
1138
1139
    /**
1140
     * Return a siteId from a siteHandle
1141
     *
1142
     * @param string|null $siteHandle
1143
     *
1144
     * @return int|null
1145
     * @throws NotFoundHttpException
1146
     */
1147
    protected function getSiteIdFromHandle($siteHandle)
1148
    {
1149
        // Get the site to edit
1150
        if ($siteHandle !== null) {
1151
            $site = Craft::$app->getSites()->getSiteByHandle($siteHandle);
1152
            if (!$site) {
1153
                throw new NotFoundHttpException('Invalid site handle: ' . $siteHandle);
1154
            }
1155
            $siteId = $site->id;
1156
        } else {
1157
            $siteId = Craft::$app->getSites()->currentSite->id;
1158
        }
1159
1160
        return $siteId;
1161
    }
1162
1163
    /**
1164
     * @param $siteHandle
1165
     * @param $siteId
1166
     * @param array $variables
1167
     * @param $element
1168
     * @return void
1169
     * @throws ForbiddenHttpException
1170
     */
1171
    protected function setMultiSiteVariables($siteHandle, &$siteId, array &$variables, $element = null)
0 ignored issues
show
Unused Code introduced by
The parameter $element 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

1171
    protected function setMultiSiteVariables($siteHandle, &$siteId, array &$variables, /** @scrutinizer ignore-unused */ $element = null)

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...
1172
    {
1173
        // Enabled sites
1174
        $sites = Craft::$app->getSites();
1175
        if (Craft::$app->getIsMultiSite()) {
1176
            // Set defaults based on the section settings
1177
            $variables['enabledSiteIds'] = [];
1178
            $variables['siteIds'] = [];
1179
1180
            foreach ($sites->getEditableSiteIds() as $editableSiteId) {
1181
                $variables['enabledSiteIds'][] = $editableSiteId;
1182
                $variables['siteIds'][] = $editableSiteId;
1183
            }
1184
1185
            // Make sure the $siteId they are trying to edit is in our array of editable sites
1186
            if (!in_array($siteId, $variables['enabledSiteIds'], false)) {
1187
                if (!empty($variables['enabledSiteIds'])) {
1188
                    $siteId = reset($variables['enabledSiteIds']);
1189
                } else {
1190
                    $this->requirePermission('editSite:' . $siteId);
1191
                }
1192
            }
1193
        }
1194
1195
        // Set the currentSiteId and currentSiteHandle
1196
        $variables['currentSiteId'] = empty($siteId) ? Craft::$app->getSites()->currentSite->id : $siteId;
1197
        $variables['currentSiteHandle'] = empty($siteHandle)
1198
            ? Craft::$app->getSites()->currentSite->handle
1199
            : $siteHandle;
1200
1201
        // Page title
1202
        $variables['showSites'] = (
1203
            Craft::$app->getIsMultiSite() &&
1204
            count($variables['enabledSiteIds'])
1205
        );
1206
1207
        if ($variables['showSites']) {
1208
            $variables['sitesMenuLabel'] = Craft::t(
1209
                'site',
1210
                $sites->getSiteById((int)$variables['currentSiteId'])->name
1211
            );
1212
        } else {
1213
            $variables['sitesMenuLabel'] = '';
1214
        }
1215
    }
1216
1217
    /**
1218
     * @param array $variables
1219
     */
1220
    protected function setGlobalFieldSourceVariables(array &$variables)
1221
    {
1222
        $variables['textFieldSources'] = array_merge(
1223
            ['globalsGroup' => ['optgroup' => 'Globals Fields']],
1224
            FieldHelper::fieldsOfTypeFromGlobals(
1225
                FieldHelper::TEXT_FIELD_CLASS_KEY,
1226
                false
1227
            )
1228
        );
1229
        $variables['assetFieldSources'] = array_merge(
1230
            ['globalsGroup' => ['optgroup' => 'Globals Fields']],
1231
            FieldHelper::fieldsOfTypeFromGlobals(
1232
                FieldHelper::ASSET_FIELD_CLASS_KEY,
1233
                false
1234
            )
1235
        );
1236
    }
1237
1238
    /**
1239
     * Remove any sites for which meta bundles do not exist (they may be
1240
     * disabled for this section)
1241
     *
1242
     * @param string $sourceBundleType
1243
     * @param string $sourceHandle
1244
     * @param array $variables
1245
     */
1246
    protected function cullDisabledSites(string $sourceBundleType, string $sourceHandle, array &$variables)
1247
    {
1248
        if (isset($variables['enabledSiteIds'])) {
1249
            foreach ($variables['enabledSiteIds'] as $key => $value) {
1250
                $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
1251
                    $sourceBundleType,
1252
                    $sourceHandle,
1253
                    $value
1254
                );
1255
                if ($metaBundle === null) {
1256
                    unset($variables['enabledSiteIds'][$key]);
1257
                }
1258
            }
1259
        }
1260
    }
1261
1262
    /**
1263
     * @param string $sourceBundleType
1264
     * @param string $sourceHandle
1265
     * @param string $groupName
1266
     * @param array $variables
1267
     * @param int|string|null $typeId
1268
     */
1269
    protected function setContentFieldSourceVariables(
1270
        string $sourceBundleType,
1271
        string $sourceHandle,
1272
        string $groupName,
1273
        array  &$variables,
1274
               $typeId = null,
1275
    ) {
1276
        $variables['textFieldSources'] = array_merge(
1277
            ['entryGroup' => ['optgroup' => $groupName . ' Fields'], 'title' => 'Title'],
1278
            FieldHelper::fieldsOfTypeFromSource(
1279
                $sourceBundleType,
1280
                $sourceHandle,
1281
                FieldHelper::TEXT_FIELD_CLASS_KEY,
1282
                false,
1283
                $typeId
1284
            )
1285
        );
1286
        $variables['assetFieldSources'] = array_merge(
1287
            ['entryGroup' => ['optgroup' => $groupName . ' Fields']],
1288
            FieldHelper::fieldsOfTypeFromSource(
1289
                $sourceBundleType,
1290
                $sourceHandle,
1291
                FieldHelper::ASSET_FIELD_CLASS_KEY,
1292
                false,
1293
                $typeId
1294
            )
1295
        );
1296
        $variables['assetVolumeTextFieldSources'] = array_merge(
1297
            ['entryGroup' => ['optgroup' => 'Asset Volume Fields'], '' => '--', 'title' => 'Title'],
1298
            array_merge(
1299
                FieldHelper::fieldsOfTypeFromAssetVolumes(
1300
                    FieldHelper::TEXT_FIELD_CLASS_KEY,
1301
                    false
1302
                )
1303
            )
1304
        );
1305
        $variables['userFieldSources'] = array_merge(
1306
            ['entryGroup' => ['optgroup' => 'User Fields']],
1307
            FieldHelper::fieldsOfTypeFromUsers(
1308
                FieldHelper::TEXT_FIELD_CLASS_KEY,
1309
                false
1310
            )
1311
        );
1312
    }
1313
1314
    /**
1315
     * @param string $sourceBundleType
1316
     * @param string $sourceHandle
1317
     * @param null|int $siteId
1318
     * @param int|string|null $typeId
1319
     *
1320
     * @return string
1321
     */
1322
    protected function uriFromSourceBundle(string $sourceBundleType, string $sourceHandle, $siteId, $typeId): string
1323
    {
1324
        $uri = null;
1325
        // Pick an Element to be used for the preview
1326
        if ($sourceBundleType === MetaBundles::GLOBAL_META_BUNDLE) {
1327
            $uri = MetaBundles::GLOBAL_META_BUNDLE;
1328
        } else {
1329
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
1330
            if ($seoElement !== null) {
1331
                $uri = $seoElement::previewUri($sourceHandle, $siteId, $typeId);
1332
            }
1333
        }
1334
        // Special-case for the __home__ slug, and default to /
1335
        if (($uri === '__home__') || ($uri === null)) {
1336
            $uri = '/';
1337
        }
1338
1339
        return $uri;
1340
    }
1341
1342
    /**
1343
     * Prep the entity settings for saving to the db
1344
     *
1345
     * @param array &$settings
1346
     */
1347
    protected function prepEntitySettings(&$settings)
1348
    {
1349
        DynamicMetaHelper::normalizeTimes($settings['localBusinessOpeningHours']);
1350
        if (!empty($settings['siteType'])) {
1351
            $settings['computedType'] = SchemaHelper::getSpecificEntityType($settings);
1352
        } else {
1353
            $settings['computedType'] = 'WebPage';
1354
        }
1355
        // Empty out the entity image settings to ensure the image gets removed if it no longer exists
1356
        $settings['genericImage'] = '';
1357
        $settings['genericImageWidth'] = '';
1358
        $settings['genericImageHeight'] = '';
1359
        if (!empty($settings['genericImageIds'])) {
1360
            $asset = Craft::$app->getAssets()->getAssetById($settings['genericImageIds'][0]);
1361
            if ($asset !== null) {
1362
                $settings['genericImage'] = $asset->getUrl();
1363
                $settings['genericImageWidth'] = $asset->getWidth();
1364
                $settings['genericImageHeight'] = $asset->getHeight();
1365
            }
1366
        }
1367
    }
1368
}
1369