Initial commit

This commit is contained in:
Paul Nicoué 2021-10-29 18:05:46 +02:00
commit 1ff19bf38f
830 changed files with 159212 additions and 0 deletions

View file

@ -0,0 +1,61 @@
<?php
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
return [
'mixins' => ['min', 'options'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Arranges the checkboxes in the given number of columns
*/
'columns' => function (int $columns = 1) {
return $columns;
},
/**
* Default value for the field, which will be used when a page/file/user is created
*/
'default' => function ($default = null) {
return Str::split($default, ',');
},
/**
* Maximum number of checked boxes
*/
'max' => function (int $max = null) {
return $max;
},
/**
* Minimum number of checked boxes
*/
'min' => function (int $min = null) {
return $min;
},
'value' => function ($value = null) {
return Str::split($value, ',');
},
],
'computed' => [
'default' => function () {
return $this->sanitizeOptions($this->default);
},
'value' => function () {
return $this->sanitizeOptions($this->value);
},
],
'save' => function ($value): string {
return A::join($value, ', ');
},
'validations' => [
'options',
'max',
'min'
]
];

View file

@ -0,0 +1,179 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Form\Field;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
return [
'mixins' => ['datetime'],
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Activate/deactivate the dropdown calendar
*/
'calendar' => function (bool $calendar = true) {
return $calendar;
},
/**
* Default date when a new page/file/user gets created
*/
'default' => function (string $default = null) {
return $default;
},
/**
* Custom format (dayjs tokens: `DD`, `MM`, `YYYY`) that is
* used to display the field in the Panel
*/
'display' => function ($display = 'YYYY-MM-DD') {
return I18n::translate($display, $display);
},
/**
* Changes the calendar icon to something custom
*/
'icon' => function (string $icon = 'calendar') {
return $icon;
},
/**
* Latest date, which can be selected/saved (Y-m-d)
*/
'max' => function (string $max = null) {
return $this->toDatetime($max);
},
/**
* Earliest date, which can be selected/saved (Y-m-d)
*/
'min' => function (string $min = null) {
return $this->toDatetime($min);
},
/**
* Round to the nearest: sub-options for `unit` (day) and `size` (1)
*/
'step' => function ($step = null) {
$default = [
'size' => 1,
'unit' => 'day'
];
if ($step === null) {
return $default;
}
if (is_array($step) === true) {
$step = array_merge($default, $step);
$step['unit'] = strtolower($step['unit']);
return $step;
}
if (is_int($step) === true) {
return array_merge($default, ['size' => $step]);
}
if (is_string($step) === true) {
return array_merge($default, ['unit' => strtolower($step)]);
}
},
/**
* Pass `true` or an array of time field options to show the time selector.
*/
'time' => function ($time = false) {
return $time;
},
/**
* Must be a parseable date string
*/
'value' => function ($value = null) {
return $value;
},
],
'computed' => [
'default' => function () {
return $this->toDatetime($this->default);
},
'display' => function () {
if ($this->display) {
return Str::upper($this->display);
}
},
'format' => function () {
return $this->props['format'] ?? ($this->time === false ? 'Y-m-d' : 'Y-m-d H:i:s');
},
'time' => function () {
if ($this->time === false) {
return false;
}
$props = is_array($this->time) ? $this->time : [];
$props['model'] = $this->model();
$field = new Field('time', $props);
return $field->toArray();
},
'step' => function () {
if ($this->time === false) {
return $this->step;
}
return $this->time['step'];
},
'value' => function () {
return $this->toDatetime($this->value);
},
],
'validations' => [
'date',
'minMax' => function ($value) {
$min = $this->min ? strtotime($this->min) : null;
$max = $this->max ? strtotime($this->max) : null;
$value = strtotime($this->value());
$format = $this->time === false ? 'd.m.Y' : 'd.m.Y H:i';
$errors = [];
if ($value && $min && $value < $min) {
$errors['min'] = $min;
}
if ($value && $max && $value > $max) {
$errors['max'] = $max;
}
if (empty($errors) === false) {
if ($min && $max) {
throw new Exception([
'key' => 'validation.date.between',
'data' => [
'min' => date($format, $min),
'max' => date($format, $max)
]
]);
} elseif ($min) {
throw new Exception([
'key' => 'validation.date.after',
'data' => [
'date' => date($format, $min),
]
]);
} else {
throw new Exception([
'key' => 'validation.date.before',
'data' => [
'date' => date($format, $max),
]
]);
}
}
return true;
},
]
];

View file

@ -0,0 +1,40 @@
<?php
use Kirby\Toolkit\I18n;
return [
'extends' => 'text',
'props' => [
/**
* Unset inherited props
*/
'converter' => null,
'counter' => null,
/**
* Sets the HTML5 autocomplete mode for the input
*/
'autocomplete' => function (string $autocomplete = 'email') {
return $autocomplete;
},
/**
* Changes the email icon to something custom
*/
'icon' => function (string $icon = 'email') {
return $icon;
},
/**
* Custom placeholder text, when the field is empty.
*/
'placeholder' => function ($value = null) {
return I18n::translate($value, $value) ?? I18n::translate('email.placeholder');
}
],
'validations' => [
'minlength',
'maxlength',
'email'
]
];

View file

@ -0,0 +1,138 @@
<?php
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
'mixins' => [
'picker',
'filepicker',
'min',
'upload'
],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'autofocus' => null,
'icon' => null,
'placeholder' => null,
/**
* Sets the file(s), which are selected by default when a new page is created
*/
'default' => function ($default = null) {
return $default;
},
/**
* Changes the layout of the selected files. Available layouts: `list`, `cards`
*/
'layout' => function (string $layout = 'list') {
return $layout;
},
/**
* Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge`
*/
'size' => function (string $size = 'auto') {
return $size;
},
'value' => function ($value = null) {
return $value;
}
],
'computed' => [
'parentModel' => function () {
if (is_string($this->parent) === true && $model = $this->model()->query($this->parent, 'Kirby\Cms\Model')) {
return $model;
}
return $this->model();
},
'parent' => function () {
return $this->parentModel->apiUrl(true);
},
'query' => function () {
return $this->query ?? $this->parentModel::CLASS_ALIAS . '.files';
},
'default' => function () {
return $this->toFiles($this->default);
},
'value' => function () {
return $this->toFiles($this->value);
},
],
'methods' => [
'fileResponse' => function ($file) {
return $file->panelPickerData([
'image' => $this->image,
'info' => $this->info ?? false,
'model' => $this->model(),
'text' => $this->text,
]);
},
'toFiles' => function ($value = null) {
$files = [];
foreach (Data::decode($value, 'yaml') as $id) {
if (is_array($id) === true) {
$id = $id['id'] ?? null;
}
if ($id !== null && ($file = $this->kirby()->file($id, $this->model()))) {
$files[] = $this->fileResponse($file);
}
}
return $files;
}
],
'api' => function () {
return [
[
'pattern' => '/',
'action' => function () {
$field = $this->field();
return $field->filepicker([
'image' => $field->image(),
'info' => $field->info(),
'limit' => $field->limit(),
'page' => $this->requestQuery('page'),
'query' => $field->query(),
'search' => $this->requestQuery('search'),
'text' => $field->text()
]);
}
],
[
'pattern' => 'upload',
'method' => 'POST',
'action' => function () {
$field = $this->field();
$uploads = $field->uploads();
return $field->upload($this, $uploads, function ($file, $parent) use ($field) {
return $file->panelPickerData([
'image' => $field->image(),
'info' => $field->info(),
'model' => $field->model(),
'text' => $field->text(),
]);
});
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'uuid');
},
'validations' => [
'max',
'min'
]
];

View file

@ -0,0 +1,5 @@
<?php
return [
'save' => false
];

View file

@ -0,0 +1,26 @@
<?php
return [
'save' => false,
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'default' => null,
'disabled' => null,
'icon' => null,
'placeholder' => null,
'required' => null,
'translate' => null,
/**
* If `false`, the prepended number will be hidden
*/
'numbered' => function (bool $numbered = true) {
return $numbered;
}
]
];

View file

@ -0,0 +1,3 @@
<?php
return [];

View file

@ -0,0 +1,44 @@
<?php
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'default' => null,
'disabled' => null,
'icon' => null,
'placeholder' => null,
'required' => null,
'translate' => null,
/**
* Text to be displayed
*/
'text' => function ($value = null) {
return I18n::translate($value, $value);
},
/**
* Change the design of the info box
*/
'theme' => function (string $theme = null) {
return $theme;
}
],
'computed' => [
'text' => function () {
if ($text = $this->text) {
$text = $this->model()->toString($text);
$text = $this->kirby()->kirbytext($text);
return $text;
}
}
],
'save' => false,
];

View file

@ -0,0 +1,5 @@
<?php
return [
'save' => false
];

View file

@ -0,0 +1,17 @@
<?php
return [
'props' => [
/**
* Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`. Activate them all by passing `true`. Deactivate them all by passing `false`
*/
'marks' => function ($marks = true) {
return $marks;
}
],
'computed' => [
'value' => function () {
return trim($this->value);
}
]
];

View file

@ -0,0 +1,28 @@
<?php
return [
'props' => [
/**
* Defines a custom format that is used when the field is saved
*/
'format' => function (string $format = null) {
return $format;
}
],
'methods' => [
'toDatetime' => function ($value, string $format = 'Y-m-d H:i:s') {
if ($timestamp = timestamp($value, $this->step)) {
return date($format, $timestamp);
}
return null;
}
],
'save' => function ($value) {
if ($value !== null && $timestamp = strtotime($value)) {
return date($this->format, $timestamp);
}
return '';
},
];

View file

@ -0,0 +1,14 @@
<?php
use Kirby\Cms\FilePicker;
return [
'methods' => [
'filepicker' => function (array $params = []) {
// fetch the parent model
$params['model'] = $this->model();
return (new FilePicker($params))->toArray();
}
]
];

View file

@ -0,0 +1,22 @@
<?php
return [
'computed' => [
'min' => function () {
// set min to at least 1, if required
if ($this->required === true) {
return $this->min ?? 1;
}
return $this->min;
},
'required' => function () {
// set required to true if min is set
if ($this->min) {
return true;
}
return $this->required;
}
]
];

View file

@ -0,0 +1,48 @@
<?php
use Kirby\Form\Options;
return [
'props' => [
/**
* API settings for options requests. This will only take affect when `options` is set to `api`.
*/
'api' => function ($api = null) {
return $api;
},
/**
* An array with options
*/
'options' => function ($options = []) {
return $options;
},
/**
* Query settings for options queries. This will only take affect when `options` is set to `query`.
*/
'query' => function ($query = null) {
return $query;
},
],
'computed' => [
'options' => function (): array {
return $this->getOptions();
}
],
'methods' => [
'getOptions' => function () {
return Options::factory(
$this->options(),
$this->props,
$this->model()
);
},
'sanitizeOption' => function ($option) {
$allowed = array_column($this->options(), 'value');
return in_array($option, $allowed, true) === true ? $option : null;
},
'sanitizeOptions' => function ($options) {
$allowed = array_column($this->options(), 'value');
return array_intersect($options, $allowed);
},
]
];

View file

@ -0,0 +1,14 @@
<?php
use Kirby\Cms\PagePicker;
return [
'methods' => [
'pagepicker' => function (array $params = []) {
// inject the current model
$params['model'] = $this->model();
return (new PagePicker($params))->toArray();
}
]
];

View file

@ -0,0 +1,78 @@
<?php
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* The placeholder text if none have been selected yet
*/
'empty' => function ($empty = null) {
return I18n::translate($empty, $empty);
},
/**
* Image settings for each item
*/
'image' => function ($image = null) {
return $image;
},
/**
* Info text for each item
*/
'info' => function (string $info = null) {
return $info;
},
/**
* Whether each item should be clickable
*/
'link' => function (bool $link = true) {
return $link;
},
/**
* The minimum number of required selected
*/
'min' => function (int $min = null) {
return $min;
},
/**
* The maximum number of allowed selected
*/
'max' => function (int $max = null) {
return $max;
},
/**
* If `false`, only a single one can be selected
*/
'multiple' => function (bool $multiple = true) {
return $multiple;
},
/**
* Query for the items to be included in the picker
*/
'query' => function (string $query = null) {
return $query;
},
/**
* Enable/disable the search field in the picker
*/
'search' => function (bool $search = true) {
return $search;
},
/**
* Main text for each item
*/
'text' => function (string $text = null) {
return $text;
},
],
];

View file

@ -0,0 +1,72 @@
<?php
use Kirby\Cms\Api;
use Kirby\Cms\File;
use Kirby\Exception\Exception;
return [
'props' => [
/**
* Sets the upload options for linked files (since 3.2.0)
*/
'uploads' => function ($uploads = []) {
if ($uploads === false) {
return false;
}
if (is_string($uploads) === true) {
$uploads = ['template' => $uploads];
}
if (is_array($uploads) === false) {
$uploads = [];
}
$template = $uploads['template'] ?? null;
if ($template) {
$file = new File([
'filename' => 'tmp',
'template' => $template
]);
$uploads['accept'] = $file->blueprint()->acceptMime();
} else {
$uploads['accept'] = '*';
}
return $uploads;
},
],
'methods' => [
'upload' => function (Api $api, $params, Closure $map) {
if ($params === false) {
throw new Exception('Uploads are disabled for this field');
}
if ($parentQuery = ($params['parent'] ?? null)) {
$parent = $this->model()->query($parentQuery);
} else {
$parent = $this->model();
}
if (is_a($parent, 'Kirby\Cms\File') === true) {
$parent = $parent->parent();
}
return $api->upload(function ($source, $filename) use ($parent, $params, $map) {
$file = $parent->createFile([
'source' => $source,
'template' => $params['template'] ?? null,
'filename' => $filename,
]);
if (is_a($file, 'Kirby\Cms\File') === false) {
throw new Exception('The file could not be uploaded');
}
return $map($file, $parent);
});
}
]
];

View file

@ -0,0 +1,13 @@
<?php
use Kirby\Cms\UserPicker;
return [
'methods' => [
'userpicker' => function (array $params = []) {
$params['model'] = $this->model();
return (new UserPicker($params))->toArray();
}
]
];

View file

@ -0,0 +1,32 @@
<?php
return [
'extends' => 'tags',
'props' => [
/**
* Unset inherited props
*/
'accept' => null,
/**
* Custom icon to replace the arrow down.
*/
'icon' => function (string $icon = null) {
return $icon;
},
/**
* Enable/disable the search in the dropdown
* Also limit displayed items (display: 20)
* and set minimum number of characters to search (min: 3)
*/
'search' => function ($search = true) {
return $search;
},
/**
* If `true`, selected entries will be sorted
* according to their position in the dropdown
*/
'sort' => function (bool $sort = false) {
return $sort;
},
]
];

View file

@ -0,0 +1,48 @@
<?php
use Kirby\Toolkit\Str;
return [
'props' => [
/**
* Default number that will be saved when a new page/user/file is created
*/
'default' => function ($default = null) {
return $this->toNumber($default);
},
/**
* The lowest allowed number
*/
'min' => function (float $min = null) {
return $min;
},
/**
* The highest allowed number
*/
'max' => function (float $max = null) {
return $max;
},
/**
* Allowed incremental steps between numbers (i.e `0.5`)
*/
'step' => function ($step = null) {
return $this->toNumber($step);
},
'value' => function ($value = null) {
return $this->toNumber($value);
}
],
'methods' => [
'toNumber' => function ($value) {
if ($this->isEmpty($value) === true) {
return null;
}
return is_float($value) === true ? $value : (float)Str::float($value);
}
],
'validations' => [
'min',
'max'
]
];

View file

@ -0,0 +1,117 @@
<?php
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
'mixins' => ['min', 'pagepicker', 'picker'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Default selected page(s) when a new page/file/user is created
*/
'default' => function ($default = null) {
return $this->toPages($default);
},
/**
* Changes the layout of the selected files. Available layouts: `list`, `cards`
*/
'layout' => function (string $layout = 'list') {
return $layout;
},
/**
* Optional query to select a specific set of pages
*/
'query' => function (string $query = null) {
return $query;
},
/**
* Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge`
*/
'size' => function (string $size = 'auto') {
return $size;
},
/**
* Optionally include subpages of pages
*/
'subpages' => function (bool $subpages = true) {
return $subpages;
},
'value' => function ($value = null) {
return $this->toPages($value);
},
],
'computed' => [
/**
* Unset inherited computed
*/
'default' => null
],
'methods' => [
'pageResponse' => function ($page) {
return $page->panelPickerData([
'image' => $this->image,
'info' => $this->info,
'text' => $this->text,
]);
},
'toPages' => function ($value = null) {
$pages = [];
$kirby = kirby();
foreach (Data::decode($value, 'yaml') as $id) {
if (is_array($id) === true) {
$id = $id['id'] ?? null;
}
if ($id !== null && ($page = $kirby->page($id))) {
$pages[] = $this->pageResponse($page);
}
}
return $pages;
}
],
'api' => function () {
return [
[
'pattern' => '/',
'action' => function () {
$field = $this->field();
return $field->pagepicker([
'image' => $field->image(),
'info' => $field->info(),
'limit' => $field->limit(),
'page' => $this->requestQuery('page'),
'parent' => $this->requestQuery('parent'),
'query' => $field->query(),
'search' => $this->requestQuery('search'),
'subpages' => $field->subpages(),
'text' => $field->text()
]);
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'id');
},
'validations' => [
'max',
'min'
]
];

View file

@ -0,0 +1,29 @@
<?php
return [
'mixins' => ['options'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Arranges the radio buttons in the given number of columns
*/
'columns' => function (int $columns = 1) {
return $columns;
},
],
'computed' => [
'default' => function () {
return $this->sanitizeOption($this->default);
},
'value' => function () {
return $this->sanitizeOption($this->value) ?? '';
}
]
];

View file

@ -0,0 +1,24 @@
<?php
return [
'extends' => 'number',
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* The maximum value on the slider
*/
'max' => function (float $max = 100) {
return $max;
},
/**
* Enables/disables the tooltip and set the before and after values
*/
'tooltip' => function ($tooltip = true) {
return $tooltip;
},
]
];

View file

@ -0,0 +1,24 @@
<?php
return [
'extends' => 'radio',
'props' => [
/**
* Unset inherited props
*/
'columns' => null,
/**
* Custom icon to replace the arrow down.
*/
'icon' => function (string $icon = null) {
return $icon;
},
/**
* Custom placeholder string for empty option.
*/
'placeholder' => function (string $placeholder = '—') {
return $placeholder;
},
]
];

View file

@ -0,0 +1,193 @@
<?php
use Kirby\Cms\Form;
use Kirby\Data\Data;
use Kirby\Toolkit\I18n;
return [
'mixins' => ['min'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'autofocus' => null,
'icon' => null,
'placeholder' => null,
/**
* Optional columns definition to only show selected fields in the structure table.
*/
'columns' => function (array $columns = []) {
// lower case all keys, because field names will
// be lowercase as well.
return array_change_key_case($columns);
},
/**
* Toggles duplicating rows for the structure
*/
'duplicate' => function (bool $duplicate = true) {
return $duplicate;
},
/**
* The placeholder text if no items have been added yet
*/
'empty' => function ($empty = null) {
return I18n::translate($empty, $empty);
},
/**
* Set the default rows for the structure
*/
'default' => function (array $default = null) {
return $default;
},
/**
* Fields setup for the structure form. Works just like fields in regular forms.
*/
'fields' => function (array $fields) {
return $fields;
},
/**
* The number of entries that will be displayed on a single page. Afterwards pagination kicks in.
*/
'limit' => function (int $limit = null) {
return $limit;
},
/**
* Maximum allowed entries in the structure. Afterwards the "Add" button will be switched off.
*/
'max' => function (int $max = null) {
return $max;
},
/**
* Minimum required entries in the structure
*/
'min' => function (int $min = null) {
return $min;
},
/**
* Toggles adding to the top or bottom of the list
*/
'prepend' => function (bool $prepend = null) {
return $prepend;
},
/**
* Toggles drag & drop sorting
*/
'sortable' => function (bool $sortable = null) {
return $sortable;
},
/**
* Sorts the entries by the given field and order (i.e. `title desc`)
* Drag & drop is disabled in this case
*/
'sortBy' => function (string $sort = null) {
return $sort;
}
],
'computed' => [
'default' => function () {
return $this->rows($this->default);
},
'value' => function () {
return $this->rows($this->value);
},
'fields' => function () {
if (empty($this->fields) === true) {
throw new Exception('Please provide some fields for the structure');
}
return $this->form()->fields()->toArray();
},
'columns' => function () {
$columns = [];
if (empty($this->columns)) {
foreach ($this->fields as $field) {
// Skip hidden and unsaveable fields
// They should never be included as column
if ($field['type'] === 'hidden' || $field['saveable'] === false) {
continue;
}
$columns[$field['name']] = [
'type' => $field['type'],
'label' => $field['label'] ?? $field['name']
];
}
} else {
foreach ($this->columns as $columnName => $columnProps) {
if (is_array($columnProps) === false) {
$columnProps = [];
}
$field = $this->fields[$columnName] ?? null;
if (empty($field) === true || $field['saveable'] === false) {
continue;
}
$columns[$columnName] = array_merge($columnProps, [
'type' => $field['type'],
'label' => $field['label'] ?? $field['name']
]);
}
}
return $columns;
}
],
'methods' => [
'rows' => function ($value) {
$rows = Data::decode($value, 'yaml');
$value = [];
foreach ($rows as $index => $row) {
if (is_array($row) === false) {
continue;
}
$value[] = $this->form($row)->values();
}
return $value;
},
'form' => function (array $values = []) {
return new Form([
'fields' => $this->attrs['fields'],
'values' => $values,
'model' => $this->model
]);
},
],
'api' => function () {
return [
[
'pattern' => 'validate',
'method' => 'ALL',
'action' => function () {
return array_values($this->field()->form($this->requestBody())->errors());
}
]
];
},
'save' => function ($value) {
$data = [];
foreach ($value as $row) {
$data[] = $this->form($row)->content();
}
return $data;
},
'validations' => [
'min',
'max'
]
];

View file

@ -0,0 +1,103 @@
<?php
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
return [
'mixins' => ['min', 'options'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
'placeholder' => null,
/**
* If set to `all`, any type of input is accepted. If set to `options` only the predefined options are accepted as input.
*/
'accept' => function ($value = 'all') {
return V::in($value, ['all', 'options']) ? $value : 'all';
},
/**
* Changes the tag icon
*/
'icon' => function ($icon = 'tag') {
return $icon;
},
/**
* Set to `list` to display each tag with 100% width,
* otherwise the tags are displayed inline
*/
'layout' => function (?string $layout = null) {
return $layout;
},
/**
* Minimum number of required entries/tags
*/
'min' => function (int $min = null) {
return $min;
},
/**
* Maximum number of allowed entries/tags
*/
'max' => function (int $max = null) {
return $max;
},
/**
* Custom tags separator, which will be used to store tags in the content file
*/
'separator' => function (string $separator = ',') {
return $separator;
},
],
'computed' => [
'default' => function (): array {
return $this->toTags($this->default);
},
'value' => function (): array {
return $this->toTags($this->value);
}
],
'methods' => [
'toTags' => function ($value) {
if (is_null($value) === true) {
return [];
}
$options = $this->options();
// transform into value-text objects
return array_map(function ($option) use ($options) {
// already a valid object
if (is_array($option) === true && isset($option['value'], $option['text']) === true) {
return $option;
}
$index = array_search($option, array_column($options, 'value'));
if ($index !== false) {
return $options[$index];
}
return [
'value' => $option,
'text' => $option,
];
}, Str::split($value, $this->separator()));
}
],
'save' => function (array $value = null): string {
return A::join(
A::pluck($value, 'value'),
$this->separator() . ' '
);
},
'validations' => [
'min',
'max'
]
];

View file

@ -0,0 +1,27 @@
<?php
return [
'extends' => 'text',
'props' => [
/**
* Unset inherited props
*/
'converter' => null,
'counter' => null,
'spellcheck' => null,
/**
* Sets the HTML5 autocomplete attribute
*/
'autocomplete' => function (string $autocomplete = 'tel') {
return $autocomplete;
},
/**
* Changes the phone icon
*/
'icon' => function (string $icon = 'phone') {
return $icon;
}
]
];

View file

@ -0,0 +1,103 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\Str;
return [
'props' => [
/**
* 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 && in_array($value, array_keys($this->converters())) === false) {
throw new InvalidArgumentException([
'key' => 'field.converter.invalid',
'data' => ['converter' => $value]
]);
}
return $value;
},
/**
* Shows or hides the character counter in the top right corner
*/
'counter' => function (bool $counter = true) {
return $counter;
},
/**
* Maximum number of allowed characters
*/
'maxlength' => function (int $maxlength = null) {
return $maxlength;
},
/**
* Minimum number of required characters
*/
'minlength' => function (int $minlength = null) {
return $minlength;
},
/**
* A regular expression, which will be used to validate the input
*/
'pattern' => function (string $pattern = null) {
return $pattern;
},
/**
* If `false`, spellcheck will be switched off
*/
'spellcheck' => function (bool $spellcheck = false) {
return $spellcheck;
},
],
'computed' => [
'default' => function () {
return $this->convert($this->default);
},
'value' => function () {
return (string)$this->convert($this->value);
}
],
'methods' => [
'convert' => function ($value) {
if ($this->converter() === null) {
return $value;
}
$value = trim($value);
$converter = $this->converters()[$this->converter()];
if (is_array($value) === true) {
return array_map($converter, $value);
}
return call_user_func($converter, $value);
},
'converters' => function (): array {
return [
'lower' => function ($value) {
return Str::lower($value);
},
'slug' => function ($value) {
return Str::slug($value);
},
'ucfirst' => function ($value) {
return Str::ucfirst($value);
},
'upper' => function ($value) {
return Str::upper($value);
},
];
},
],
'validations' => [
'minlength',
'maxlength',
'pattern'
]
];

View file

@ -0,0 +1,123 @@
<?php
return [
'mixins' => ['filepicker', 'upload'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
/**
* Enables/disables the format buttons. Can either be `true`/`false` or a list of allowed buttons. Available buttons: `headlines`, `italic`, `bold`, `link`, `email`, `file`, `code`, `ul`, `ol` (as well as `|` for a divider)
*/
'buttons' => function ($buttons = true) {
return $buttons;
},
/**
* Enables/disables the character counter in the top right corner
*/
'counter' => function (bool $counter = true) {
return $counter;
},
/**
* Sets the default text when a new page/file/user is created
*/
'default' => function (string $default = null) {
return trim($default);
},
/**
* Sets the options for the files picker
*/
'files' => function ($files = []) {
if (is_string($files) === true) {
return ['query' => $files];
}
if (is_array($files) === false) {
$files = [];
}
return $files;
},
/**
* Sets the font family (sans or monospace)
*/
'font' => function (string $font = null) {
return $font === 'monospace' ? 'monospace' : 'sans-serif';
},
/**
* Maximum number of allowed characters
*/
'maxlength' => function (int $maxlength = null) {
return $maxlength;
},
/**
* Minimum number of required characters
*/
'minlength' => function (int $minlength = null) {
return $minlength;
},
/**
* Changes the size of the textarea. Available sizes: `small`, `medium`, `large`, `huge`
*/
'size' => function (string $size = null) {
return $size;
},
/**
* If `false`, spellcheck will be switched off
*/
'spellcheck' => function (bool $spellcheck = true) {
return $spellcheck;
},
'value' => function (string $value = null) {
return trim($value);
}
],
'api' => function () {
return [
[
'pattern' => 'files',
'action' => function () {
$params = array_merge($this->field()->files(), [
'page' => $this->requestQuery('page'),
'search' => $this->requestQuery('search')
]);
return $this->field()->filepicker($params);
}
],
[
'pattern' => 'upload',
'method' => 'POST',
'action' => function () {
$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->dragText('auto', $absolute),
];
});
}
]
];
},
'validations' => [
'minlength',
'maxlength'
]
];

View file

@ -0,0 +1,151 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Toolkit\I18n;
return [
'mixins' => ['datetime'],
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Sets the default time when a new page/file/user is created
*/
'default' => function ($default = null) {
return $default;
},
/**
* Custom format (dayjs tokens: `HH`, `hh`, `mm`, `ss`, `a`) that is
* used to display the field in the Panel
*/
'display' => function ($display = null) {
return I18n::translate($display, $display);
},
/**
* Changes the clock icon
*/
'icon' => function (string $icon = 'clock') {
return $icon;
},
/**
* Latest time, which can be selected/saved (H:i or H:i:s)
*/
'max' => function (string $max = null) {
return $max ? $this->toDatetime(date('Y-m-d ') . $max) : null;
},
/**
* Earliest time, which can be selected/saved (H:i or H:i:s)
*/
'min' => function (string $min = null) {
return $min ? $this->toDatetime(date('Y-m-d ') . $min) : null;
},
/**
* `12` or `24` hour notation. If `12`, an AM/PM selector will be shown.
* If `display` is defined, that option will take priority.
*/
'notation' => function (int $value = 24) {
return $value === 24 ? 24 : 12;
},
/**
* Round to the nearest: sub-options for `unit` (minute) and `size` (5)
*/
'step' => function ($step = null) {
$default = [
'size' => 5,
'unit' => 'minute'
];
if ($step === null) {
return $default;
}
if (is_array($step) === true) {
$step = array_merge($default, $step);
$step['unit'] = strtolower($step['unit']);
return $step;
}
if (is_int($step) === true) {
return array_merge($default, ['size' => $step]);
}
if (is_string($step) === true) {
return array_merge($default, ['unit' => strtolower($step)]);
}
},
'value' => function ($value = null) {
return $value;
}
],
'computed' => [
'default' => function () {
return $this->toDatetime($this->default, 'H:i:s');
},
'display' => function () {
if ($this->display) {
return $this->display;
}
return $this->notation === 24 ? 'HH:mm' : 'h:mm a';
},
'format' => function () {
return $this->props['format'] ?? 'H:i:s';
},
'value' => function () {
return $this->toDatetime($this->value, 'H:i:s');
}
],
'validations' => [
'time',
'minMax' => function ($value) {
$min = $this->min ? strtotime($this->min) : null;
$max = $this->max ? strtotime($this->max) : null;
$value = strtotime($this->value());
$format = 'H:i:s';
$errors = [];
if ($value && $min && $value < $min) {
$errors['min'] = $min;
}
if ($value && $max && $value > $max) {
$errors['max'] = $max;
}
if (empty($errors) === false) {
if ($min && $max) {
throw new Exception([
'key' => 'validation.time.between',
'data' => [
'min' => date($format, $min),
'max' => date($format, $max)
]
]);
} elseif ($min) {
throw new Exception([
'key' => 'validation.time.after',
'data' => [
'time' => date($format, $min),
]
]);
} else {
throw new Exception([
'key' => 'validation.time.before',
'data' => [
'time' => date($format, $max),
]
]);
}
}
return true;
},
]
];

View file

@ -0,0 +1,66 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Default value which will be saved when a new page/user/file is created
*/
'default' => function ($default = null) {
return $this->default = $default;
},
/**
* Sets the text next to the toggle. The text can be a string or an array of two options. The first one is the negative text and the second one the positive. The text will automatically switch when the toggle is triggered.
*/
'text' => function ($value = null) {
if (is_array($value) === true) {
if (A::isAssociative($value) === true) {
return I18n::translate($value, $value);
}
foreach ($value as $key => $val) {
$value[$key] = I18n::translate($val, $val);
}
return $value;
}
return I18n::translate($value, $value);
},
],
'computed' => [
'default' => function () {
return $this->toBool($this->default);
},
'value' => function () {
if ($this->props['value'] === null) {
return $this->default();
} else {
return $this->toBool($this->props['value']);
}
}
],
'methods' => [
'toBool' => function ($value) {
return in_array($value, [true, 'true', 1, '1', 'on'], true) === true;
}
],
'save' => function (): string {
return $this->value() === true ? 'true' : 'false';
},
'validations' => [
'boolean',
'required' => function ($value) {
if ($this->isRequired() && ($value === false || $this->isEmpty($value))) {
throw new InvalidArgumentException(I18n::translate('field.required'));
}
},
]
];

View file

@ -0,0 +1,41 @@
<?php
use Kirby\Toolkit\I18n;
return [
'extends' => 'text',
'props' => [
/**
* Unset inherited props
*/
'converter' => null,
'counter' => null,
'spellcheck' => null,
/**
* Sets the HTML5 autocomplete attribute
*/
'autocomplete' => function (string $autocomplete = 'url') {
return $autocomplete;
},
/**
* Changes the link icon
*/
'icon' => function (string $icon = 'url') {
return $icon;
},
/**
* Sets custom placeholder text, when the field is empty
*/
'placeholder' => function ($value = null) {
return I18n::translate($value, $value) ?? 'https://example.com';
}
],
'validations' => [
'minlength',
'maxlength',
'url'
],
];

View file

@ -0,0 +1,97 @@
<?php
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
'mixins' => ['min', 'picker', 'userpicker'],
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* Default selected user(s) when a new page/file/user is created
*/
'default' => function ($default = null) {
if ($default === false) {
return [];
}
if ($default === null && $user = $this->kirby()->user()) {
return [
$this->userResponse($user)
];
}
return $this->toUsers($default);
},
'value' => function ($value = null) {
return $this->toUsers($value);
},
],
'computed' => [
/**
* Unset inherited computed
*/
'default' => null
],
'methods' => [
'userResponse' => function ($user) {
return $user->panelPickerData([
'info' => $this->info,
'image' => $this->image,
'text' => $this->text,
]);
},
'toUsers' => function ($value = null) {
$users = [];
$kirby = kirby();
foreach (Data::decode($value, 'yaml') as $email) {
if (is_array($email) === true) {
$email = $email['email'] ?? null;
}
if ($email !== null && ($user = $kirby->user($email))) {
$users[] = $this->userResponse($user);
}
}
return $users;
}
],
'api' => function () {
return [
[
'pattern' => '/',
'action' => function () {
$field = $this->field();
return $field->userpicker([
'image' => $field->image(),
'info' => $field->info(),
'limit' => $field->limit(),
'page' => $this->requestQuery('page'),
'query' => $field->query(),
'search' => $this->requestQuery('search'),
'text' => $field->text()
]);
}
]
];
},
'save' => function ($value = null) {
return A::pluck($value, 'id');
},
'validations' => [
'max',
'min'
]
];

View file

@ -0,0 +1,33 @@
<?php
return [
'props' => [
/**
* Enables inline mode, which will not wrap new lines in paragraphs and creates hard breaks instead.
*
* @param bool $inline
*/
'inline' => function (bool $inline = false) {
return $inline;
},
/**
* Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`. Activate them all by passing `true`. Deactivate them all by passing `false`
* @param array|bool $marks
*/
'marks' => function ($marks = true) {
return $marks;
},
/**
* Sets the allowed nodes. Available nodes: `bulletList`, `orderedList`, `heading`, `horizontalRule`, `listItem`. Activate/deactivate them all by passing `true`/`false`. Default nodes are `heading`, `bulletList`, `orderedList`.
* @param array|bool|null $nodes
*/
'nodes' => function ($nodes = null) {
return $nodes;
}
],
'computed' => [
'value' => function () {
return trim($this->value);
}
],
];