* @link https://getkirby.com * @copyright Bastian Allgeier * @license https://getkirby.com/license * @since 5.0.0 */ class EntriesField extends FieldClass { use EmptyState; use Max; use Min; protected array $field; protected Form $form; protected bool $sortable = true; public function __construct(array $params = []) { parent::__construct($params); $this->setEmpty($params['empty'] ?? null); $this->setField($params['field'] ?? null); $this->setMax($params['max'] ?? null); $this->setMin($params['min'] ?? null); $this->setSortable($params['sortable'] ?? true); } public function field(): array { return $this->field; } public function fieldProps(): array { return $this->form()->fields()->first()->toArray(); } /** * @psalm-suppress MethodSignatureMismatch * @todo Remove psalm suppress after https://github.com/vimeo/psalm/issues/8673 is fixed */ public function fill(mixed $value): static { $this->value = Data::decode($value ?? '', 'yaml'); return $this; } public function form(): Form { return $this->form ??= new Form( fields: [$this->field()], model: $this->model ); } public function props(): array { return [ ...parent::props(), 'empty' => $this->empty(), 'field' => $this->fieldProps(), 'max' => $this->max(), 'min' => $this->min(), 'sortable' => $this->sortable(), ]; } protected function setField(array|string|null $attrs = null): void { if (is_string($attrs) === true) { $attrs = ['type' => $attrs]; } $attrs ??= ['type' => 'text']; if (in_array($attrs['type'], $this->supports()) === false) { throw new InvalidArgumentException( key: 'entries.supports', data: ['type' => $attrs['type']] ); } // remove the unsupported props from the entry field unset($attrs['counter'], $attrs['label']); $this->field = $attrs; } protected function setSortable(bool|null $sortable = true): void { $this->sortable = $sortable; } public function sortable(): bool { return $this->sortable; } public function supports(): array { return [ 'color', 'date', 'email', 'number', 'select', 'slug', 'tel', 'text', 'time', 'url' ]; } public function toFormValue(): mixed { $form = $this->form(); $value = parent::toFormValue() ?? []; return A::map( $value, fn ($value) => $form ->reset() ->fill(input: [$value]) ->fields() ->first() ->toFormValue() ); } public function toStoredValue(): mixed { $form = $this->form(); $value = parent::toStoredValue(); return A::map( $value, fn ($value) => $form ->reset() ->submit(input: [$value]) ->fields() ->first() ->toStoredValue() ); } public function validations(): array { return [ 'entries' => function ($value) { if ($this->min && count($value) < $this->min) { throw new InvalidArgumentException( key: match ($this->min) { 1 => 'entries.min.singular', default => 'entries.min.plural' }, data: ['min' => $this->min] ); } if ($this->max && count($value) > $this->max) { throw new InvalidArgumentException( key: match ($this->max) { 1 => 'entries.max.singular', default => 'entries.max.plural' }, data: ['max' => $this->max] ); } $form = $this->form(); foreach ($value as $index => $val) { $form->reset()->submit(input: [$val]); foreach ($form->fields() as $field) { $errors = $field->errors(); if ($errors !== []) { throw new InvalidArgumentException( key: 'entries.validation', data: [ 'field' => $this->label() ?? Str::ucfirst($this->name()), 'index' => $index + 1 ] ); } } } } ]; } }