Completed
Push — master ( 470ea9...6263bb )
by Nate
05:06 queued 03:24
created

Meta::setTemplate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 8
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/meta/license
6
 * @link       https://www.flipboxfactory.com/software/meta/
7
 */
8
9
namespace flipbox\meta\fields;
10
11
use Craft;
12
use craft\base\EagerLoadingFieldInterface;
13
use craft\base\Element;
14
use craft\base\ElementInterface;
15
use craft\base\Field;
16
use craft\base\FieldInterface;
17
use craft\behaviors\FieldLayoutBehavior;
18
use craft\db\Query;
19
use craft\elements\db\ElementQuery;
20
use craft\elements\db\ElementQueryInterface;
21
use craft\fields\Matrix;
22
use craft\fields\MissingField;
23
use craft\fields\PlainText;
24
use craft\helpers\Json;
25
use craft\helpers\StringHelper;
26
use craft\models\FieldLayout;
27
use craft\validators\ArrayValidator;
28
use flipbox\meta\elements\db\Meta as MetaQuery;
29
use flipbox\meta\elements\Meta as MetaElement;
30
use flipbox\meta\helpers\Field as FieldHelper;
31
use flipbox\meta\Meta as MetaPlugin;
32
use flipbox\meta\records\Meta as MetaRecord;
33
use flipbox\meta\web\assets\input\Input as MetaInputAsset;
34
use flipbox\meta\web\assets\settings\Settings as MetaSettingsAsset;
35
36
/**
37
 * @author Flipbox Factory <[email protected]>
38
 * @since 1.0.0
39
 *
40
 * @method setFieldLayout(FieldLayout $fieldLayout)
41
 * @method FieldLayout getFieldLayout()
42
 */
43
class Meta extends Field implements EagerLoadingFieldInterface
44
{
45
46
    const DEFAULT_TEMPLATE = FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'layout';
47
48
    /**
49
     * @var int|null
50
     */
51
    public $max;
52
53
    /**
54
     * @var int|null
55
     */
56
    public $min;
57
58
    /**
59
     * @var string
60
     */
61
    public $selectionLabel = "Add meta";
62
63
    /**
64
     * @var int
65
     */
66
    public $localize = false;
67
68
    /**
69
     * @var int|null
70
     */
71
    public $fieldLayoutId;
72
73
    /**
74
     * @var bool
75
     */
76
    public $templateOverride = false;
77
78
    /**
79
     * @var string
80
     */
81
    protected $template = self::DEFAULT_TEMPLATE;
82
83
    /**
84
     * @var bool
85
     */
86
    public $hasFieldErrors = false;
87
88
    /**
89
     * @inheritdoc
90
     */
91
    public static function displayName(): string
92
    {
93
        return Craft::t('meta', 'Meta');
94
    }
95
96
    /**
97
     * @inheritdoc
98
     */
99
    public static function supportedTranslationMethods(): array
100
    {
101
        // Don't ever automatically propagate values to other sites.
102
        return [
103
            self::TRANSLATION_METHOD_SITE,
104
        ];
105
    }
106
107
    /**
108
     * @inheritdoc
109
     */
110
    public static function hasContentColumn(): bool
111
    {
112
        return false;
113
    }
114
115
    /**
116
     * @inheritdoc
117
     */
118
    public function settingsAttributes(): array
119
    {
120
        return [
121
            'max',
122
            'min',
123
            'selectionLabel',
124
            'fieldLayoutId',
125
            'templateOverride',
126
            'template'
127
        ];
128
    }
129
130
    /**
131
     * @inheritdoc
132
     */
133
    public function behaviors()
134
    {
135
        return [
136
            'fieldLayout' => [
137
                'class' => FieldLayoutBehavior::class,
138
                'elementType' => self::class
139
            ],
140
        ];
141
    }
142
143
    /**
144
     * @inheritdoc
145
     */
146
    public function rules()
147
    {
148
        return array_merge(
149
            parent::rules(),
150
            [
151
                [
152
                    [
153
                        'min',
154
                        'max'
155
                    ],
156
                    'integer',
157
                    'min' => 0
158
                ]
159
            ]
160
        );
161
    }
162
163
    /**
164
     * @inheritdoc
165
     */
166
    public function validate($attributeNames = null, $clearErrors = true): bool
167
    {
168
        // Run basic model validation first
169
        $validates = parent::validate($attributeNames, $clearErrors);
170
171
        // Run field validation as well
172
        if (!MetaPlugin::getInstance()->getConfiguration()->validate($this)) {
173
            $validates = false;
174
        }
175
176
        return $validates;
177
    }
178
179
    /**
180
     * @inheritdoc
181
     */
182
    public function getSettingsHtml()
183
    {
184
        // Get the available field types data
185
        $fieldTypeInfo = $this->getFieldOptionsForConfiguration();
186
187
        $view = Craft::$app->getView();
188
189
        $view->registerAssetBundle(MetaSettingsAsset::class);
190
        $view->registerJs(
191
            'new Craft.MetaConfiguration(' .
192
            Json::encode($fieldTypeInfo, JSON_UNESCAPED_UNICODE) . ', ' .
193
            Json::encode(Craft::$app->getView()->getNamespace(), JSON_UNESCAPED_UNICODE) .
194
            ');'
195
        );
196
197
        $view->registerTranslations('meta', [
198
            'New field'
199
        ]);
200
201
        $fieldTypeOptions = [];
202
203
        /** @var Field|string $class */
204
        foreach (Craft::$app->getFields()->getAllFieldTypes() as $class) {
205
            $fieldTypeOptions[] = [
206
                'value' => $class,
207
                'label' => $class::displayName()
208
            ];
209
        }
210
211
        // Handle missing fields
212
        $fields = $this->getFields();
213
        foreach ($fields as $i => $field) {
214
            if ($field instanceof MissingField) {
215
                $fields[$i] = $field->createFallback(PlainText::class);
216
                $fields[$i]->addError('type', Craft::t('app', 'The field type “{type}” could not be found.', [
217
                    'type' => $field->expectedType
218
                ]));
219
                $this->hasFieldErrors = true;
220
            }
221
        }
222
        $this->setFields($fields);
0 ignored issues
show
Documentation introduced by
$fields is of type array<integer,object<cra...se\ComponentInterface>>, but the function expects a array<integer,object<craft\base\FieldInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
223
224
        return Craft::$app->getView()->renderTemplate(
225
            FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'settings',
226
            [
227
                'field' => $this,
228
                'fieldTypes' => $fieldTypeOptions
229
            ]
230
        );
231
    }
232
233
    /**
234
     * @inheritdoc
235
     */
236
    public function normalizeValue($value, ElementInterface $element = null)
237
    {
238
        /** @var Element|null $element */
239
240
        if ($value instanceof MetaQuery) {
241
            return $value;
242
        }
243
244
        // New element query
245
        $query = MetaElement::find();
246
247
        // Existing element?
248
        if ($element && $element->id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $element->id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
249
            $query->ownerId($element->id);
250
        } else {
251
            $query->id(false);
252
        }
253
254
        // Set our field and site to the query
255
        $query
256
            ->fieldId($this->id)
257
            ->siteId($element->siteId);
258
259
        // Set the initially matched elements if $value is already set, which is the case if there was a validation
260
        // error or we're loading an entry revision.
261
        if (is_array($value) || $value === '') {
262
            $query->status = null;
263
            $query->enabledForSite = false;
264
            $query->limit = null;
265
            $query->setCachedResult(
266
                $this->createElementsFromSerializedData($value, $element)
267
            );
268
        }
269
270
        return $query;
271
    }
272
273
    /**
274
     * @inheritdoc
275
     */
276
    public function modifyElementsQuery(ElementQueryInterface $query, $value)
277
    {
278
        /** @var ElementQuery $query */
279
        if ($value === 'not :empty:') {
280
            $value = ':notempty:';
281
        }
282
283
        if ($value === ':notempty:' || $value === ':empty:') {
284
            $alias = MetaRecord::tableAlias() . '_' . $this->handle;
285
            $operator = ($value === ':notempty:' ? '!=' : '=');
286
287
            $query->subQuery->andWhere(
288
                "(select count([[{$alias}.id]]) from " .
289
                MetaRecord::tableName() .
290
                " {{{$alias}}} where [[{$alias}.ownerId]] = [[elements.id]]" .
291
                " and [[{$alias}.fieldId]] = :fieldId) {$operator} 0",
292
                [':fieldId' => $this->id]
293
            );
294
        } elseif ($value !== null) {
295
            return false;
296
        }
297
298
        return null;
299
    }
300
301
    /**
302
     * @return string
303
     */
304
    public function getDefaultTemplate(): string
305
    {
306
        return $this->templateOverride ? $this->template : self::DEFAULT_TEMPLATE;
307
    }
308
309
    /**
310
     * @return string
311
     */
312
    public function getTemplate(): string
313
    {
314
        return $this->templateOverride ? $this->template : self::DEFAULT_TEMPLATE;
315
    }
316
317
    /**
318
     * @param $template
319
     * @return $this
320
     */
321
    public function setTemplate($template)
322
    {
323
        if (!$this->templateOverride) {
324
            $template = null;
325
        }
326
        $this->template = $template;
327
        return $this;
328
    }
329
330
    /**
331
     * @inheritdoc
332
     */
333
    public function getInputHtml($value, ElementInterface $element = null): string
334
    {
335
        $id = Craft::$app->getView()->formatInputId($this->handle);
336
337
        // Get the field data
338
        $fieldInfo = $this->getFieldInfoForInput();
339
340
        Craft::$app->getView()->registerAssetBundle(MetaInputAsset::class);
341
342
        Craft::$app->getView()->registerJs(
343
            'new Craft.MetaInput(' .
344
            '"' . Craft::$app->getView()->namespaceInputId($id) . '", ' .
345
            Json::encode($fieldInfo, JSON_UNESCAPED_UNICODE) . ', ' .
346
            '"' . Craft::$app->getView()->namespaceInputName($this->handle) . '", ' .
347
            ($this->min ?: 'null') . ', ' .
348
            ($this->max ?: 'null') .
349
            ');'
350
        );
351
352
        Craft::$app->getView()->registerTranslations('meta', [
353
            'Add new',
354
            'Add new above'
355
        ]);
356
357
        if ($value instanceof MetaQuery) {
358
            $value
359
                ->limit(null)
360
                ->status(null)
361
                ->enabledForSite(false);
362
        }
363
364
        return Craft::$app->getView()->renderTemplate(
365
            FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'input',
366
            [
367
                'id' => $id,
368
                'name' => $this->handle,
369
                'field' => $this,
370
                'elements' => $value,
371
                'static' => false,
372
                'template' => self::DEFAULT_TEMPLATE
373
            ]
374
        );
375
    }
376
377
    /**
378
     * @inheritdoc
379
     */
380
    public function getElementValidationRules(): array
381
    {
382
        return [
383
            'validateMeta',
384
            [
385
                ArrayValidator::class,
386
                'min' => $this->required ? ($this->min ?: 1) : null,
387
                'max' => $this->max ?: null,
388
                'tooFew' => Craft::t(
389
                    'app',
390
                    '{attribute} should contain at least {min, number} {min, plural, one{record} other{records}}.'
391
                ),
392
                'tooMany' => Craft::t(
393
                    'app',
394
                    '{attribute} should contain at most {max, number} {max, plural, one{record} other{records}}.'
395
                ),
396
            ],
397
        ];
398
    }
399
400
    /**
401
     * Validates an owner element’s Meta.
402
     *
403
     * @param ElementInterface $element
404
     *
405
     * @return void
406
     */
407
    public function validateMeta(ElementInterface $element)
408
    {
409
        /** @var Element $element */
410
        /** @var MetaQuery $value */
411
        $value = $element->getFieldValue($this->handle);
412
        $validate = true;
413
414
        foreach ($value as $meta) {
415
            /** @var MetaElement $meta */
416
            if (!$meta->validate()) {
417
                $validate = false;
418
            }
419
        }
420
421
        if (!$validate) {
422
            $element->addError($this->handle, Craft::t('app', 'Correct the errors listed above.'));
423
        }
424
    }
425
426
    /**
427
     * @param mixed $value
428
     * @param Element|ElementInterface $element
429
     * @return string
430
     */
431
    public function getSearchKeywords($value, ElementInterface $element): string
432
    {
433
        /** @var MetaQuery $value */
434
435
        $keywords = [];
436
        $contentService = Craft::$app->getContent();
437
438
        /** @var MetaElement $meta */
439
        foreach ($value as $meta) {
440
            $originalContentTable = $contentService->contentTable;
441
            $originalFieldContext = $contentService->fieldContext;
442
443
            $contentService->contentTable = $meta->getContentTable();
444
            $contentService->fieldContext = $meta->getFieldContext();
445
446
            /** @var Field $field */
447
            foreach (Craft::$app->getFields()->getAllFields() as $field) {
448
                $fieldValue = $meta->getFieldValue($field->handle);
449
                $keywords[] = $field->getSearchKeywords($fieldValue, $element);
450
            }
451
452
            $contentService->contentTable = $originalContentTable;
453
            $contentService->fieldContext = $originalFieldContext;
454
        }
455
456
        return parent::getSearchKeywords($keywords, $element);
457
    }
458
459
    /**
460
     * todo - review this
461
     *
462
     * @inheritdoc
463
     */
464
    public function getStaticHtml($value, ElementInterface $element): string
465
    {
466
        if ($value) {
467
            $id = StringHelper::randomString();
468
            return Craft::$app->getView()->renderTemplate(
469
                FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'input',
470
                [
471
                    'id' => $id,
472
                    'name' => $this->handle,
473
                    'elements' => $value,
474
                    'static' => true
475
                ]
476
            );
477
        } else {
478
            Craft::$app->getView()->registerTranslations('meta', [
479
                'No meta'
480
            ]);
481
482
            return '<p class="light">' . Craft::t('meta', 'No meta') . '</p>';
483
        }
484
    }
485
486
    /**
487
     * @inheritdoc
488
     */
489
    public function getEagerLoadingMap(array $sourceElements)
490
    {
491
        // Get the source element IDs
492
        $sourceElementIds = [];
493
494
        foreach ($sourceElements as $sourceElement) {
495
            $sourceElementIds[] = $sourceElement->id;
496
        }
497
498
        // Return any relation data on these elements, defined with this field
499
        $map = (new Query())
500
            ->select(['ownerId as source', 'id as target'])
501
            ->from([MetaRecord::tableName()])
502
            ->where([
503
                'fieldId' => $this->id,
504
                'ownerId' => $sourceElementIds,
505
            ])
506
            ->orderBy(['sortOrder' => SORT_ASC])
507
            ->all();
508
509
        return [
510
            'elementType' => MetaElement::class,
511
            'map' => $map,
512
            'criteria' => ['fieldId' => $this->id]
513
        ];
514
    }
515
516
    /**
517
     * @inheritdoc
518
     */
519
    public function beforeSave(bool $isNew): bool
520
    {
521
        // Save field settings (and field content)
522
        if (!MetaPlugin::getInstance()->getConfiguration()->beforeSave($this)) {
523
            return false;
524
        }
525
526
        // Trigger an 'afterSave' event
527
        return parent::beforeSave($isNew);
528
    }
529
530
    /**
531
     * @inheritdoc
532
     */
533
    public function afterSave(bool $isNew)
534
    {
535
        // Save field settings (and field content)
536
        MetaPlugin::getInstance()->getConfiguration()->afterSave($this);
537
538
        // Trigger an 'afterSave' event
539
        parent::afterSave($isNew);
540
    }
541
542
    /**
543
     * @inheritdoc
544
     */
545
    public function beforeDelete(): bool
546
    {
547
        // Delete field content table
548
        MetaPlugin::getInstance()->getConfiguration()->beforeDelete($this);
549
550
        // Trigger a 'beforeDelete' event
551
        return parent::beforeDelete();
552
    }
553
554
    /**
555
     * @inheritdoc
556
     */
557
    public function afterElementSave(ElementInterface $element, bool $isNew)
558
    {
559
        // Save meta element
560
        MetaPlugin::getInstance()->getField()->afterElementSave($this, $element);
561
562
        // Trigger an 'afterElementSave' event
563
        parent::afterElementSave($element, $isNew);
564
    }
565
566
    /**
567
     * @inheritdoc
568
     */
569
    public function beforeElementDelete(ElementInterface $element): bool
570
    {
571
        // Delete meta elements
572
        if (!MetaPlugin::getInstance()->getField()->beforeElementDelete($this, $element)) {
573
            return false;
574
        }
575
576
        return parent::beforeElementDelete($element);
577
    }
578
579
    /**
580
     * @inheritdoc
581
     */
582
    protected function isValueEmpty($value, ElementInterface $element): bool
0 ignored issues
show
Unused Code introduced by
The parameter $element is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
583
    {
584
        /** @var MetaQuery $value */
585
        return $value->count() === 0;
586
    }
587
588
    /**
589
     *
590
     * Returns info about each field type for the configurator.
591
     *
592
     * @return array
593
     */
594
    private function getFieldOptionsForConfiguration()
595
    {
596
        $disallowedFields = [
597
            self::class,
598
            Matrix::class
599
        ];
600
601
        $fieldTypes = [];
602
603
        // Set a temporary namespace for these
604
        $originalNamespace = Craft::$app->getView()->getNamespace();
605
        $namespace = Craft::$app->getView()->namespaceInputName('fields[__META_FIELD__][settings]', $originalNamespace);
606
        Craft::$app->getView()->setNamespace($namespace);
607
608
        /** @var Field|string $class */
609
        foreach (Craft::$app->getFields()->getAllFieldTypes() as $class) {
610
611
            // Ignore disallowed fields
612
            if (in_array($class, $disallowedFields)) {
613
                continue;
614
            }
615
616
            Craft::$app->getView()->startJsBuffer();
617
618
            /** @var FieldInterface $field */
619
            $field = new $class();
620
621
            if ($settingsHtml = (string)$field->getSettingsHtml()) {
622
                $settingsHtml = Craft::$app->getView()->namespaceInputs($settingsHtml);
623
            }
624
625
            $settingsBodyHtml = $settingsHtml;
626
            $settingsFootHtml = Craft::$app->getView()->clearJsBuffer();
627
628
            $fieldTypes[] = [
629
                'type' => $class,
630
                'name' => $class::displayName(),
631
                'settingsBodyHtml' => $settingsBodyHtml,
632
                'settingsFootHtml' => $settingsFootHtml,
633
            ];
634
635
        }
636
637
        Craft::$app->getView()->setNamespace($originalNamespace);
638
639
        return $fieldTypes;
640
    }
641
642
    /**
643
     * Returns html for all associated field types for the Meta field input.
644
     *
645
     * @return array
646
     */
647
    private function getFieldInfoForInput(): array
648
    {
649
        // Set a temporary namespace for these
650
        $originalNamespace = Craft::$app->getView()->getNamespace();
651
        $namespace = Craft::$app->getView()->namespaceInputName(
652
            $this->handle . '[__META__][fields]',
653
            $originalNamespace
654
        );
655
        Craft::$app->getView()->setNamespace($namespace);
656
657
        $fieldLayoutFields = $this->getFields();
658
659
        // Set $_isFresh's
660
        foreach ($fieldLayoutFields as $field) {
661
            $field->setIsFresh(true);
662
        }
663
664
        Craft::$app->getView()->startJsBuffer();
665
666
        $bodyHtml = Craft::$app->getView()->namespaceInputs(
667
            Craft::$app->getView()->renderTemplate(
668
                '_includes/fields',
669
                [
670
                    'namespace' => null,
671
                    'fields' => $fieldLayoutFields
672
                ]
673
            )
674
        );
675
676
        // Reset $_isFresh's
677
        foreach ($fieldLayoutFields as $field) {
678
            $field->setIsFresh(null);
679
        }
680
681
        $footHtml = Craft::$app->getView()->clearJsBuffer();
682
683
        $fields = [
684
            'bodyHtml' => $bodyHtml,
685
            'footHtml' => $footHtml,
686
        ];
687
688
        // Revert namespace
689
        Craft::$app->getView()->setNamespace($originalNamespace);
690
691
        return $fields;
692
    }
693
694
    /**
695
     * Creates an array of elements based on the given serialized data.
696
     *
697
     * @param array|string $value The raw field value
698
     * @param ElementInterface|null $element The element the field is associated with, if there is one
699
     *
700
     * @return MetaElement[]
701
     */
702
    private function createElementsFromSerializedData($value, ElementInterface $element = null): array
703
    {
704
        /** @var Element $element */
705
706
        if (!is_array($value)) {
707
            return [];
708
        }
709
710
        $oldElementsById = [];
711
712
        // Get the old elements that are still around
713
        if (!empty($element->id)) {
714
            $ownerId = $element->id;
715
716
            $ids = [];
717
718
            foreach ($value as $metaId => &$meta) {
719
                if (is_numeric($metaId) && $metaId !== 0) {
720
                    $ids[] = $metaId;
721
                }
722
            }
723
            unset($meta);
724
725
            if (!empty($ids)) {
726
                $oldMetaQuery = MetaElement::find();
727
                $oldMetaQuery->fieldId($this->id);
728
                $oldMetaQuery->ownerId($ownerId);
729
                $oldMetaQuery->id($ids);
730
                $oldMetaQuery->limit(null);
731
                $oldMetaQuery->status(null);
732
                $oldMetaQuery->enabledForSite(false);
733
                $oldMetaQuery->siteId($element->siteId);
734
                $oldMetaQuery->indexBy('id');
735
                $oldElementsById = $oldMetaQuery->all();
736
            }
737
        } else {
738
            $ownerId = null;
739
        }
740
741
        $elements = [];
742
        $sortOrder = 0;
743
        $prevElement = null;
744
745
        foreach ($value as $metaId => $metaData) {
746
            // Is this new? (Or has it been deleted?)
747
            if (strpos($metaId, 'new') === 0 || !isset($oldElementsById[$metaId])) {
748
                $meta = new MetaElement();
749
                $meta->fieldId = $this->id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->id can also be of type string. However, the property $fieldId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
750
                $meta->ownerId = $ownerId;
751
                $meta->siteId = $element->siteId;
752
            } else {
753
                $meta = $oldElementsById[$metaId];
754
            }
755
756
            $meta->setOwner($element);
757
            $meta->enabled = (isset($metaData['enabled']) ? (bool)$metaData['enabled'] : true);
758
759
            // Set the content post location on the element if we can
760
            $fieldNamespace = $element->getFieldParamNamespace();
761
762
            if ($fieldNamespace !== null) {
763
                $metaFieldNamespace = ($fieldNamespace ? $fieldNamespace . '.' : '') .
764
                    '.' . $this->handle .
765
                    '.' . $metaId .
766
                    '.fields';
767
                $meta->setFieldParamNamespace($metaFieldNamespace);
768
            }
769
770
            if (isset($metaData['fields'])) {
771
                $meta->setFieldValues($metaData['fields']);
772
            }
773
774
            $sortOrder++;
775
            $meta->sortOrder = $sortOrder;
776
777
            // Set the prev/next elements
778
            if ($prevElement) {
779
                /** @var ElementInterface $prevElement */
780
                $prevElement->setNext($meta);
781
                /** @var ElementInterface $meta */
782
                $meta->setPrev($prevElement);
783
            }
784
            $prevElement = $meta;
785
786
            $elements[] = $meta;
787
        }
788
789
        return $elements;
790
    }
791
792
    /**
793
     * Returns the fields associated with this element.
794
     *
795
     * @return FieldInterface[]
796
     */
797
    public function getFields(): array
798
    {
799
        return $this->getFieldLayout()->getFields();
800
    }
801
802
    /**
803
     * Sets the fields associated with this element.
804
     *
805
     * @param FieldInterface[] $fields
806
     *
807
     * @return void
808
     */
809
    public function setFields(array $fields)
810
    {
811
        $defaultFieldConfig = [
812
            'type' => null,
813
            'name' => null,
814
            'handle' => null,
815
            'instructions' => null,
816
            'required' => false,
817
            'translationMethod' => Field::TRANSLATION_METHOD_NONE,
818
            'translationKeyFormat' => null,
819
            'settings' => null,
820
        ];
821
822
        foreach ($fields as $fieldId => $fieldConfig) {
823
824
            if (!$fieldConfig instanceof FieldInterface) {
825
826
                /** @noinspection SlowArrayOperationsInLoopInspection */
827
                $fieldConfig = array_merge($defaultFieldConfig, $fieldConfig);
828
829
                $fields[$fieldId] = Craft::$app->getFields()->createField([
830
                    'type' => $fieldConfig['type'],
831
                    'id' => $fieldId,
832
                    'name' => $fieldConfig['name'],
833
                    'handle' => $fieldConfig['handle'],
834
                    'instructions' => $fieldConfig['instructions'],
835
                    'required' => (bool)$fieldConfig['required'],
836
                    'translationMethod' => $fieldConfig['translationMethod'],
837
                    'translationKeyFormat' => $fieldConfig['translationKeyFormat'],
838
                    'settings' => $fieldConfig['settings'],
839
                ]);
840
841
            }
842
843
        }
844
845
        $this->getFieldLayout()->setFields($fields);
846
    }
847
}
848