Test Failed
Push — v5 ( ba4c33...be19dd )
by Andrew
32:02 queued 20:39
created

SettingsController::getCpSiteHandle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

561
        /** @scrutinizer ignore-call */ 
562
        $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...
562
        );
563
        // Enabled sites
564
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
565
        $this->cullDisabledSites($sourceBundleType, $sourceHandle, $variables);
566
        // Meta Bundle settings
567
        Seomatic::$previewingMetaContainers = true;
568
        // Get the site to copy the settings from, if any
569
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
570
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
571
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
introduced by
The condition $loadFromSiteHandle === null is always false.
Loading history...
572
        // Load the metabundle
573
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
574
            $sourceBundleType,
575
            $sourceHandle,
576
            $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

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

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

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

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

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

835
                    $metaBundle->metaSiteVars->identity->/** @scrutinizer ignore-call */ 
836
                                                         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...
836
                    $siteSettings['identity'] = $metaBundle->metaSiteVars->identity;
837
                }
838
                if (!empty($siteSettings['creator'])) {
839
                    $settings = $siteSettings['creator'];
840
                    $this->prepEntitySettings($settings);
841
                    $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

841
                    $metaBundle->metaSiteVars->creator->/** @scrutinizer ignore-call */ 
842
                                                        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...
842
                    $siteSettings['creator'] = $metaBundle->metaSiteVars->creator;
843
                }
844
                if (!empty($siteSettings['additionalSitemapUrls'])) {
845
                    $siteSettings['additionalSitemapUrlsDateUpdated'] = new DateTime();
846
                    Seomatic::$plugin->sitemaps->submitCustomSitemap($siteId);
847
                }
848
                $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

848
                $metaBundle->metaSiteVars->/** @scrutinizer ignore-call */ 
849
                                           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...
849
            }
850
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
851
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
852
853
            Seomatic::$plugin->clearAllCaches();
854
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic site settings saved.'));
855
        }
856
857
        return $this->redirectToPostedUrl();
858
    }
859
860
    /**
861
     * Plugin settings
862
     *
863
     * @return Response The rendered result
864
     * @throws ForbiddenHttpException
865
     */
866
    public function actionPlugin(): Response
867
    {
868
        // Ensure they have permission to edit the plugin settings
869
        $currentUser = Craft::$app->getUser()->getIdentity();
870
        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

870
        if (!$currentUser->/** @scrutinizer ignore-call */ can('seomatic:plugin-settings')) {
Loading history...
871
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
872
        }
873
        $general = Craft::$app->getConfig()->getGeneral();
874
        if (!$general->allowAdminChanges) {
875
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
876
        }
877
        // Edit the plugin settings
878
        $variables = [];
879
        $pluginName = Seomatic::$settings->pluginName;
880
        $templateTitle = Craft::t('seomatic', 'Plugin Settings');
881
        // Asset bundle
882
        try {
883
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
884
        } catch (InvalidConfigException $e) {
885
            Craft::error($e->getMessage(), __METHOD__);
886
        }
887
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
888
            '@nystudio107/seomatic/web/assets/dist',
889
            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

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

945
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $siteIdToLoad);
Loading history...
946
        if ($editedMetaBundle) {
947
            $metaBundle = $editedMetaBundle;
948
        }
949
        Seomatic::$previewingMetaContainers = false;
950
        if ($metaBundle !== null) {
951
            $variables['scripts'] = Seomatic::$plugin->metaBundles->getContainerDataFromBundle(
952
                $metaBundle,
953
                MetaScriptContainer::CONTAINER_TYPE
954
            );
955
        }
956
        // Add in the variables to the autocomplete cache so they can be accessed across requests
957
        $subSectionSettings = $variables['scripts'][$subSection];
958
        $variables['codeEditorOptions'] = [
959
            TrackingVarsAutocomplete::OPTIONS_DATA_KEY => $subSectionSettings->vars,
960
        ];
961
        // Plugin and section settings
962
        $pluginName = Seomatic::$settings->pluginName;
963
        $templateTitle = Craft::t('seomatic', 'Tracking Scripts');
964
        $subSectionTitle = $variables['scripts'][$subSection]->name;
965
        $subSectionTitle = Craft::t('seomatic', $subSectionTitle);
966
        // Asset bundle
967
        try {
968
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
969
        } catch (InvalidConfigException $e) {
970
            Craft::error($e->getMessage(), __METHOD__);
971
        }
972
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
973
            '@nystudio107/seomatic/web/assets/dist',
974
            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

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

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