Completed
Push — master ( 2c7560...1eea38 )
by Nate
01:32
created

Meta::getDefaultTemplate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 0
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\ElementQueryInterface;
20
use craft\models\FieldLayout;
21
use craft\validators\ArrayValidator;
22
use flipbox\ember\helpers\ArrayHelper;
23
use flipbox\meta\db\MetaQuery;
24
use flipbox\meta\elements\Meta as MetaElement;
25
use flipbox\meta\helpers\Field as FieldHelper;
26
use flipbox\meta\Meta as MetaPlugin;
27
use flipbox\meta\records\Meta as MetaRecord;
28
29
/**
30
 * @author Flipbox Factory <[email protected]>
31
 * @since 1.0.0
32
 *
33
 * @method setFieldLayout(FieldLayout $fieldLayout)
34
 * @method FieldLayout getFieldLayout()
35
 */
36
class Meta extends Field implements EagerLoadingFieldInterface
37
{
38
    /**
39
     * Default layout template
40
     */
41
    const DEFAULT_TEMPLATE = FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'layout';
42
43
    /**
44
     * Maximum number of meta
45
     *
46
     * @var int|null
47
     */
48
    public $max;
49
50
    /**
51
     * Minimum number of meta
52
     *
53
     * @var int|null
54
     */
55
    public $min;
56
57
    /**
58
     * @var string
59
     */
60
    public $selectionLabel = "Add meta";
61
62
    /**
63
     * Whether each site should get its own unique set of meta
64
     *
65
     * @var int
66
     */
67
    public $localize = false;
68
69
    /**
70
     * @var int|null
71
     */
72
    public $fieldLayoutId;
73
74
    /**
75
     * @var string
76
     */
77
    protected $template = self::DEFAULT_TEMPLATE;
78
79
    /**
80
     * Todo - Remove this (don't like it)
81
     *
82
     * @var bool
83
     */
84
    public $hasFieldErrors = false;
85
86
    /**
87
     * @inheritdoc
88
     */
89
    public static function displayName(): string
90
    {
91
        return Craft::t('meta', 'Meta');
92
    }
93
94
    /**
95
     * @inheritdoc
96
     */
97
    public static function supportedTranslationMethods(): array
98
    {
99
        return [
100
            self::TRANSLATION_METHOD_SITE,
101
        ];
102
    }
103
104
    /**
105
     * @inheritdoc
106
     */
107
    public static function hasContentColumn(): bool
108
    {
109
        return false;
110
    }
111
112
    /**
113
     * @inheritdoc
114
     */
115
    public function settingsAttributes(): array
116
    {
117
        return [
118
            'max',
119
            'min',
120
            'selectionLabel',
121
            'fieldLayoutId',
122
            'template',
123
            'localize'
124
        ];
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130
    public function behaviors()
131
    {
132
        return [
133
            'fieldLayout' => [
134
                'class' => FieldLayoutBehavior::class,
135
                'elementType' => MetaElement::class
136
            ]
137
        ];
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143
    public function rules()
144
    {
145
        return array_merge(
146
            parent::rules(),
147
            [
148
                [
149
                    [
150
                        'min',
151
                        'max'
152
                    ],
153
                    'integer',
154
                    'min' => 0
155
                ]
156
            ]
157
        );
158
    }
159
160
161
    /*******************************************
162
     * VALUE
163
     *******************************************/
164
165
    /**
166
     * @inheritdoc
167
     */
168
    public function isValueEmpty($value, ElementInterface $element): bool
169
    {
170
        /** @var MetaQuery $value */
171
        return $value->count() === 0;
172
    }
173
174
    /**
175
     * @inheritdoc
176
     */
177
    public function serializeValue($value, ElementInterface $element = null)
178
    {
179
        return MetaPlugin::getInstance()->getFields()->serializeValue($this, $value, $element);
180
    }
181
182
    /**
183
     * @inheritdoc
184
     */
185
    public function normalizeValue($value, ElementInterface $element = null)
186
    {
187
        return MetaPlugin::getInstance()->getFields()->normalizeValue($this, $value, $element);
188
    }
189
190
191
    /*******************************************
192
     * QUERY
193
     *******************************************/
194
195
    /**
196
     * @inheritdoc
197
     */
198
    public function modifyElementsQuery(ElementQueryInterface $query, $value)
199
    {
200
        return MetaPlugin::getInstance()->getFields()->modifyElementsQuery($this, $query, $value);
201
    }
202
203
204
    /*******************************************
205
     * TEMPLATE GETTER/SETTER
206
     *******************************************/
207
208
    /**
209
     * @return string
210
     */
211
    public function getTemplate(): string
212
    {
213
        return $this->template ?: self::DEFAULT_TEMPLATE;
214
    }
215
216
    /**
217
     * @param $template
218
     * @return $this
219
     */
220
    public function setTemplate(string $template = null)
221
    {
222
        $this->template = $template;
223
        return $this;
224
    }
225
226
227
    /*******************************************
228
     * HTML
229
     *******************************************/
230
231
    /**
232
     * @inheritdoc
233
     */
234
    public function getInputHtml($value, ElementInterface $element = null): string
235
    {
236
        return MetaPlugin::getInstance()->getConfiguration()->getInputHtml($this, $value);
237
    }
238
239
    /**
240
     * @inheritdoc
241
     */
242
    public function getSettingsHtml()
243
    {
244
        return MetaPlugin::getInstance()->getConfiguration()->getSettingsHtml($this);
245
    }
246
247
    /**
248
     * @inheritdoc
249
     */
250
    public function getStaticHtml($value, ElementInterface $element): string
251
    {
252
        // Todo - implement this
253
        return '';
254
    }
255
256
257
    /*******************************************
258
     * VALIDATION
259
     *******************************************/
260
261
    /**
262
     * @inheritdoc
263
     */
264
    public function validate($attributeNames = null, $clearErrors = true): bool
265
    {
266
        // Run basic model validation first
267
        $validates = parent::validate($attributeNames, $clearErrors);
268
269
        // Run field validation as well
270
        if (!MetaPlugin::getInstance()->getConfiguration()->validate($this)) {
271
            $validates = false;
272
        }
273
274
        return $validates;
275
    }
276
277
    /**
278
     * @inheritdoc
279
     */
280
    public function getElementValidationRules(): array
281
    {
282
        return [
283
            'validateMeta',
284
            [
285
                ArrayValidator::class,
286
                'min' => $this->min ?: null,
287
                'max' => $this->max ?: null,
288
                'tooFew' => Craft::t(
289
                    'app',
290
                    '{attribute} should contain at least {min, number} {min, plural, one{block} other{blocks}}.'
291
                ),
292
                'tooMany' => Craft::t(
293
                    'app',
294
                    '{attribute} should contain at most {max, number} {max, plural, one{block} other{blocks}}.'
295
                ),
296
                'skipOnEmpty' => false,
297
                'on' => Element::SCENARIO_LIVE,
298
            ],
299
        ];
300
    }
301
302
    /**
303
     * Validates an owner element’s Meta.
304
     *
305
     * @param ElementInterface $element
306
     */
307
    public function validateMeta(ElementInterface $element)
308
    {
309
        /** @var Element $element */
310
        /** @var MetaQuery $value */
311
        $value = $element->getFieldValue($this->handle);
312
313
        foreach ($value->all() as $i => $meta) {
314
            /** @var MetaElement $meta */
315
316
            if ($element->getScenario() === Element::SCENARIO_LIVE) {
317
                $meta->setScenario(Element::SCENARIO_LIVE);
318
            }
319
320
            if (!$meta->validate()) {
321
                $element->addModelErrors($meta, "{$this->handle}[{$i}]");
322
            }
323
        }
324
    }
325
326
327
    /*******************************************
328
     * SEARCH
329
     *******************************************/
330
331
    /**
332
     * @inheritdoc
333
     *
334
     * @param MetaQuery $value
335
     * @param Element $element
336
     */
337
    public function getSearchKeywords($value, ElementInterface $element): string
338
    {
339
        /** @var MetaQuery $value */
340
        $keywords = [];
341
342
        foreach ($value->all() as $meta) {
343
            /** @var Field $field */
344
            foreach ($meta->getFieldLayout()->getFields() as $field) {
345
                $fieldValue = $meta->getFieldValue($field->handle);
346
                $keywords[] = $field->getSearchKeywords($fieldValue, $element);
347
            }
348
        }
349
350
        return parent::getSearchKeywords($keywords, $element);
351
    }
352
353
354
    /*******************************************
355
     * EAGER LOADING
356
     *******************************************/
357
358
    /**
359
     * @inheritdoc
360
     */
361
    public function getEagerLoadingMap(array $sourceElements)
362
    {
363
        // Get the source element IDs
364
        $sourceElementIds = [];
365
366
        foreach ($sourceElements as $sourceElement) {
367
            $sourceElementIds[] = $sourceElement->id;
368
        }
369
370
        // Return any relation data on these elements, defined with this field
371
        $map = (new Query())
372
            ->select(['ownerId as source', 'id as target'])
373
            ->from([MetaRecord::tableName()])
374
            ->where([
375
                'fieldId' => $this->id,
376
                'ownerId' => $sourceElementIds,
377
            ])
378
            ->orderBy(['sortOrder' => SORT_ASC])
379
            ->all();
380
381
        return [
382
            'elementType' => MetaElement::class,
383
            'map' => $map,
384
            'criteria' => ['fieldId' => $this->id]
385
        ];
386
    }
387
388
    /*******************************************
389
     * FIELDS
390
     *******************************************/
391
392
    /**
393
     * Fields are
394
     * @param $fields
395
     */
396
    public function setFields(array $fields)
397
    {
398
        $defaultFieldConfig = [
399
            'type' => null,
400
            'name' => null,
401
            'handle' => null,
402
            'instructions' => null,
403
            'required' => false,
404
            'translationMethod' => Field::TRANSLATION_METHOD_NONE,
405
            'translationKeyFormat' => null,
406
            'settings' => null,
407
        ];
408
409
        foreach ($fields as $fieldId => $fieldConfig) {
410
            if (!$fieldConfig instanceof FieldInterface) {
411
                if (empty($fieldConfig['handle'] ?? null)) {
412
                    unset($fields[$fieldId]);
413
                    continue;
414
                }
415
416
                /** @noinspection SlowArrayOperationsInLoopInspection */
417
                $fieldConfig = array_merge($defaultFieldConfig, $fieldConfig);
418
419
                $fields[$fieldId] = Craft::$app->getFields()->createField([
420
                    'type' => $fieldConfig['type'],
421
                    'id' => $fieldId,
422
                    'name' => $fieldConfig['name'],
423
                    'handle' => $fieldConfig['handle'],
424
                    'instructions' => $fieldConfig['instructions'],
425
                    'required' => (bool)$fieldConfig['required'],
426
                    'translationMethod' => $fieldConfig['translationMethod'],
427
                    'translationKeyFormat' => $fieldConfig['translationKeyFormat'],
428
                    'settings' => $fieldConfig['settings'],
429
                ]);
430
            }
431
        }
432
433
        $this->getFieldLayout()->setFields($fields);
434
    }
435
436
    /*******************************************
437
     * EVENTS
438
     *******************************************/
439
440
    /**
441
     * @inheritdoc
442
     */
443
    public function afterSave(bool $isNew)
444
    {
445
        MetaPlugin::getInstance()->getConfiguration()->save($this);
446
        parent::afterSave($isNew);
447
    }
448
449
    /**
450
     * @inheritdoc
451
     */
452
    public function beforeDelete(): bool
453
    {
454
        MetaPlugin::getInstance()->getConfiguration()->beforeDelete($this);
455
        return parent::beforeDelete();
456
    }
457
458
    /**
459
     * @inheritdoc
460
     */
461
    public function afterElementSave(ElementInterface $element, bool $isNew)
462
    {
463
        MetaPlugin::getInstance()->getFields()->afterElementSave($this, $element);
464
        parent::afterElementSave($element, $isNew);
465
    }
466
467
    /**
468
     * @inheritdoc
469
     */
470
    public function beforeElementDelete(ElementInterface $element): bool
471
    {
472
        if (!MetaPlugin::getInstance()->getFields()->beforeElementDelete($this, $element)) {
473
            return false;
474
        }
475
        return parent::beforeElementDelete($element);
476
    }
477
}
478