This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
|
|||
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
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 For 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
![]() |
|||
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 | 'max' => $this->max ?: null, |
||
387 | 'tooMany' => Craft::t( |
||
388 | 'app', |
||
389 | '{attribute} should contain at most {max, number} {max, plural, one{record} other{records}}.' |
||
390 | ), |
||
391 | ], |
||
392 | ]; |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Validates an owner element’s Meta. |
||
397 | * |
||
398 | * @param ElementInterface $element |
||
399 | * |
||
400 | * @return void |
||
401 | */ |
||
402 | public function validateMeta(ElementInterface $element) |
||
403 | { |
||
404 | /** @var Element $element */ |
||
405 | /** @var MetaQuery $value */ |
||
406 | $value = $element->getFieldValue($this->handle); |
||
407 | $validate = true; |
||
408 | |||
409 | foreach ($value->all() as $meta) { |
||
410 | /** @var MetaElement $meta */ |
||
411 | if (!$meta->validate()) { |
||
412 | $validate = false; |
||
413 | } |
||
414 | } |
||
415 | |||
416 | if (!$validate) { |
||
417 | $element->addError($this->handle, Craft::t('app', 'Correct the errors listed above.')); |
||
418 | } |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * @param mixed $value |
||
423 | * @param Element|ElementInterface $element |
||
424 | * @return string |
||
425 | */ |
||
426 | public function getSearchKeywords($value, ElementInterface $element): string |
||
427 | { |
||
428 | /** @var MetaQuery $value */ |
||
429 | |||
430 | $keywords = []; |
||
431 | $contentService = Craft::$app->getContent(); |
||
432 | |||
433 | /** @var MetaElement $meta */ |
||
434 | foreach ($value->all() as $meta) { |
||
435 | $originalContentTable = $contentService->contentTable; |
||
436 | $originalFieldContext = $contentService->fieldContext; |
||
437 | |||
438 | $contentService->contentTable = $meta->getContentTable(); |
||
439 | $contentService->fieldContext = $meta->getFieldContext(); |
||
440 | |||
441 | /** @var Field $field */ |
||
442 | foreach (Craft::$app->getFields()->getAllFields() as $field) { |
||
443 | $fieldValue = $meta->getFieldValue($field->handle); |
||
444 | $keywords[] = $field->getSearchKeywords($fieldValue, $element); |
||
445 | } |
||
446 | |||
447 | $contentService->contentTable = $originalContentTable; |
||
448 | $contentService->fieldContext = $originalFieldContext; |
||
449 | } |
||
450 | |||
451 | return parent::getSearchKeywords($keywords, $element); |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * todo - review this |
||
456 | * |
||
457 | * @inheritdoc |
||
458 | */ |
||
459 | public function getStaticHtml($value, ElementInterface $element): string |
||
460 | { |
||
461 | if ($value) { |
||
462 | $id = StringHelper::randomString(); |
||
463 | return Craft::$app->getView()->renderTemplate( |
||
464 | FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'input', |
||
465 | [ |
||
466 | 'id' => $id, |
||
467 | 'name' => $this->handle, |
||
468 | 'elements' => $value, |
||
469 | 'static' => true |
||
470 | ] |
||
471 | ); |
||
472 | } else { |
||
473 | Craft::$app->getView()->registerTranslations('meta', [ |
||
474 | 'No meta' |
||
475 | ]); |
||
476 | |||
477 | return '<p class="light">' . Craft::t('meta', 'No meta') . '</p>'; |
||
478 | } |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * @inheritdoc |
||
483 | */ |
||
484 | public function getEagerLoadingMap(array $sourceElements) |
||
485 | { |
||
486 | // Get the source element IDs |
||
487 | $sourceElementIds = []; |
||
488 | |||
489 | foreach ($sourceElements as $sourceElement) { |
||
490 | $sourceElementIds[] = $sourceElement->id; |
||
491 | } |
||
492 | |||
493 | // Return any relation data on these elements, defined with this field |
||
494 | $map = (new Query()) |
||
495 | ->select(['ownerId as source', 'id as target']) |
||
496 | ->from([MetaRecord::tableName()]) |
||
497 | ->where([ |
||
498 | 'fieldId' => $this->id, |
||
499 | 'ownerId' => $sourceElementIds, |
||
500 | ]) |
||
501 | ->orderBy(['sortOrder' => SORT_ASC]) |
||
502 | ->all(); |
||
503 | |||
504 | return [ |
||
505 | 'elementType' => MetaElement::class, |
||
506 | 'map' => $map, |
||
507 | 'criteria' => ['fieldId' => $this->id] |
||
508 | ]; |
||
509 | } |
||
510 | |||
511 | /** |
||
512 | * @inheritdoc |
||
513 | */ |
||
514 | public function beforeSave(bool $isNew): bool |
||
515 | { |
||
516 | // Save field settings (and field content) |
||
517 | if (!MetaPlugin::getInstance()->getConfiguration()->beforeSave($this)) { |
||
518 | return false; |
||
519 | } |
||
520 | |||
521 | // Trigger an 'afterSave' event |
||
522 | return parent::beforeSave($isNew); |
||
523 | } |
||
524 | |||
525 | /** |
||
526 | * @inheritdoc |
||
527 | */ |
||
528 | public function afterSave(bool $isNew) |
||
529 | { |
||
530 | // Save field settings (and field content) |
||
531 | MetaPlugin::getInstance()->getConfiguration()->afterSave($this); |
||
532 | |||
533 | // Trigger an 'afterSave' event |
||
534 | parent::afterSave($isNew); |
||
535 | } |
||
536 | |||
537 | /** |
||
538 | * @inheritdoc |
||
539 | */ |
||
540 | public function beforeDelete(): bool |
||
541 | { |
||
542 | // Delete field content table |
||
543 | MetaPlugin::getInstance()->getConfiguration()->beforeDelete($this); |
||
544 | |||
545 | // Trigger a 'beforeDelete' event |
||
546 | return parent::beforeDelete(); |
||
547 | } |
||
548 | |||
549 | /** |
||
550 | * @inheritdoc |
||
551 | */ |
||
552 | public function afterElementSave(ElementInterface $element, bool $isNew) |
||
553 | { |
||
554 | // Save meta element |
||
555 | MetaPlugin::getInstance()->getField()->afterElementSave($this, $element); |
||
556 | |||
557 | // Trigger an 'afterElementSave' event |
||
558 | parent::afterElementSave($element, $isNew); |
||
559 | } |
||
560 | |||
561 | /** |
||
562 | * @inheritdoc |
||
563 | */ |
||
564 | public function beforeElementDelete(ElementInterface $element): bool |
||
565 | { |
||
566 | // Delete meta elements |
||
567 | if (!MetaPlugin::getInstance()->getField()->beforeElementDelete($this, $element)) { |
||
568 | return false; |
||
569 | } |
||
570 | |||
571 | return parent::beforeElementDelete($element); |
||
572 | } |
||
573 | |||
574 | /** |
||
575 | * @inheritdoc |
||
576 | */ |
||
577 | protected function isValueEmpty($value, ElementInterface $element): bool |
||
0 ignored issues
–
show
|
|||
578 | { |
||
579 | /** @var MetaQuery $value */ |
||
580 | return $value->count() === 0; |
||
581 | } |
||
582 | |||
583 | /** |
||
584 | * |
||
585 | * Returns info about each field type for the configurator. |
||
586 | * |
||
587 | * @return array |
||
588 | */ |
||
589 | private function getFieldOptionsForConfiguration() |
||
590 | { |
||
591 | $disallowedFields = [ |
||
592 | self::class, |
||
593 | Matrix::class |
||
594 | ]; |
||
595 | |||
596 | $fieldTypes = []; |
||
597 | |||
598 | // Set a temporary namespace for these |
||
599 | $originalNamespace = Craft::$app->getView()->getNamespace(); |
||
600 | $namespace = Craft::$app->getView()->namespaceInputName('fields[__META_FIELD__][settings]', $originalNamespace); |
||
601 | Craft::$app->getView()->setNamespace($namespace); |
||
602 | |||
603 | /** @var Field|string $class */ |
||
604 | foreach (Craft::$app->getFields()->getAllFieldTypes() as $class) { |
||
605 | // Ignore disallowed fields |
||
606 | if (in_array($class, $disallowedFields)) { |
||
607 | continue; |
||
608 | } |
||
609 | |||
610 | Craft::$app->getView()->startJsBuffer(); |
||
611 | |||
612 | /** @var FieldInterface $field */ |
||
613 | $field = new $class(); |
||
614 | |||
615 | if ($settingsHtml = (string)$field->getSettingsHtml()) { |
||
616 | $settingsHtml = Craft::$app->getView()->namespaceInputs($settingsHtml); |
||
617 | } |
||
618 | |||
619 | $settingsBodyHtml = $settingsHtml; |
||
620 | $settingsFootHtml = Craft::$app->getView()->clearJsBuffer(); |
||
621 | |||
622 | $fieldTypes[] = [ |
||
623 | 'type' => $class, |
||
624 | 'name' => $class::displayName(), |
||
625 | 'settingsBodyHtml' => $settingsBodyHtml, |
||
626 | 'settingsFootHtml' => $settingsFootHtml, |
||
627 | ]; |
||
628 | } |
||
629 | |||
630 | Craft::$app->getView()->setNamespace($originalNamespace); |
||
631 | |||
632 | return $fieldTypes; |
||
633 | } |
||
634 | |||
635 | /** |
||
636 | * Returns html for all associated field types for the Meta field input. |
||
637 | * |
||
638 | * @return array |
||
639 | */ |
||
640 | private function getFieldInfoForInput(): array |
||
641 | { |
||
642 | // Set a temporary namespace for these |
||
643 | $originalNamespace = Craft::$app->getView()->getNamespace(); |
||
644 | $namespace = Craft::$app->getView()->namespaceInputName( |
||
645 | $this->handle . '[__META__][fields]', |
||
646 | $originalNamespace |
||
647 | ); |
||
648 | Craft::$app->getView()->setNamespace($namespace); |
||
649 | |||
650 | $fieldLayoutFields = $this->getFields(); |
||
651 | |||
652 | // Set $_isFresh's |
||
653 | foreach ($fieldLayoutFields as $field) { |
||
654 | $field->setIsFresh(true); |
||
655 | } |
||
656 | |||
657 | Craft::$app->getView()->startJsBuffer(); |
||
658 | |||
659 | $bodyHtml = Craft::$app->getView()->namespaceInputs( |
||
660 | Craft::$app->getView()->renderTemplate( |
||
661 | '_includes/fields', |
||
662 | [ |
||
663 | 'namespace' => null, |
||
664 | 'fields' => $fieldLayoutFields |
||
665 | ] |
||
666 | ) |
||
667 | ); |
||
668 | |||
669 | // Reset $_isFresh's |
||
670 | foreach ($fieldLayoutFields as $field) { |
||
671 | $field->setIsFresh(null); |
||
672 | } |
||
673 | |||
674 | $footHtml = Craft::$app->getView()->clearJsBuffer(); |
||
675 | |||
676 | $fields = [ |
||
677 | 'bodyHtml' => $bodyHtml, |
||
678 | 'footHtml' => $footHtml, |
||
679 | ]; |
||
680 | |||
681 | // Revert namespace |
||
682 | Craft::$app->getView()->setNamespace($originalNamespace); |
||
683 | |||
684 | return $fields; |
||
685 | } |
||
686 | |||
687 | /** |
||
688 | * Creates an array of elements based on the given serialized data. |
||
689 | * |
||
690 | * @param array|string $value The raw field value |
||
691 | * @param ElementInterface|null $element The element the field is associated with, if there is one |
||
692 | * |
||
693 | * @return MetaElement[] |
||
694 | */ |
||
695 | private function createElementsFromSerializedData($value, ElementInterface $element = null): array |
||
696 | { |
||
697 | /** @var Element $element */ |
||
698 | |||
699 | if (!is_array($value)) { |
||
700 | return []; |
||
701 | } |
||
702 | |||
703 | $oldElementsById = []; |
||
704 | |||
705 | // Get the old elements that are still around |
||
706 | if (!empty($element->id)) { |
||
707 | $ownerId = $element->id; |
||
708 | |||
709 | $ids = []; |
||
710 | |||
711 | foreach ($value as $metaId => &$meta) { |
||
712 | if (is_numeric($metaId) && $metaId !== 0) { |
||
713 | $ids[] = $metaId; |
||
714 | } |
||
715 | } |
||
716 | unset($meta); |
||
717 | |||
718 | if (!empty($ids)) { |
||
719 | $oldMetaQuery = MetaElement::find(); |
||
720 | $oldMetaQuery->fieldId($this->id); |
||
721 | $oldMetaQuery->ownerId($ownerId); |
||
722 | $oldMetaQuery->id($ids); |
||
723 | $oldMetaQuery->limit(null); |
||
724 | $oldMetaQuery->status(null); |
||
725 | $oldMetaQuery->enabledForSite(false); |
||
726 | $oldMetaQuery->siteId($element->siteId); |
||
727 | $oldMetaQuery->indexBy('id'); |
||
728 | $oldElementsById = $oldMetaQuery->all(); |
||
729 | } |
||
730 | } else { |
||
731 | $ownerId = null; |
||
732 | } |
||
733 | |||
734 | $elements = []; |
||
735 | $sortOrder = 0; |
||
736 | $prevElement = null; |
||
737 | |||
738 | foreach ($value as $metaId => $metaData) { |
||
739 | // Is this new? (Or has it been deleted?) |
||
740 | if (strpos($metaId, 'new') === 0 || !isset($oldElementsById[$metaId])) { |
||
741 | $meta = new MetaElement(); |
||
742 | $meta->fieldId = $this->id; |
||
0 ignored issues
–
show
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 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;
}
![]() |
|||
743 | $meta->ownerId = $ownerId; |
||
744 | $meta->siteId = $element->siteId; |
||
745 | } else { |
||
746 | $meta = $oldElementsById[$metaId]; |
||
747 | } |
||
748 | |||
749 | $meta->setOwner($element); |
||
750 | $meta->enabled = (isset($metaData['enabled']) ? (bool)$metaData['enabled'] : true); |
||
751 | |||
752 | // Set the content post location on the element if we can |
||
753 | $fieldNamespace = $element->getFieldParamNamespace(); |
||
754 | |||
755 | if ($fieldNamespace !== null) { |
||
756 | $metaFieldNamespace = ($fieldNamespace ? $fieldNamespace . '.' : '') . |
||
757 | '.' . $this->handle . |
||
758 | '.' . $metaId . |
||
759 | '.fields'; |
||
760 | $meta->setFieldParamNamespace($metaFieldNamespace); |
||
761 | } |
||
762 | |||
763 | if (isset($metaData['fields'])) { |
||
764 | $meta->setFieldValues($metaData['fields']); |
||
765 | } |
||
766 | |||
767 | $sortOrder++; |
||
768 | $meta->sortOrder = $sortOrder; |
||
769 | |||
770 | // Set the prev/next elements |
||
771 | if ($prevElement) { |
||
772 | /** @var ElementInterface $prevElement */ |
||
773 | $prevElement->setNext($meta); |
||
774 | /** @var ElementInterface $meta */ |
||
775 | $meta->setPrev($prevElement); |
||
776 | } |
||
777 | $prevElement = $meta; |
||
778 | |||
779 | $elements[] = $meta; |
||
780 | } |
||
781 | |||
782 | return $elements; |
||
783 | } |
||
784 | |||
785 | /** |
||
786 | * Returns the fields associated with this element. |
||
787 | * |
||
788 | * @return FieldInterface[] |
||
789 | */ |
||
790 | public function getFields(): array |
||
791 | { |
||
792 | return $this->getFieldLayout()->getFields(); |
||
793 | } |
||
794 | |||
795 | /** |
||
796 | * Sets the fields associated with this element. |
||
797 | * |
||
798 | * @param FieldInterface[] $fields |
||
799 | * |
||
800 | * @return void |
||
801 | */ |
||
802 | public function setFields(array $fields) |
||
803 | { |
||
804 | $defaultFieldConfig = [ |
||
805 | 'type' => null, |
||
806 | 'name' => null, |
||
807 | 'handle' => null, |
||
808 | 'instructions' => null, |
||
809 | 'required' => false, |
||
810 | 'translationMethod' => Field::TRANSLATION_METHOD_NONE, |
||
811 | 'translationKeyFormat' => null, |
||
812 | 'settings' => null, |
||
813 | ]; |
||
814 | |||
815 | foreach ($fields as $fieldId => $fieldConfig) { |
||
816 | if (!$fieldConfig instanceof FieldInterface) { |
||
817 | |||
818 | /** @noinspection SlowArrayOperationsInLoopInspection */ |
||
819 | $fieldConfig = array_merge($defaultFieldConfig, $fieldConfig); |
||
820 | |||
821 | $fields[$fieldId] = Craft::$app->getFields()->createField([ |
||
822 | 'type' => $fieldConfig['type'], |
||
823 | 'id' => $fieldId, |
||
824 | 'name' => $fieldConfig['name'], |
||
825 | 'handle' => $fieldConfig['handle'], |
||
826 | 'instructions' => $fieldConfig['instructions'], |
||
827 | 'required' => (bool)$fieldConfig['required'], |
||
828 | 'translationMethod' => $fieldConfig['translationMethod'], |
||
829 | 'translationKeyFormat' => $fieldConfig['translationKeyFormat'], |
||
830 | 'settings' => $fieldConfig['settings'], |
||
831 | ]); |
||
832 | } |
||
833 | } |
||
834 | |||
835 | $this->getFieldLayout()->setFields($fields); |
||
836 | } |
||
837 | } |
||
838 |
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: