Passed
Push — develop ( 1b8d9e...f02b7a )
by Andrew
24:07 queued 09:20
created

SeoSettings::getInputHtml()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 62
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 38
c 4
b 0
f 0
dl 0
loc 62
ccs 0
cts 46
cp 0
rs 9.0008
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 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\Config as ConfigHelper;
23
use nystudio107\seomatic\helpers\Field as FieldHelper;
24
use nystudio107\seomatic\helpers\Migration as MigrationHelper;
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\seoelements\SeoEntry;
29
use nystudio107\seomatic\Seomatic;
30
use nystudio107\seomatic\services\MetaContainers;
31
use yii\base\InvalidConfigException;
32
use yii\caching\TagDependency;
33
use yii\db\Schema;
34
35
/**
36
 * @author    nystudio107
37
 * @package   Seomatic
38
 * @since     3.0.0
39
 */
40
class SeoSettings extends Field implements PreviewableFieldInterface
41
{
42
    // Constants
43
    // =========================================================================
44
45
    const CACHE_KEY = 'seomatic_fieldmeta_';
46
47
    const BUNDLE_COMPARE_FIELDS = [
48
        'metaGlobalVars',
49
    ];
50
51
    // Public Properties
52
    // =========================================================================
53
54
    /**
55
     * @var string
56
     */
57
    public $elementDisplayPreviewType = 'google';
58
59
    /**
60
     * @var bool
61
     */
62
    public $generalTabEnabled = true;
63
64
    /**
65
     * @var array
66
     */
67
    public $generalEnabledFields = [
68
        'seoTitle',
69
        'seoDescription',
70
        'seoImage',
71
    ];
72
73
    /**
74
     * @var bool
75
     */
76
    public $twitterTabEnabled = false;
77
78
    /**
79
     * @var array
80
     */
81
    public $twitterEnabledFields = [];
82
83
    /**
84
     * @var bool
85
     */
86
    public $facebookTabEnabled = false;
87
88
    /**
89
     * @var array
90
     */
91
    public $facebookEnabledFields = [];
92
93
    /**
94
     * @var bool
95
     */
96
    public $sitemapTabEnabled = false;
97
98
    /**
99
     * @var array
100
     */
101
    public $sitemapEnabledFields = [];
102
103
    // Static Methods
104
    // =========================================================================
105
106
    /**
107
     * @inheritdoc
108
     */
109
    public static function displayName(): string
110
    {
111
        return Craft::t('seomatic', 'SEO Settings');
112
    }
113
114
    // Public Methods
115
    // =========================================================================
116
117
    /**
118
     * @inheritdoc
119
     */
120
    public function rules()
121
    {
122
        $rules = parent::rules();
123
        $rules = array_merge($rules, [
124
            [
125
                [
126
                    'elementDisplayPreviewType',
127
                ],
128
                'string',
129
            ],
130
            [
131
                [
132
                    'generalTabEnabled',
133
                    'twitterTabEnabled',
134
                    'facebookTabEnabled',
135
                    'sitemapTabEnabled',
136
                ],
137
                'boolean',
138
            ],
139
            [
140
                [
141
                    'generalEnabledFields',
142
                    'twitterEnabledFields',
143
                    'facebookEnabledFields',
144
                    'sitemapEnabledFields',
145
                ],
146
                'each', 'rule' => ['string'],
147
            ],
148
149
        ]);
150
151
        return $rules;
152
    }
153
154
    /**
155
     * @inheritdoc
156
     */
157
    public function getContentColumnType(): string
158
    {
159
        return Schema::TYPE_TEXT;
160
    }
161
162
    /**
163
     * @inheritdoc
164
     * @since 2.0.0
165
     */
166
    public function useFieldset(): bool
167
    {
168
        return false;
169
    }
170
171
    /**
172
     * @inheritdoc
173
     */
174
    public function normalizeValue($value, ElementInterface $element = null)
175
    {
176
        $config = [];
177
        // Handle incoming values potentially being JSON, an array, or an object
178
        if (!empty($value)) {
179
            if (\is_string($value)) {
180
                // Decode any html entities
181
                $value = html_entity_decode($value, ENT_NOQUOTES, 'UTF-8');
182
                $config = Json::decodeIfJson($value);
183
            }
184
            if (\is_array($value)) {
185
                $config = $value;
186
            }
187
            if (\is_object($value) && $value instanceof MetaBundle) {
188
                $config = $value->toArray();
189
            }
190
        } else {
191
            /** @var null|Element $element */
192
            $config = MigrationHelper::configFromSeomaticMeta(
193
                $element,
194
                MigrationHelper::FIELD_MIGRATION_CONTEXT
195
            );
196
        }
197
        // If the config isn't empty, do some processing on the values
198
        if (!empty($config)) {
199
            $elementName = '';
200
            /** @var Element $element */
201
            if ($element !== null) {
202
                try {
203
                    $reflector = new \ReflectionClass($element);
204
                } catch (\ReflectionException $e) {
205
                    $reflector = null;
206
                    Craft::error($e->getMessage(), __METHOD__);
207
                }
208
                if ($reflector) {
209
                    $elementName = strtolower($reflector->getShortName());
210
                }
211
            }
212
            // Handle the pull fields
213
            if (!empty($config['metaGlobalVars']) && !empty($config['metaBundleSettings'])) {
214
                PullFieldHelper::parseTextSources(
215
                    $elementName,
216
                    $config['metaGlobalVars'],
217
                    $config['metaBundleSettings']
218
                );
219
                PullFieldHelper::parseImageSources(
220
                    $elementName,
221
                    $config['metaGlobalVars'],
222
                    $config['metaBundleSettings'],
223
                    null
224
                );
225
            }
226
            // Handle the mainEntityOfPage
227
            $mainEntity = '';
228
            if (\in_array('mainEntityOfPage', $this->generalEnabledFields, false) &&
229
                !empty($config['metaBundleSettings'])) {
230
                $mainEntity = SchemaHelper::getSpecificEntityType($config['metaBundleSettings'], true);
231
            }
232
            if (!empty($config['metaGlobalVars'])) {
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($value, ElementInterface $element = null)
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()
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);
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($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
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);
357
            $contentMeta = Seomatic::$plugin->metaBundles->getContentMetaBundleForElement($element);
358
            $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

358
            $globalMeta = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $element->siteId);
Loading history...
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($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
                ],
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);
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
    {
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