SeoSettings::setContentFieldSourceVariables()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 22
nc 1
nop 3
dl 0
loc 33
ccs 0
cts 33
cp 0
crap 2
rs 9.568
c 0
b 0
f 0
1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS 3.x
4
 *
5
 * @link      https://nystudio107.com/
6
 * @copyright Copyright (c) 2017 nystudio107
7
 * @license   https://nystudio107.com/license
8
 */
9
10
namespace nystudio107\seomatic\fields;
11
12
use Craft;
13
use craft\base\Element;
14
use craft\base\ElementInterface;
15
use craft\base\Field;
16
use craft\base\PreviewableFieldInterface;
17
use craft\elements\Asset;
18
use craft\helpers\Json;
19
use craft\helpers\StringHelper;
20
use nystudio107\seomatic\assetbundles\seomatic\SeomaticAsset;
21
use nystudio107\seomatic\helpers\ArrayHelper;
22
use nystudio107\seomatic\helpers\AssetHelper;
23
use nystudio107\seomatic\helpers\Config as ConfigHelper;
24
use nystudio107\seomatic\helpers\Field as FieldHelper;
25
use nystudio107\seomatic\helpers\Migration as MigrationHelper;
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\seoelements\SeoEntry;
30
use nystudio107\seomatic\Seomatic;
31
use nystudio107\seomatic\services\MetaContainers;
32
use ReflectionClass;
33
use yii\base\InvalidConfigException;
34
use yii\caching\TagDependency;
35
use yii\db\Schema;
36
use function in_array;
37
use function is_array;
38
use function is_object;
39
use function is_string;
40
41
/**
42
 * @author    nystudio107
43
 * @package   Seomatic
44
 * @since     3.0.0
45
 */
46
class SeoSettings extends Field implements PreviewableFieldInterface
47
{
48
    // Constants
49
    // =========================================================================
50
51
    const CACHE_KEY = 'seomatic_fieldmeta_';
52
53
    const BUNDLE_COMPARE_FIELDS = [
54
        'metaGlobalVars',
55
    ];
56
57
    // Public Properties
58
    // =========================================================================
59
60
    /**
61
     * @var string
62
     */
63
    public $elementDisplayPreviewType = 'google';
64
65
    /**
66
     * @var bool
67
     */
68
    public $generalTabEnabled = true;
69
70
    /**
71
     * @var array
72
     */
73
    public $generalEnabledFields = [
74
        'seoTitle',
75
        'seoDescription',
76
        'seoImage',
77
    ];
78
79
    /**
80
     * @var bool
81
     */
82
    public $twitterTabEnabled = false;
83
84
    /**
85
     * @var array
86
     */
87
    public $twitterEnabledFields = [];
88
89
    /**
90
     * @var bool
91
     */
92
    public $facebookTabEnabled = false;
93
94
    /**
95
     * @var array
96
     */
97
    public $facebookEnabledFields = [];
98
99
    /**
100
     * @var bool
101
     */
102
    public $sitemapTabEnabled = false;
103
104
    /**
105
     * @var array
106
     */
107
    public $sitemapEnabledFields = [];
108
109
    // Static Methods
110
    // =========================================================================
111
112
    /**
113
     * @inheritdoc
114
     */
115
    public static function displayName(): string
116
    {
117
        return Craft::t('seomatic', 'SEO Settings');
118
    }
119
120
    // Public Methods
121
    // =========================================================================
122
123
    /**
124
     * @inheritdoc
125
     */
126
    public function rules()
127
    {
128
        $rules = parent::rules();
129
        $rules = array_merge($rules, [
130
            [
131
                [
132
                    'elementDisplayPreviewType',
133
                ],
134
                'string',
135
            ],
136
            [
137
                [
138
                    'generalTabEnabled',
139
                    'twitterTabEnabled',
140
                    'facebookTabEnabled',
141
                    'sitemapTabEnabled',
142
                ],
143
                'boolean',
144
            ],
145
            [
146
                [
147
                    'generalEnabledFields',
148
                    'twitterEnabledFields',
149
                    'facebookEnabledFields',
150
                    'sitemapEnabledFields',
151
                ],
152
                'each', 'rule' => ['string'],
153
            ],
154
155
        ]);
156
157
        return $rules;
158
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163
    public function getContentColumnType(): string
164
    {
165
        return Schema::TYPE_TEXT;
166
    }
167
168
    /**
169
     * @inheritdoc
170
     * @since 2.0.0
171
     */
172
    public function useFieldset(): bool
173
    {
174
        return false;
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180
    public function normalizeValue($value, ElementInterface $element = null)
181
    {
182
        $config = [];
183
        // Handle incoming values potentially being JSON, an array, or an object
184
        if (!empty($value)) {
185
            if (is_string($value)) {
186
                // Decode any html entities
187
                $value = html_entity_decode($value, ENT_NOQUOTES, 'UTF-8');
188
                $config = Json::decodeIfJson($value);
189
            }
190
            if (is_array($value)) {
191
                $config = $value;
192
            }
193
            if (is_object($value) && $value instanceof MetaBundle) {
194
                $config = $value->toArray();
195
            }
196
        } else {
197
            /** @var null|Element $element */
198
            $config = MigrationHelper::configFromSeomaticMeta(
199
                $element,
200
                MigrationHelper::FIELD_MIGRATION_CONTEXT
201
            );
202
        }
203
        // If the config isn't empty, do some processing on the values
204
        if (!empty($config)) {
205
            $elementName = '';
206
            /** @var Element $element */
207
            if ($element !== null) {
208
                $reflector = new ReflectionClass($element);
209
                $elementName = strtolower($reflector->getShortName());
210
            }
211
            // Handle the pull fields
212
            if (!empty($config['metaGlobalVars']) && !empty($config['metaBundleSettings'])) {
213
                PullFieldHelper::parseTextSources(
214
                    $elementName,
215
                    $config['metaGlobalVars'],
216
                    $config['metaBundleSettings']
217
                );
218
                PullFieldHelper::parseImageSources(
219
                    $elementName,
220
                    $config['metaGlobalVars'],
221
                    $config['metaBundleSettings'],
222
                    null
223
                );
224
            }
225
            // Handle the mainEntityOfPage
226
            $mainEntity = '';
227
            if (in_array('mainEntityOfPage', $this->generalEnabledFields, false) &&
228
                !empty($config['metaBundleSettings'])) {
229
                $mainEntity = SchemaHelper::getSpecificEntityType($config['metaBundleSettings'], true);
230
            }
231
            if (!empty($config['metaGlobalVars'])) {
232
                $config['metaGlobalVars']['mainEntityOfPage'] = $mainEntity;
233
            }
234
        }
235
        // Create a new meta bundle with propagated defaults
236
        $metaBundleDefaults = ArrayHelper::merge(
237
            ConfigHelper::getConfigFromFile('fieldmeta/Bundle'),
238
            $config
239
        );
240
241
        return MetaBundle::create($metaBundleDefaults);
242
    }
243
244
    /**
245
     * @inheritdoc
246
     */
247
    public function serializeValue($value, ElementInterface $element = null)
248
    {
249
        $value = parent::serializeValue($value, $element);
250
        if (!Craft::$app->getDb()->getSupportsMb4()) {
251
            if (is_string($value)) {
252
                // Encode any 4-byte UTF-8 characters.
253
                $value = StringHelper::encodeMb4($value);
254
            }
255
            if (is_array($value)) {
256
                array_walk_recursive($value, function(&$arrayValue, $arrayKey) {
0 ignored issues
show
Unused Code introduced by
The parameter $arrayKey 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

256
                array_walk_recursive($value, function(&$arrayValue, /** @scrutinizer ignore-unused */ $arrayKey) {

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...
257
                    if ($arrayValue !== null && is_string($arrayValue)) {
258
                        $arrayValue = StringHelper::encodeMb4($arrayValue);
259
                    }
260
                });
261
            }
262
        }
263
264
        return $value;
265
    }
266
267
    /**
268
     * @inheritdoc
269
     */
270
    public function getSettingsHtml()
271
    {
272
        $variables = [];
273
        $tagOptions = [
274
            'depends' => [
275
                'nystudio107\\seomatic\\assetbundles\\seomatic\\SeomaticAsset',
276
            ],
277
        ];
278
        // JS/CSS modules
279
        try {
280
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
281
            Seomatic::$plugin->vite->register('src/js/seomatic.js', false, $tagOptions, $tagOptions);
282
            Seomatic::$plugin->vite->register('src/js/seomatic-meta.js', false, $tagOptions, $tagOptions);
283
        } catch (InvalidConfigException $e) {
284
            Craft::error($e->getMessage(), __METHOD__);
285
        }
286
        // Asset bundle
287
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
288
            '@nystudio107/seomatic/web/assets/dist',
289
            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

289
        /** @scrutinizer ignore-call */ 
290
        $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...
290
        );
291
        $variables['field'] = $this;
292
293
        // Render the settings template
294
        return Craft::$app->getView()->renderTemplate(
295
            'seomatic/_components/fields/SeoSettings_settings',
296
            $variables
297
        );
298
    }
299
300
    /**
301
     * @inheritdoc
302
     */
303
    public function getInputHtml($value, ElementInterface $element = null): string
304
    {
305
        $variables = [];
306
        // JS/CSS modules
307
        $tagOptions = [
308
            'depends' => [
309
                'nystudio107\\seomatic\\assetbundles\\seomatic\\SeomaticAsset',
310
            ],
311
        ];
312
        // JS/CSS modules
313
        try {
314
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
315
            Seomatic::$plugin->vite->register('src/js/seomatic.js', false, $tagOptions, $tagOptions);
316
            Seomatic::$plugin->vite->register('src/js/seomatic-meta.js', false, $tagOptions, $tagOptions);
317
        } catch (InvalidConfigException $e) {
318
            Craft::error($e->getMessage(), __METHOD__);
319
        }
320
        // Asset bundle
321
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
322
            '@nystudio107/seomatic/web/assets/dist',
323
            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

323
        /** @scrutinizer ignore-call */ 
324
        $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...
324
        );
325
        // Basic variables
326
        $variables['name'] = $this->handle;
327
        $variables['value'] = $value;
328
        $variables['field'] = $this;
329
        $variables['currentSourceBundleType'] = 'entry';
330
        $variables['entitySchemaPath'] = SchemaHelper::getEntityPath($value->metaBundleSettings);
331
332
        // Get our id and namespace
333
        $id = Craft::$app->getView()->formatInputId($this->handle);
334
        $nameSpacedId = Craft::$app->getView()->namespaceInputId($id);
335
        $variables['id'] = $id;
336
        $variables['nameSpacedId'] = $nameSpacedId;
337
338
        // Make sure the *Sources variables at least exist, for things like the QuickPost widget
339
        $variables['textFieldSources'] = [];
340
        $variables['assetFieldSources'] = [];
341
        $variables['assetVolumeTextFieldSources'] = [];
342
        $variables['userFieldSources'] = [];
343
        // Pull field sources
344
        if ($element !== null) {
345
            /** @var Element $element */
346
            $this->setContentFieldSourceVariables($element, 'Entry', $variables);
347
        }
348
349
        /** @var MetaBundle $value */
350
        $variables['elementType'] = Asset::class;
351
        $variables['assetVolumeSources'] = AssetHelper::getAssetInputSources();
352
        $variables['parentBundles'] = [];
353
        // Preview the containers so the preview is correct in the field
354
        if ($element !== null && $element->uri !== null) {
355
            Seomatic::$plugin->metaContainers->previewMetaContainers($element->uri, $element->siteId, true, true, $element);
356
            $contentMeta = Seomatic::$plugin->metaBundles->getContentMetaBundleForElement($element);
357
            $globalMeta = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($element->siteId);
0 ignored issues
show
Bug introduced by
It seems like $element->siteId 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

357
            $globalMeta = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $element->siteId);
Loading history...
358
            $variables['parentBundles'] = [$contentMeta, $globalMeta];
359
        }
360
361
        // Render the input template
362
        return Craft::$app->getView()->renderTemplate(
363
            'seomatic/_components/fields/SeoSettings_input',
364
            $variables
365
        );
366
    }
367
368
    /**
369
     * @inheritdoc
370
     */
371
    public function getTableAttributeHtml($value, ElementInterface $element): string
372
    {
373
        $html = '';
374
        // Reset this each time to avoid caching issues
375
        Seomatic::$previewingMetaContainers = false;
376
        /** @var Element $element */
377
        if ($element !== null && $element->uri !== null) {
378
            $siteId = $element->siteId;
379
            $uri = $element->uri;
380
            $cacheKey = self::CACHE_KEY . $uri . $siteId . $this->elementDisplayPreviewType;
381
            $metaBundleSourceType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
382
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundleSourceType);
383
            $metaBundleSourceType = SeoEntry::getMetaBundleType();
384
            $metaBundleSourceId = '';
385
            if ($seoElement !== null) {
386
                $metaBundleSourceId = $seoElement::sourceIdFromElement($element);
387
            }
388
            $dependency = new TagDependency([
389
                'tags' => [
390
                    MetaContainers::GLOBAL_METACONTAINER_CACHE_TAG,
391
                    MetaContainers::METACONTAINER_CACHE_TAG . $metaBundleSourceId . $metaBundleSourceType . $siteId,
392
                    MetaContainers::METACONTAINER_CACHE_TAG . $uri . $siteId,
393
                    MetaContainers::METACONTAINER_CACHE_TAG . $metaBundleSourceId . $metaBundleSourceType,
394
                ],
395
            ]);
396
            $cache = Craft::$app->getCache();
397
            $cacheDuration = null;
398
            $html = $cache->getOrSet(
399
                self::CACHE_KEY . $cacheKey,
400
                function() use ($uri, $siteId, $element) {
401
                    Seomatic::$plugin->metaContainers->previewMetaContainers($uri, $siteId, true, true, $element);
402
                    $variables = [
403
                        'previewTypes' => [
404
                            $this->elementDisplayPreviewType,
405
                        ],
406
                        'previewElementId' => $element->id,
407
                    ];
408
                    // Render our preview table template
409
                    if (Seomatic::$matchedElement) {
410
                        return Craft::$app->getView()->renderTemplate(
411
                            'seomatic/_includes/table-preview.twig',
412
                            $variables
413
                        );
414
                    }
415
416
                    return '';
417
                },
418
                $cacheDuration,
419
                $dependency
420
            );
421
        }
422
423
        // Render the input template
424
        return $html;
425
    }
426
427
    // Protected Methods
428
    // =========================================================================
429
430
    /**
431
     * @param Element $element
432
     * @param string $groupName
433
     * @param array $variables
434
     */
435
    protected function setContentFieldSourceVariables(
436
        Element $element,
437
        string  $groupName,
438
        array   &$variables
439
    ) {
440
        $variables['textFieldSources'] = array_merge(
441
            ['entryGroup' => ['optgroup' => $groupName . ' Fields'], 'title' => 'Title'],
442
            FieldHelper::fieldsOfTypeFromElement(
443
                $element,
444
                FieldHelper::TEXT_FIELD_CLASS_KEY,
445
                false
446
            )
447
        );
448
        $variables['assetFieldSources'] = array_merge(
449
            ['entryGroup' => ['optgroup' => $groupName . ' Fields']],
450
            FieldHelper::fieldsOfTypeFromElement(
451
                $element,
452
                FieldHelper::ASSET_FIELD_CLASS_KEY,
453
                false
454
            )
455
        );
456
        $variables['assetVolumeTextFieldSources'] = array_merge(
457
            ['entryGroup' => ['optgroup' => 'Asset Volume Fields'], 'title' => 'Title'],
458
            FieldHelper::fieldsOfTypeFromAssetVolumes(
459
                FieldHelper::TEXT_FIELD_CLASS_KEY,
460
                false
461
            )
462
        );
463
        $variables['userFieldSources'] = array_merge(
464
            ['entryGroup' => ['optgroup' => 'User Fields']],
465
            FieldHelper::fieldsOfTypeFromUsers(
466
                FieldHelper::TEXT_FIELD_CLASS_KEY,
467
                false
468
            )
469
        );
470
    }
471
}
472