Test Failed
Push — v5 ( 567dd3...527085 )
by Andrew
46:05 queued 21:29
created

SeoSettings::getInputHtml()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 62
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
c 0
b 0
f 0
dl 0
loc 62
rs 8.9848
cc 5
nc 16
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\fields;
11
12
use Craft;
13
use craft\base\Element;
14
use craft\base\ElementInterface;
0 ignored issues
show
Bug introduced by
The type craft\base\ElementInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
    public const CACHE_KEY = 'seomatic_fieldmeta_';
52
53
    public 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 dbType(): array|string|null
116
    {
117
        return Schema::TYPE_TEXT;
118
    }
119
120
    /**
121
     * @inheritdoc
122
     */
123
    public static function displayName(): string
124
    {
125
        return Craft::t('seomatic', 'SEO Settings');
126
    }
127
128
    /**
129
     * @inheritdoc
130
     */
131
    public static function icon(): string
132
    {
133
        return '@nystudio107/seomatic/icon-mask.svg';
134
    }
135
136
    /**
137
     * @inheritdoc
138
     */
139
    public static function phpType(): string
140
    {
141
        return sprintf('\\%s', MetaBundle::class);
142
    }
143
144
    // Public Methods
145
    // =========================================================================
146
147
    /**
148
     * @inheritdoc
149
     */
150
    public function rules(): array
151
    {
152
        $rules = parent::rules();
153
        $rules = array_merge($rules, [
154
            [
155
                [
156
                    'elementDisplayPreviewType',
157
                ],
158
                'string',
159
            ],
160
            [
161
                [
162
                    'generalTabEnabled',
163
                    'twitterTabEnabled',
164
                    'facebookTabEnabled',
165
                    'sitemapTabEnabled',
166
                ],
167
                'boolean',
168
            ],
169
            [
170
                [
171
                    'generalEnabledFields',
172
                    'twitterEnabledFields',
173
                    'facebookEnabledFields',
174
                    'sitemapEnabledFields',
175
                ],
176
                'each', 'rule' => ['string'],
177
            ],
178
179
        ]);
180
181
        return $rules;
182
    }
183
184
    /**
185
     * @inheritdoc
186
     * @since 2.0.0
187
     */
188
    public function useFieldset(): bool
189
    {
190
        return false;
191
    }
192
193
    /**
194
     * @inheritdoc
195
     */
196
    public function normalizeValue(mixed $value, ?ElementInterface $element = null): mixed
197
    {
198
        $config = [];
199
        // Handle incoming values potentially being JSON, an array, or an object
200
        if (!empty($value)) {
201
            if (is_string($value)) {
202
                // Decode any html entities
203
                $value = html_entity_decode($value, ENT_NOQUOTES, 'UTF-8');
204
                $config = Json::decodeIfJson($value);
205
            }
206
            if (is_array($value)) {
207
                $config = $value;
208
            }
209
            if (is_object($value) && $value instanceof MetaBundle) {
210
                $config = $value->toArray();
211
            }
212
        } else {
213
            /** @var null|Element $element */
214
            $config = MigrationHelper::configFromSeomaticMeta(
215
                $element,
216
                MigrationHelper::FIELD_MIGRATION_CONTEXT
217
            );
218
        }
219
        // If the config isn't empty, do some processing on the values
220
        if (!empty($config)) {
221
            $elementName = '';
222
            /** @var Element $element */
223
            if ($element !== null) {
224
                $reflector = new ReflectionClass($element);
225
                $elementName = strtolower($reflector->getShortName());
226
            }
227
            // Handle the pull fields
228
            if (!empty($config['metaGlobalVars']) && !empty($config['metaBundleSettings'])) {
229
                PullFieldHelper::parseTextSources(
230
                    $elementName,
231
                    $config['metaGlobalVars'],
232
                    $config['metaBundleSettings']
233
                );
234
                PullFieldHelper::parseImageSources(
235
                    $elementName,
236
                    $config['metaGlobalVars'],
237
                    $config['metaBundleSettings'],
238
                    null
239
                );
240
            }
241
            // Handle the mainEntityOfPage
242
            $mainEntity = '';
243
            if (in_array('mainEntityOfPage', (array)$this->generalEnabledFields, false) &&
244
                !empty($config['metaBundleSettings'])) {
245
                $mainEntity = SchemaHelper::getSpecificEntityType($config['metaBundleSettings'], true);
246
            }
247
            if (!empty($config['metaGlobalVars'])) {
248
                /* @var array $config */
249
                $config['metaGlobalVars']['mainEntityOfPage'] = $mainEntity;
250
            }
251
        }
252
        // Create a new meta bundle with propagated defaults
253
        $metaBundleDefaults = ArrayHelper::merge(
254
            ConfigHelper::getConfigFromFile('fieldmeta/Bundle'),
255
            $config
256
        );
257
258
        return MetaBundle::create($metaBundleDefaults);
259
    }
260
261
    /**
262
     * @inheritdoc
263
     */
264
    public function serializeValue(mixed $value, ?ElementInterface $element = null): mixed
265
    {
266
        $value = parent::serializeValue($value, $element);
267
        if (!Craft::$app->getDb()->getSupportsMb4()) {
268
            if (is_string($value)) {
269
                // Encode any 4-byte UTF-8 characters.
270
                $value = StringHelper::encodeMb4($value);
271
            }
272
            if (is_array($value)) {
273
                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

273
                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...
274
                    if ($arrayValue !== null && is_string($arrayValue)) {
275
                        $arrayValue = StringHelper::encodeMb4($arrayValue);
276
                    }
277
                });
278
            }
279
        }
280
281
        return $value;
282
    }
283
284
    /**
285
     * @inheritdoc
286
     */
287
    public function getSettingsHtml(): ?string
288
    {
289
        $variables = [];
290
        $tagOptions = [
291
            'depends' => [
292
                'nystudio107\\seomatic\\assetbundles\\seomatic\\SeomaticAsset',
293
            ],
294
        ];
295
        // JS/CSS modules
296
        try {
297
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
0 ignored issues
show
Bug introduced by
The method registerAssetBundle() does not exist on null. ( Ignorable by Annotation )

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

297
            Seomatic::$view->/** @scrutinizer ignore-call */ 
298
                             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...
298
            Seomatic::$plugin->vite->register('src/js/seomatic.js', false, $tagOptions, $tagOptions);
299
            Seomatic::$plugin->vite->register('src/js/seomatic-meta.js', false, $tagOptions, $tagOptions);
300
        } catch (InvalidConfigException $e) {
301
            Craft::error($e->getMessage(), __METHOD__);
302
        }
303
        // Asset bundle
304
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
305
            '@nystudio107/seomatic/web/assets/dist',
306
            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

306
        /** @scrutinizer ignore-call */ 
307
        $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...
307
        );
308
        $variables['field'] = $this;
309
310
        // Render the settings template
311
        return Craft::$app->getView()->renderTemplate(
312
            'seomatic/_components/fields/SeoSettings_settings',
313
            $variables
314
        );
315
    }
316
317
    /**
318
     * @inheritdoc
319
     */
320
    public function getInputHtml(mixed $value, ?ElementInterface $element = null): string
321
    {
322
        $variables = [];
323
        // JS/CSS modules
324
        $tagOptions = [
325
            'depends' => [
326
                'nystudio107\\seomatic\\assetbundles\\seomatic\\SeomaticAsset',
327
            ],
328
        ];
329
        // JS/CSS modules
330
        try {
331
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
332
            Seomatic::$plugin->vite->register('src/js/seomatic.js', false, $tagOptions, $tagOptions);
333
            Seomatic::$plugin->vite->register('src/js/seomatic-meta.js', false, $tagOptions, $tagOptions);
334
        } catch (InvalidConfigException $e) {
335
            Craft::error($e->getMessage(), __METHOD__);
336
        }
337
        // Asset bundle
338
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
339
            '@nystudio107/seomatic/web/assets/dist',
340
            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

340
        /** @scrutinizer ignore-call */ 
341
        $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...
341
        );
342
        // Basic variables
343
        $variables['name'] = $this->handle;
344
        $variables['value'] = $value;
345
        $variables['field'] = $this;
346
        $variables['currentSourceBundleType'] = 'entry';
347
        $variables['entitySchemaPath'] = SchemaHelper::getEntityPath($value->metaBundleSettings);
348
349
        // Get our id and namespace
350
        $id = Craft::$app->getView()->formatInputId($this->handle);
351
        $nameSpacedId = Craft::$app->getView()->namespaceInputId($id);
352
        $variables['id'] = $id;
353
        $variables['nameSpacedId'] = $nameSpacedId;
354
355
        // Make sure the *Sources variables at least exist, for things like the QuickPost widget
356
        $variables['textFieldSources'] = [];
357
        $variables['assetFieldSources'] = [];
358
        $variables['assetVolumeTextFieldSources'] = [];
359
        $variables['userFieldSources'] = [];
360
        // Pull field sources
361
        if ($element !== null) {
362
            /** @var Element $element */
363
            $this->setContentFieldSourceVariables($element, 'Entry', $variables);
364
        }
365
366
        /** @var MetaBundle $value */
367
        $variables['elementType'] = Asset::class;
368
        $variables['assetVolumeSources'] = AssetHelper::getAssetInputSources();
369
        $variables['parentBundles'] = [];
370
        // Preview the containers so the preview is correct in the field
371
        if ($element !== null && $element->uri !== null) {
372
            Seomatic::$plugin->metaContainers->previewMetaContainers($element->uri, $element->siteId, true, true, $element);
373
            $contentMeta = Seomatic::$plugin->metaBundles->getContentMetaBundleForElement($element);
374
            $globalMeta = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($element->siteId);
375
            $variables['parentBundles'] = [$contentMeta, $globalMeta];
376
        }
377
378
        // Render the input template
379
        return Craft::$app->getView()->renderTemplate(
380
            'seomatic/_components/fields/SeoSettings_input',
381
            $variables
382
        );
383
    }
384
385
    /**
386
     * @inheritdoc
387
     */
388
    public function getPreviewHtml(mixed $value, ElementInterface $element): string
389
    {
390
        $html = '';
391
        // Reset this each time to avoid caching issues
392
        Seomatic::$previewingMetaContainers = false;
393
        /** @var Element $element */
394
        if ($element !== null && $element->uri !== null) {
395
            $siteId = $element->siteId;
396
            $uri = $element->uri;
397
            $cacheKey = self::CACHE_KEY . $uri . $siteId . $this->elementDisplayPreviewType;
398
            $metaBundleSourceType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
399
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundleSourceType);
400
            $metaBundleSourceType = SeoEntry::getMetaBundleType();
401
            $metaBundleSourceId = '';
402
            if ($seoElement !== null) {
403
                $metaBundleSourceId = $seoElement::sourceIdFromElement($element);
404
            }
405
            $dependency = new TagDependency([
406
                'tags' => [
407
                    MetaContainers::GLOBAL_METACONTAINER_CACHE_TAG,
408
                    MetaContainers::METACONTAINER_CACHE_TAG . $metaBundleSourceId . $metaBundleSourceType . $siteId,
409
                    MetaContainers::METACONTAINER_CACHE_TAG . $uri . $siteId,
410
                    MetaContainers::METACONTAINER_CACHE_TAG . $metaBundleSourceId . $metaBundleSourceType,
411
                ],
412
            ]);
413
            $cache = Craft::$app->getCache();
414
            $cacheDuration = null;
415
            $html = $cache->getOrSet(
416
                self::CACHE_KEY . $cacheKey,
417
                function() use ($uri, $siteId, $element) {
418
                    Seomatic::$plugin->metaContainers->previewMetaContainers($uri, $siteId, true, true, $element);
419
                    $variables = [
420
                        'previewTypes' => [
421
                            $this->elementDisplayPreviewType,
422
                        ],
423
                        'previewElementId' => $element->id,
424
                    ];
425
                    // Render our preview table template
426
                    if (Seomatic::$matchedElement) {
427
                        return Craft::$app->getView()->renderTemplate(
428
                            'seomatic/_includes/table-preview.twig',
429
                            $variables
430
                        );
431
                    }
432
433
                    return '';
434
                },
435
                $cacheDuration,
436
                $dependency
437
            );
438
        }
439
440
        // Render the input template
441
        return $html;
442
    }
443
444
    /**
445
     * @inheritdoc
446
     */
447
    public function previewPlaceholderHtml(mixed $value, ?ElementInterface $element): string
448
    {
449
        if ($element !== null) {
450
            return $this->getPreviewHtml($value, $element);
451
        }
452
453
        return '';
454
    }
455
456
    // Protected Methods
457
    // =========================================================================
458
459
    /**
460
     * @param Element $element
461
     * @param string $groupName
462
     * @param array $variables
463
     */
464
    protected function setContentFieldSourceVariables(
465
        Element $element,
466
        string  $groupName,
467
        array   &$variables,
468
    ) {
469
        $variables['textFieldSources'] = array_merge(
470
            ['entryGroup' => ['optgroup' => $groupName . ' Fields'], 'title' => 'Title'],
471
            FieldHelper::fieldsOfTypeFromElement(
472
                $element,
473
                FieldHelper::TEXT_FIELD_CLASS_KEY,
474
                false
475
            )
476
        );
477
        $variables['assetFieldSources'] = array_merge(
478
            ['entryGroup' => ['optgroup' => $groupName . ' Fields']],
479
            FieldHelper::fieldsOfTypeFromElement(
480
                $element,
481
                FieldHelper::ASSET_FIELD_CLASS_KEY,
482
                false
483
            )
484
        );
485
        $variables['assetVolumeTextFieldSources'] = array_merge(
486
            ['entryGroup' => ['optgroup' => 'Asset Volume Fields'], 'title' => 'Title'],
487
            FieldHelper::fieldsOfTypeFromAssetVolumes(
488
                FieldHelper::TEXT_FIELD_CLASS_KEY,
489
                false
490
            )
491
        );
492
        $variables['userFieldSources'] = array_merge(
493
            ['entryGroup' => ['optgroup' => 'User Fields']],
494
            FieldHelper::fieldsOfTypeFromUsers(
495
                FieldHelper::TEXT_FIELD_CLASS_KEY,
496
                false
497
            )
498
        );
499
    }
500
}
501