Test Failed
Push — v4 ( 12fad0...ca6617 )
by Andrew
42:57 queued 19:35
created

SeoSettings::getInputHtml()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 62
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 39
c 0
b 0
f 0
dl 0
loc 62
ccs 0
cts 41
cp 0
rs 8.9848
cc 5
nc 16
nop 2
crap 30

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 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(): array
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(): array|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(mixed $value, ?ElementInterface $element = null): mixed
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', (array)$this->generalEnabledFields, false) &&
228
                !empty($config['metaBundleSettings'])) {
229
                $mainEntity = SchemaHelper::getSpecificEntityType($config['metaBundleSettings'], true);
230
            }
231
            if (!empty($config['metaGlobalVars'])) {
232
                /* @var array $config */
233
                $config['metaGlobalVars']['mainEntityOfPage'] = $mainEntity;
234
            }
235
        }
236
        // Create a new meta bundle with propagated defaults
237
        $metaBundleDefaults = ArrayHelper::merge(
238
            ConfigHelper::getConfigFromFile('fieldmeta/Bundle'),
239
            $config
240
        );
241
242
        return MetaBundle::create($metaBundleDefaults);
243
    }
244
245
    /**
246
     * @inheritdoc
247
     */
248
    public function serializeValue(mixed $value, ?ElementInterface $element = null): mixed
249
    {
250
        $value = parent::serializeValue($value, $element);
251
        if (!Craft::$app->getDb()->getSupportsMb4()) {
252
            if (is_string($value)) {
253
                // Encode any 4-byte UTF-8 characters.
254
                $value = StringHelper::encodeMb4($value);
255
            }
256
            if (is_array($value)) {
257
                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

257
                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...
258
                    if ($arrayValue !== null && is_string($arrayValue)) {
259
                        $arrayValue = StringHelper::encodeMb4($arrayValue);
260
                    }
261
                });
262
            }
263
        }
264
265
        return $value;
266
    }
267
268
    /**
269
     * @inheritdoc
270
     */
271
    public function getSettingsHtml(): ?string
272
    {
273
        $variables = [];
274
        $tagOptions = [
275
            'depends' => [
276
                'nystudio107\\seomatic\\assetbundles\\seomatic\\SeomaticAsset',
277
            ],
278
        ];
279
        // JS/CSS modules
280
        try {
281
            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

281
            Seomatic::$view->/** @scrutinizer ignore-call */ 
282
                             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...
282
            Seomatic::$plugin->vite->register('src/js/seomatic.js', false, $tagOptions, $tagOptions);
283
            Seomatic::$plugin->vite->register('src/js/seomatic-meta.js', false, $tagOptions, $tagOptions);
284
        } catch (InvalidConfigException $e) {
285
            Craft::error($e->getMessage(), __METHOD__);
286
        }
287
        // Asset bundle
288
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
289
            '@nystudio107/seomatic/web/assets/dist',
290
            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

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

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