Issues (260)

src/controllers/SettingsController.php (27 issues)

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

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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
116
        } catch (InvalidConfigException $e) {
117
            Craft::error($e->getMessage(), __METHOD__);
118
        }
119
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
120
            '@nystudio107/seomatic/web/assets/dist',
121
            true
0 ignored issues
show
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

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

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

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

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

416
                Craft::error(/** @scrutinizer ignore-type */ print_r($metaBundle->metaGlobalVars->getErrors(), true), __METHOD__);
Loading history...
417
                Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save settings due to a Twig error."));
418
                // Send the redirect back to the template
419
                /** @var UrlManager $urlManager */
420
                $urlManager = Craft::$app->getUrlManager();
421
                $urlManager->setRouteParams([
422
                    'editedMetaBundle' => $metaBundle,
423
                ]);
424
425
                return null;
426
            }
427
428
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
429
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
430
431
            Seomatic::$plugin->clearAllCaches();
0 ignored issues
show
The method clearAllCaches() does not exist on null. ( Ignorable by Annotation )

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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
432
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic global settings saved.'));
433
        }
434
435
        return $this->redirectToPostedUrl();
436
    }
437
438
    /**
439
     * Content settings
440
     *
441
     * @param string|null $siteHandle
442
     *
443
     * @return Response The rendered result
444
     * @throws NotFoundHttpException
445
     * @throws ForbiddenHttpException
446
     */
447
    public function actionContent(string $siteHandle = null): Response
448
    {
449
        $variables = [];
450
        // Get the site to edit
451
        $siteId = $this->getSiteIdFromHandle($siteHandle);
452
453
        $pluginName = Seomatic::$settings->pluginName;
454
        $templateTitle = Craft::t('seomatic', 'Content SEO');
455
        // Asset bundle
456
        try {
457
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
458
        } catch (InvalidConfigException $e) {
459
            Craft::error($e->getMessage(), __METHOD__);
460
        }
461
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
462
            '@nystudio107/seomatic/web/assets/dist',
463
            true
0 ignored issues
show
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

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

544
        /** @scrutinizer ignore-call */ 
545
        $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...
545
        );
546
        // Enabled sites
547
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
548
        $this->cullDisabledSites($sourceBundleType, $sourceHandle, $variables);
549
        // Meta Bundle settings
550
        Seomatic::$previewingMetaContainers = true;
551
        // Get the site to copy the settings from, if any
552
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
553
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
554
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
The condition $loadFromSiteHandle === null is always false.
Loading history...
555
        // Load the metabundle
556
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
557
            $sourceBundleType,
558
            $sourceHandle,
559
            $siteIdToLoad,
0 ignored issues
show
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

559
            /** @scrutinizer ignore-type */ $siteIdToLoad,
Loading history...
560
            $typeId
0 ignored issues
show
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

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

676
                $metaBundle->metaSitemapVars->/** @scrutinizer ignore-call */ 
677
                                              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...
677
            }
678
679
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
680
            $metaBundle->typeId = $typeId;
681
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
682
683
            Seomatic::$plugin->clearAllCaches();
684
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic content settings saved.'));
685
        }
686
687
        return $this->redirectToPostedUrl();
688
    }
689
690
    /**
691
     * Site settings
692
     *
693
     * @param string $subSection
694
     * @param string|null $siteHandle
695
     * @param string|null $loadFromSiteHandle
696
     *
697
     * @return Response The rendered result
698
     * @throws NotFoundHttpException
699
     * @throws ForbiddenHttpException
700
     */
701
    public function actionSite(string $subSection = 'identity', string $siteHandle = null, $loadFromSiteHandle = null): Response
702
    {
703
        $variables = [];
704
        // Get the site to edit
705
        $siteId = $this->getSiteIdFromHandle($siteHandle);
706
707
        $pluginName = Seomatic::$settings->pluginName;
708
        $templateTitle = Craft::t('seomatic', 'Site Settings');
709
        $subSectionSuffix = '';
710
        if ($subSection === 'social') {
711
            $subSectionSuffix = ' Media';
712
        }
713
        $subSectionTitle = Craft::t('seomatic', ucfirst($subSection) . $subSectionSuffix);
714
        // Asset bundle
715
        try {
716
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
717
        } catch (InvalidConfigException $e) {
718
            Craft::error($e->getMessage(), __METHOD__);
719
        }
720
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
721
            '@nystudio107/seomatic/web/assets/dist',
722
            true
0 ignored issues
show
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

722
        /** @scrutinizer ignore-call */ 
723
        $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...
723
        );
724
        // Basic variables
725
        $variables['fullPageForm'] = true;
726
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
727
        $variables['pluginName'] = Seomatic::$settings->pluginName;
728
        $variables['title'] = $templateTitle;
729
        $variables['subSectionTitle'] = $subSectionTitle;
730
        $variables['docTitle'] = "{$pluginName} - {$templateTitle} - {$subSectionTitle}";
731
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
732
        $variables['crumbs'] = [
733
            [
734
                'label' => $pluginName,
735
                'url' => UrlHelper::cpUrl('seomatic'),
736
            ],
737
            [
738
                'label' => $templateTitle,
739
                'url' => UrlHelper::cpUrl('seomatic/site/identity' . $siteHandleUri),
740
            ],
741
            [
742
                'label' => $subSectionTitle,
743
                'url' => UrlHelper::cpUrl('seomatic/site/' . $subSection . $siteHandleUri),
744
            ],
745
        ];
746
        $variables['selectedSubnavItem'] = 'site';
747
        $variables['currentSubSection'] = $subSection;
748
749
        // Enabled sites
750
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
751
        $variables['controllerHandle'] = 'site' . '/' . $subSection;
752
753
        // The site settings for the appropriate meta bundle
754
        Seomatic::$previewingMetaContainers = true;
755
        // Get the site to copy the settings from, if any
756
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
757
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
758
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
The condition $loadFromSiteHandle === null is always false.
Loading history...
759
        // Load the metabundle
760
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteIdToLoad);
0 ignored issues
show
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

760
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $siteIdToLoad);
Loading history...
761
        Seomatic::$previewingMetaContainers = false;
762
        if ($metaBundle !== null) {
763
            $variables['site'] = $metaBundle->metaSiteVars;
764
            $variables['identityImageElements'] = ImageTransformHelper::assetElementsFromIds(
765
                $variables['site']->identity->genericImageIds,
766
                $siteId
767
            );
768
            $variables['creatorImageElements'] = ImageTransformHelper::assetElementsFromIds(
769
                $variables['site']->creator->genericImageIds,
770
                $siteId
771
            );
772
        }
773
        $variables['elementType'] = Asset::class;
774
775
        // Render the template
776
        return $this->renderTemplate('seomatic/settings/site/' . $subSection, $variables);
777
    }
778
779
    /**
780
     * @return Response
781
     * @throws BadRequestHttpException
782
     * @throws MissingComponentException
783
     */
784
    public function actionSaveSite(): Response
785
    {
786
        $this->requirePostRequest();
787
        $request = Craft::$app->getRequest();
788
        $siteId = $request->getParam('siteId');
789
        $siteSettings = $request->getParam('seomaticSite');
790
791
        // Make sure the twitter handle isn't prefixed with an @
792
        if (!empty($siteSettings['twitterHandle'])) {
793
            $siteSettings['twitterHandle'] = ltrim($siteSettings['twitterHandle'], '@');
794
        }
795
        // Make sure the sameAsLinks are indexed by the handle
796
        if (!empty($siteSettings['sameAsLinks'])) {
797
            $siteSettings['sameAsLinks'] = ArrayHelper::index($siteSettings['sameAsLinks'], 'handle');
798
        }
799
        // The site settings for the appropriate meta bundle
800
        Seomatic::$previewingMetaContainers = true;
801
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteId);
802
        Seomatic::$previewingMetaContainers = false;
803
        if ($metaBundle) {
804
            if (is_array($siteSettings)) {
805
                if (!empty($siteSettings['identity'])) {
806
                    $settings = $siteSettings['identity'];
807
                    $this->prepEntitySettings($settings);
808
                    $metaBundle->metaSiteVars->identity->setAttributes($settings);
0 ignored issues
show
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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
809
                    $siteSettings['identity'] = $metaBundle->metaSiteVars->identity;
810
                }
811
                if (!empty($siteSettings['creator'])) {
812
                    $settings = $siteSettings['creator'];
813
                    $this->prepEntitySettings($settings);
814
                    $metaBundle->metaSiteVars->creator->setAttributes($settings);
0 ignored issues
show
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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
815
                    $siteSettings['creator'] = $metaBundle->metaSiteVars->creator;
816
                }
817
                if (!empty($siteSettings['additionalSitemapUrls'])) {
818
                    $siteSettings['additionalSitemapUrlsDateUpdated'] = new DateTime();
819
                    Seomatic::$plugin->sitemaps->submitCustomSitemap($siteId);
820
                }
821
                $metaBundle->metaSiteVars->setAttributes($siteSettings);
0 ignored issues
show
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

821
                $metaBundle->metaSiteVars->/** @scrutinizer ignore-call */ 
822
                                           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...
822
            }
823
            Seomatic::$plugin->metaBundles->syncBundleWithConfig($metaBundle, true);
824
            Seomatic::$plugin->metaBundles->updateMetaBundle($metaBundle, $siteId);
825
826
            Seomatic::$plugin->clearAllCaches();
827
            Craft::$app->getSession()->setNotice(Craft::t('seomatic', 'SEOmatic site settings saved.'));
828
        }
829
830
        return $this->redirectToPostedUrl();
831
    }
832
833
    /**
834
     * Plugin settings
835
     *
836
     * @return Response The rendered result
837
     * @throws ForbiddenHttpException
838
     */
839
    public function actionPlugin(): Response
840
    {
841
        // Ensure they have permission to edit the plugin settings
842
        $currentUser = Craft::$app->getUser()->getIdentity();
843
        if (!$currentUser->can('seomatic:plugin-settings')) {
0 ignored issues
show
The method can() does not exist on yii\web\IdentityInterface. It seems like you code against a sub-type of yii\web\IdentityInterface such as craft\elements\User. ( Ignorable by Annotation )

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

843
        if (!$currentUser->/** @scrutinizer ignore-call */ can('seomatic:plugin-settings')) {
Loading history...
844
            throw new ForbiddenHttpException('You do not have permission to edit SEOmatic plugin settings.');
845
        }
846
        $general = Craft::$app->getConfig()->getGeneral();
847
        if (!$general->allowAdminChanges) {
848
            throw new ForbiddenHttpException('Unable to edit SEOmatic plugin settings because admin changes are disabled in this environment.');
849
        }
850
        // Edit the plugin settings
851
        $variables = [];
852
        $pluginName = Seomatic::$settings->pluginName;
853
        $templateTitle = Craft::t('seomatic', 'Plugin Settings');
854
        // Asset bundle
855
        try {
856
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
857
        } catch (InvalidConfigException $e) {
858
            Craft::error($e->getMessage(), __METHOD__);
859
        }
860
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
861
            '@nystudio107/seomatic/web/assets/dist',
862
            true
0 ignored issues
show
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

862
        /** @scrutinizer ignore-call */ 
863
        $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...
863
        );
864
        // Basic variables
865
        $variables['fullPageForm'] = true;
866
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
867
        $variables['pluginName'] = Seomatic::$settings->pluginName;
868
        $variables['title'] = $templateTitle;
869
        $variables['docTitle'] = "{$pluginName} - {$templateTitle}";
870
        $variables['crumbs'] = [
871
            [
872
                'label' => $pluginName,
873
                'url' => UrlHelper::cpUrl('seomatic'),
874
            ],
875
            [
876
                'label' => $templateTitle,
877
                'url' => UrlHelper::cpUrl('seomatic/plugin'),
878
            ],
879
        ];
880
        $variables['selectedSubnavItem'] = 'plugin';
881
        $variables['settings'] = Seomatic::$settings;
882
883
        // Render the template
884
        return $this->renderTemplate('seomatic/settings/plugin/_edit', $variables);
885
    }
886
887
    /**
888
     * Tracking settings
889
     *
890
     * @param string $subSection
891
     * @param string|null $siteHandle
892
     * @param string|null $loadFromSiteHandle
893
     *
894
     * @return Response The rendered result
895
     * @throws NotFoundHttpException
896
     * @throws ForbiddenHttpException
897
     */
898
    public function actionTracking(string $subSection = 'gtag', string $siteHandle = null, $loadFromSiteHandle = null, $editedMetaBundle = null): Response
899
    {
900
        $variables = [];
901
        // Get the site to edit
902
        $siteId = $this->getSiteIdFromHandle($siteHandle);
903
        // Enabled sites
904
        $this->setMultiSiteVariables($siteHandle, $siteId, $variables);
905
        $variables['controllerHandle'] = 'tracking' . '/' . $subSection;
906
        $variables['currentSubSection'] = $subSection;
907
908
        // The script meta containers for the global meta bundle
909
        Seomatic::$previewingMetaContainers = true;
910
        // Get the site to copy the settings from, if any
911
        $variables['loadFromSiteHandle'] = $loadFromSiteHandle;
912
        $loadFromSiteId = $this->getSiteIdFromHandle($loadFromSiteHandle);
913
        $siteIdToLoad = $loadFromSiteHandle === null ? (int)$variables['currentSiteId'] : $loadFromSiteId;
0 ignored issues
show
The condition $loadFromSiteHandle === null is always false.
Loading history...
914
        // Load the metabundle
915
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteIdToLoad);
0 ignored issues
show
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

915
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $siteIdToLoad);
Loading history...
916
        if ($editedMetaBundle) {
917
            $metaBundle = $editedMetaBundle;
918
        }
919
        Seomatic::$previewingMetaContainers = false;
920
        if ($metaBundle !== null) {
921
            $variables['scripts'] = Seomatic::$plugin->metaBundles->getContainerDataFromBundle(
922
                $metaBundle,
923
                MetaScriptContainer::CONTAINER_TYPE
924
            );
925
        }
926
        // Add in the variables to the autocomplete cache so they can be accessed across requests
927
        $subSectionSettings = $variables['scripts'][$subSection];
928
        $variables['codeEditorOptions'] = [
929
            TrackingVarsAutocomplete::OPTIONS_DATA_KEY => $subSectionSettings->vars,
930
        ];
931
        // Plugin and section settings
932
        $pluginName = Seomatic::$settings->pluginName;
933
        $templateTitle = Craft::t('seomatic', 'Tracking Scripts');
934
        $subSectionTitle = $variables['scripts'][$subSection]->name;
935
        $subSectionTitle = Craft::t('seomatic', $subSectionTitle);
936
        // Asset bundle
937
        try {
938
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
939
        } catch (InvalidConfigException $e) {
940
            Craft::error($e->getMessage(), __METHOD__);
941
        }
942
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
943
            '@nystudio107/seomatic/web/assets/dist',
944
            true
0 ignored issues
show
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

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

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