SettingsController::actionContent()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

844
                $metaBundle->metaSiteVars->/** @scrutinizer ignore-call */ 
845
                                           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...
845
            }
846
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
847
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
848
849
            Seomatic::$plugin->clearAllCaches();
850
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic site settings saved.'));
851
        }
852
853
        return $this->redirectToPostedUrl();
854
    }
855
856
    /**
857
     * Plugin settings
858
     *
859
     * @return Response The rendered result
860
     * @throws ForbiddenHttpException
861
     */
862
    public function actionPlugin(): Response
863
    {
864
        // Ensure they have permission to edit the plugin settings
865
        /** @var User $user */
866
        $user = Craft::$app->getUser();
867
        $currentUser = $user->getIdentity();
868
        if (!$currentUser || !$currentUser->can('seomatic:plugin-settings')) {
869
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
870
        }
871
        $general = Craft::$app->getConfig()->getGeneral();
872
        if (!$general->allowAdminChanges) {
873
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
874
        }
875
        // Edit the plugin settings
876
        $variables = [];
877
        $pluginName = Seomatic::$settings->pluginName;
878
        $templateTitle = Craft::t('seomatic', 'Plugin Settings');
879
        // Asset bundle
880
        try {
881
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
882
        } catch (InvalidConfigException $e) {
883
            Craft::error($e->getMessage(), __METHOD__);
884
        }
885
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
886
            '@nystudio107/seomatic/web/assets/dist',
887
            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

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

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

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

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