Upgrade to 4.1.0

This commit is contained in:
Bastian Allgeier
2024-01-30 16:41:06 +01:00
parent 5c44c8fcfd
commit 9345fc1a0b
59 changed files with 678 additions and 274 deletions

View File

@@ -632,10 +632,20 @@ class Api
$uploadMaxFileSize = Str::toBytes(ini_get('upload_max_filesize'));
if ($postMaxSize < $uploadMaxFileSize) {
throw new Exception(I18n::translate('upload.error.iniPostSize'));
throw new Exception(
I18n::translate(
'upload.error.iniPostSize',
'The uploaded file exceeds the post_max_size directive in php.ini'
)
);
}
throw new Exception(I18n::translate('upload.error.noFiles'));
throw new Exception(
I18n::translate(
'upload.error.noFiles',
'No files were uploaded'
)
);
}
foreach ($files as $upload) {
@@ -651,7 +661,8 @@ class Api
try {
if ($upload['error'] !== 0) {
throw new Exception(
$errorMessages[$upload['error']] ?? I18n::translate('upload.error.default')
$errorMessages[$upload['error']] ??
I18n::translate('upload.error.default', 'The file could not be uploaded')
);
}

View File

@@ -655,7 +655,7 @@ class App
* specified by the path
*
* Example:
* <?= App::image('some/page/myimage.jpg') ?>
* <?= $kirby->image('some/page/myimage.jpg') ?>
*
* @todo merge with App::file()
*/
@@ -831,9 +831,9 @@ class App
}
}
$data['kirby'] = $data['kirby'] ?? $this;
$data['site'] = $data['site'] ?? $data['kirby']->site();
$data['parent'] = $data['parent'] ?? $data['site']->page();
$data['kirby'] ??= $this;
$data['site'] ??= $data['kirby']->site();
$data['parent'] ??= $data['site']->page();
return (new KirbyTag($type, $value, $attr, $data, $this->options))->render();
}
@@ -1464,9 +1464,7 @@ class App
protected function setRoles(array $roles = null): static
{
if ($roles !== null) {
$this->roles = Roles::factory($roles, [
'kirby' => $this
]);
$this->roles = Roles::factory($roles);
}
return $this;
@@ -1480,9 +1478,7 @@ class App
protected function setSite(Site|array $site = null): static
{
if (is_array($site) === true) {
$site = new Site($site + [
'kirby' => $this
]);
$site = new Site($site);
}
$this->site = $site;
@@ -1497,7 +1493,6 @@ class App
return $this->site ??= new Site([
'errorPageId' => $this->options['error'] ?? 'error',
'homePageId' => $this->options['home'] ?? 'home',
'kirby' => $this,
'url' => $this->url('index'),
]);
}

View File

@@ -86,9 +86,7 @@ trait AppUsers
protected function setUsers(array $users = null): static
{
if ($users !== null) {
$this->users = Users::factory($users, [
'kirby' => $this
]);
$this->users = Users::factory($users);
}
return $this;
@@ -128,7 +126,6 @@ trait AppUsers
{
return $this->users ??= Users::load(
$this->root('accounts'),
['kirby' => $this]
);
}
}

View File

@@ -696,6 +696,10 @@ class Blueprint
return null;
}
if ($this->sections[$name] instanceof Section) {
return $this->sections[$name]; //@codeCoverageIgnore
}
// get all props
$props = $this->sections[$name];
@@ -703,7 +707,7 @@ class Blueprint
$props['model'] = $this->model();
// create a new section object
return new Section($props['type'], $props);
return $this->sections[$name] = new Section($props['type'], $props);
}
/**
@@ -713,7 +717,10 @@ class Blueprint
{
return A::map(
$this->sections,
fn ($section) => $this->section($section['name'])
fn ($section) => match (true) {
$section instanceof Section => $section,
default => $this->section($section['name'])
}
);
}

View File

@@ -30,6 +30,11 @@ use Kirby\Form\Field\LayoutField;
*/
class Core
{
/**
* Optional override for the auto-detected index root
*/
public static string|null $indexRoot = null;
protected array $cache = [];
protected string $root;
@@ -316,7 +321,7 @@ class Core
'i18n:translations' => fn (array $roots) => $roots['i18n'] . '/translations',
'i18n:rules' => fn (array $roots) => $roots['i18n'] . '/rules',
'index' => fn (array $roots) => dirname(__DIR__, 3),
'index' => fn (array $roots) => static::$indexRoot ?? dirname(__DIR__, 3),
'assets' => fn (array $roots) => $roots['index'] . '/assets',
'content' => fn (array $roots) => $roots['index'] . '/content',
'media' => fn (array $roots) => $roots['index'] . '/media',

View File

@@ -229,9 +229,25 @@ trait FileActions
// gather content
$content = $props['content'] ?? [];
// make sure that a UUID gets generated and
// added to content right away
if (Uuids::enabled() === true) {
// make sure that a UUID gets generated
// and added to content right away
if (
Uuids::enabled() === true &&
empty($content['uuid']) === true
) {
// sets the current uuid if it is the exact same file
if ($file->exists() === true) {
$existing = $file->parent()->file($file->filename());
if (
$file->sha1() === $upload->sha1() &&
$file->template() === $existing->template()
) {
// use existing content data if it is the exact same file
$content = $existing->content()->toArray();
}
}
$content['uuid'] ??= Uuid::generate();
}

View File

@@ -90,11 +90,9 @@ class Files extends Collection
public static function factory(array $files, Page|Site|User $parent): static
{
$collection = new static([], $parent);
$kirby = $parent->kirby();
foreach ($files as $props) {
$props['collection'] = $collection;
$props['kirby'] = $kirby;
$props['parent'] = $parent;
$file = File::factory($props);

View File

@@ -135,9 +135,14 @@ class Helpers
return true;
});
$result = $action();
restore_error_handler();
try {
$result = $action();
} finally {
// always restore the error handler, even if the
// action or the standard error handler threw an
// exception; this avoids modifying global state
restore_error_handler();
}
return $override ?? $result;
}

View File

@@ -49,9 +49,11 @@ class License
/**
* Returns the activation date if available
*/
public function activation(string|IntlDateFormatter|null $format = null): int|string|null
{
return $this->activation !== null ? Str::date(strtotime($this->activation), $format) : null;
public function activation(
string|IntlDateFormatter|null $format = null,
string|null $handler = null
): int|string|null {
return $this->activation !== null ? Str::date(strtotime($this->activation), $format, $handler) : null;
}
/**
@@ -85,9 +87,11 @@ class License
/**
* Returns the purchase date if available
*/
public function date(string|IntlDateFormatter|null $format = null): int|string|null
{
return $this->date !== null ? Str::date(strtotime($this->date), $format) : null;
public function date(
string|IntlDateFormatter|null $format = null,
string|null $handler = null
): int|string|null {
return $this->date !== null ? Str::date(strtotime($this->date), $format, $handler) : null;
}
/**
@@ -355,14 +359,16 @@ class License
/**
* Returns the renewal date
*/
public function renewal(string|IntlDateFormatter|null $format = null): int|string|null
{
public function renewal(
string|IntlDateFormatter|null $format = null,
string|null $handler = null
): int|string|null {
if ($this->activation === null) {
return null;
}
$time = strtotime('+3 years', $this->activation());
return Str::date($time, $format);
return Str::date($time, $format, $handler);
}
/**

View File

@@ -784,10 +784,17 @@ abstract class ModelWithContent implements Identifiable
]);
}
$arguments = [static::CLASS_ALIAS => $this, 'values' => $form->data(), 'strings' => $form->strings(), 'languageCode' => $languageCode];
return $this->commit('update', $arguments, function ($model, $values, $strings, $languageCode) {
return $model->save($strings, $languageCode, true);
});
return $this->commit(
'update',
[
static::CLASS_ALIAS => $this,
'values' => $form->data(),
'strings' => $form->strings(),
'languageCode' => $languageCode
],
fn ($model, $values, $strings, $languageCode) =>
$model->save($strings, $languageCode, true)
);
}
/**

View File

@@ -282,10 +282,16 @@ trait PageActions
return $this;
}
$arguments = ['page' => $this, 'status' => 'listed', 'position' => $num];
$page = $this->commit('changeStatus', $arguments, function ($page, $status, $position) {
return $page->publish()->changeNum($position);
});
$page = $this->commit(
'changeStatus',
[
'page' => $this,
'status' => 'listed',
'position' => $num
],
fn ($page, $status, $position) =>
$page->publish()->changeNum($position)
);
if ($this->blueprint()->num() === 'default') {
$page->resortSiblingsAfterListing($num);
@@ -303,10 +309,15 @@ trait PageActions
return $this;
}
$arguments = ['page' => $this, 'status' => 'unlisted', 'position' => null];
$page = $this->commit('changeStatus', $arguments, function ($page) {
return $page->publish()->changeNum(null);
});
$page = $this->commit(
'changeStatus',
[
'page' => $this,
'status' => 'unlisted',
'position' => null
],
fn ($page) => $page->publish()->changeNum(null)
);
$this->resortSiblingsAfterUnlisting();
@@ -355,7 +366,19 @@ trait PageActions
string $title,
string|null $languageCode = null
): static {
// if the `$languageCode` argument is not set and is not the default language
// the `$languageCode` argument is sent as the current language
if (
$languageCode === null &&
$language = $this->kirby()->language()
) {
if ($language->isDefault() === false) {
$languageCode = $language->code();
}
}
$arguments = ['page' => $this, 'title' => $title, 'languageCode' => $languageCode];
return $this->commit('changeTitle', $arguments, function ($page, $title, $languageCode) {
$page = $page->save(['title' => $title], $languageCode);

View File

@@ -147,7 +147,6 @@ class Pages extends Collection
): static {
$model ??= App::instance()->site();
$children = new static([], $model);
$kirby = $model->kirby();
if ($model instanceof Page) {
$parent = $model;
@@ -158,7 +157,6 @@ class Pages extends Collection
}
foreach ($pages as $props) {
$props['kirby'] = $kirby;
$props['parent'] = $parent;
$props['site'] = $site;
$props['isDraft'] = $draft ?? $props['isDraft'] ?? $props['draft'] ?? false;
@@ -445,9 +443,10 @@ class Pages extends Collection
$templates = [$templates];
}
return $this->filter(function ($page) use ($templates) {
return !in_array($page->intendedTemplate()->name(), $templates);
});
return $this->filter(
fn ($page) =>
!in_array($page->intendedTemplate()->name(), $templates)
);
}
/**
@@ -480,9 +479,10 @@ class Pages extends Collection
$templates = [$templates];
}
return $this->filter(function ($page) use ($templates) {
return in_array($page->intendedTemplate()->name(), $templates);
});
return $this->filter(
fn ($page) =>
in_array($page->intendedTemplate()->name(), $templates)
);
}
/**

View File

@@ -49,15 +49,26 @@ trait SiteActions
*/
public function changeTitle(
string $title,
string $languageCode = null
string|null $languageCode = null
): static {
$site = $this;
$title = trim($title);
$arguments = compact('site', 'title', 'languageCode');
// if the `$languageCode` argument is not set and is not the default language
// the `$languageCode` argument is sent as the current language
if (
$languageCode === null &&
$language = $this->kirby()->language()
) {
if ($language->isDefault() === false) {
$languageCode = $language->code();
}
}
return $this->commit('changeTitle', $arguments, function ($site, $title, $languageCode) {
return $site->save(['title' => $title], $languageCode);
});
$arguments = ['site' => $this, 'title' => trim($title), 'languageCode' => $languageCode];
return $this->commit(
'changeTitle',
$arguments,
fn ($site, $title, $languageCode) => $site->save(['title' => $title], $languageCode)
);
}
/**

View File

@@ -33,13 +33,17 @@ class Structure extends Items
array $items = null,
array $params = []
): static {
// Bake-in index as ID for all items
// TODO: remove when adding UUID supports to Structures
if (is_array($items) === true) {
$items = array_map(function ($item, $index) {
if (is_array($item) === true) {
// pass a clean content array without special `Item` keys
$item['content'] = $item;
// bake-in index as ID for all items
// TODO: remove when adding UUID supports to Structures
$item['id'] ??= $index;
}
return $item;
}, $items, array_keys($items));
}

View File

@@ -566,7 +566,7 @@ Database::$types['mysql'] = [
$parts[] = 'dbname=' . $params['database'];
}
$parts[] = 'charset=' . ($params['charset'] ?? 'utf8');
$parts[] = 'charset=' . ($params['charset'] ?? 'utf8mb4');
return 'mysql:' . implode(';', $parts);
}

View File

@@ -47,7 +47,8 @@ class Db
'password' => Config::get('db.password', ''),
'database' => Config::get('db.database', ''),
'prefix' => Config::get('db.prefix', ''),
'port' => Config::get('db.port', '')
'port' => Config::get('db.port', ''),
'charset' => Config::get('db.charset')
];
return static::$connection = new Database($params);

View File

@@ -169,7 +169,10 @@ class Dir
$result[] = $entry;
if ($recursive === true && is_dir($root) === true) {
$result = array_merge($result, static::index($root, true, $ignore, $entry));
$result = [
...$result,
...static::index($root, true, $ignore, $entry)
];
}
}
@@ -402,10 +405,8 @@ class Dir
$parent = dirname($dir);
if ($recursive === true) {
if (is_dir($parent) === false) {
static::make($parent, true);
}
if ($recursive === true && is_dir($parent) === false) {
static::make($parent, true);
}
if (is_writable($parent) === false) {
@@ -429,18 +430,19 @@ class Dir
* @param 'date'|'intl'|'strftime'|null $handler Custom date handler or `null`
* for the globally configured one
*/
public static function modified(string $dir, string $format = null, string|null $handler = null): int|string
{
public static function modified(
string $dir,
string $format = null,
string|null $handler = null
): int|string {
$modified = filemtime($dir);
$items = static::read($dir);
foreach ($items as $item) {
if (is_file($dir . '/' . $item) === true) {
$newModified = filemtime($dir . '/' . $item);
} else {
$newModified = static::modified($dir . '/' . $item);
}
$newModified = match (is_file($dir . '/' . $item)) {
true => filemtime($dir . '/' . $item),
false => static::modified($dir . '/' . $item)
};
$modified = ($newModified > $modified) ? $newModified : $modified;
}
@@ -595,7 +597,10 @@ class Dir
return true;
}
if (is_dir($subdir) === true && static::wasModifiedAfter($subdir, $time) === true) {
if (
is_dir($subdir) === true &&
static::wasModifiedAfter($subdir, $time) === true
) {
return true;
}
}

View File

@@ -800,11 +800,11 @@ class F
);
}
try {
return filesize($file);
} catch (Throwable) {
return 0;
if ($size = @filesize($file)) {
return $size;
}
return 0;
}
/**

View File

@@ -89,6 +89,7 @@ class Filename
'blur' => $this->blur(),
'bw' => $this->grayscale(),
'q' => $this->quality(),
'sharpen' => $this->sharpen(),
];
$array = array_filter(
@@ -244,6 +245,18 @@ class Filename
return F::safeBasename($name);
}
/**
* Normalizes the sharpen option value
*/
public function sharpen(): int|false
{
return match ($this->attributes['sharpen'] ?? false) {
false => false,
true => 50,
default => (int)$this->attributes['sharpen']
};
}
/**
* Returns the converted filename as string
*/

View File

@@ -296,7 +296,7 @@ class Form
$kirby = App::instance(null, true);
// only modify the fields if we have a valid Kirby multilang instance
if ($kirby?->multilang() === false) {
if ($kirby?->multilang() !== true) {
return $fields;
}

View File

@@ -322,10 +322,15 @@ class Environment
}
// @codeCoverageIgnoreStart
$sapi = php_sapi_name();
if ($sapi === 'cli') {
return true;
}
$term = getenv('TERM');
if (
substr(PHP_SAPI, 0, 3) === 'cgi' &&
substr($sapi, 0, 3) === 'cgi' &&
$term &&
$term !== 'unknown'
) {

View File

@@ -60,6 +60,7 @@ class Darkroom
'quality' => 90,
'scaleHeight' => null,
'scaleWidth' => null,
'sharpen' => null,
'width' => null,
];
}
@@ -93,6 +94,11 @@ class Darkroom
unset($options['bw']);
}
// normalize the sharpen option
if ($options['sharpen'] === true) {
$options['sharpen'] = 50;
}
$options['quality'] ??= $this->settings['quality'];
return $options;

View File

@@ -33,6 +33,7 @@ class GdLib extends Darkroom
$image = $this->autoOrient($image, $options);
$image = $this->blur($image, $options);
$image = $this->grayscale($image, $options);
$image = $this->sharpen($image, $options);
$image->toFile($file, $mime, $options);
@@ -116,6 +117,18 @@ class GdLib extends Darkroom
return $image->desaturate();
}
/**
* Applies sharpening if activated in the options.
*/
protected function sharpen(SimpleImage $image, array $options): SimpleImage
{
if (is_int($options['sharpen']) === false) {
return $image;
}
return $image->sharpen($options['sharpen']);
}
/**
* Returns mime type based on `format` option
*/

View File

@@ -65,14 +65,6 @@ class ImageMagick extends Darkroom
// limit to single-threading to keep CPU usage sane
$command .= ' -limit thread 1';
// add JPEG size hint to optimize CPU and memory usage
if (F::mime($file) === 'image/jpeg') {
// add hint only when downscaling
if ($options['scaleWidth'] < 1 && $options['scaleHeight'] < 1) {
$command .= ' -define ' . escapeshellarg(sprintf('jpeg:size=%dx%d', $options['width'], $options['height']));
}
}
// append input file
return $command . ' ' . escapeshellarg($file);
}
@@ -100,6 +92,19 @@ class ImageMagick extends Darkroom
return null;
}
/**
* Applies sharpening if activated in the options.
*/
protected function sharpen(string $file, array $options): string|null
{
if (is_int($options['sharpen']) === false) {
return null;
}
$amount = max(1, min(100, $options['sharpen'])) / 100;
return '-sharpen ' . escapeshellarg('0x' . $amount);
}
/**
* Applies the correct settings for interlaced JPEGs if
* activated via options
@@ -133,6 +138,7 @@ class ImageMagick extends Darkroom
$command[] = $this->resize($file, $options);
$command[] = $this->quality($file, $options);
$command[] = $this->blur($file, $options);
$command[] = $this->sharpen($file, $options);
$command[] = $this->save($file, $options);
// remove all null values and join the parts

View File

@@ -76,10 +76,11 @@ class Image extends File
}
if (in_array($this->mime(), [
'image/avif',
'image/gif',
'image/jpeg',
'image/jp2',
'image/png',
'image/gif',
'image/webp'
])) {
return $this->dimensions = Dimensions::forImage($this->root);

View File

@@ -159,7 +159,9 @@ class OptionsQuery extends OptionsProvider
}
if ($result instanceof Collection === false) {
throw new InvalidArgumentException('Invalid query result data: ' . get_class($result));
$type = is_object($result) === true ? get_class($result) : gettype($result);
throw new InvalidArgumentException('Invalid query result data: ' . $type);
}
// create options array

View File

@@ -103,6 +103,18 @@ abstract class Model
return null;
}
// switched off from blueprint,
// only if not overwritten by $settings
$blueprint = $this->model->blueprint()->image();
if ($blueprint === false) {
if (empty($settings) === true) {
return null;
}
$blueprint = null;
}
// skip image thumbnail if option
// is explicitly set to show the icon
if ($settings === 'icon') {
@@ -116,7 +128,7 @@ abstract class Model
$settings = array_merge(
$this->imageDefaults(),
$settings ?? [],
$this->model->blueprint()->image() ?? [],
$blueprint ?? [],
);
if ($image = $this->imageSource($settings['query'] ?? null)) {

View File

@@ -105,19 +105,37 @@ class PageCreateDialog
*/
public function coreFields(): array
{
$title = $this->blueprint()->create()['title']['label'] ?? 'title';
$fields = [];
return [
'title' => Field::title([
'label' => I18n::translate($title, $title),
$title = $this->blueprint()->create()['title'] ?? null;
$slug = $this->blueprint()->create()['slug'] ?? null;
if ($title === false || $slug === false) {
throw new InvalidArgumentException('Page create dialog: title and slug must not be false');
}
// title field
if ($title === null || is_array($title) === true) {
$label = $title['label'] ?? 'title';
$fields['title'] = Field::title([
...$title ?? [],
'label' => I18n::translate($label, $label),
'required' => true,
'preselect' => true
]),
'slug' => Field::slug([
]);
}
// slug field
if ($slug === null) {
$fields['slug'] = Field::slug([
'required' => true,
'sync' => 'title',
'path' => $this->parent instanceof Page ? '/' . $this->parent->id() . '/' : '/'
]),
]);
}
return [
...$fields,
'parent' => Field::hidden(),
'section' => Field::hidden(),
'template' => Field::hidden(),
@@ -171,10 +189,10 @@ class PageCreateDialog
*/
public function fields(): array
{
return array_merge(
$this->coreFields(),
$this->customFields()
);
return [
...$this->coreFields(),
...$this->customFields()
];
}
/**
@@ -188,14 +206,29 @@ class PageCreateDialog
$this->template ??= $blueprints[0]['name'];
$status = $this->blueprint()->create()['status'] ?? 'draft';
$status = $this->blueprint()->status()[$status]['label'] ?? I18n::translate('page.status.' . $status);
$status = $this->blueprint()->create()['status'] ?? 'draft';
$status = $this->blueprint()->status()[$status]['label'] ?? null;
$status ??= I18n::translate('page.status.' . $status);
$fields = $this->fields();
$visible = array_filter(
$fields,
fn ($field) => ($field['hidden'] ?? null) !== true
);
// immediately submit the dialog if there is no editable field
if (count($visible) === 0 && count($blueprints) < 2) {
$input = $this->value();
$response = $this->submit($input);
$response['redirect'] ??= $this->parent->panel()->url(true);
Panel::go($response['redirect']);
}
return [
'component' => 'k-page-create-dialog',
'props' => [
'blueprints' => $blueprints,
'fields' => $this->fields(),
'fields' => $fields,
'submitButton' => I18n::template('page.create', [
'status' => $status
]),
@@ -220,24 +253,56 @@ class PageCreateDialog
]);
}
/**
* Generates values for title and slug
* from template strings from the blueprint
*/
public function resolveFieldTemplates(array $input): array
{
$title = $this->blueprint()->create()['title'] ?? null;
$slug = $this->blueprint()->create()['slug'] ?? null;
// create temporary page object
// to resolve the template strings
$page = new Page([
'slug' => 'tmp',
'template' => $this->template,
'parent' => $this->model(),
'content' => $input
]);
if (is_string($title)) {
$input['title'] = $page->toSafeString($title);
}
if (is_string($slug)) {
$input['slug'] = $page->toSafeString($slug);
}
return $input;
}
/**
* Prepares and cleans up the input data
*/
public function sanitize(array $input): array
{
$input['slug'] ??= $this->slug ?? '';
$input['title'] ??= $this->title ?? '';
$input['slug'] ??= $this->slug ?? '';
$content = [
'title' => trim($input['title']),
];
$input = $this->resolveFieldTemplates($input);
$content = ['title' => trim($input['title'])];
foreach ($this->customFields() as $name => $field) {
$content[$name] = $input[$name] ?? null;
}
// create temporary form to sanitize the input
// and add default values
$form = Form::for($this->model(), ['values' => $content]);
return [
'content' => $content,
'content' => $form->strings(true),
'slug' => $input['slug'],
'template' => $this->template,
];
@@ -301,13 +366,22 @@ class PageCreateDialog
public function value(): array
{
return [
$value = [
'parent' => $this->parentId,
'section' => $this->sectionId,
'slug' => $this->slug ?? '',
'slug' => '',
'template' => $this->template,
'title' => $this->title ?? '',
'title' => '',
'view' => $this->viewId,
];
// add default values for custom fields
foreach ($this->customFields() as $name => $field) {
if ($default = $field['default'] ?? null) {
$value[$name] = $default;
}
}
return $value;
}
}

View File

@@ -11,7 +11,6 @@ use Kirby\Http\Cookie;
use Kirby\Http\Url;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\SymmetricCrypto;
use Throwable;
/**
* @package Kirby Session
@@ -661,6 +660,7 @@ class Session
// skip if we don't have the key (only the case for moved sessions)
$hmac = Str::before($data, "\n");
$data = trim(Str::after($data, "\n"));
if (
$this->tokenKey !== null &&
hash_equals(hash_hmac('sha256', $data, $this->tokenKey), $hmac) !== true
@@ -675,16 +675,15 @@ class Session
}
// decode the serialized data
try {
$data = unserialize($data);
} catch (Throwable $e) {
$data = @unserialize($data);
if ($data === false) {
throw new LogicException([
'key' => 'session.invalid',
'data' => ['token' => $this->token()],
'fallback' => 'Session "' . $this->token() . '" is invalid',
'translate' => false,
'httpCode' => 500,
'previous' => $e
'httpCode' => 500
]);
}

View File

@@ -351,6 +351,31 @@ class A
return in_array($value, $array, $strict);
}
/**
* Join array elements as a string,
* also supporting nested arrays
*/
public static function implode(
array $array,
string $separator = ''
): string {
$result = '';
foreach ($array as $value) {
if (empty($result) === false) {
$result .= $separator;
}
if (is_array($value) === true) {
$value = static::implode($value, $separator);
}
$result .= $value;
}
return $result;
}
/**
* Checks whether an array is associative or not
*
@@ -914,9 +939,7 @@ class A
*
* // with callback
* A::update($user, [
* 'username' => function ($username) {
* return $username . ' j. simpson'
* }
* 'username' => fn ($username) => $username . ' j. simpson'
* ]);
* </code>
*/

View File

@@ -1258,74 +1258,58 @@ Collection::$filters['!='] = function ($collection, $field, $test, $split = fals
* In Filter
*/
Collection::$filters['in'] = [
'validator' => function ($value, $test) {
return in_array($value, $test) === true;
},
'strict' => false
'validator' => fn ($value, $test) => in_array($value, $test) === true,
'strict' => false
];
/**
* Not In Filter
*/
Collection::$filters['not in'] = [
'validator' => function ($value, $test) {
return in_array($value, $test) === false;
},
'validator' => fn ($value, $test) => in_array($value, $test) === false
];
/**
* Contains Filter
*/
Collection::$filters['*='] = [
'validator' => function ($value, $test) {
return strpos($value, $test) !== false;
},
'strict' => false
'validator' => fn ($value, $test) => strpos($value, $test) !== false,
'strict' => false
];
/**
* Not Contains Filter
*/
Collection::$filters['!*='] = [
'validator' => function ($value, $test) {
return strpos($value, $test) === false;
},
'validator' => fn ($value, $test) => strpos($value, $test) === false
];
/**
* More Filter
*/
Collection::$filters['>'] = [
'validator' => function ($value, $test) {
return $value > $test;
}
'validator' => fn ($value, $test) => $value > $test
];
/**
* Min Filter
*/
Collection::$filters['>='] = [
'validator' => function ($value, $test) {
return $value >= $test;
}
'validator' => fn ($value, $test) => $value >= $test
];
/**
* Less Filter
*/
Collection::$filters['<'] = [
'validator' => function ($value, $test) {
return $value < $test;
}
'validator' => fn ($value, $test) => $value < $test
];
/**
* Max Filter
*/
Collection::$filters['<='] = [
'validator' => function ($value, $test) {
return $value <= $test;
}
'validator' => fn ($value, $test) => $value <= $test
];
/**
@@ -1340,9 +1324,7 @@ Collection::$filters['$='] = [
* Not Ends With Filter
*/
Collection::$filters['!$='] = [
'validator' => function ($value, $test) {
return V::endsWith($value, $test) === false;
}
'validator' => fn ($value, $test) => V::endsWith($value, $test) === false
];
/**
@@ -1357,19 +1339,15 @@ Collection::$filters['^='] = [
* Not Starts With Filter
*/
Collection::$filters['!^='] = [
'validator' => function ($value, $test) {
return V::startsWith($value, $test) === false;
}
'validator' => fn ($value, $test) => V::startsWith($value, $test) === false
];
/**
* Between Filter
*/
Collection::$filters['between'] = Collection::$filters['..'] = [
'validator' => function ($value, $test) {
return V::between($value, ...$test) === true;
},
'strict' => false
'validator' => fn ($value, $test) => V::between($value, ...$test) === true,
'strict' => false
];
/**
@@ -1384,9 +1362,7 @@ Collection::$filters['*'] = [
* Not Match Filter
*/
Collection::$filters['!*'] = [
'validator' => function ($value, $test) {
return V::match($value, $test) === false;
}
'validator' => fn ($value, $test) => V::match($value, $test) === false
];
/**
@@ -1421,63 +1397,49 @@ Collection::$filters['minwords'] = [
* Date Equals Filter
*/
Collection::$filters['date =='] = [
'validator' => function ($value, $test) {
return V::date($value, '==', $test);
}
'validator' => fn ($value, $test) => V::date($value, '==', $test)
];
/**
* Date Not Equals Filter
*/
Collection::$filters['date !='] = [
'validator' => function ($value, $test) {
return V::date($value, '!=', $test);
}
'validator' => fn ($value, $test) => V::date($value, '!=', $test)
];
/**
* Date More Filter
*/
Collection::$filters['date >'] = [
'validator' => function ($value, $test) {
return V::date($value, '>', $test);
}
'validator' => fn ($value, $test) => V::date($value, '>', $test)
];
/**
* Date Min Filter
*/
Collection::$filters['date >='] = [
'validator' => function ($value, $test) {
return V::date($value, '>=', $test);
}
'validator' => fn ($value, $test) => V::date($value, '>=', $test)
];
/**
* Date Less Filter
*/
Collection::$filters['date <'] = [
'validator' => function ($value, $test) {
return V::date($value, '<', $test);
}
'validator' => fn ($value, $test) => V::date($value, '<', $test)
];
/**
* Date Max Filter
*/
Collection::$filters['date <='] = [
'validator' => function ($value, $test) {
return V::date($value, '<=', $test);
}
'validator' => fn ($value, $test) => V::date($value, '<=', $test)
];
/**
* Date Between Filter
*/
Collection::$filters['date between'] = Collection::$filters['date ..'] = [
'validator' => function ($value, $test) {
return
'validator' => fn ($value, $test) =>
V::date($value, '>=', $test[0]) &&
V::date($value, '<=', $test[1]);
}
V::date($value, '<=', $test[1])
];

View File

@@ -222,7 +222,7 @@ class Date extends DateTime
*/
public function microsecond(): int
{
return $this->format('u');
return (int)$this->format('u');
}
/**
@@ -230,7 +230,7 @@ class Date extends DateTime
*/
public function millisecond(): int
{
return $this->format('v');
return (int)$this->format('v');
}
/**

View File

@@ -698,7 +698,7 @@ class Str
string $string = null,
string $needle,
bool $caseInsensitive = false
): int|bool {
): int|false {
if ($needle === '') {
throw new InvalidArgumentException('The needle must not be empty');
}

View File

@@ -134,16 +134,13 @@ class V
$value = $params[$index] ?? null;
if (is_array($value) === true) {
try {
foreach ($value as $key => $item) {
if (is_array($item) === true) {
$value[$key] = implode('|', $item);
}
foreach ($value as $key => $item) {
if (is_array($item) === true) {
$value[$key] = A::implode($item, '|');
}
$value = implode(', ', $value);
} catch (Throwable) {
$value = '-';
}
$value = implode(', ', $value);
}
$arguments[$parameter->getName()] = $value;