Update to Kirby 5

This commit is contained in:
Paul Nicoué 2025-07-11 14:41:34 +02:00
parent 5d9979fca8
commit 0fefc5e2e1
472 changed files with 30853 additions and 10301 deletions

View file

@ -1,5 +1,6 @@
<?php
use Kirby\Cms\Helpers;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Field\FieldOptions;
use Kirby\Toolkit\A;
@ -24,8 +25,10 @@ return [
* The CSS format (hex, rgb, hsl) to display and store the value
*/
'format' => function (string $format = 'hex'): string {
if (in_array($format, ['hex', 'hsl', 'rgb']) === false) {
throw new InvalidArgumentException('Unsupported format for color field (supported: hex, rgb, hsl)');
if (in_array($format, ['hex', 'hsl', 'rgb'], true) === false) {
throw new InvalidArgumentException(
message: 'Unsupported format for color field (supported: hex, rgb, hsl)'
);
}
return $format;
@ -35,8 +38,10 @@ return [
* show the `options` as toggles
*/
'mode' => function (string $mode = 'picker'): string {
if (in_array($mode, ['picker', 'input', 'options']) === false) {
throw new InvalidArgumentException('Unsupported mode for color field (supported: picker, input, options)');
if (in_array($mode, ['picker', 'input', 'options'], true) === false) {
throw new InvalidArgumentException(
message: 'Unsupported mode for color field (supported: picker, input, options)'
);
}
return $mode;
@ -69,30 +74,33 @@ return [
return [];
}
$options = match (true) {
// simple array of values
// or value=text (from Options class)
if (
is_numeric($options[0]['value']) ||
$options[0]['value'] === $options[0]['text']
=> A::map($options, fn ($option) => [
'value' => $option['text']
]),
) {
// simple array of values
// or value=text (from Options class)
$options = A::map($options, fn ($option) => [
'value' => $option['text']
]);
// deprecated: name => value, flipping
// TODO: start throwing in warning in v5
$this->isColor($options[0]['text'])
=> A::map($options, fn ($option) => [
'value' => $option['text'],
// ensure that any HTML in the new text is escaped
'text' => Escape::html($option['value'])
]),
} elseif ($this->isColor($options[0]['text'])) {
// @deprecated 4.0.0
// TODO: Remove in Kirby 6
default
=> A::map($options, fn ($option) => [
Helpers::deprecated('Color field "' . $this->name . '": the text => value notation for options has been deprecated and will be removed in Kirby 6. Please rewrite your options as value => text.');
$options = A::map($options, fn ($option) => [
'value' => $option['text'],
// ensure that any HTML in the new text is escaped
'text' => Escape::html($option['value'])
]);
} else {
$options = A::map($options, fn ($option) => [
'value' => $option['value'],
'text' => $option['text']
]),
};
]);
}
return $options;
}
@ -121,24 +129,24 @@ return [
}
if ($this->format === 'hex' && $this->isHex($value) === false) {
throw new InvalidArgumentException([
'key' => 'validation.color',
'data' => ['format' => 'hex']
]);
throw new InvalidArgumentException(
key: 'validation.color',
data: ['format' => 'hex']
);
}
if ($this->format === 'rgb' && $this->isRgb($value) === false) {
throw new InvalidArgumentException([
'key' => 'validation.color',
'data' => ['format' => 'rgb']
]);
throw new InvalidArgumentException(
key: 'validation.color',
data: ['format' => 'rgb']
);
}
if ($this->format === 'hsl' && $this->isHsl($value) === false) {
throw new InvalidArgumentException([
'key' => 'validation.color',
'data' => ['format' => 'hsl']
]);
throw new InvalidArgumentException(
key: 'validation.color',
data: ['format' => 'hsl']
);
}
}
]

View file

@ -125,27 +125,27 @@ return [
$format = $this->time === false ? 'd.m.Y' : 'd.m.Y H:i';
if ($min && $max && $value->isBetween($min, $max) === false) {
throw new Exception([
'key' => 'validation.date.between',
'data' => [
throw new Exception(
key: 'validation.date.between',
data: [
'min' => $min->format($format),
'max' => $max->format($format)
]
]);
} elseif ($min && $value->isMin($min) === false) {
throw new Exception([
'key' => 'validation.date.after',
'data' => [
'date' => $min->format($format),
]
]);
} elseif ($max && $value->isMax($max) === false) {
throw new Exception([
'key' => 'validation.date.before',
'data' => [
'date' => $max->format($format),
]
]);
);
}
if ($min && $value->isMin($min) === false) {
throw new Exception(
key: 'validation.date.after',
data: ['date' => $min->format($format)]
);
}
if ($max && $value->isMax($max) === false) {
throw new Exception(
key: 'validation.date.before',
data: ['date' => $max->format($format)]
);
}
return true;

View file

@ -48,7 +48,7 @@ return [
'activeTypes' => function () {
return array_filter(
$this->availableTypes(),
fn (string $type) => in_array($type, $this->props['options']),
fn (string $type) => in_array($type, $this->props['options'], true),
ARRAY_FILTER_USE_KEY
);
},
@ -153,17 +153,17 @@ return [
$detected = true;
if ($options['validate']($link) === false) {
throw new InvalidArgumentException([
'key' => 'validation.' . $type
]);
throw new InvalidArgumentException(
key: 'validation.' . $type
);
}
}
// none of the configured types has been detected
if ($detected === false) {
throw new InvalidArgumentException([
'key' => 'validation.linkType'
]);
throw new InvalidArgumentException(
key: 'validation.linkType'
);
}
return true;

View file

@ -7,8 +7,11 @@ return [
* Available layouts: `list`, `cardlets`, `cards`
*/
'layout' => function (string $layout = 'list') {
$layouts = ['list', 'cardlets', 'cards'];
return in_array($layout, $layouts) ? $layout : 'list';
return match ($layout) {
'cards' => 'cards',
'cardlets' => 'cardlets',
default => 'list'
};
},
/**

View file

@ -36,7 +36,7 @@ return [
},
'sanitizeOption' => function ($value) {
$options = array_column($this->options(), 'value');
return in_array($value, $options) === true ? $value : null;
return in_array($value, $options) ? $value : null;
},
'sanitizeOptions' => function ($values) {
$options = array_column($this->options(), 'value');

View file

@ -34,7 +34,9 @@ return [
$parent = $this->uploadParent($uploads['parent'] ?? null);
if ($parent === null) {
throw new InvalidArgumentException('"' . $uploads['parent'] . '" could not be resolved as a valid parent for the upload');
throw new InvalidArgumentException(
message: '"' . $uploads['parent'] . '" could not be resolved as a valid parent for the upload'
);
}
$file = new File([
@ -52,7 +54,9 @@ return [
'methods' => [
'upload' => function (Api $api, $params, Closure $map) {
if ($params === false) {
throw new Exception('Uploads are disabled for this field');
throw new Exception(
message: 'Uploads are disabled for this field'
);
}
$parent = $this->uploadParent($params['parent'] ?? null);
@ -68,7 +72,9 @@ return [
$file = $parent->createFile($props, true);
if ($file instanceof File === false) {
throw new Exception('The file could not be uploaded');
throw new Exception(
message: 'The file could not be uploaded'
);
}
return $map($file, $parent);

View file

@ -38,7 +38,7 @@ return [
],
'methods' => [
'toNumber' => function ($value): float|null {
if ($this->isEmpty($value) === true) {
if ($this->isEmptyValue($value) === true) {
return null;
}

View file

@ -91,13 +91,13 @@ return [
$name = array_key_first($errors);
$error = $errors[$name];
throw new InvalidArgumentException([
'key' => 'object.validation',
'data' => [
throw new InvalidArgumentException(
key: 'object.validation',
data: [
'label' => $error['label'] ?? $name,
'message' => implode("\n", $error['message'])
]
]);
);
}
}
]

View file

@ -20,7 +20,8 @@ return [
],
'computed' => [
'default' => function () {
return $this->sanitizeOption($this->default);
$default = $this->model()->toString($this->default);
return $this->sanitizeOption($default);
},
'value' => function () {
return $this->sanitizeOption($this->value) ?? '';

View file

@ -18,7 +18,7 @@ return [
return $icon;
},
/**
* Custom placeholder string for empty option.
* Text shown when no option is selected yet
*/
'placeholder' => function (string|array $placeholder = '—') {
return I18n::translate($placeholder, $placeholder);

View file

@ -149,7 +149,7 @@ return [
// make the first column visible on mobile
// if no other mobile columns are defined
if (in_array(true, array_column($columns, 'mobile')) === false) {
if (in_array(true, array_column($columns, 'mobile'), true) === false) {
$columns[array_key_first($columns)]['mobile'] = true;
}
@ -166,24 +166,37 @@ return [
continue;
}
$value[] = $this->form($row)->values();
$value[] = $this->form()->fill(input: $row, passthrough: true)->toFormValues();
}
return $value;
},
'form' => function (array $values = []) {
return new Form([
'fields' => $this->attrs['fields'] ?? [],
'values' => $values,
'model' => $this->model
]);
},
'form' => function () {
$this->form ??= new Form(
fields: $this->attrs['fields'] ?? [],
model: $this->model,
language: 'current'
);
return $this->form->reset();
}
],
'save' => function ($value) {
$data = [];
$data = [];
$form = $this->form();
$defaults = $form->defaults();
foreach ($value as $row) {
$row = $this->form($row)->content();
foreach ($value as $index => $row) {
$row = $form
->reset()
->fill(
input: $defaults,
)
->submit(
input: $row,
passthrough: true
)
->toStoredValues();
// remove frontend helper id
unset($row['_id']);
@ -204,19 +217,20 @@ return [
$values = A::wrap($value);
foreach ($values as $index => $value) {
$form = $this->form($value);
$form = $this->form();
$form->fill(input: $value);
foreach ($form->fields() as $field) {
$errors = $field->errors();
if (empty($errors) === false) {
throw new InvalidArgumentException([
'key' => 'structure.validation',
'data' => [
throw new InvalidArgumentException(
key: 'structure.validation',
data: [
'field' => $field->label() ?? Str::ucfirst($field->name()),
'index' => $index + 1
]
]);
);
}
}
}

View file

@ -10,11 +10,14 @@ return [
* The field value will be converted with the selected converter before the value gets saved. Available converters: `lower`, `upper`, `ucfirst`, `slug`
*/
'converter' => function ($value = null) {
if ($value !== null && array_key_exists($value, $this->converters()) === false) {
throw new InvalidArgumentException([
'key' => 'field.converter.invalid',
'data' => ['converter' => $value]
]);
if (
$value !== null &&
array_key_exists($value, $this->converters()) === false
) {
throw new InvalidArgumentException(
key: 'field.converter.invalid',
data: ['converter' => $value]
);
}
return $value;

View file

@ -89,12 +89,11 @@ return [
[
'pattern' => 'files',
'action' => function () {
$params = array_merge($this->field()->files(), [
return $this->field()->filepicker([
...$this->field()->files(),
'page' => $this->requestQuery('page'),
'search' => $this->requestQuery('search')
]);
return $this->field()->filepicker($params);
}
],
[
@ -104,14 +103,12 @@ return [
$field = $this->field();
$uploads = $field->uploads();
return $this->field()->upload($this, $uploads, function ($file, $parent) use ($field) {
$absolute = $field->model()->is($parent) === false;
return [
'filename' => $file->filename(),
'dragText' => $file->panel()->dragText('auto', $absolute),
];
});
return $this->field()->upload($this, $uploads, fn ($file, $parent) => [
'filename' => $file->filename(),
'dragText' => $file->panel()->dragText(
absolute: $field->model()->is($parent) === false
),
]);
}
]
];

View file

@ -1,6 +1,6 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\Date;
use Kirby\Toolkit\I18n;
@ -97,27 +97,27 @@ return [
$format = 'H:i:s';
if ($min && $max && $value->isBetween($min, $max) === false) {
throw new Exception([
'key' => 'validation.time.between',
'data' => [
throw new InvalidArgumentException(
key: 'validation.time.between',
data: [
'min' => $min->format($format),
'max' => $min->format($format)
]
]);
} elseif ($min && $value->isMin($min) === false) {
throw new Exception([
'key' => 'validation.time.after',
'data' => [
'time' => $min->format($format),
]
]);
} elseif ($max && $value->isMax($max) === false) {
throw new Exception([
'key' => 'validation.time.before',
'data' => [
'time' => $max->format($format),
]
]);
);
}
if ($min && $value->isMin($min) === false) {
throw new InvalidArgumentException(
key: 'validation.time.after',
data: ['time' => $min->format($format)]
);
}
if ($max && $value->isMax($max) === false) {
throw new InvalidArgumentException(
key: 'validation.time.before',
data: ['time' => $max->format($format)]
);
}
return true;

View file

@ -65,8 +65,13 @@ return [
'validations' => [
'boolean',
'required' => function ($value) {
if ($this->isRequired() && ($value === false || $this->isEmpty($value))) {
throw new InvalidArgumentException(I18n::translate('field.required'));
if (
$this->isRequired() &&
($value === false || $this->isEmptyValue($value))
) {
throw new InvalidArgumentException(
message: I18n::translate('field.required')
);
}
},
]

View file

@ -79,10 +79,10 @@ return [
$this->minlength &&
V::minLength(strip_tags($value), $this->minlength) === false
) {
throw new InvalidArgumentException([
'key' => 'validation.minlength',
'data' => ['min' => $this->minlength]
]);
throw new InvalidArgumentException(
key: 'validation.minlength',
data: ['min' => $this->minlength]
);
}
},
'maxlength' => function ($value) {
@ -90,10 +90,10 @@ return [
$this->maxlength &&
V::maxLength(strip_tags($value), $this->maxlength) === false
) {
throw new InvalidArgumentException([
'key' => 'validation.maxlength',
'data' => ['max' => $this->maxlength]
]);
throw new InvalidArgumentException(
key: 'validation.maxlength',
data: ['max' => $this->maxlength]
);
}
},
]