setGlobalFieldSourceVariables()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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

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

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

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

419
                Craft::error(/** @scrutinizer ignore-type */ print_r($metaBundle->metaGlobalVars->getErrors(), true), __METHOD__);
Loading history...
420
                Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save settings due to a Twig error."));
421
                // Send the redirect back to the template
422
                /** @var UrlManager $urlManager */
423
                $urlManager = Craft::$app->getUrlManager();
424
                $urlManager->setRouteParams([
425
                    'editedMetaBundle' => $metaBundle,
426
                ]);
427
428
                return null;
429
            }
430
431
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
432
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
433
434
            Seomatic::$plugin->clearAllCaches();
435
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic global settings saved.'));
436
        }
437
438
        return $this->redirectToPostedUrl();
439
    }
440
441
    /**
442
     * Content settings
443
     *
444
     * @param string|null $siteHandle
445
     *
446
     * @return Response The rendered result
447
     * @throws NotFoundHttpException
448
     * @throws ForbiddenHttpException
449
     */
450
    public function actionContent(string $siteHandle = null): Response
451
    {
452
        $variables = [];
453
        // Get the site to edit
454
        $siteId = $this->getSiteIdFromHandle($siteHandle);
455
456
        $pluginName = Seomatic::$settings->pluginName;
457
        $templateTitle = Craft::t('seomatic', 'Content SEO');
458
        // Asset bundle
459
        try {
460
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
461
        } catch (InvalidConfigException $e) {
462
            Craft::error($e->getMessage(), __METHOD__);
463
        }
464
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
465
            '@nystudio107/seomatic/web/assets/dist',
466
            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

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

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

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

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

696
                $metaBundle->metaSitemapVars->/** @scrutinizer ignore-call */ 
697
                                              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...
697
            }
698
699
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
700
            $metaBundle->typeId = $typeId;
701
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
702
            // If there's also a specific typeId associated with this section, save the same
703
            // metabundle there too, to fix: https://github.com/nystudio107/craft-seomatic/issues/1557
704
            if (!empty($specificTypeId)) {
705
                $metaBundle->typeId = $specificTypeId;
706
                Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
707
            }
708
709
            Seomatic::$plugin->clearAllCaches();
710
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic content settings saved.'));
711
        }
712
713
        return $this->redirectToPostedUrl();
714
    }
715
716
    /**
717
     * Site settings
718
     *
719
     * @param string $subSection
720
     * @param string|null $siteHandle
721
     * @param string|null $loadFromSiteHandle
722
     *
723
     * @return Response The rendered result
724
     * @throws NotFoundHttpException
725
     * @throws ForbiddenHttpException
726
     */
727
    public function actionSite(string $subSection = 'identity', string $siteHandle = null, $loadFromSiteHandle = null): Response
728
    {
729
        $variables = [];
730
        // Get the site to edit
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
        $variables['assetVolumeSources'] = AssetHelper::getAssetInputSources();
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('site');
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);
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);
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
        /** @var User $user */
870
        $user = Craft::$app->getUser();
871
        $currentUser = $user->getIdentity();
872
        if (!$currentUser || !$currentUser->can('seomatic:plugin-settings')) {
873
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
874
        }
875
        $general = Craft::$app->getConfig()->getGeneral();
876
        if (!$general->allowAdminChanges) {
877
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
878
        }
879
        // Edit the plugin settings
880
        $variables = [];
881
        $pluginName = Seomatic::$settings->pluginName;
882
        $templateTitle = Craft::t('seomatic', 'Plugin Settings');
883
        // Asset bundle
884
        try {
885
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
886
        } catch (InvalidConfigException $e) {
887
            Craft::error($e->getMessage(), __METHOD__);
888
        }
889
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
890
            '@nystudio107/seomatic/web/assets/dist',
891
            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

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

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

975
        /** @scrutinizer ignore-call */ 
976
        $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...
976
        );
977
        // Basic variables
978
        $variables['fullPageForm'] = true;
979
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
980
        $variables['pluginName'] = Seomatic::$settings->pluginName;
981
        $variables['title'] = $templateTitle;
982
        $variables['subSectionTitle'] = $subSectionTitle;
983
        $variables['docTitle'] = "{$pluginName} - {$templateTitle} - {$subSectionTitle}";
984
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
985
        $variables['crumbs'] = [
986
            [
987
                'label' => $pluginName,
988
                'url' => UrlHelper::cpUrl('seomatic'),
989
            ],
990
            [
991
                'label' => $templateTitle,
992
                'url' => UrlHelper::cpUrl('seomatic/tracking'),
993
            ],
994
            [
995
                'label' => $subSectionTitle,
996
                'url' => UrlHelper::cpUrl('seomatic/tracking/' . $subSection . $siteHandleUri),
997
            ],
998
        ];
999
        $variables['selectedSubnavItem'] = 'tracking';
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
        /** @var User $user */
1080
        $user = Craft::$app->getUser();
1081
        $currentUser = $user->getIdentity();
1082
        if (!$currentUser || !$currentUser->can('seomatic:plugin-settings')) {
1083
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
1084
        }
1085
        $general = Craft::$app->getConfig()->getGeneral();
1086
        if (!$general->allowAdminChanges) {
1087
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
1088
        }
1089
        // Save the plugin settings
1090
        $this->requirePostRequest();
1091
        $pluginHandle = Craft::$app->getRequest()->getRequiredBodyParam('pluginHandle');
1092
        $settings = Craft::$app->getRequest()->getBodyParam('settings', []);
1093
        $plugin = Craft::$app->getPlugins()->getPlugin($pluginHandle);
1094
1095
        if ($plugin === null) {
1096
            throw new NotFoundHttpException('Plugin not found');
1097
        }
1098
1099
        if (!Craft::$app->getPlugins()->savePluginSettings($plugin, $settings)) {
1100
            Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save plugin settings."));
1101
1102
            // Send the plugin back to the template
1103
            /** @var UrlManager $urlManager */
1104
            $urlManager = Craft::$app->getUrlManager();
1105
            $urlManager->setRouteParams([
1106
                'plugin' => $plugin,
1107
            ]);
1108
1109
            return null;
1110
        }
1111
1112
        Seomatic::$plugin->clearAllCaches();
1113
        Craft::$app->getSession()->setNotice(Craft::t('app', 'Plugin settings saved.'));
1114
1115
        return $this->redirectToPostedUrl();
1116
    }
1117
1118
    // Protected Methods
1119
    // =========================================================================
1120
1121
    /**
1122
     * Return a siteId from a siteHandle
1123
     *
1124
     * @param string|null $siteHandle
1125
     *
1126
     * @return int|null
1127
     * @throws NotFoundHttpException
1128
     */
1129
    protected function getSiteIdFromHandle($siteHandle)
1130
    {
1131
        // Get the site to edit
1132
        if ($siteHandle !== null) {
1133
            $site = Craft::$app->getSites()->getSiteByHandle($siteHandle);
1134
            if (!$site) {
1135
                throw new NotFoundHttpException('Invalid site handle: ' . $siteHandle);
1136
            }
1137
            $siteId = $site->id;
1138
        } else {
1139
            $siteId = Craft::$app->getSites()->currentSite->id;
1140
        }
1141
1142
        return $siteId;
1143
    }
1144
1145
    /**
1146
     * @param string $siteHandle
1147
     * @param        $siteId
1148
     * @param        $variables
1149
     *
1150
     * @throws ForbiddenHttpException
1151
     */
1152
    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

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