Passed
Push — v3 ( 9618fe...01dae8 )
by Andrew
47:57 queued 20:05
created

SeoSettings::rules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

260
                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...
261
                    if ($arrayValue !== null && \is_string($arrayValue)) {
262
                        $arrayValue = StringHelper::encodeMb4($arrayValue);
263
                    }
264
                });
265
            }
266
        }
267
268
        return $value;
269
    }
270
271
    /**
272
     * @inheritdoc
273
     */
274
    public function getSettingsHtml()
275
    {
276
        $variables = [];
277
        // JS/CSS modules
278
        try {
279
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
280
            Seomatic::$plugin->manifest->registerCssModules([
281
                'styles.css',
282
                'vendors.css',
283
            ]);
284
            Seomatic::$plugin->manifest->registerJsModules([
285
                'runtime.js',
286
                'vendors.js',
287
                'commons.js',
288
                'seomatic.js',
289
                'seomatic-tokens.js',
290
                'seomatic-meta.js',
291
            ]);
292
        } catch (InvalidConfigException $e) {
293
            Craft::error($e->getMessage(), __METHOD__);
294
        }
295
        // Asset bundle
296
        try {
297
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
298
        } catch (InvalidConfigException $e) {
299
            Craft::error($e->getMessage(), __METHOD__);
300
        }
301
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
302
            '@nystudio107/seomatic/assetbundles/seomatic/dist',
303
            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

303
        /** @scrutinizer ignore-call */ 
304
        $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...
304
        );
305
        $variables['field'] = $this;
306
307
        // Render the settings template
308
        return Craft::$app->getView()->renderTemplate(
309
            'seomatic/_components/fields/SeoSettings_settings',
310
            $variables
311
        );
312
    }
313
314
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $element should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $value should have a doc-comment as per coding-style.
Loading history...
315
     * @inheritdoc
316
     */
317
    public function getInputHtml($value, ElementInterface $element = null): string
318
    {
319
        $variables = [];
320
        // JS/CSS modules
321
        try {
322
            Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
323
            Seomatic::$plugin->manifest->registerCssModules([
324
                'styles.css',
325
                'vendors.css',
326
            ]);
327
            Seomatic::$plugin->manifest->registerJsModules([
328
                'runtime.js',
329
                'vendors.js',
330
                'commons.js',
331
                'seomatic.js',
332
                'seomatic-tokens.js',
333
                'seomatic-meta.js',
334
            ]);
335
        } catch (InvalidConfigException $e) {
336
            Craft::error($e->getMessage(), __METHOD__);
337
        }
338
        // Asset bundle
339
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
340
            '@nystudio107/seomatic/assetbundles/seomatic/dist',
341
            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

341
        /** @scrutinizer ignore-call */ 
342
        $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...
342
        );
343
        // Basic variables
344
        $variables['name'] = $this->handle;
345
        $variables['value'] = $value;
346
        $variables['field'] = $this;
347
        $variables['currentSourceBundleType'] = 'entry';
348
        $variables['entitySchemaPath'] = SchemaHelper::getEntityPath($value->metaBundleSettings);
349
350
        // Get our id and namespace
351
        $id = Craft::$app->getView()->formatInputId($this->handle);
352
        $nameSpacedId = Craft::$app->getView()->namespaceInputId($id);
353
        $variables['id'] = $id;
354
        $variables['nameSpacedId'] = $nameSpacedId;
355
        // Pull field sources
356
        if ($element !== null) {
357
            /** @var Element $element */
0 ignored issues
show
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
358
            $this->setContentFieldSourceVariables($element, 'Entry', $variables);
359
        }
360
361
        /** @var MetaBundle $value */
0 ignored issues
show
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
362
        $variables['elementType'] = Asset::class;
363
364
        // Preview the containers so the preview is correct in the field
365
        if ($element !== null && $element->uri !== null) {
366
            Seomatic::$plugin->metaContainers->previewMetaContainers($element->uri, $element->siteId, true);
367
        }
368
369
        $contentMeta = Seomatic::$plugin->metaBundles->getContentMetaBundleForElement($element);
0 ignored issues
show
Bug introduced by
It seems like $element can also be of type null; however, parameter $element of nystudio107\seomatic\ser...tMetaBundleForElement() does only seem to accept craft\base\Element, 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

369
        $contentMeta = Seomatic::$plugin->metaBundles->getContentMetaBundleForElement(/** @scrutinizer ignore-type */ $element);
Loading history...
370
        $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

370
        $globalMeta = Seomatic::$plugin->metaBundles->getGlobalMetaBundle(/** @scrutinizer ignore-type */ $element->siteId);
Loading history...
371
372
        $variables['parentBundles'] = [$contentMeta, $globalMeta];
373
374
        // Render the input template
375
        return Craft::$app->getView()->renderTemplate(
376
            'seomatic/_components/fields/SeoSettings_input',
377
            $variables
378
        );
379
    }
380
381
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $element should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $value should have a doc-comment as per coding-style.
Loading history...
382
     * @inheritdoc
383
     */
384
    public function getTableAttributeHtml($value, ElementInterface $element): string
385
    {
386
        $html = '';
387
        /** @var Element $element */
0 ignored issues
show
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
388
        if ($element !== null && $element->uri !== null) {
389
            $siteId = $element->siteId;
390
            $uri = $element->uri;
391
            $cacheKey = self::CACHE_KEY.$uri.$siteId.$this->elementDisplayPreviewType;
392
            $metaBundleSourceType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
393
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundleSourceType);
0 ignored issues
show
Bug introduced by
It seems like $metaBundleSourceType can also be of type null; however, parameter $metaBundleType of nystudio107\seomatic\ser...ementByMetaBundleType() does only seem to accept 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

393
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType(/** @scrutinizer ignore-type */ $metaBundleSourceType);
Loading history...
394
            $metaBundleSourceType = SeoEntry::getMetaBundleType();
395
            $metaBundleSourceId = '';
396
            if ($seoElement !== null) {
397
                $metaBundleSourceId = $seoElement::sourceIdFromElement($element);
398
            }
399
            $dependency = new TagDependency([
400
                'tags' => [
401
                    MetaContainers::GLOBAL_METACONTAINER_CACHE_TAG,
402
                    MetaContainers::METACONTAINER_CACHE_TAG.$metaBundleSourceId.$metaBundleSourceType.$siteId,
403
                    MetaContainers::METACONTAINER_CACHE_TAG.$uri.$siteId,
404
                ],
405
            ]);
406
            $cache = Craft::$app->getCache();
407
            $cacheDuration = null;
408
            $html = $cache->getOrSet(
409
                self::CACHE_KEY.$cacheKey,
410
                function () use ($uri, $siteId, $element) {
411
                    Seomatic::$plugin->metaContainers->previewMetaContainers($uri, $siteId, true);
412
                    $variables = [
413
                        'previewTypes' => [
414
                            $this->elementDisplayPreviewType ?? '',
415
                        ],
416
                        'previewElementId' => $element->id,
417
                    ];
418
                    // Render our preview table template
419
                    if (Seomatic::$matchedElement) {
420
                        return Craft::$app->getView()->renderTemplate(
421
                            'seomatic/_includes/table-preview.twig',
422
                            $variables
423
                        );
424
                    }
425
426
                    return '';
427
                },
428
                $cacheDuration,
429
                $dependency
430
            );
431
        }
432
433
        // Render the input template
434
        return $html;
435
    }
436
437
    // Protected Methods
438
    // =========================================================================
439
440
    /**
441
     * @param Element $element
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
442
     * @param string  $groupName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
443
     * @param array   $variables
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
444
     */
445
    protected function setContentFieldSourceVariables(
446
        Element $element,
447
        string $groupName,
448
        array &$variables
449
    ) {
450
        $variables['textFieldSources'] = array_merge(
451
            ['entryGroup' => ['optgroup' => $groupName.' Fields'], 'title' => 'Title'],
452
            FieldHelper::fieldsOfTypeFromElement(
453
                $element,
454
                FieldHelper::TEXT_FIELD_CLASS_KEY,
455
                false
456
            )
457
        );
458
        $variables['assetFieldSources'] = array_merge(
459
            ['entryGroup' => ['optgroup' => $groupName.' Fields']],
460
            FieldHelper::fieldsOfTypeFromElement(
461
                $element,
462
                FieldHelper::ASSET_FIELD_CLASS_KEY,
463
                false
464
            )
465
        );
466
        $variables['assetVolumeTextFieldSources'] = array_merge(
467
            ['entryGroup' => ['optgroup' => 'Asset Volume Fields'], 'title' => 'Title'],
468
            FieldHelper::fieldsOfTypeFromAssetVolumes(
469
                FieldHelper::TEXT_FIELD_CLASS_KEY,
470
                false
471
            )
472
        );
473
        $variables['userFieldSources'] = array_merge(
474
            ['entryGroup' => ['optgroup' => 'User Fields']],
475
            FieldHelper::fieldsOfTypeFromUsers(
476
                FieldHelper::TEXT_FIELD_CLASS_KEY,
477
                false
478
            )
479
        );
480
    }
481
}
482