SettingsController::actionSaveGlobal()   F
last analyzed

Complexity

Conditions 21
Paths 2919

Size

Total Lines 88
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 60
dl 0
loc 88
rs 0
c 2
b 1
f 0
cc 21
nc 2919
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS
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\commerce\models\ProductType;
14
use craft\elements\Asset;
15
use craft\errors\MissingComponentException;
16
use craft\helpers\Cp;
0 ignored issues
show
Bug introduced by
The type craft\helpers\Cp was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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

123
            Seomatic::$view->/** @scrutinizer ignore-call */ 
124
                             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...
124
        } catch (InvalidConfigException $e) {
125
            Craft::error($e->getMessage(), __METHOD__);
126
        }
127
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
128
            '@nystudio107/seomatic/web/assets/dist',
129
            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

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

256
        /** @scrutinizer ignore-call */ 
257
        $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...
257
        );
258
        // Basic variables
259
        $variables['fullPageForm'] = true;
260
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
261
        $variables['pluginName'] = Seomatic::$settings->pluginName;
262
        $variables['title'] = $templateTitle;
263
        $variables['subSectionTitle'] = $subSectionTitle;
264
        $variables['docTitle'] = "{$pluginName} - {$templateTitle} - {$subSectionTitle}";
265
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
266
        $variables['crumbs'] = [
267
            [
268
                'label' => $pluginName,
269
                'url' => UrlHelper::cpUrl('seomatic'),
270
            ],
271
            [
272
                'label' => $templateTitle,
273
                'url' => UrlHelper::cpUrl('seomatic/global/general' . $siteHandleUri),
274
            ],
275
            [
276
                'label' => $subSectionTitle,
277
                'url' => UrlHelper::cpUrl('seomatic/global/' . $subSection . $siteHandleUri),
278
            ],
279
        ];
280
        $variables['selectedSubnavItem'] = 'global';
281
        // Pass in the pull fields
282
        $this->setGlobalFieldSourceVariables($variables);
283
        // Enabled sites
284
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
285
        $variables['controllerHandle'] = 'global' . '/' . $subSection;
286
        $variables['currentSubSection'] = $subSection;
287
        // Meta bundle settings
288
        Seomatic::$previewingMetaContainers = true;
289
        // Get the site to copy the settings from, if any
290
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
291
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
292
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
293
        // Load the metabundle
294
        $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

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

392
                $metaBundle->metaGlobalVars->/** @scrutinizer ignore-call */ 
393
                                             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...
393
                $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

393
                $metaBundle->metaBundleSettings->/** @scrutinizer ignore-call */ 
394
                                                 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...
394
            }
395
            $templateContainers = $metaBundle->frontendTemplatesContainer->data;
396
            $robotsContainer = $templateContainers[FrontendTemplates::ROBOTS_TXT_HANDLE];
397
            if ($robotsContainer !== null && is_array($robotsTemplate)) {
398
                $robotsContainer->setAttributes($robotsTemplate);
399
                if (!$robotsContainer->validate()) {
400
                    $hasErrors = true;
401
                }
402
            }
403
            $humansContainer = $templateContainers[FrontendTemplates::HUMANS_TXT_HANDLE];
404
            if ($humansContainer !== null && is_array($humansTemplate)) {
405
                $humansContainer->setAttributes($humansTemplate);
406
                if (!$humansContainer->validate()) {
407
                    $hasErrors = true;
408
                }
409
            }
410
            $adsContainer = $templateContainers[FrontendTemplates::ADS_TXT_HANDLE];
411
            if ($adsContainer !== null && is_array($adsTemplate)) {
412
                $adsContainer->setAttributes($adsTemplate);
413
                if (!$adsContainer->validate()) {
414
                    $hasErrors = true;
415
                }
416
            }
417
            $securityContainer = $templateContainers[FrontendTemplates::SECURITY_TXT_HANDLE];
418
            if ($securityContainer !== null && is_array($securityTemplate)) {
419
                $securityContainer->setAttributes($securityTemplate);
420
                if (!$securityContainer->validate()) {
421
                    $hasErrors = true;
422
                }
423
            }
424
            if (!$metaBundle->metaGlobalVars->validate()) {
425
                $hasErrors = true;
426
            }
427
428
            if ($hasErrors) {
429
                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

429
                Craft::error(/** @scrutinizer ignore-type */ print_r($metaBundle->metaGlobalVars->getErrors(), true), __METHOD__);
Loading history...
430
                Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save settings due to a Twig error."));
431
                // Send the redirect back to the template
432
                /** @var UrlManager $urlManager */
433
                $urlManager = Craft::$app->getUrlManager();
434
                $urlManager->setRouteParams([
435
                    'editedMetaBundle' => $metaBundle,
436
                ]);
437
438
                return null;
439
            }
440
441
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
442
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
443
444
            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

444
            Seomatic::$plugin->/** @scrutinizer ignore-call */ 
445
                               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...
445
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic global settings saved.'));
446
        }
447
448
        return $this->redirectToPostedUrl();
449
    }
450
451
    /**
452
     * Content settings
453
     *
454
     * @param string|null $siteHandle
455
     *
456
     * @return Response The rendered result
457
     * @throws NotFoundHttpException
458
     * @throws ForbiddenHttpException
459
     */
460
    public function actionContent(string $siteHandle = null): Response
461
    {
462
        $variables = [];
463
        // Get the site to edit
464
        $siteHandle = $this->getCpSiteHandle($siteHandle);
465
        $siteId = $this->getSiteIdFromHandle($siteHandle);
466
467
        $pluginName = Seomatic::$settings->pluginName;
468
        $templateTitle = Craft::t('seomatic', 'Content SEO');
469
        // Asset bundle
470
        try {
471
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
472
        } catch (InvalidConfigException $e) {
473
            Craft::error($e->getMessage(), __METHOD__);
474
        }
475
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
476
            '@nystudio107/seomatic/web/assets/dist',
477
            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

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

575
        /** @scrutinizer ignore-call */ 
576
        $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...
576
        );
577
        // Enabled sites
578
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
579
        $this->cullDisabledSites($seoElement, $sourceBundleType, $sourceHandle, $variables);
580
        // Meta Bundle settings
581
        Seomatic::$previewingMetaContainers = true;
582
        // Get the site to copy the settings from, if any
583
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
584
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
585
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
586
        // Load the metabundle
587
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
588
            $sourceBundleType,
589
            $sourceHandle,
590
            $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

590
            /** @scrutinizer ignore-type */ $siteIdToLoad,
Loading history...
591
            $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

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

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

763
        /** @scrutinizer ignore-call */ 
764
        $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...
764
        );
765
        // Basic variables
766
        $variables['fullPageForm'] = true;
767
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
768
        $variables['pluginName'] = Seomatic::$settings->pluginName;
769
        $variables['title'] = $templateTitle;
770
        $variables['subSectionTitle'] = $subSectionTitle;
771
        $variables['docTitle'] = "{$pluginName} - {$templateTitle} - {$subSectionTitle}";
772
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
773
        $variables['crumbs'] = [
774
            [
775
                'label' => $pluginName,
776
                'url' => UrlHelper::cpUrl('seomatic'),
777
            ],
778
            [
779
                'label' => $templateTitle,
780
                'url' => UrlHelper::cpUrl('seomatic/site/identity' . $siteHandleUri),
781
            ],
782
            [
783
                'label' => $subSectionTitle,
784
                'url' => UrlHelper::cpUrl('seomatic/site/' . $subSection . $siteHandleUri),
785
            ],
786
        ];
787
        $variables['selectedSubnavItem'] = 'site';
788
        $variables['currentSubSection'] = $subSection;
789
790
        // Enabled sites
791
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
792
        $variables['controllerHandle'] = 'site' . '/' . $subSection;
793
794
        // The site settings for the appropriate meta bundle
795
        Seomatic::$previewingMetaContainers = true;
796
        // Get the site to copy the settings from, if any
797
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
798
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
799
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
800
        // Load the metabundle
801
        $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

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

851
                    $metaBundle->metaSiteVars->identity->/** @scrutinizer ignore-call */ 
852
                                                         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...
852
                    $siteSettings['identity'] = $metaBundle->metaSiteVars->identity;
853
                }
854
                if (!empty($siteSettings['creator'])) {
855
                    $settings = $siteSettings['creator'];
856
                    $this->prepEntitySettings($settings);
857
                    $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

857
                    $metaBundle->metaSiteVars->creator->/** @scrutinizer ignore-call */ 
858
                                                        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...
858
                    $siteSettings['creator'] = $metaBundle->metaSiteVars->creator;
859
                }
860
                if (!empty($siteSettings['additionalSitemapUrls'])) {
861
                    $siteSettings['additionalSitemapUrlsDateUpdated'] = new DateTime();
862
                    Seomatic::$plugin->sitemaps->submitCustomSitemap($siteId);
863
                }
864
                $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

864
                $metaBundle->metaSiteVars->/** @scrutinizer ignore-call */ 
865
                                           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...
865
            }
866
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
867
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
868
869
            Seomatic::$plugin->clearAllCaches();
870
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic site settings saved.'));
871
        }
872
873
        return $this->redirectToPostedUrl();
874
    }
875
876
    /**
877
     * Plugin settings
878
     *
879
     * @return Response The rendered result
880
     * @throws ForbiddenHttpException
881
     */
882
    public function actionPlugin(): Response
883
    {
884
        // Ensure they have permission to edit the plugin settings
885
        $currentUser = Craft::$app->getUser()->getIdentity();
886
        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

886
        if (!$currentUser->/** @scrutinizer ignore-call */ can('seomatic:plugin-settings')) {
Loading history...
887
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
888
        }
889
        $general = Craft::$app->getConfig()->getGeneral();
890
        if (!$general->allowAdminChanges) {
891
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
892
        }
893
        // Edit the plugin settings
894
        $variables = [];
895
        $pluginName = Seomatic::$settings->pluginName;
896
        $templateTitle = Craft::t('seomatic', 'Plugin Settings');
897
        // Asset bundle
898
        try {
899
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
900
        } catch (InvalidConfigException $e) {
901
            Craft::error($e->getMessage(), __METHOD__);
902
        }
903
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
904
            '@nystudio107/seomatic/web/assets/dist',
905
            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

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

961
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $siteIdToLoad);
Loading history...
962
        if ($editedMetaBundle) {
963
            $metaBundle = $editedMetaBundle;
964
        }
965
        Seomatic::$previewingMetaContainers = false;
966
        if ($metaBundle !== null) {
967
            $variables['scripts'] = Seomatic::$plugin->metaBundles->getContainerDataFromBundle(
968
                $metaBundle,
969
                MetaScriptContainer::CONTAINER_TYPE
970
            );
971
        }
972
        // Add in the variables to the autocomplete cache so they can be accessed across requests
973
        $subSectionSettings = $variables['scripts'][$subSection];
974
        $variables['codeEditorOptions'] = [
975
            TrackingVarsAutocomplete::OPTIONS_DATA_KEY => $subSectionSettings->vars,
976
        ];
977
        // Plugin and section settings
978
        $pluginName = Seomatic::$settings->pluginName;
979
        $templateTitle = Craft::t('seomatic', 'Tracking Scripts');
980
        $subSectionTitle = $variables['scripts'][$subSection]->name;
981
        $subSectionTitle = Craft::t('seomatic', $subSectionTitle);
982
        // Asset bundle
983
        try {
984
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
985
        } catch (InvalidConfigException $e) {
986
            Craft::error($e->getMessage(), __METHOD__);
987
        }
988
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
989
            '@nystudio107/seomatic/web/assets/dist',
990
            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

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

1183
    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...
1184
    {
1185
        // Enabled sites
1186
        $sites = Craft::$app->getSites();
1187
        if (Craft::$app->getIsMultiSite()) {
1188
            // Set defaults based on the section settings
1189
            $variables['enabledSiteIds'] = [];
1190
            $variables['siteIds'] = [];
1191
1192
            foreach ($sites->getEditableSiteIds() as $editableSiteId) {
1193
                $variables['enabledSiteIds'][] = $editableSiteId;
1194
                $variables['siteIds'][] = $editableSiteId;
1195
            }
1196
1197
            // Make sure the $siteId they are trying to edit is in our array of editable sites
1198
            if (!in_array($siteId, $variables['enabledSiteIds'], false)) {
1199
                if (!empty($variables['enabledSiteIds'])) {
1200
                    $siteId = reset($variables['enabledSiteIds']);
1201
                } else {
1202
                    $this->requirePermission('editSite:' . $siteId);
1203
                }
1204
            }
1205
        }
1206
1207
        // Set the currentSiteId and currentSiteHandle
1208
        $variables['currentSiteId'] = empty($siteId) ? Craft::$app->getSites()->currentSite->id : $siteId;
1209
        $variables['currentSiteHandle'] = empty($siteHandle)
1210
            ? Craft::$app->getSites()->currentSite->handle
1211
            : $siteHandle;
1212
1213
        // Page title
1214
        $variables['showSites'] = (
1215
            Craft::$app->getIsMultiSite() &&
1216
            count($variables['enabledSiteIds'])
1217
        );
1218
    }
1219
1220
    /**
1221
     * @param array $variables
1222
     * @return void
1223
     */
1224
    protected function setCrumbVariables(array &$variables)
1225
    {
1226
        $sites = Craft::$app->getSites();
1227
        if (!array_key_exists('crumbs', $variables)) {
1228
            $variables['crumbs'] = [];
1229
        }
1230
        // Handle adding in the Sites menu if there are multiple sites
1231
        if ($variables['showSites']) {
1232
            $siteCrumbItems = [];
1233
            $siteGroups = Craft::$app->getSites()->getAllGroups();
1234
            $crumbSites = Collection::make($sites->getAllSites())
1235
                ->map(fn(Site $site) => ['site' => $site])
1236
                ->keyBy(fn(array $site) => $site['site']->id)
1237
                ->filter(fn(array $site) => in_array($site['site']->id, $variables['enabledSiteIds']))
1238
                ->all();
1239
1240
            foreach ($siteGroups as $siteGroup) {
1241
                $groupSites = $siteGroup->getSites();
1242
1243
                if (empty($groupSites)) {
1244
                    continue;
1245
                }
1246
1247
                $groupSiteItems = array_map(fn(Site $site) => [
1248
                    'status' => $crumbSites[$site->id]['site']->status ?? null,
1249
                    'label' => Craft::t('site', $site->name),
1250
                    'url' => UrlHelper::cpUrl("seomatic/{$variables['controllerHandle']}?site=$site->handle"),
1251
                    'hidden' => !isset($crumbSites[$site->id]),
1252
                    'selected' => $site->id === $variables['currentSiteId'],
1253
                    'attributes' => [
1254
                        'data' => [
1255
                            'site-id' => $site->id,
1256
                        ],
1257
                    ],
1258
                ], $groupSites);
1259
1260
                if (count($siteGroups) > 1) {
1261
                    $siteCrumbItems[] = [
1262
                        'heading' => Craft::t('site', $siteGroup->name),
1263
                        'items' => $groupSiteItems,
1264
                        'hidden' => !ArrayHelper::contains($groupSiteItems, fn(array $item) => !$item['hidden']),
1265
                    ];
1266
                } else {
1267
                    array_push($siteCrumbItems, ...$groupSiteItems);
1268
                }
1269
            }
1270
            // Add in the breadcrumbs
1271
            $variables['crumbs'] = [
1272
                [
1273
                    'id' => 'language-menu',
1274
                    'icon' => 'world',
1275
                    'label' => Craft::t(
1276
                        'site',
1277
                        $sites->getSiteById((int)$variables['currentSiteId'])->name
1278
                    ),
1279
                    'menu' => [
1280
                        'items' => $siteCrumbItems,
1281
                        'label' => Craft::t('site', 'Select site'),
1282
                    ],
1283
                ],
1284
                ...$variables['crumbs'],
1285
            ];
1286
1287
            $variables['sitesMenuLabel'] = Craft::t(
1288
                'site',
1289
                $sites->getSiteById((int)$variables['currentSiteId'])->name
1290
            );
1291
        } else {
1292
            $variables['sitesMenuLabel'] = '';
1293
        }
1294
        // Handle adding in the Entry Types menu if there are multiple entry types
1295
        if (isset($variables['typeMenu']) && !empty($variables['typeMenu'])) {
1296
            $typeCrumbItems = [];
1297
            foreach ($variables['typeMenu'] as $key => $value) {
1298
                $typeCrumbItems[] = [
1299
                    'status' => null,
1300
                    'url' => UrlHelper::url("seomatic/{$variables['controllerHandle']}{$variables['siteHandleUri']}", [
1301
                        'site' => $variables['currentSiteHandle'],
1302
                        'typeId' => $key,
1303
                    ]),
1304
                    'label' => $value,
1305
                    'selected' => $variables['currentTypeId'] === $key,
1306
                ];
1307
            }
1308
            $variables['crumbs'][] =
1309
                [
1310
                    'id' => 'types-menu',
1311
                    'icon' => 'list',
1312
                    'label' => $variables['typeMenu'][$variables['currentTypeId']],
1313
                    'menu' => [
1314
                        'items' => $typeCrumbItems,
1315
                        'label' => Craft::t('seomatic', 'Entry Types'),
1316
                    ],
1317
                ];
1318
        }
1319
    }
1320
1321
    /**
1322
     * @param array $variables
1323
     */
1324
    protected function setGlobalFieldSourceVariables(array &$variables)
1325
    {
1326
        $variables['textFieldSources'] = array_merge(
1327
            ['globalsGroup' => ['optgroup' => 'Globals Fields']],
1328
            FieldHelper::fieldsOfTypeFromGlobals(
1329
                FieldHelper::TEXT_FIELD_CLASS_KEY,
1330
                false
1331
            )
1332
        );
1333
        $variables['assetFieldSources'] = array_merge(
1334
            ['globalsGroup' => ['optgroup' => 'Globals Fields']],
1335
            FieldHelper::fieldsOfTypeFromGlobals(
1336
                FieldHelper::ASSET_FIELD_CLASS_KEY,
1337
                false
1338
            )
1339
        );
1340
    }
1341
1342
    /**
1343
     * Remove any sites for which meta bundles do not exist (they may be
1344
     * disabled for this section)
1345
     *
1346
     * @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...
1347
     * @param string $sourceBundleType
1348
     * @param string $sourceHandle
1349
     * @param array $variables
1350
     */
1351
    protected function cullDisabledSites($seoElement, string $sourceBundleType, string $sourceHandle, array &$variables)
1352
    {
1353
        /** @var Section|ProductType|null $section */
1354
        $section = $seoElement::sourceModelFromHandle($sourceHandle);
1355
        $sectionSiteIds = [];
1356
        if ($section) {
1357
            $sectionSettings = $section->getSiteSettings();
1358
            foreach ($sectionSettings as $sectionSetting) {
1359
                $sectionSiteIds[] = $sectionSetting->siteId;
1360
            }
1361
        }
1362
        if (isset($variables['enabledSiteIds'])) {
1363
            foreach ($variables['enabledSiteIds'] as $key => $value) {
1364
                $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
1365
                    $sourceBundleType,
1366
                    $sourceHandle,
1367
                    $value
1368
                );
1369
                // Make sure the site exists for this Section
1370
                if (!in_array($value, $sectionSiteIds, true)) {
1371
                    unset($variables['enabledSiteIds'][$key]);
1372
                }
1373
                if ($metaBundle === null) {
1374
                    unset($variables['enabledSiteIds'][$key]);
1375
                }
1376
            }
1377
        }
1378
    }
1379
1380
    /**
1381
     * @param string $sourceBundleType
1382
     * @param string $sourceHandle
1383
     * @param string $groupName
1384
     * @param array $variables
1385
     * @param int|string|null $typeId
1386
     */
1387
    protected function setContentFieldSourceVariables(
1388
        string $sourceBundleType,
1389
        string $sourceHandle,
1390
        string $groupName,
1391
        array  &$variables,
1392
               $typeId = null,
1393
    ) {
1394
        $variables['textFieldSources'] = array_merge(
1395
            ['entryGroup' => ['optgroup' => $groupName . ' Fields'], 'title' => 'Title'],
1396
            FieldHelper::fieldsOfTypeFromSource(
1397
                $sourceBundleType,
1398
                $sourceHandle,
1399
                FieldHelper::TEXT_FIELD_CLASS_KEY,
1400
                false,
1401
                $typeId
1402
            )
1403
        );
1404
        $variables['assetFieldSources'] = array_merge(
1405
            ['entryGroup' => ['optgroup' => $groupName . ' Fields']],
1406
            FieldHelper::fieldsOfTypeFromSource(
1407
                $sourceBundleType,
1408
                $sourceHandle,
1409
                FieldHelper::ASSET_FIELD_CLASS_KEY,
1410
                false,
1411
                $typeId
1412
            )
1413
        );
1414
        $variables['assetVolumeTextFieldSources'] = array_merge(
1415
            ['entryGroup' => ['optgroup' => 'Asset Volume Fields'], '' => '--', 'title' => 'Title'],
1416
            array_merge(
1417
                FieldHelper::fieldsOfTypeFromAssetVolumes(
1418
                    FieldHelper::TEXT_FIELD_CLASS_KEY,
1419
                    false
1420
                )
1421
            )
1422
        );
1423
        $variables['userFieldSources'] = array_merge(
1424
            ['entryGroup' => ['optgroup' => 'User Fields']],
1425
            FieldHelper::fieldsOfTypeFromUsers(
1426
                FieldHelper::TEXT_FIELD_CLASS_KEY,
1427
                false
1428
            )
1429
        );
1430
    }
1431
1432
    /**
1433
     * @param string $sourceBundleType
1434
     * @param string $sourceHandle
1435
     * @param null|int $siteId
1436
     * @param int|string|null $typeId
1437
     *
1438
     * @return string
1439
     */
1440
    protected function uriFromSourceBundle(string $sourceBundleType, string $sourceHandle, $siteId, $typeId): string
1441
    {
1442
        $uri = null;
1443
        // Pick an Element to be used for the preview
1444
        if ($sourceBundleType === MetaBundles::GLOBAL_META_BUNDLE) {
1445
            $uri = MetaBundles::GLOBAL_META_BUNDLE;
1446
        } else {
1447
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
1448
            if ($seoElement !== null) {
1449
                $uri = $seoElement::previewUri($sourceHandle, $siteId, $typeId);
1450
            }
1451
        }
1452
        // Special-case for the __home__ slug, and default to /
1453
        if (($uri === '__home__') || ($uri === null)) {
1454
            $uri = '/';
1455
        }
1456
1457
        return $uri;
1458
    }
1459
1460
    /**
1461
     * Prep the entity settings for saving to the db
1462
     *
1463
     * @param array &$settings
1464
     */
1465
    protected function prepEntitySettings(&$settings)
1466
    {
1467
        DynamicMetaHelper::normalizeTimes($settings['localBusinessOpeningHours']);
1468
        if (!empty($settings['siteType'])) {
1469
            $settings['computedType'] = SchemaHelper::getSpecificEntityType($settings);
1470
        } else {
1471
            $settings['computedType'] = 'WebPage';
1472
        }
1473
        // Empty out the entity image settings to ensure the image gets removed if it no longer exists
1474
        $settings['genericImage'] = '';
1475
        $settings['genericImageWidth'] = '';
1476
        $settings['genericImageHeight'] = '';
1477
        if (!empty($settings['genericImageIds'])) {
1478
            $asset = Craft::$app->getAssets()->getAssetById($settings['genericImageIds'][0]);
1479
            if ($asset !== null) {
1480
                $settings['genericImage'] = $asset->getUrl();
1481
                $settings['genericImageWidth'] = $asset->getWidth();
1482
                $settings['genericImageHeight'] = $asset->getHeight();
1483
            }
1484
        }
1485
    }
1486
}
1487