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