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

src/fields/SeoSettings.php (1 issue)

Labels
Severity
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 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
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
    /**
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 */
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 */
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'])) {
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
    /**
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) {
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
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
    /**
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
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 */
358
            $this->setContentFieldSourceVariables($element, 'Entry', $variables);
359
        }
360
361
        /** @var MetaBundle $value */
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
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);
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
    /**
382
     * @inheritdoc
383
     */
384
    public function getTableAttributeHtml($value, ElementInterface $element): string
385
    {
386
        $html = '';
387
        /** @var Element $element */
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);
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
442
     * @param string  $groupName
443
     * @param array   $variables
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