Upgrade to 3.6.1

This commit is contained in:
Bastian Allgeier
2021-12-07 12:39:37 +01:00
parent 9fc43ea22c
commit 70b8439c49
134 changed files with 19987 additions and 1100 deletions

6
kirby/.vscode/extensions.json vendored Executable file
View File

@@ -0,0 +1,6 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

4
kirby/.vscode/settings.json vendored Executable file
View File

@@ -0,0 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}

View File

@@ -6,7 +6,7 @@
*/ */
if ( if (
version_compare(PHP_VERSION, '7.4.0', '>=') === false || version_compare(PHP_VERSION, '7.4.0', '>=') === false ||
version_compare(PHP_VERSION, '8.1.0', '<') === false version_compare(PHP_VERSION, '8.2.0', '<') === false
) { ) {
die(include __DIR__ . '/views/php.php'); die(include __DIR__ . '/views/php.php');
} }

View File

@@ -8,7 +8,7 @@
"core" "core"
], ],
"homepage": "https://getkirby.com", "homepage": "https://getkirby.com",
"version": "3.6.0", "version": "3.6.1",
"license": "proprietary", "license": "proprietary",
"authors": [ "authors": [
{ {
@@ -21,7 +21,7 @@
"php": ">=7.4.0 <8.1.0", "php": ">=7.4.0 <8.1.0",
"ext-ctype": "*", "ext-ctype": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"claviska/simpleimage": "3.6.3", "claviska/simpleimage": "3.6.4",
"filp/whoops": "2.14.4", "filp/whoops": "2.14.4",
"getkirby/composer-installer": "^1.2.1", "getkirby/composer-installer": "^1.2.1",
"laminas/laminas-escaper": "2.9.0", "laminas/laminas-escaper": "2.9.0",
@@ -29,12 +29,21 @@
"mustangostang/spyc": "0.6.3", "mustangostang/spyc": "0.6.3",
"phpmailer/phpmailer": "6.5.1", "phpmailer/phpmailer": "6.5.1",
"psr/log": "1.1.4", "psr/log": "1.1.4",
"true/punycode": "2.1.1" "symfony/polyfill-intl-idn": "1.23.0",
"symfony/polyfill-mbstring": "1.23.1"
},
"replace": {
"symfony/polyfill-php72": "*"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,
"platform-check": false "platform-check": false
}, },
"extra": {
"unused": [
"symfony/polyfill-intl-idn"
]
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Kirby\\": "src/" "Kirby\\": "src/"

227
kirby/composer.lock generated
View File

@@ -4,11 +4,11 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "8712d96f826f859411fec3cce4f16e63", "content-hash": "9327de85e3414653ef22b3a147bb676e",
"packages": [ "packages": [
{ {
"name": "claviska/simpleimage", "name": "claviska/simpleimage",
"version": "3.6.3", "version": "3.6.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/claviska/SimpleImage.git", "url": "https://github.com/claviska/SimpleImage.git",
@@ -45,7 +45,7 @@
"description": "A PHP class that makes working with images as simple as possible.", "description": "A PHP class that makes working with images as simple as possible.",
"support": { "support": {
"issues": "https://github.com/claviska/SimpleImage/issues", "issues": "https://github.com/claviska/SimpleImage/issues",
"source": "https://github.com/claviska/SimpleImage/tree/3.6.3" "source": "https://github.com/claviska/SimpleImage/tree/3.6.4"
}, },
"funding": [ "funding": [
{ {
@@ -525,6 +525,177 @@
}, },
"time": "2021-05-03T11:20:27+00:00" "time": "2021-05-03T11:20:27+00:00"
}, },
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "65bd267525e82759e7d8c4e8ceea44f398838e65"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65",
"reference": "65bd267525e82759e7d8c4e8ceea44f398838e65",
"shasum": ""
},
"require": {
"php": ">=7.1",
"symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php72": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laurent Bassin",
"email": "laurent@bassin.info"
},
{
"name": "Trevor Rowbotham",
"email": "trevor.rowbotham@pm.me"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"idn",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-05-27T09:27:20+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-02-19T12:13:01+00:00"
},
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.23.1", "version": "v1.23.1",
@@ -604,56 +775,6 @@
} }
], ],
"time": "2021-05-27T12:26:48+00:00" "time": "2021-05-27T12:26:48+00:00"
},
{
"name": "true/punycode",
"version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/true/php-punycode.git",
"reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
"reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"phpunit/phpunit": "~4.7",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"TrueBV\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Renan Gonçalves",
"email": "renan.saddam@gmail.com"
}
],
"description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)",
"homepage": "https://github.com/true/php-punycode",
"keywords": [
"idna",
"punycode"
],
"support": {
"issues": "https://github.com/true/php-punycode/issues",
"source": "https://github.com/true/php-punycode/tree/master"
},
"time": "2016-11-16T10:37:54+00:00"
} }
], ],
"packages-dev": [], "packages-dev": [],

View File

@@ -200,16 +200,20 @@ return [
}, },
'submit' => function (string $id) { 'submit' => function (string $id) {
$page = Find::page($id); $page = Find::page($id);
$title = trim(get('title')); $title = trim(get('title', ''));
$slug = trim(get('slug')); $slug = trim(get('slug', ''));
// basic input validation before we move on // basic input validation before we move on
if (Str::length($title) === 0) { if (Str::length($title) === 0) {
throw new InvalidArgumentException(['key' => 'page.changeTitle.empty']); throw new InvalidArgumentException([
'key' => 'page.changeTitle.empty'
]);
} }
if (Str::length($slug) === 0) { if (Str::length($slug) === 0) {
throw new InvalidArgumentException(['key' => 'page.slug.invalid']); throw new InvalidArgumentException([
'key' => 'page.slug.invalid'
]);
} }
// nothing changed // nothing changed
@@ -318,7 +322,7 @@ return [
]; ];
}, },
'submit' => function () { 'submit' => function () {
$title = trim(get('title')); $title = trim(get('title', ''));
if (Str::length($title) === 0) { if (Str::length($title) === 0) {
throw new InvalidArgumentException([ throw new InvalidArgumentException([

View File

@@ -37,7 +37,7 @@ return [
'plugins' => $plugins, 'plugins' => $plugins,
'php' => phpversion(), 'php' => phpversion(),
'server' => $system->serverSoftware(), 'server' => $system->serverSoftware(),
'ssl' => Server::https(), 'https' => Server::https(),
'version' => $kirby->version(), 'version' => $kirby->version(),
] ]
]; ];

View File

@@ -28,9 +28,7 @@ return [
* @param string $url Relative or absolute URL * @param string $url Relative or absolute URL
* @param string|array $options An array of attributes for the link tag or a media attribute string * @param string|array $options An array of attributes for the link tag or a media attribute string
*/ */
'css' => function (App $kirby, string $url, $options = null): string { 'css' => fn (App $kirby, string $url, $options = null): string => $url,
return $url;
},
/** /**
@@ -84,9 +82,10 @@ return [
* @param \Kirby\Cms\App $kirby Kirby instance * @param \Kirby\Cms\App $kirby Kirby instance
* @param \Kirby\Cms\File|\Kirby\Filesystem\Asset $file The file object * @param \Kirby\Cms\File|\Kirby\Filesystem\Asset $file The file object
* @param array $options All thumb options (width, height, crop, blur, grayscale) * @param array $options All thumb options (width, height, crop, blur, grayscale)
* @return \Kirby\Cms\File|\Kirby\Cms\FileVersion * @return \Kirby\Cms\File|\Kirby\Cms\FileVersion|\Kirby\Filesystem\Asset
*/ */
'file::version' => function (App $kirby, $file, array $options = []) { 'file::version' => function (App $kirby, $file, array $options = []) {
// if file is not resizable, return
if ($file->isResizable() === false) { if ($file->isResizable() === false) {
return $file; return $file;
} }
@@ -96,14 +95,20 @@ return [
$template = $mediaRoot . '/{{ name }}{{ attributes }}.{{ extension }}'; $template = $mediaRoot . '/{{ name }}{{ attributes }}.{{ extension }}';
$thumbRoot = (new Filename($file->root(), $template, $options))->toString(); $thumbRoot = (new Filename($file->root(), $template, $options))->toString();
$thumbName = basename($thumbRoot); $thumbName = basename($thumbRoot);
$job = $mediaRoot . '/.jobs/' . $thumbName . '.json';
// check if the thumb already exists
if (file_exists($thumbRoot) === false) { if (file_exists($thumbRoot) === false) {
// if not, create job file
$job = $mediaRoot . '/.jobs/' . $thumbName . '.json';
try { try {
Data::write($job, array_merge($options, [ Data::write($job, array_merge($options, [
'filename' => $file->filename() 'filename' => $file->filename()
])); ]));
} catch (Throwable $e) { } catch (Throwable $e) {
// if thumb doesn't exist yet and job file cannot
// be created, return
return $file; return $file;
} }
} }
@@ -123,9 +128,7 @@ return [
* @param string $url Relative or absolute URL * @param string $url Relative or absolute URL
* @param string|array $options An array of attributes for the link tag or a media attribute string * @param string|array $options An array of attributes for the link tag or a media attribute string
*/ */
'js' => function (App $kirby, string $url, $options = null): string { 'js' => fn (App $kirby, string $url, $options = null): string => $url,
return $url;
},
/** /**
* Add your own Markdown parser * Add your own Markdown parser

View File

@@ -11,7 +11,7 @@ return [
], ],
'computed' => [ 'computed' => [
'value' => function () { 'value' => function () {
return trim($this->value); return trim($this->value ?? '');
} }
] ]
]; ];

View File

@@ -27,7 +27,7 @@ return [
* Sets the default text when a new page/file/user is created * Sets the default text when a new page/file/user is created
*/ */
'default' => function (string $default = null) { 'default' => function (string $default = null) {
return trim($default); return trim($default ?? '');
}, },
/** /**
@@ -81,7 +81,7 @@ return [
}, },
'value' => function (string $value = null) { 'value' => function (string $value = null) {
return trim($value); return trim($value ?? '');
} }
], ],
'api' => function () { 'api' => function () {

View File

@@ -29,7 +29,8 @@ return [
], ],
'computed' => [ 'computed' => [
'value' => function () { 'value' => function () {
return Sane::sanitize(trim($this->value), 'html'); $value = trim($this->value ?? '');
return Sane::sanitize($value, 'html');
} }
], ],
]; ];

View File

@@ -98,10 +98,7 @@ function csrf(?string $check = null)
function css($url, $options = null): ?string function css($url, $options = null): ?string
{ {
if (is_array($url) === true) { if (is_array($url) === true) {
$links = array_map(function ($url) use ($options) { $links = A::map($url, fn ($url) => css($url, $options));
return css($url, $options);
}, $url);
return implode(PHP_EOL, $links); return implode(PHP_EOL, $links);
} }
@@ -373,10 +370,7 @@ function invalid(array $data = [], array $rules = [], array $messages = []): arr
function js($url, $options = null): ?string function js($url, $options = null): ?string
{ {
if (is_array($url) === true) { if (is_array($url) === true) {
$scripts = array_map(function ($url) use ($options) { $scripts = A::map($url, fn ($url) => js($url, $options));
return js($url, $options);
}, $url);
return implode(PHP_EOL, $scripts); return implode(PHP_EOL, $scripts);
} }
@@ -584,7 +578,7 @@ function page(...$id)
*/ */
function pages(...$id) function pages(...$id)
{ {
if (count($id) === 1) { if (count($id) === 1 && is_array($id[0]) === false) {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
deprecated('Passing a single id to the `pages()` helper will return a Kirby\Cms\Pages collection with a single element instead of the single Kirby\Cms\Page object itself - starting in 3.7.0.'); deprecated('Passing a single id to the `pages()` helper will return a Kirby\Cms\Pages collection with a single element instead of the single Kirby\Cms\Page object itself - starting in 3.7.0.');
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd

View File

@@ -121,13 +121,14 @@ return function (App $app) {
return null; return null;
} }
$time = empty($field->value) === true ? strtotime($fallback) : $field->toTimestamp(); if (empty($field->value) === false) {
$time = $field->toTimestamp();
if ($format === null) { } else {
return $time; $time = strtotime($fallback);
} }
return ($app->option('date.handler', 'date'))($format, $time); $handler = $app->option('date.handler', 'date');
return Str::date($time, $format, $handler);
}, },
/** /**
@@ -384,7 +385,7 @@ return function (App $app) {
// Obsolete elements, script tags, image maps and form elements have // Obsolete elements, script tags, image maps and form elements have
// been excluded for safety reasons and as they are most likely not // been excluded for safety reasons and as they are most likely not
// needed in most cases. // needed in most cases.
$field->value = strip_tags($field->value, '<b><i><small><abbr><cite><code><dfn><em><kbd><strong><samp><var><a><bdo><br><img><q><span><sub><sup>'); $field->value = strip_tags($field->value, Html::$inlineList);
return $field; return $field;
}, },

View File

@@ -247,7 +247,7 @@
"field.blocks.image.caption": "Titulek", "field.blocks.image.caption": "Titulek",
"field.blocks.image.crop": "Oříznout", "field.blocks.image.crop": "Oříznout",
"field.blocks.image.link": "Odkaz", "field.blocks.image.link": "Odkaz",
"field.blocks.image.location": "Pozice", "field.blocks.image.location": "Umístění",
"field.blocks.image.name": "Obrázek", "field.blocks.image.name": "Obrázek",
"field.blocks.image.placeholder": "Vyberte obrázek", "field.blocks.image.placeholder": "Vyberte obrázek",
"field.blocks.image.ratio": "Poměr stran", "field.blocks.image.ratio": "Poměr stran",

View File

@@ -1,20 +1,20 @@
{ {
"account.changeName": "Change your name", "account.changeName": "Cambiar nombre",
"account.delete": "Delete your account", "account.delete": "Eliminar cuenta",
"account.delete.confirm": "Do you really want to delete your account? You will be logged out immediately. Your account cannot be recovered.", "account.delete.confirm": "¿Realmente quieres eliminar tu cuenta? Tu sesión se cerrará inmediatamente. Tu cuenta no podrá ser recuperada. ",
"add": "Agregar", "add": "Agregar",
"author": "Author", "author": "Autor",
"avatar": "Foto de perfil", "avatar": "Foto de perfil",
"back": "Regresar", "back": "Regresar",
"cancel": "Cancelar", "cancel": "Cancelar",
"change": "Cambiar", "change": "Cambiar",
"close": "Cerrar", "close": "Cerrar",
"confirm": "De acuerdo", "confirm": "De acuerdo",
"collapse": "Collapse", "collapse": "Colapsar",
"collapse.all": "Collapse All", "collapse.all": "Colapsar todos",
"copy": "Copiar", "copy": "Copiar",
"copy.all": "Copy all", "copy.all": "Copiar todo",
"create": "Crear", "create": "Crear",
"date": "Fecha", "date": "Fecha",
@@ -39,7 +39,7 @@
"dialog.users.empty": "No has seleccionado ningún usuario", "dialog.users.empty": "No has seleccionado ningún usuario",
"dimensions": "Dimensiones", "dimensions": "Dimensiones",
"disabled": "Desabilitado", "disabled": "Deshabilitado",
"discard": "Descartar", "discard": "Descartar",
"download": "Descargar", "download": "Descargar",
"duplicate": "Duplicar", "duplicate": "Duplicar",
@@ -49,7 +49,7 @@
"email": "Correo Electrónico", "email": "Correo Electrónico",
"email.placeholder": "correo@ejemplo.com", "email.placeholder": "correo@ejemplo.com",
"environment": "Environment", "environment": "Ambiente",
"error.access.code": "Código inválido", "error.access.code": "Código inválido",
"error.access.login": "Ingreso inválido", "error.access.login": "Ingreso inválido",
@@ -63,11 +63,11 @@
"error.blueprint.notFound": "El blueprint \"{name}\" no se pudo cargar.", "error.blueprint.notFound": "El blueprint \"{name}\" no se pudo cargar.",
"error.blocks.max.plural": "You must not add more than {max} blocks", "error.blocks.max.plural": "No debes añadir más de {max} bloques",
"error.blocks.max.singular": "You must not add more than one block", "error.blocks.max.singular": "No debes añadir más de un bloque",
"error.blocks.min.plural": "You must add at least {min} blocks", "error.blocks.min.plural": "Debes añadir al menos {min} bloques ",
"error.blocks.min.singular": "You must add at least one block", "error.blocks.min.singular": "Debes añadir al menos un bloque",
"error.blocks.validation": "There's an error in block {index}", "error.blocks.validation": "Hay un error en el bloque {index}",
"error.email.preset.notFound": "El preajuste de email \"{name}\" no se pudo encontrar.", "error.email.preset.notFound": "El preajuste de email \"{name}\" no se pudo encontrar.",
@@ -102,7 +102,7 @@
"error.language.code": "Por favor introduce un código válido para el idioma", "error.language.code": "Por favor introduce un código válido para el idioma",
"error.language.duplicate": "El idioma ya existe", "error.language.duplicate": "El idioma ya existe",
"error.language.name": "Por favor introduce un nombre válido para el idioma", "error.language.name": "Por favor introduce un nombre válido para el idioma",
"error.language.notFound": "The language could not be found", "error.language.notFound": "No se pudo encontrar el idioma",
"error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}",
"error.layout.validation.settings": "There's an error in layout {index} settings", "error.layout.validation.settings": "There's an error in layout {index} settings",
@@ -111,7 +111,7 @@
"error.license.email": "Por favor ingresa un correo electrónico valido", "error.license.email": "Por favor ingresa un correo electrónico valido",
"error.license.verification": "La licencia no pude ser verificada", "error.license.verification": "La licencia no pude ser verificada",
"error.offline": "The Panel is currently offline", "error.offline": "El Panel se encuentra fuera de linea ",
"error.page.changeSlug.permission": "No está permitido cambiar el apéndice de URL para \"{slug}\".", "error.page.changeSlug.permission": "No está permitido cambiar el apéndice de URL para \"{slug}\".",
"error.page.changeStatus.incomplete": "La página tiene errores y no puede ser publicada.", "error.page.changeStatus.incomplete": "La página tiene errores y no puede ser publicada.",
@@ -176,7 +176,7 @@
"error.user.password.invalid": "Por favor ingresa una contraseña valida. Las contraseñas deben tener al menos 8 caracteres de largo.", "error.user.password.invalid": "Por favor ingresa una contraseña valida. Las contraseñas deben tener al menos 8 caracteres de largo.",
"error.user.password.notSame": "Por favor confirma la contrase\u00f1a", "error.user.password.notSame": "Por favor confirma la contrase\u00f1a",
"error.user.password.undefined": "El usuario no tiene contraseña", "error.user.password.undefined": "El usuario no tiene contraseña",
"error.user.password.wrong": "Wrong password", "error.user.password.wrong": "Contraseña incorrecta",
"error.user.role.invalid": "Por favor ingresa un rol valido", "error.user.role.invalid": "Por favor ingresa un rol valido",
"error.user.undefined": "El usuario no pudo ser encontrado", "error.user.undefined": "El usuario no pudo ser encontrado",
"error.user.update.permission": "No tienes permiso para actualizar al usuario \"{name}\"", "error.user.update.permission": "No tienes permiso para actualizar al usuario \"{name}\"",
@@ -217,54 +217,54 @@
"error.validation.size": "El tamaño del valor debe ser \"{size}\"", "error.validation.size": "El tamaño del valor debe ser \"{size}\"",
"error.validation.startswith": "El valor debe comenzar con \"{start}\"", "error.validation.startswith": "El valor debe comenzar con \"{start}\"",
"error.validation.time": "Por favor ingresa una hora válida", "error.validation.time": "Por favor ingresa una hora válida",
"error.validation.time.after": "Please enter a time after {time}", "error.validation.time.after": "Por favor ingresa una fecha después de {time}",
"error.validation.time.before": "Please enter a time before {time}", "error.validation.time.before": "Por favor ingresa una fecha antes de {time}",
"error.validation.time.between": "Please enter a time between {min} and {max}", "error.validation.time.between": "Por favor ingresa un fecha entre {min} y {max}",
"error.validation.url": "Por favor ingresa un URL válido", "error.validation.url": "Por favor ingresa un URL válido",
"expand": "Expandir", "expand": "Expandir",
"expand.all": "Expandir todo", "expand.all": "Expandir todo",
"field.required": "Este campo es requerido", "field.required": "Este campo es requerido",
"field.blocks.changeType": "Change type", "field.blocks.changeType": "Cambiar tipo",
"field.blocks.code.name": "Código", "field.blocks.code.name": "Código",
"field.blocks.code.language": "Idioma", "field.blocks.code.language": "Idioma",
"field.blocks.code.placeholder": "Your code …", "field.blocks.code.placeholder": "Tu código...",
"field.blocks.delete.confirm": "Do you really want to delete this block?", "field.blocks.delete.confirm": "¿Seguro que quieres eliminar este bloque?",
"field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", "field.blocks.delete.confirm.all": "¿Seguro que quieres eliminar todos los bloques?",
"field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", "field.blocks.delete.confirm.selected": "¿Seguro que quieres eliminar los bloques seleccionados?",
"field.blocks.empty": "No blocks yet", "field.blocks.empty": "No hay bloques aún",
"field.blocks.fieldsets.label": "Please select a block type …", "field.blocks.fieldsets.label": "Por favor selecciona un tipo de bloque...",
"field.blocks.fieldsets.paste": "Press <kbd>{{ shortcut }}</kbd> to paste/import blocks from your clipboard", "field.blocks.fieldsets.paste": "Presiona <kbd>{{ shortcut }}</kbd>para pegar/importar bloques en tu portapapeles ",
"field.blocks.gallery.name": "Gallery", "field.blocks.gallery.name": "Galería",
"field.blocks.gallery.images.empty": "No images yet", "field.blocks.gallery.images.empty": "No hay imágenes aún",
"field.blocks.gallery.images.label": "Images", "field.blocks.gallery.images.label": "Imágenes",
"field.blocks.heading.level": "Level", "field.blocks.heading.level": "Nivel",
"field.blocks.heading.name": "Heading", "field.blocks.heading.name": "Encabezado",
"field.blocks.heading.text": "Text", "field.blocks.heading.text": "Texto",
"field.blocks.heading.placeholder": "Heading …", "field.blocks.heading.placeholder": "Encabezado...",
"field.blocks.image.alt": "Alternative text", "field.blocks.image.alt": "Texto alternativo",
"field.blocks.image.caption": "Caption", "field.blocks.image.caption": "Leyenda",
"field.blocks.image.crop": "Crop", "field.blocks.image.crop": "Cortar",
"field.blocks.image.link": "Enlace", "field.blocks.image.link": "Enlace",
"field.blocks.image.location": "Location", "field.blocks.image.location": "Ubicación",
"field.blocks.image.name": "Imágen", "field.blocks.image.name": "Imágen",
"field.blocks.image.placeholder": "Select an image", "field.blocks.image.placeholder": "Selecciona una imagen",
"field.blocks.image.ratio": "Ratio", "field.blocks.image.ratio": "Proporción",
"field.blocks.image.url": "Image URL", "field.blocks.image.url": "URL de imágen",
"field.blocks.line.name": "Line", "field.blocks.line.name": "Linea",
"field.blocks.list.name": "List", "field.blocks.list.name": "Lista",
"field.blocks.markdown.name": "Markdown", "field.blocks.markdown.name": "Markdown",
"field.blocks.markdown.label": "Text", "field.blocks.markdown.label": "Texto",
"field.blocks.markdown.placeholder": "Markdown", "field.blocks.markdown.placeholder": "Markdown...",
"field.blocks.quote.name": "Quote", "field.blocks.quote.name": "Cita",
"field.blocks.quote.text.label": "Text", "field.blocks.quote.text.label": "Texto",
"field.blocks.quote.text.placeholder": "Quote …", "field.blocks.quote.text.placeholder": "Cita...",
"field.blocks.quote.citation.label": "Citation", "field.blocks.quote.citation.label": "Citation",
"field.blocks.quote.citation.placeholder": "by …", "field.blocks.quote.citation.placeholder": "Por ...",
"field.blocks.text.name": "Text", "field.blocks.text.name": "Texto",
"field.blocks.text.placeholder": "Text …", "field.blocks.text.placeholder": "Text …",
"field.blocks.video.caption": "Caption", "field.blocks.video.caption": "Leyenda",
"field.blocks.video.name": "Video", "field.blocks.video.name": "Video",
"field.blocks.video.placeholder": "Enter a video URL", "field.blocks.video.placeholder": "Enter a video URL",
"field.blocks.video.url.label": "Video-URL", "field.blocks.video.url.label": "Video-URL",

View File

@@ -1,10 +1,10 @@
{ {
"account.changeName": "Change your name", "account.changeName": "Cambia tu nombre",
"account.delete": "Delete your account", "account.delete": "Borrar tu cuenta",
"account.delete.confirm": "Do you really want to delete your account? You will be logged out immediately. Your account cannot be recovered.", "account.delete.confirm": "¿Realmente quieres eliminar tu cuenta? Tu sesión se cerrará inmediatamente. La cuenta no podrá ser recuperada.",
"add": "Añadir", "add": "Añadir",
"author": "Author", "author": "Autor",
"avatar": "Foto de perfil", "avatar": "Foto de perfil",
"back": "Atrás", "back": "Atrás",
"cancel": "Cancelar", "cancel": "Cancelar",

View File

@@ -1,10 +1,10 @@
{ {
"account.changeName": "Change your name", "account.changeName": "Muuta nimesi",
"account.delete": "Delete your account", "account.delete": "Poista tilisi",
"account.delete.confirm": "Do you really want to delete your account? You will be logged out immediately. Your account cannot be recovered.", "account.delete.confirm": "Haluatko varmasti poistaa tilisi? Sinut kirjataan ulos välittömästi, eikä tiliäsi voi palauttaa.",
"add": "Lis\u00e4\u00e4", "add": "Lis\u00e4\u00e4",
"author": "Author", "author": "Tekijä",
"avatar": "Profiilikuva", "avatar": "Profiilikuva",
"back": "Takaisin", "back": "Takaisin",
"cancel": "Peruuta", "cancel": "Peruuta",
@@ -14,7 +14,7 @@
"collapse": "Pienennä", "collapse": "Pienennä",
"collapse.all": "Pienennä kaikki", "collapse.all": "Pienennä kaikki",
"copy": "Kopioi", "copy": "Kopioi",
"copy.all": "Copy all", "copy.all": "Kopioi kaikki",
"create": "Luo", "create": "Luo",
"date": "Päivämäärä", "date": "Päivämäärä",
@@ -49,7 +49,7 @@
"email": "S\u00e4hk\u00f6posti", "email": "S\u00e4hk\u00f6posti",
"email.placeholder": "nimi@osoite.fi", "email.placeholder": "nimi@osoite.fi",
"environment": "Environment", "environment": "Ympäristö",
"error.access.code": "Väärä koodi", "error.access.code": "Väärä koodi",
"error.access.login": "Kirjautumistiedot eivät kelpaa", "error.access.login": "Kirjautumistiedot eivät kelpaa",
@@ -102,7 +102,7 @@
"error.language.code": "Anna kielen lyhenne", "error.language.code": "Anna kielen lyhenne",
"error.language.duplicate": "Kieli on jo olemassa", "error.language.duplicate": "Kieli on jo olemassa",
"error.language.name": "Anna kielen nimi", "error.language.name": "Anna kielen nimi",
"error.language.notFound": "The language could not be found", "error.language.notFound": "Kieltä ei löytynyt",
"error.layout.validation.block": "Lohkon {blockIndex} asetelmassa {layoutIndex} tapahtui virhe", "error.layout.validation.block": "Lohkon {blockIndex} asetelmassa {layoutIndex} tapahtui virhe",
"error.layout.validation.settings": "Virhe asetelman {index} asetuksissa", "error.layout.validation.settings": "Virhe asetelman {index} asetuksissa",
@@ -111,7 +111,7 @@
"error.license.email": "Anna sähköpostiosoite", "error.license.email": "Anna sähköpostiosoite",
"error.license.verification": "Lisenssiä ei voitu vahvistaa", "error.license.verification": "Lisenssiä ei voitu vahvistaa",
"error.offline": "The Panel is currently offline", "error.offline": "Paneeli on offline-tilassa",
"error.page.changeSlug.permission": "Sinulla ei ole oikeutta muuttaa URL-liitettä sivulle \"{slug}\"", "error.page.changeSlug.permission": "Sinulla ei ole oikeutta muuttaa URL-liitettä sivulle \"{slug}\"",
"error.page.changeStatus.incomplete": "Sivulla on virheitä eikä sitä voitu julkaista", "error.page.changeStatus.incomplete": "Sivulla on virheitä eikä sitä voitu julkaista",
@@ -235,7 +235,7 @@
"field.blocks.delete.confirm.selected": "Haluatko varmasti poistaa valitut lohkot?", "field.blocks.delete.confirm.selected": "Haluatko varmasti poistaa valitut lohkot?",
"field.blocks.empty": "Ei lohkoja", "field.blocks.empty": "Ei lohkoja",
"field.blocks.fieldsets.label": "Valitse lohkon tyyppi …", "field.blocks.fieldsets.label": "Valitse lohkon tyyppi …",
"field.blocks.fieldsets.paste": "Press <kbd>{{ shortcut }}</kbd> to paste/import blocks from your clipboard", "field.blocks.fieldsets.paste": "Paina <kbd>{{ shortcut }}</kbd> liittääksesi tai tuodaksesi lohkoja leikepöydältä",
"field.blocks.gallery.name": "Galleria", "field.blocks.gallery.name": "Galleria",
"field.blocks.gallery.images.empty": "Ei kuvia", "field.blocks.gallery.images.empty": "Ei kuvia",
"field.blocks.gallery.images.label": "Kuvat", "field.blocks.gallery.images.label": "Kuvat",
@@ -282,7 +282,7 @@
"field.structure.empty": "Rivejä ei ole vielä lisätty", "field.structure.empty": "Rivejä ei ole vielä lisätty",
"field.users.empty": "Käyttäjiä ei ole vielä valittu", "field.users.empty": "Käyttäjiä ei ole vielä valittu",
"file.blueprint": "This file has no blueprint yet. You can define the setup in <strong>/site/blueprints/files/{blueprint}.yml</strong>", "file.blueprint": "Tällä tiedostolla ei ole vielä suunnitelmaa. Voit määrittää suunnitelman tiedostoon <strong>/site/blueprints/files/{blueprint}.yml</strong>",
"file.delete.confirm": "Haluatko varmasti poistaa tiedoston <br><strong>{filename}</strong>?", "file.delete.confirm": "Haluatko varmasti poistaa tiedoston <br><strong>{filename}</strong>?",
"file.sort": "Muuta järjestyspaikkaa", "file.sort": "Muuta järjestyspaikkaa",
@@ -291,7 +291,7 @@
"hide": "Piilota", "hide": "Piilota",
"hour": "Tunti", "hour": "Tunti",
"import": "Import", "import": "Tuo",
"insert": "Lis\u00e4\u00e4", "insert": "Lis\u00e4\u00e4",
"insert.after": "Lisää eteen", "insert.after": "Lisää eteen",
"insert.before": "Lisää jälkeen", "insert.before": "Lisää jälkeen",
@@ -405,7 +405,7 @@
"orientation.portrait": "Pystysuuntainen", "orientation.portrait": "Pystysuuntainen",
"orientation.square": "Neliskulmainen", "orientation.square": "Neliskulmainen",
"page.blueprint": "This page has no blueprint yet. You can define the setup in <strong>/site/blueprints/pages/{blueprint}.yml</strong>", "page.blueprint": "Tällä sivulla ei ole vielä suunnitelmaa. Voit määrittää suunnitelman tiedostoon <strong>/site/blueprints/pages/{blueprint}.yml</strong>",
"page.changeSlug": "Vaihda URL-osoite", "page.changeSlug": "Vaihda URL-osoite",
"page.changeSlug.fromTitle": "Luo nimen perusteella", "page.changeSlug.fromTitle": "Luo nimen perusteella",
"page.changeStatus": "Muuta tilaa", "page.changeStatus": "Muuta tilaa",
@@ -437,10 +437,10 @@
"pagination.page": "Sivu", "pagination.page": "Sivu",
"password": "Salasana", "password": "Salasana",
"paste": "Paste", "paste": "Liitä",
"paste.after": "Paste after", "paste.after": "Liitä jälkeen",
"pixel": "Pikseli", "pixel": "Pikseli",
"plugins": "Plugins", "plugins": "Liitännäiset",
"prev": "Edellinen", "prev": "Edellinen",
"preview": "Esikatselu", "preview": "Esikatselu",
"remove": "Poista", "remove": "Poista",
@@ -477,7 +477,7 @@
"template": "Sivupohja", "template": "Sivupohja",
"today": "Tänään", "today": "Tänään",
"server": "Server", "server": "Palvelin",
"site.blueprint": "Tällä sivustolla ei ole vielä suunnitelmaa. Voit määrittää suunnitelman tiedostoon <strong>/site/blueprints/site.yml</strong>", "site.blueprint": "Tällä sivustolla ei ole vielä suunnitelmaa. Voit määrittää suunnitelman tiedostoon <strong>/site/blueprints/site.yml</strong>",
@@ -488,15 +488,15 @@
"toolbar.button.heading.1": "Otsikko 1", "toolbar.button.heading.1": "Otsikko 1",
"toolbar.button.heading.2": "Otsikko 2", "toolbar.button.heading.2": "Otsikko 2",
"toolbar.button.heading.3": "Otsikko 3", "toolbar.button.heading.3": "Otsikko 3",
"toolbar.button.heading.4": "Heading 4", "toolbar.button.heading.4": "Otsikko 4",
"toolbar.button.heading.5": "Heading 5", "toolbar.button.heading.5": "Otsikko 5",
"toolbar.button.heading.6": "Heading 6", "toolbar.button.heading.6": "Otsikko 6",
"toolbar.button.italic": "Kursivointi", "toolbar.button.italic": "Kursivointi",
"toolbar.button.file": "Tiedosto", "toolbar.button.file": "Tiedosto",
"toolbar.button.file.select": "Valitse tiedosto", "toolbar.button.file.select": "Valitse tiedosto",
"toolbar.button.file.upload": "Lähetä tiedosto", "toolbar.button.file.upload": "Lähetä tiedosto",
"toolbar.button.link": "Linkki", "toolbar.button.link": "Linkki",
"toolbar.button.paragraph": "Paragraph", "toolbar.button.paragraph": "Kappale",
"toolbar.button.strike": "Yliviivaus", "toolbar.button.strike": "Yliviivaus",
"toolbar.button.ol": "Järjestetty lista", "toolbar.button.ol": "Järjestetty lista",
"toolbar.button.underline": "Alaviiva", "toolbar.button.underline": "Alaviiva",
@@ -526,7 +526,7 @@
"url.placeholder": "https://esimerkki.fi", "url.placeholder": "https://esimerkki.fi",
"user": "Käyttäjä", "user": "Käyttäjä",
"user.blueprint": "You can define additional sections and form fields for this user role in <strong>/site/blueprints/users/{blueprint}.yml</strong>", "user.blueprint": "Voit määrittää lisää osioita ja lomakekenttiä tälle käyttäjälle suunnitelmassa <strong>/site/blueprints/users/{blueprint}.yml</strong>",
"user.changeEmail": "Muuta sähköpostiosoite", "user.changeEmail": "Muuta sähköpostiosoite",
"user.changeLanguage": "Vaihda kieli", "user.changeLanguage": "Vaihda kieli",
"user.changeName": "Nimeä uudelleen", "user.changeName": "Nimeä uudelleen",
@@ -548,7 +548,7 @@
"view.languages": "Kielet", "view.languages": "Kielet",
"view.resetPassword": "Aseta salasana", "view.resetPassword": "Aseta salasana",
"view.site": "Sivusto", "view.site": "Sivusto",
"view.system": "System", "view.system": "Järjestelmä",
"view.users": "K\u00e4ytt\u00e4j\u00e4t", "view.users": "K\u00e4ytt\u00e4j\u00e4t",
"welcome": "Tervetuloa", "welcome": "Tervetuloa",

View File

@@ -235,7 +235,7 @@
"field.blocks.delete.confirm.selected": "Viltu virkilega eyða völdum bálkum?", "field.blocks.delete.confirm.selected": "Viltu virkilega eyða völdum bálkum?",
"field.blocks.empty": "Öngvir bálkar enn", "field.blocks.empty": "Öngvir bálkar enn",
"field.blocks.fieldsets.label": "Veldu bálkagerð …", "field.blocks.fieldsets.label": "Veldu bálkagerð …",
"field.blocks.fieldsets.paste": "Notaðu 1{{ shortcut }}1 flýtilyklaaðgerðina til að setja blokkina hér.", "field.blocks.fieldsets.paste": "Notaðu<kbd> {{ shortcut }}</kbd> flýtilyklaaðgerðina til að setja blokkina hér.",
"field.blocks.gallery.name": "Myndasafn", "field.blocks.gallery.name": "Myndasafn",
"field.blocks.gallery.images.empty": "Engar myndir enn", "field.blocks.gallery.images.empty": "Engar myndir enn",
"field.blocks.gallery.images.label": "Myndir", "field.blocks.gallery.images.label": "Myndir",

View File

@@ -6,7 +6,7 @@
"add": "\ucd94\uac00", "add": "\ucd94\uac00",
"author": "저자", "author": "저자",
"avatar": "프로필 이미지", "avatar": "프로필 이미지",
"back": "복귀", "back": "뒤로",
"cancel": "\ucde8\uc18c", "cancel": "\ucde8\uc18c",
"change": "\ubcc0\uacbd", "change": "\ubcc0\uacbd",
"close": "\ub2eb\uae30", "close": "\ub2eb\uae30",
@@ -34,9 +34,9 @@
"delete": "\uc0ad\uc81c", "delete": "\uc0ad\uc81c",
"delete.all": "모두 삭제", "delete.all": "모두 삭제",
"dialog.files.empty": "선택 파일이 없습니다.", "dialog.files.empty": "선택 파일이 없습니다.",
"dialog.pages.empty": "선택 페이지가 없습니다.", "dialog.pages.empty": "선택 페이지가 없습니다.",
"dialog.users.empty": "선택 사용자가 없습니다.", "dialog.users.empty": "선택 사용자가 없습니다.",
"dimensions": "크기", "dimensions": "크기",
"disabled": "비활성화", "disabled": "비활성화",
@@ -49,7 +49,7 @@
"email": "\uc774\uba54\uc77c \uc8fc\uc18c", "email": "\uc774\uba54\uc77c \uc8fc\uc18c",
"email.placeholder": "mail@example.com", "email.placeholder": "mail@example.com",
"environment": "환경", "environment": "구동 환경",
"error.access.code": "코드가 올바르지 않습니다.", "error.access.code": "코드가 올바르지 않습니다.",
"error.access.login": "로그인할 수 없습니다.", "error.access.login": "로그인할 수 없습니다.",
@@ -194,7 +194,7 @@
"error.validation.denied": "취소하세요.", "error.validation.denied": "취소하세요.",
"error.validation.different": "{other}에 포함된 값은 입력할 수 없습니다.", "error.validation.different": "{other}에 포함된 값은 입력할 수 없습니다.",
"error.validation.email": "올바른 이메일 주소를 입력하세요.", "error.validation.email": "올바른 이메일 주소를 입력하세요.",
"error.validation.endswith": "값은 다음으로 끝나야 합니다: {end}", "error.validation.endswith": "값은 다음({end})으로 끝나야 합니다.",
"error.validation.filename": "올바른 파일명을 입력하세요.", "error.validation.filename": "올바른 파일명을 입력하세요.",
"error.validation.in": "{in} 중 하나를 입력하세요.", "error.validation.in": "{in} 중 하나를 입력하세요.",
"error.validation.integer": "올바른 정수를 입력하세요.", "error.validation.integer": "올바른 정수를 입력하세요.",
@@ -215,7 +215,7 @@
"error.validation.required": "해당 항목을 확인하세요.", "error.validation.required": "해당 항목을 확인하세요.",
"error.validation.same": "이 값({other})을 입력하세요.", "error.validation.same": "이 값({other})을 입력하세요.",
"error.validation.size": "값의 크기({size})를 확인하세요. ", "error.validation.size": "값의 크기({size})를 확인하세요. ",
"error.validation.startswith": "값은 다음으로 시작해야 합니다: {start}", "error.validation.startswith": "값은 다음({start})으로 시작해야 합니다.",
"error.validation.time": "올바른 시각을 입력하세요.", "error.validation.time": "올바른 시각을 입력하세요.",
"error.validation.time.after": "{time} 이후 시각을 입력하세요.", "error.validation.time.after": "{time} 이후 시각을 입력하세요.",
"error.validation.time.before": "{time} 이전 시각을 입력하세요.", "error.validation.time.before": "{time} 이전 시각을 입력하세요.",
@@ -274,7 +274,7 @@
"field.layout.delete": "레이아웃 삭제", "field.layout.delete": "레이아웃 삭제",
"field.layout.delete.confirm": "레이아웃을 삭제할까요?", "field.layout.delete.confirm": "레이아웃을 삭제할까요?",
"field.layout.empty": "레이아웃이 없습니다.", "field.layout.empty": "이 없습니다.",
"field.layout.select": "레이아웃 선택", "field.layout.select": "레이아웃 선택",
"field.pages.empty": "선택한 페이지가 없습니다.", "field.pages.empty": "선택한 페이지가 없습니다.",
@@ -392,7 +392,7 @@
"more": "더 보기", "more": "더 보기",
"name": "이름", "name": "이름",
"next": "다음", "next": "다음",
"no": "", "no": "아니요",
"off": "끔", "off": "끔",
"on": "켬", "on": "켬",
"open": "열기", "open": "열기",
@@ -482,7 +482,7 @@
"site.blueprint": "블루프린트(<strong>/site/blueprints/site.yml</strong>)를 설정하세요.", "site.blueprint": "블루프린트(<strong>/site/blueprints/site.yml</strong>)를 설정하세요.",
"toolbar.button.code": "코드", "toolbar.button.code": "코드",
"toolbar.button.bold": "강조 1", "toolbar.button.bold": "강조",
"toolbar.button.email": "이메일 주소", "toolbar.button.email": "이메일 주소",
"toolbar.button.headings": "제목", "toolbar.button.headings": "제목",
"toolbar.button.heading.1": "제목 1", "toolbar.button.heading.1": "제목 1",
@@ -553,5 +553,5 @@
"welcome": "반갑습니다.", "welcome": "반갑습니다.",
"year": "년", "year": "년",
"yes": "아니요" "yes": ""
} }

View File

@@ -1,10 +1,10 @@
{ {
"account.changeName": "Change your name", "account.changeName": "Ändra ditt namn",
"account.delete": "Delete your account", "account.delete": "Radera ditt konto",
"account.delete.confirm": "Do you really want to delete your account? You will be logged out immediately. Your account cannot be recovered.", "account.delete.confirm": "Vill du verkligen radera ditt konto? Du kommer att loggas ut omedelbart. Ditt konto kan inte återställas.",
"add": "L\u00e4gg till", "add": "L\u00e4gg till",
"author": "Author", "author": "Författare",
"avatar": "Profilbild", "avatar": "Profilbild",
"back": "Tillbaka", "back": "Tillbaka",
"cancel": "Avbryt", "cancel": "Avbryt",
@@ -29,7 +29,7 @@
"days.tue": "Tis", "days.tue": "Tis",
"days.wed": "Ons", "days.wed": "Ons",
"debugging": "Debugging", "debugging": "Felsökning",
"delete": "Radera", "delete": "Radera",
"delete.all": "Radera allt", "delete.all": "Radera allt",
@@ -49,7 +49,7 @@
"email": "E-postadress", "email": "E-postadress",
"email.placeholder": "namn@exempel.se", "email.placeholder": "namn@exempel.se",
"environment": "Environment", "environment": "Miljö",
"error.access.code": "Ogiltig kod", "error.access.code": "Ogiltig kod",
"error.access.login": "Ogiltig inloggning", "error.access.login": "Ogiltig inloggning",
@@ -111,7 +111,7 @@
"error.license.email": "Ange en giltig e-postadress", "error.license.email": "Ange en giltig e-postadress",
"error.license.verification": "Licensen kunde inte verifieras", "error.license.verification": "Licensen kunde inte verifieras",
"error.offline": "The Panel is currently offline", "error.offline": "Panelen är för närvarande offline",
"error.page.changeSlug.permission": "Du har inte behörighet att ändra URL-appendixen för \"{slug}\"", "error.page.changeSlug.permission": "Du har inte behörighet att ändra URL-appendixen för \"{slug}\"",
"error.page.changeStatus.incomplete": "Sidan innehåller fel och kan inte publiceras", "error.page.changeStatus.incomplete": "Sidan innehåller fel och kan inte publiceras",
@@ -440,7 +440,7 @@
"paste": "Klistra in", "paste": "Klistra in",
"paste.after": "Klistra in efter", "paste.after": "Klistra in efter",
"pixel": "Pixel", "pixel": "Pixel",
"plugins": "Plugins", "plugins": "Tillägg",
"prev": "Föregående", "prev": "Föregående",
"preview": "Förhandsgranska", "preview": "Förhandsgranska",
"remove": "Ta bort", "remove": "Ta bort",

View File

@@ -2,29 +2,21 @@ module.exports = {
extends: [ extends: [
"eslint:recommended", "eslint:recommended",
"plugin:cypress/recommended", "plugin:cypress/recommended",
"plugin:vue/recommended" "plugin:vue/recommended",
"prettier"
], ],
rules: { rules: {
"vue/component-definition-name-casing": "off",
"vue/require-default-prop": "off",
"vue/attributes-order": "error", "vue/attributes-order": "error",
"vue/require-prop-types": "error", "vue/component-definition-name-casing": "off",
"vue/max-attributes-per-line": [
"error",
{
"singleline": 3,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}
],
"vue/html-closing-bracket-newline": [ "vue/html-closing-bracket-newline": [
"error", "error",
{ {
"singleline": "never", singleline: "never",
"multiline": "always" multiline: "always"
} }
] ],
"vue/multi-word-component-names": "off",
"vue/require-default-prop": "off",
"vue/require-prop-types": "error"
} }
} };

3
kirby/panel/.prettierrc.json Executable file
View File

@@ -0,0 +1,3 @@
{
"trailingComma": "none"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,3 @@
window.panel = window.panel || {}; window.panel = window.panel || {};
window.panel.plugins = { window.panel.plugins = {
components: {}, components: {},
@@ -17,7 +16,7 @@ window.panel.plugin = function (plugin, parts) {
options = { template: options }; options = { template: options };
} }
window.panel.plugins["components"][`k-block-type-${name}`] = { window.panel.plugins.components[`k-block-type-${name}`] = {
extends: "k-block-type", extends: "k-block-type",
...options ...options
}; };
@@ -25,35 +24,35 @@ window.panel.plugin = function (plugin, parts) {
// Components // Components
resolve(parts, "components", function (name, options) { resolve(parts, "components", function (name, options) {
window.panel.plugins["components"][name] = options; window.panel.plugins.components[name] = options;
}); });
// Fields // Fields
resolve(parts, "fields", function (name, options) { resolve(parts, "fields", function (name, options) {
window.panel.plugins["components"][`k-${name}-field`] = options; window.panel.plugins.components[`k-${name}-field`] = options;
}); });
// Icons // Icons
resolve(parts, "icons", function (name, options) { resolve(parts, "icons", function (name, options) {
window.panel.plugins["icons"][name] = options; window.panel.plugins.icons[name] = options;
}); });
// Sections // Sections
resolve(parts, "sections", function (name, options) { resolve(parts, "sections", function (name, options) {
window.panel.plugins["components"][`k-${name}-section`] = { window.panel.plugins.components[`k-${name}-section`] = {
...options, ...options,
mixins: ["section"].concat(options.mixins || []) mixins: ["section", ...(options.mixins || [])]
}; };
}); });
// Vue.use // `Vue.use`
resolve(parts, "use", function (name, options) { resolve(parts, "use", function (name, options) {
window.panel.plugins["use"].push(options); window.panel.plugins.use.push(options);
}); });
// created callback // Vue `created` callback
if (parts["created"]) { if (parts["created"]) {
window.panel.plugins["created"].push(parts["created"]); window.panel.plugins.created.push(parts["created"]);
} }
// Login // Login
@@ -62,20 +61,15 @@ window.panel.plugin = function (plugin, parts) {
} }
// Third-party plugins // Third-party plugins
resolve(parts, "thirdParty", function(name, options) { resolve(parts, "thirdParty", function (name, options) {
window.panel.plugins["thirdParty"][name] = options; window.panel.plugins.thirdParty[name] = options;
}); });
}; };
function resolve(object, type, callback) { function resolve(object, type, callback) {
if (object[type]) { if (object[type]) {
for (const [name, options] of Object.entries(object[type])) {
if (Object.entries) { callback(name, options);
Object.entries(object[type]).forEach(function ([name, options]) {
callback(name, options);
});
} }
} }
} }

View File

@@ -1,3 +1,4 @@
/* eslint-env node */
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
@@ -24,7 +25,7 @@ export default defineConfig(({ command }) => {
// Delete the flag file on any kind of exit // Delete the flag file on any kind of exit
for (const eventType of ["exit", "SIGINT", "uncaughtException"]) { for (const eventType of ["exit", "SIGINT", "uncaughtException"]) {
process.on(eventType, function(err) { process.on(eventType, function (err) {
if (fs.existsSync(runningPath) === true) { if (fs.existsSync(runningPath) === true) {
fs.unlinkSync(runningPath); fs.unlinkSync(runningPath);
} }

View File

@@ -1,12 +1,14 @@
<?php <?php
$root = dirname(__DIR__); $uri = urldecode(
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);
// https://yourdomain.com/media/super/nice.jpg // Emulate Apache's `mod_rewrite` functionality
if (file_exists($root . '/' . $_SERVER['SCRIPT_NAME'])) { if ($uri !== '/' && file_exists($_SERVER['DOCUMENT_ROOT'] . '/' . $uri)) {
return false; // serve the requested resource as-is. return false;
} }
$_SERVER['SCRIPT_NAME'] = str_replace($_SERVER['DOCUMENT_ROOT'], '', $index = $root . '/index.php'); $_SERVER['SCRIPT_NAME'] = '/index.php';
include $index; require $_SERVER['DOCUMENT_ROOT'] . '/' . $_SERVER['SCRIPT_NAME'];

View File

@@ -162,7 +162,7 @@ class Api
*/ */
public function call(string $path = null, string $method = 'GET', array $requestData = []) public function call(string $path = null, string $method = 'GET', array $requestData = [])
{ {
$path = rtrim($path, '/'); $path = rtrim($path ?? '', '/');
$this->setRequestMethod($method); $this->setRequestMethod($method);
$this->setRequestData($requestData); $this->setRequestData($requestData);

View File

@@ -330,6 +330,10 @@ class App
{ {
$router = $this->router(); $router = $this->router();
/**
* @todo Closures should not defined statically but
* for each instance to avoid this constant setting and unsetting
*/
$router::$beforeEach = function ($route, $path, $method) { $router::$beforeEach = function ($route, $path, $method) {
$this->trigger('route:before', compact('route', 'path', 'method')); $this->trigger('route:before', compact('route', 'path', 'method'));
}; };
@@ -338,7 +342,12 @@ class App
return $this->apply('route:after', compact('route', 'path', 'method', 'result', 'final'), 'result'); return $this->apply('route:after', compact('route', 'path', 'method', 'result', 'final'), 'result');
}; };
return $router->call($path ?? $this->path(), $method ?? $this->request()->method()); $result = $router->call($path ?? $this->path(), $method ?? $this->request()->method());
$router::$beforeEach = null;
$router::$afterEach = null;
return $result;
} }
/** /**

View File

@@ -128,7 +128,7 @@ trait AppErrors
'code' => $code, 'code' => $code,
'message' => $exception->getMessage(), 'message' => $exception->getMessage(),
'details' => $details, 'details' => $details,
'file' => ltrim($exception->getFile(), $_SERVER['DOCUMENT_ROOT'] ?? null), 'file' => ltrim($exception->getFile(), $_SERVER['DOCUMENT_ROOT'] ?? ''),
'line' => $exception->getLine(), 'line' => $exception->getLine(),
], $httpCode); ], $httpCode);
} else { } else {
@@ -158,9 +158,23 @@ trait AppErrors
$whoops = $this->whoops(); $whoops = $this->whoops();
$whoops->clearHandlers(); $whoops->clearHandlers();
$whoops->pushHandler($handler); $whoops->pushHandler($handler);
$whoops->pushHandler($this->getExceptionHookWhoopsHandler());
$whoops->register(); // will only do something if not already registered $whoops->register(); // will only do something if not already registered
} }
/**
* Initializes a callback handler for triggering the `system.exception` hook
*
* @return \Whoops\Handler\CallbackHandler
*/
protected function getExceptionHookWhoopsHandler(): CallbackHandler
{
return new CallbackHandler(function ($exception, $inspector, $run) {
$this->trigger('system.exception', compact('exception'));
return Handler::DONE;
});
}
/** /**
* Clears the Whoops handlers and disables Whoops * Clears the Whoops handlers and disables Whoops
* *

View File

@@ -605,9 +605,10 @@ class Auth
$originalLog = $log; $originalLog = $log;
$time = time() - $this->kirby->option('auth.timeout', 3600); $time = time() - $this->kirby->option('auth.timeout', 3600);
foreach ($log as $category => $entries) { foreach ($log as $category => $entries) {
$log[$category] = array_filter($entries, function ($entry) use ($time) { $log[$category] = array_filter(
return $entry['time'] > $time; $entries,
}); fn ($entry) => $entry['time'] > $time
);
} }
// write new log to the file system if it changed // write new log to the file system if it changed

View File

@@ -74,7 +74,7 @@ class Blueprint
$props = $this->preset($props); $props = $this->preset($props);
// normalize the name // normalize the name
$props['name'] = $props['name'] ?? 'default'; $props['name'] ??= 'default';
// normalize and translate the title // normalize and translate the title
$props['title'] = $this->i18n($props['title'] ?? ucfirst($props['name'])); $props['title'] = $this->i18n($props['title'] ?? ucfirst($props['name']));
@@ -337,7 +337,7 @@ class Blueprint
$normalize = function ($props) use ($name) { $normalize = function ($props) use ($name) {
// inject the filename as name if no name is set // inject the filename as name if no name is set
$props['name'] = $props['name'] ?? $name; $props['name'] ??= $name;
// normalize the title // normalize the title
$title = $props['title'] ?? ucfirst($props['name']); $title = $props['title'] ?? ucfirst($props['name']);
@@ -567,9 +567,7 @@ class Blueprint
// set all options to false // set all options to false
if ($options === false) { if ($options === false) {
return array_map(function () { return array_map(fn () => false, $defaults);
return false;
}, $defaults);
} }
// extend options if possible // extend options if possible
@@ -579,7 +577,7 @@ class Blueprint
$alias = $aliases[$key] ?? null; $alias = $aliases[$key] ?? null;
if ($alias !== null) { if ($alias !== null) {
$options[$alias] = $options[$alias] ?? $value; $options[$alias] ??= $value;
unset($options[$key]); unset($options[$key]);
} }
} }
@@ -765,9 +763,10 @@ class Blueprint
*/ */
public function sections(): array public function sections(): array
{ {
return array_map(function ($section) { return A::map(
return $this->section($section['name']); $this->sections,
}, $this->sections); fn ($section) => $this->section($section['name'])
);
} }
/** /**

View File

@@ -208,7 +208,7 @@ class ContentLock
} }
// add lock user to unlocked data // add lock user to unlocked data
$this->data['unlock'] = $this->data['unlock'] ?? []; $this->data['unlock'] ??= [];
$this->data['unlock'][] = $this->data['lock']['user']; $this->data['unlock'][] = $this->data['lock']['user'];
return $this->clearLock(); return $this->clearLock();

View File

@@ -208,7 +208,7 @@ class ContentTranslation
*/ */
public function slug(): ?string public function slug(): ?string
{ {
return $this->slug = $this->slug ?? ($this->content()['slug'] ?? null); return $this->slug ??= ($this->content()['slug'] ?? null);
} }
/** /**

View File

@@ -167,7 +167,7 @@ class Core
*/ */
public function components(): array public function components(): array
{ {
return $this->cache['components'] ?? $this->cache['components'] = include $this->root . '/components.php'; return $this->cache['components'] ??= include $this->root . '/components.php';
} }
/** /**
@@ -203,7 +203,7 @@ class Core
*/ */
public function fieldMethods(): array public function fieldMethods(): array
{ {
return $this->cache['fieldMethods'] ?? $this->cache['fieldMethods'] = (include $this->root . '/methods.php')($this->kirby); return $this->cache['fieldMethods'] ??= (include $this->root . '/methods.php')($this->kirby);
} }
/** /**
@@ -295,7 +295,7 @@ class Core
*/ */
public function kirbyTags(): array public function kirbyTags(): array
{ {
return $this->cache['kirbytags'] ?? $this->cache['kirbytags'] = include $this->root . '/tags.php'; return $this->cache['kirbytags'] ??= include $this->root . '/tags.php';
} }
/** /**
@@ -321,7 +321,7 @@ class Core
*/ */
public function roots(): array public function roots(): array
{ {
return $this->cache['roots'] ?? $this->cache['roots'] = [ return $this->cache['roots'] ??= [
// kirby // kirby
'kirby' => function (array $roots) { 'kirby' => function (array $roots) {
return dirname(__DIR__, 2); return dirname(__DIR__, 2);
@@ -428,7 +428,7 @@ class Core
*/ */
public function routes(): array public function routes(): array
{ {
return $this->cache['routes'] ?? $this->cache['routes'] = (include $this->root . '/routes.php')($this->kirby); return $this->cache['routes'] ??= (include $this->root . '/routes.php')($this->kirby);
} }
/** /**
@@ -517,7 +517,7 @@ class Core
*/ */
public function urls(): array public function urls(): array
{ {
return $this->cache['urls'] ?? $this->cache['urls'] = [ return $this->cache['urls'] ??= [
'index' => function () { 'index' => function () {
return Url::index(); return Url::index();
}, },

View File

@@ -3,6 +3,7 @@
namespace Kirby\Cms; namespace Kirby\Cms;
use Closure; use Closure;
use Kirby\Toolkit\A;
use Kirby\Toolkit\I18n; use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str; use Kirby\Toolkit\Str;
@@ -42,7 +43,7 @@ class Fieldsets extends Items
$fieldset = Blueprint::extend($fieldset); $fieldset = Blueprint::extend($fieldset);
// make sure the type is always set // make sure the type is always set
$fieldset['type'] = $fieldset['type'] ?? $type; $fieldset['type'] ??= $type;
// extract groups // extract groups
if ($fieldset['type'] === 'group') { if ($fieldset['type'] === 'group') {
@@ -69,7 +70,7 @@ class Fieldsets extends Items
public static function factory(array $items = null, array $params = []) public static function factory(array $items = null, array $params = [])
{ {
$items = $items ?? option('blocks.fieldsets', [ $items ??= option('blocks.fieldsets', [
'code' => 'blocks/code', 'code' => 'blocks/code',
'gallery' => 'blocks/gallery', 'gallery' => 'blocks/gallery',
'heading' => 'blocks/heading', 'heading' => 'blocks/heading',
@@ -94,8 +95,9 @@ class Fieldsets extends Items
public function toArray(?Closure $map = null): array public function toArray(?Closure $map = null): array
{ {
return array_map($map ?? function ($fieldset) { return A::map(
return $fieldset->toArray(); $this->data,
}, $this->data); $map ?? fn ($fieldset) => $fieldset->toArray()
);
} }
} }

View File

@@ -6,6 +6,7 @@ use Kirby\Filesystem\F;
use Kirby\Filesystem\IsFile; use Kirby\Filesystem\IsFile;
use Kirby\Panel\File as Panel; use Kirby\Panel\File as Panel;
use Kirby\Toolkit\A; use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
/** /**
* The `$file` object provides a set * The `$file` object provides a set
@@ -362,14 +363,9 @@ class File extends ModelWithContent
$file = $this->modifiedFile(); $file = $this->modifiedFile();
$content = $this->modifiedContent($languageCode); $content = $this->modifiedContent($languageCode);
$modified = max($file, $content); $modified = max($file, $content);
$handler ??= $this->kirby()->option('date.handler', 'date');
if (is_null($format) === true) { return Str::date($modified, $format, $handler);
return $modified;
}
$handler = $handler ?? $this->kirby()->option('date.handler', 'date');
return $handler($format, $modified);
} }
/** /**
@@ -422,7 +418,7 @@ class File extends ModelWithContent
*/ */
public function parent() public function parent()
{ {
return $this->parent = $this->parent ?? $this->kirby()->site(); return $this->parent ??= $this->kirby()->site();
} }
/** /**
@@ -472,7 +468,7 @@ class File extends ModelWithContent
*/ */
public function root(): ?string public function root(): ?string
{ {
return $this->root = $this->root ?? $this->parent()->root() . '/' . $this->filename(); return $this->root ??= $this->parent()->root() . '/' . $this->filename();
} }
/** /**
@@ -598,7 +594,7 @@ class File extends ModelWithContent
*/ */
public function template(): ?string public function template(): ?string
{ {
return $this->template = $this->template ?? $this->content()->get('template')->value(); return $this->template ??= $this->content()->get('template')->value();
} }
/** /**
@@ -631,7 +627,7 @@ class File extends ModelWithContent
*/ */
public function url(): string public function url(): string
{ {
return $this->url ?? $this->url = ($this->kirby()->component('file::url'))($this->kirby(), $this); return $this->url ??= ($this->kirby()->component('file::url'))($this->kirby(), $this);
} }

View File

@@ -163,17 +163,24 @@ class FileBlueprint extends Blueprint
// normalize the MIME, extension and type from strings into arrays // normalize the MIME, extension and type from strings into arrays
if (is_string($accept['mime']) === true) { if (is_string($accept['mime']) === true) {
$accept['mime'] = array_map(function ($mime) { $accept['mime'] = array_map(
return $mime['value']; fn ($mime) => $mime['value'],
}, Str::accepted($accept['mime'])); Str::accepted($accept['mime'])
);
} }
if (is_string($accept['extension']) === true) { if (is_string($accept['extension']) === true) {
$accept['extension'] = array_map('trim', explode(',', $accept['extension'])); $accept['extension'] = array_map(
'trim',
explode(',', $accept['extension'])
);
} }
if (is_string($accept['type']) === true) { if (is_string($accept['type']) === true) {
$accept['type'] = array_map('trim', explode(',', $accept['type'])); $accept['type'] = array_map(
'trim',
explode(',', $accept['type'])
);
} }
return $accept; return $accept;

View File

@@ -203,9 +203,10 @@ trait FileModifications
if ( if (
is_a($result, 'Kirby\Cms\FileVersion') === false && is_a($result, 'Kirby\Cms\FileVersion') === false &&
is_a($result, 'Kirby\Cms\File') === false is_a($result, 'Kirby\Cms\File') === false &&
is_a($result, 'Kirby\Filesystem\Asset') === false
) { ) {
throw new InvalidArgumentException('The file::version component must return a File or FileVersion object'); throw new InvalidArgumentException('The file::version component must return a File, FileVersion or Asset object');
} }
return $result; return $result;

View File

@@ -87,9 +87,10 @@ class LanguageRouter
$patterns = A::wrap($route['pattern']); $patterns = A::wrap($route['pattern']);
// prefix all patterns with the page slug // prefix all patterns with the page slug
$patterns = array_map(function ($pattern) use ($page, $language) { $patterns = A::map(
return $page->uri($language) . '/' . $pattern; $patterns,
}, $patterns); fn ($pattern) => $page->uri($language) . '/' . $pattern
);
// re-inject the pattern and the full page object // re-inject the pattern and the full page object
$routes[$index]['pattern'] = $patterns; $routes[$index]['pattern'] = $patterns;

View File

@@ -25,9 +25,10 @@ class Languages extends Collection
*/ */
public function __construct($objects = [], $parent = null) public function __construct($objects = [], $parent = null)
{ {
$defaults = array_filter($objects, function ($language) { $defaults = array_filter(
return $language->isDefault() === true; $objects,
}); fn ($language) => $language->isDefault() === true
);
if (count($defaults) > 1) { if (count($defaults) > 1) {
throw new DuplicateException('You cannot have multiple default languages. Please check your language config files.'); throw new DuplicateException('You cannot have multiple default languages. Please check your language config files.');
@@ -87,8 +88,9 @@ class Languages extends Collection
$props = F::load($file); $props = F::load($file);
if (is_array($props) === true) { if (is_array($props) === true) {
// inject the language code from the filename if it does not exist // inject the language code from the filename
$props['code'] = $props['code'] ?? F::name($file); // if it does not exist
$props['code'] ??= F::name($file);
$languages[] = new Language($props); $languages[] = new Language($props);
} }

View File

@@ -67,7 +67,7 @@ abstract class Model
*/ */
public function kirby() public function kirby()
{ {
return static::$kirby = static::$kirby ?? App::instance(); return static::$kirby ??= App::instance();
} }
/** /**
@@ -77,7 +77,7 @@ abstract class Model
*/ */
public function site() public function site()
{ {
return $this->site = $this->site ?? $this->kirby()->site(); return $this->site ??= $this->kirby()->site();
} }
/** /**

View File

@@ -382,7 +382,7 @@ class Page extends ModelWithContent
*/ */
public function depth(): int public function depth(): int
{ {
return $this->depth = $this->depth ?? (substr_count($this->id(), '/') + 1); return $this->depth ??= (substr_count($this->id(), '/') + 1);
} }
/** /**
@@ -1102,7 +1102,7 @@ class Page extends ModelWithContent
*/ */
public function root(): string public function root(): string
{ {
return $this->root = $this->root ?? $this->kirby()->root('content') . '/' . $this->diruri(); return $this->root ??= $this->kirby()->root('content') . '/' . $this->diruri();
} }
/** /**

View File

@@ -151,7 +151,7 @@ class Pages extends Collection
*/ */
public static function factory(array $pages, Model $model = null, bool $draft = false) public static function factory(array $pages, Model $model = null, bool $draft = false)
{ {
$model = $model ?? App::instance()->site(); $model ??= App::instance()->site();
$children = new static([], $model); $children = new static([], $model);
$kirby = $model->kirby(); $kirby = $model->kirby();

View File

@@ -69,9 +69,9 @@ class Pagination extends BasePagination
$config = $kirby->option('pagination', []); $config = $kirby->option('pagination', []);
$request = $kirby->request(); $request = $kirby->request();
$params['limit'] = $params['limit'] ?? $config['limit'] ?? 20; $params['limit'] ??= $config['limit'] ?? 20;
$params['method'] = $params['method'] ?? $config['method'] ?? 'param'; $params['method'] ??= $config['method'] ?? 'param';
$params['variable'] = $params['variable'] ?? $config['variable'] ?? 'page'; $params['variable'] ??= $config['variable'] ?? 'page';
if (empty($params['url']) === true) { if (empty($params['url']) === true) {
$params['url'] = new Uri($kirby->url('current'), [ $params['url'] = new Uri($kirby->url('current'), [
@@ -81,9 +81,9 @@ class Pagination extends BasePagination
} }
if ($params['method'] === 'query') { if ($params['method'] === 'query') {
$params['page'] = $params['page'] ?? $params['url']->query()->get($params['variable']); $params['page'] ??= $params['url']->query()->get($params['variable']);
} elseif ($params['method'] === 'param') { } elseif ($params['method'] === 'param') {
$params['page'] = $params['page'] ?? $params['url']->params()->get($params['variable']); $params['page'] ??= $params['url']->params()->get($params['variable']);
} }
parent::__construct($params); parent::__construct($params);

View File

@@ -210,7 +210,7 @@ class Role extends Model
*/ */
public function title(): string public function title(): string
{ {
return $this->title = $this->title ?? ucfirst($this->name()); return $this->title ??= ucfirst($this->name());
} }
/** /**

View File

@@ -49,8 +49,8 @@ class Section extends Component
} }
// use the type as fallback for the name // use the type as fallback for the name
$attrs['name'] = $attrs['name'] ?? $type; $attrs['name'] ??= $type;
$attrs['type'] = $type; $attrs['type'] = $type;
parent::__construct($type, $attrs); parent::__construct($type, $attrs);
} }

View File

@@ -463,7 +463,7 @@ class Site extends ModelWithContent
*/ */
public function root(): string public function root(): string
{ {
return $this->root = $this->root ?? $this->kirby()->root('content'); return $this->root ??= $this->kirby()->root('content');
} }
/** /**

View File

@@ -405,7 +405,7 @@ class System
{ {
return return
version_compare(PHP_VERSION, '7.4.0', '>=') === true && version_compare(PHP_VERSION, '7.4.0', '>=') === true &&
version_compare(PHP_VERSION, '8.1.0', '<') === true; version_compare(PHP_VERSION, '8.2.0', '<') === true;
} }
/** /**
@@ -508,7 +508,7 @@ class System
]; ];
} }
$software = $_SERVER['SERVER_SOFTWARE'] ?? null; $software = $_SERVER['SERVER_SOFTWARE'] ?? '';
preg_match('!(' . implode('|', $servers) . ')!i', $software, $matches); preg_match('!(' . implode('|', $servers) . ')!i', $software, $matches);

View File

@@ -78,7 +78,11 @@ class Template
*/ */
public function exists(): bool public function exists(): bool
{ {
return file_exists($this->file()); if ($file = $this->file()) {
return file_exists($file);
}
return false;
} }
/** /**

View File

@@ -228,7 +228,7 @@ class User extends ModelWithContent
protected function credentials(): array protected function credentials(): array
{ {
return $this->credentials = $this->credentials ?? $this->readCredentials(); return $this->credentials ??= $this->readCredentials();
} }
/** /**
@@ -238,7 +238,7 @@ class User extends ModelWithContent
*/ */
public function email(): ?string public function email(): ?string
{ {
return $this->email = $this->email ?? $this->credentials()['email'] ?? null; return $this->email ??= $this->credentials()['email'] ?? null;
} }
/** /**
@@ -403,7 +403,7 @@ class User extends ModelWithContent
*/ */
public function language(): string public function language(): string
{ {
return $this->language ?? $this->language = $this->credentials()['language'] ?? $this->kirby()->panelLanguage(); return $this->language ??= $this->credentials()['language'] ?? $this->kirby()->panelLanguage();
} }
/** /**
@@ -530,9 +530,9 @@ class User extends ModelWithContent
$modifiedContent = F::modified($this->contentFile($languageCode)); $modifiedContent = F::modified($this->contentFile($languageCode));
$modifiedIndex = F::modified($this->root() . '/index.php'); $modifiedIndex = F::modified($this->root() . '/index.php');
$modifiedTotal = max([$modifiedContent, $modifiedIndex]); $modifiedTotal = max([$modifiedContent, $modifiedIndex]);
$handler = $handler ?? $this->kirby()->option('date.handler', 'date'); $handler ??= $this->kirby()->option('date.handler', 'date');
return $handler($format, $modifiedTotal); return Str::date($modifiedTotal, $format, $handler);
} }
/** /**

View File

@@ -65,7 +65,7 @@ class Data
static::$handlers[static::$aliases[$type] ?? null] ?? static::$handlers[static::$aliases[$type] ?? null] ??
null; null;
if (class_exists($handler)) { if ($handler !== null && class_exists($handler)) {
return new $handler(); return new $handler();
} }

View File

@@ -212,6 +212,12 @@ class Database
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// TODO: behavior without this attribute would be preferrable
// (actual types instead of all strings) but would be a breaking change
if ($this->type === 'sqlite') {
$this->connection->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
}
// store the connection // store the connection
static::$connections[$this->id] = $this; static::$connections[$this->id] = $this;

View File

@@ -45,7 +45,7 @@ class Db
// try to connect with the default // try to connect with the default
// connection settings if no params are set // connection settings if no params are set
$defaults = [ $params ??= [
'type' => Config::get('db.type', 'mysql'), 'type' => Config::get('db.type', 'mysql'),
'host' => Config::get('db.host', 'localhost'), 'host' => Config::get('db.host', 'localhost'),
'user' => Config::get('db.user', 'root'), 'user' => Config::get('db.user', 'root'),
@@ -54,7 +54,6 @@ class Db
'prefix' => Config::get('db.prefix', ''), 'prefix' => Config::get('db.prefix', ''),
'port' => Config::get('db.port', '') 'port' => Config::get('db.port', '')
]; ];
$params = $params ?? $defaults;
return static::$connection = new Database($params); return static::$connection = new Database($params);
} }

View File

@@ -290,9 +290,10 @@ abstract class Sql
// add keys // add keys
foreach ($inner['keys'] as $key => $columns) { foreach ($inner['keys'] as $key => $columns) {
// quote each column name and make a list string out of the column names // quote each column name and make a list string out of the column names
$columns = implode(', ', array_map(function ($name) { $columns = implode(', ', array_map(
return $this->quoteIdentifier($name); fn ($name) => $this->quoteIdentifier($name),
}, $columns)); $columns
));
if ($key === 'primary') { if ($key === 'primary') {
$key = 'PRIMARY KEY'; $key = 'PRIMARY KEY';
@@ -526,7 +527,7 @@ abstract class Sql
]; ];
} }
$limit = $limit ?? '18446744073709551615'; $limit ??= '18446744073709551615';
$offsetBinding = $this->bindingName('offset'); $offsetBinding = $this->bindingName('offset');
$limitBinding = $this->bindingName('limit'); $limitBinding = $this->bindingName('limit');

View File

@@ -82,9 +82,10 @@ class Sqlite extends Sql
$keys = []; $keys = [];
foreach ($inner['keys'] as $key => $columns) { foreach ($inner['keys'] as $key => $columns) {
// quote each column name and make a list string out of the column names // quote each column name and make a list string out of the column names
$columns = implode(', ', array_map(function ($name) { $columns = implode(', ', array_map(
return $this->quoteIdentifier($name); fn ($name) => $this->quoteIdentifier($name),
}, $columns)); $columns
));
if ($key === 'primary') { if ($key === 'primary') {
$inner['query'] .= ',' . PHP_EOL . 'PRIMARY KEY (' . $columns . ')'; $inner['query'] .= ',' . PHP_EOL . 'PRIMARY KEY (' . $columns . ')';

View File

@@ -128,7 +128,7 @@ class Exception extends \Exception
]); ]);
// handover to Exception parent class constructor // handover to Exception parent class constructor
parent::__construct($message, null, $args['previous'] ?? null); parent::__construct($message, 0, $args['previous'] ?? null);
} }
// set the Exception code to the key // set the Exception code to the key

View File

@@ -5,6 +5,7 @@ namespace Kirby\Filesystem;
use Exception; use Exception;
use Kirby\Cms\App; use Kirby\Cms\App;
use Kirby\Cms\Page; use Kirby\Cms\Page;
use Kirby\Toolkit\Str;
use Throwable; use Throwable;
/** /**
@@ -450,7 +451,7 @@ class Dir
$modified = ($newModified > $modified) ? $newModified : $modified; $modified = ($newModified > $modified) ? $newModified : $modified;
} }
return $format !== null ? $handler($format, $modified) : $modified; return Str::date($modified, $format, $handler);
} }
/** /**
@@ -504,8 +505,8 @@ class Dir
} }
// create the ignore pattern // create the ignore pattern
$ignore = $ignore ?? static::$ignore; $ignore ??= static::$ignore;
$ignore = array_merge($ignore, ['.', '..']); $ignore = array_merge($ignore, ['.', '..']);
// scan for all files and dirs // scan for all files and dirs
$result = array_values((array)array_diff(scandir($dir), $ignore)); $result = array_values((array)array_diff(scandir($dir), $ignore));

View File

@@ -475,11 +475,7 @@ class F
$modified = filemtime($file); $modified = filemtime($file);
if (is_null($format) === true) { return Str::date($modified, $format, $handler);
return $modified;
}
return $handler($format, $modified);
} }
/** /**

View File

@@ -109,9 +109,10 @@ class Filename
'q' => $this->quality(), 'q' => $this->quality(),
]; ];
$array = array_filter($array, function ($item) { $array = array_filter(
return $item !== null && $item !== false && $item !== ''; $array,
}); fn ($item) => $item !== null && $item !== false && $item !== ''
);
return $array; return $array;
} }

View File

@@ -320,7 +320,7 @@ class Mime
} }
// get the extension or extract it from the filename // get the extension or extract it from the filename
$extension = $extension ?? F::extension($file); $extension ??= F::extension($file);
// try to guess the mime type at least // try to guess the mime type at least
if ($mime === false) { if ($mime === false) {

View File

@@ -71,8 +71,8 @@ class Field extends Component
$this->formFields = $formFields; $this->formFields = $formFields;
// use the type as fallback for the name // use the type as fallback for the name
$attrs['name'] = $attrs['name'] ?? $type; $attrs['name'] ??= $type;
$attrs['type'] = $type; $attrs['type'] = $type;
parent::__construct($type, $attrs); parent::__construct($type, $attrs);
} }
@@ -440,9 +440,10 @@ class Field extends Component
ksort($array); ksort($array);
return array_filter($array, function ($item) { return array_filter(
return $item !== null && is_object($item) === false; $array,
}); fn ($item) => $item !== null && is_object($item) === false
);
} }
/** /**

View File

@@ -49,7 +49,7 @@ class BlocksField extends FieldClass
$type = $block['type']; $type = $block['type'];
// get and cache fields at the same time // get and cache fields at the same time
$fields[$type] = $fields[$type] ?? $this->fields($block['type']); $fields[$type] ??= $this->fields($block['type']);
// overwrite the block content with form values // overwrite the block content with form values
$block['content'] = $this->form($fields[$type], $block['content'])->$to(); $block['content'] = $this->form($fields[$type], $block['content'])->$to();

View File

@@ -116,9 +116,10 @@ class LayoutField extends BlocksField
protected function setLayouts(array $layouts = []) protected function setLayouts(array $layouts = [])
{ {
$this->layouts = array_map(function ($layout) { $this->layouts = array_map(
return Str::split($layout); fn ($layout) => Str::split($layout),
}, $layouts); $layouts
);
} }
protected function setSettings($settings = null) protected function setSettings($settings = null)

View File

@@ -726,9 +726,7 @@ abstract class FieldClass
ksort($props); ksort($props);
return array_filter($props, function ($item) { return array_filter($props, fn ($item) => $item !== null);
return $item !== null;
});
} }
/** /**

View File

@@ -29,7 +29,7 @@ class Fields extends Collection
{ {
if (is_array($field) === true) { if (is_array($field) === true) {
// use the array key as name if the name is not set // use the array key as name if the name is not set
$field['name'] = $field['name'] ?? $name; $field['name'] ??= $name;
$field = Field::factory($field['type'], $field, $this); $field = Field::factory($field['type'], $field, $this);
} }

View File

@@ -268,9 +268,9 @@ class Form
} }
// set a few defaults // set a few defaults
$props['values'] = array_merge($original, $values); $props['values'] = array_merge($original, $values);
$props['fields'] = $props['fields'] ?? []; $props['fields'] ??= [];
$props['model'] = $model; $props['model'] = $model;
// search for the blueprint // search for the blueprint
if (method_exists($model, 'blueprint') === true && $blueprint = $model->blueprint()) { if (method_exists($model, 'blueprint') === true && $blueprint = $model->blueprint()) {

View File

@@ -45,10 +45,10 @@ class Options
*/ */
public static function api($api, $model = null): array public static function api($api, $model = null): array
{ {
$model = $model ?? App::instance()->site(); $model ??= App::instance()->site();
$fetch = null; $fetch = null;
$text = null; $text = null;
$value = null; $value = null;
if (is_array($api) === true) { if (is_array($api) === true) {
$fetch = $api['fetch'] ?? null; $fetch = $api['fetch'] ?? null;
@@ -165,7 +165,7 @@ class Options
*/ */
public static function query($query, $model = null): array public static function query($query, $model = null): array
{ {
$model = $model ?? App::instance()->site(); $model ??= App::instance()->site();
// default text setup // default text setup
$text = [ $text = [

View File

@@ -3,7 +3,6 @@
namespace Kirby\Http; namespace Kirby\Http;
use Kirby\Toolkit\Str; use Kirby\Toolkit\Str;
use TrueBV\Punycode;
/** /**
* Handles Internationalized Domain Names * Handles Internationalized Domain Names
@@ -16,14 +15,26 @@ use TrueBV\Punycode;
*/ */
class Idn class Idn
{ {
/**
* Convert domain name from IDNA ASCII to Unicode
*
* @param string $domain
* @return string|false
*/
public static function decode(string $domain) public static function decode(string $domain)
{ {
return (new Punycode())->decode($domain); return idn_to_utf8($domain);
} }
/**
* Convert domain name to IDNA ASCII form
*
* @param string $domain
* @return string|false
*/
public static function encode(string $domain) public static function encode(string $domain)
{ {
return (new Punycode())->encode($domain); return idn_to_ascii($domain);
} }
/** /**

View File

@@ -43,7 +43,7 @@ class Query extends Obj
public function toString($questionMark = false): string public function toString($questionMark = false): string
{ {
$query = http_build_query($this, null, '&', PHP_QUERY_RFC3986); $query = http_build_query($this, '', '&', PHP_QUERY_RFC3986);
if (empty($query) === true) { if (empty($query) === true) {
return ''; return '';

View File

@@ -174,7 +174,7 @@ class Request
*/ */
public function body() public function body()
{ {
return $this->body = $this->body ?? new Body(); return $this->body ??= new Body();
} }
/** /**
@@ -220,7 +220,7 @@ class Request
$methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']; $methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'];
// the request method can be overwritten with a header // the request method can be overwritten with a header
$methodOverride = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? null); $methodOverride = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '');
if ($method === null && in_array($methodOverride, $methods) === true) { if ($method === null && in_array($methodOverride, $methods) === true) {
$method = $methodOverride; $method = $methodOverride;
@@ -269,7 +269,7 @@ class Request
*/ */
public function files() public function files()
{ {
return $this->files = $this->files ?? new Files(); return $this->files ??= new Files();
} }
/** /**
@@ -379,7 +379,7 @@ class Request
*/ */
public function query() public function query()
{ {
return $this->query = $this->query ?? new Query(); return $this->query ??= new Query();
} }
/** /**
@@ -407,6 +407,6 @@ class Request
return $this->url()->clone($props); return $this->url()->clone($props);
} }
return $this->url = $this->url ?? Uri::current(); return $this->url ??= Uri::current();
} }
} }

View File

@@ -160,14 +160,14 @@ class Response
throw new Exception('The file could not be found'); throw new Exception('The file could not be found');
} }
$filename = $filename ?? basename($file); $filename ??= basename($file);
$modified = filemtime($file); $modified = filemtime($file);
$body = file_get_contents($file); $body = file_get_contents($file);
$size = strlen($body); $size = strlen($body);
$props = array_replace_recursive([ $props = array_replace_recursive([
'body' => $body, 'body' => $body,
'type' => 'application/force-download', 'type' => F::mime($file),
'headers' => [ 'headers' => [
'Pragma' => 'public', 'Pragma' => 'public',
'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Cache-Control' => 'no-cache, no-store, must-revalidate',
@@ -234,7 +234,7 @@ class Response
public static function json($body = '', ?int $code = null, ?bool $pretty = null, array $headers = []) public static function json($body = '', ?int $code = null, ?bool $pretty = null, array $headers = [])
{ {
if (is_array($body) === true) { if (is_array($body) === true) {
$body = json_encode($body, $pretty === true ? JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES : null); $body = json_encode($body, $pretty === true ? JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES : 0);
} }
return new static([ return new static([

View File

@@ -5,6 +5,7 @@ namespace Kirby\Http;
use Closure; use Closure;
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
use Kirby\Toolkit\A;
/** /**
* @package Kirby Http * @package Kirby Http
@@ -59,8 +60,11 @@ class Router
throw new InvalidArgumentException('Invalid route parameters'); throw new InvalidArgumentException('Invalid route parameters');
} }
$methods = array_map('trim', explode('|', strtoupper($props['method'] ?? 'GET'))); $patterns = A::wrap($props['pattern']);
$patterns = is_array($props['pattern']) === false ? [$props['pattern']] : $props['pattern']; $methods = A::map(
explode('|', strtoupper($props['method'] ?? 'GET')),
'trim'
);
if ($methods === ['ALL']) { if ($methods === ['ALL']) {
$methods = array_keys($this->routes); $methods = array_keys($this->routes);
@@ -88,7 +92,7 @@ class Router
*/ */
public function call(string $path = null, string $method = 'GET', Closure $callback = null) public function call(string $path = null, string $method = 'GET', Closure $callback = null)
{ {
$path = $path ?? ''; $path ??= '';
$ignore = []; $ignore = [];
$result = null; $result = null;
$loop = true; $loop = true;

View File

@@ -92,6 +92,9 @@ class Server
*/ */
public static function sanitize(string $key, $value) public static function sanitize(string $key, $value)
{ {
// make sure $value is not null
$value ??= '';
switch ($key) { switch ($key) {
case 'SERVER_ADDR': case 'SERVER_ADDR':
case 'SERVER_NAME': case 'SERVER_NAME':
@@ -100,7 +103,7 @@ class Server
$value = strip_tags($value); $value = strip_tags($value);
$value = preg_replace('![^\w.:-]+!iu', '', $value); $value = preg_replace('![^\w.:-]+!iu', '', $value);
$value = trim($value, '-'); $value = trim($value, '-');
$value = htmlspecialchars($value); $value = htmlspecialchars($value, ENT_COMPAT);
break; break;
case 'SERVER_PORT': case 'SERVER_PORT':
case 'HTTP_X_FORWARDED_PORT': case 'HTTP_X_FORWARDED_PORT':

View File

@@ -144,10 +144,10 @@ class Uri
// parse the path and extract params // parse the path and extract params
if (empty($props['path']) === false) { if (empty($props['path']) === false) {
$extract = Params::extract($props['path']); $extract = Params::extract($props['path']);
$props['params'] = $props['params'] ?? $extract['params']; $props['params'] ??= $extract['params'];
$props['path'] = $extract['path']; $props['path'] = $extract['path'];
$props['slash'] = $props['slash'] ?? $extract['slash']; $props['slash'] ??= $extract['slash'];
} }
$this->setProperties($this->props = $props); $this->setProperties($this->props = $props);
@@ -246,8 +246,12 @@ class Uri
return static::$current; return static::$current;
} }
$uri = Server::get('REQUEST_URI'); $uri = Server::get('REQUEST_URI') ?? '';
$uri = preg_replace('!^(http|https)\:\/\/' . Server::get('HTTP_HOST') . '!', '', $uri); $uri = preg_replace(
'!^(http|https)\:\/\/' . Server::get('HTTP_HOST') . '!',
'',
$uri
);
$uri = parse_url('http://getkirby.com' . $uri); $uri = parse_url('http://getkirby.com' . $uri);
$url = new static(array_merge([ $url = new static(array_merge([

View File

@@ -145,8 +145,8 @@ class Url
} }
// build the full url // build the full url
$path = ltrim($path, '/'); $path = ltrim($path, '/');
$home = $home ?? static::home(); $home ??= static::home();
if (empty($path) === true) { if (empty($path) === true) {
return $home; return $home;
@@ -260,6 +260,9 @@ class Url
*/ */
public static function to(string $path = null, $options = null): string public static function to(string $path = null, $options = null): string
{ {
// make sure $path is string
$path ??= '';
// keep relative urls // keep relative urls
if (substr($path, 0, 2) === './' || substr($path, 0, 3) === '../') { if (substr($path, 0, 2) === './' || substr($path, 0, 3) === '../') {
return $path; return $path;

View File

@@ -177,7 +177,7 @@ class ImageMagick extends Darkroom
{ {
// simple resize // simple resize
if ($options['crop'] === false) { if ($options['crop'] === false) {
return sprintf('-resize %sx%s!', $options['width'], $options['height']); return sprintf('-thumbnail %sx%s!', $options['width'], $options['height']);
} }
$gravities = [ $gravities = [
@@ -195,7 +195,7 @@ class ImageMagick extends Darkroom
// translate the gravity option into something imagemagick understands // translate the gravity option into something imagemagick understands
$gravity = $gravities[$options['crop']] ?? 'Center'; $gravity = $gravities[$options['crop']] ?? 'Center';
$command = sprintf('-resize %sx%s^', $options['width'], $options['height']); $command = sprintf('-thumbnail %sx%s^', $options['width'], $options['height']);
$command .= sprintf(' -gravity %s -crop %sx%s+0+0', $gravity, $options['width'], $options['height']); $command .= sprintf(' -gravity %s -crop %sx%s+0+0', $gravity, $options['width'], $options['height']);
return $command; return $command;
@@ -227,6 +227,14 @@ class ImageMagick extends Darkroom
*/ */
protected function strip(string $file, array $options): string protected function strip(string $file, array $options): string
{ {
return '-strip'; if (F::extension($file) === 'png') {
// ImageMagick does not support keeping ICC profiles while
// stripping other privacy- and security-related information,
// such as GPS data; so discard all color profiles for PNG files
// (tested with ImageMagick 7.0.11-14 Q16 x86_64 2021-05-31)
return '-strip';
}
return '';
} }
} }

View File

@@ -116,7 +116,7 @@ class Image extends File
*/ */
public function exif() public function exif()
{ {
return $this->exif = $this->exif ?? new Exif($this); return $this->exif ??= new Exif($this);
} }
/** /**

View File

@@ -130,9 +130,10 @@ class Document
// remove missing files // remove missing files
$assets['css'] = array_filter($assets['css']); $assets['css'] = array_filter($assets['css']);
$assets['js'] = array_filter($assets['js'], function ($js) { $assets['js'] = array_filter(
return empty($js['src']) === false; $assets['js'],
}); fn ($js) => empty($js['src']) === false
);
return $assets; return $assets;
} }

View File

@@ -316,7 +316,7 @@ class File extends Model
$absolute = $parent !== $params['model']; $absolute = $parent !== $params['model'];
} }
$params['text'] = $params['text'] ?? '{{ file.filename }}'; $params['text'] ??= '{{ file.filename }}';
return array_merge(parent::pickerData($params), [ return array_merge(parent::pickerData($params), [
'filename' => $name, 'filename' => $name,

View File

@@ -68,7 +68,7 @@ abstract class Json
} }
// always inject the response code // always inject the response code
$data['code'] = $data['code'] ?? 200; $data['code'] ??= 200;
$data['path'] = $options['path'] ?? null; $data['path'] = $options['path'] ?? null;
$data['referrer'] = Panel::referrer(); $data['referrer'] = Panel::referrer();

View File

@@ -77,7 +77,7 @@ abstract class Model
*/ */
public function dragTextType(string $type = null): string public function dragTextType(string $type = null): string
{ {
$type = $type ?? 'auto'; $type ??= 'auto';
if ($type === 'auto') { if ($type === 'auto') {
$type = option('panel.kirbytext', true) ? 'kirbytext' : 'markdown'; $type = option('panel.kirbytext', true) ? 'kirbytext' : 'markdown';
@@ -141,8 +141,8 @@ abstract class Model
// main url // main url
$settings['url'] = $image->url(); $settings['url'] = $image->url();
// only create srcsets for actual File objects // only create srcsets for resizable files
if (is_a($image, 'Kirby\Cms\File') === true) { if ($image->isResizable() === true) {
$settings['src'] = static::imagePlaceholder(); $settings['src'] = static::imagePlaceholder();
switch ($layout) { switch ($layout) {
@@ -174,6 +174,8 @@ abstract class Model
] ]
]); ]);
} }
} elseif ($image->isViewable() === true) {
$settings['src'] = $image->url();
} }
} }

View File

@@ -231,7 +231,7 @@ class Page extends Model
*/ */
public function pickerData(array $params = []): array public function pickerData(array $params = []): array
{ {
$params['text'] = $params['text'] ?? '{{ page.title }}'; $params['text'] ??= '{{ page.title }}';
return array_merge(parent::pickerData($params), [ return array_merge(parent::pickerData($params), [
'dragText' => $this->dragText(), 'dragText' => $this->dragText(),

View File

@@ -36,14 +36,14 @@ class Panel
*/ */
public static function area(string $id, $area): array public static function area(string $id, $area): array
{ {
$area['id'] = $id; $area['id'] = $id;
$area['label'] = $area['label'] ?? $id; $area['label'] ??= $id;
$area['breadcrumb'] = $area['breadcrumb'] ?? []; $area['breadcrumb'] ??= [];
$area['breadcrumbLabel'] = $area['breadcrumbLabel'] ?? $area['label']; $area['breadcrumbLabel'] ??= $area['label'];
$area['title'] = $area['label']; $area['title'] = $area['label'];
$area['menu'] = $area['menu'] ?? false; $area['menu'] ??= false;
$area['link'] = $area['link'] ?? $id; $area['link'] ??= $id;
$area['search'] = $area['search'] ?? null; $area['search'] ??= null;
return $area; return $area;
} }
@@ -229,11 +229,14 @@ class Panel
/** /**
* Returns the referrer path if present * Returns the referrer path if present
* *
* @return string|null * @return string
*/ */
public static function referrer(): ?string public static function referrer(): string
{ {
$referrer = kirby()->request()->header('X-Fiber-Referrer') ?? get('_referrer'); $referrer = kirby()->request()->header('X-Fiber-Referrer')
?? get('_referrer')
?? '';
return '/' . trim($referrer, '/'); return '/' . trim($referrer, '/');
} }
@@ -302,9 +305,6 @@ class Panel
// create a micro-router for the Panel // create a micro-router for the Panel
return router($path, $method = $kirby->request()->method(), $routes, function ($route) use ($areas, $kirby, $method, $path) { return router($path, $method = $kirby->request()->method(), $routes, function ($route) use ($areas, $kirby, $method, $path) {
// trigger hook
$route = $kirby->apply('panel.route:before', compact('route', 'path', 'method'), 'route');
// route needs authentication? // route needs authentication?
$auth = $route->attributes()['auth'] ?? true; $auth = $route->attributes()['auth'] ?? true;
$areaId = $route->attributes()['area'] ?? null; $areaId = $route->attributes()['area'] ?? null;
@@ -313,6 +313,9 @@ class Panel
// call the route action to check the result // call the route action to check the result
try { try {
// trigger hook
$route = $kirby->apply('panel.route:before', compact('route', 'path', 'method'), 'route');
// check for access before executing area routes // check for access before executing area routes
if ($auth !== false) { if ($auth !== false) {
static::firewall($kirby->user(), $areaId); static::firewall($kirby->user(), $areaId);
@@ -450,6 +453,15 @@ class Panel
$routes = []; $routes = [];
foreach ($dropdowns as $name => $dropdown) { foreach ($dropdowns as $name => $dropdown) {
// Handle shortcuts for dropdowns. The name is the pattern
// and options are defined in a Closure
if (is_a($dropdown, 'Closure') === true) {
$dropdown = [
'pattern' => $name,
'action' => $dropdown
];
}
// create the full pattern with dropdowns prefix // create the full pattern with dropdowns prefix
$pattern = 'dropdowns/' . trim(($dropdown['pattern'] ?? $name), '/'); $pattern = 'dropdowns/' . trim(($dropdown['pattern'] ?? $name), '/');

View File

@@ -172,7 +172,7 @@ class User extends Model
*/ */
public function pickerData(array $params = null): array public function pickerData(array $params = null): array
{ {
$params['text'] = $params['text'] ?? '{{ user.username }}'; $params['text'] ??= '{{ user.username }}';
return array_merge(parent::pickerData($params), [ return array_merge(parent::pickerData($params), [
'email' => $this->model->email(), 'email' => $this->model->email(),

View File

@@ -47,22 +47,25 @@ class Svg extends Xml
* @var array * @var array
*/ */
public static $allowedAttrs = [ public static $allowedAttrs = [
'accent-height', // core attributes
'accumulate', 'id',
'additive', 'lang',
'alignment-baseline', 'tabindex',
'ascent', 'xml:id',
'attributeName', 'xml:lang',
'attributeType', 'xml:space',
'azimuth',
'baseFrequency', // styling attributes
'baseline-shift',
'begin',
'bias',
'by',
'class', 'class',
'style',
// conditional processing attributes
'systemLanguage',
// presentation attributes
'alignment-baseline',
'baseline-shift',
'clip', 'clip',
'clipPathUnits',
'clip-path', 'clip-path',
'clip-rule', 'clip-rule',
'color', 'color',
@@ -70,24 +73,15 @@ class Svg extends Xml
'color-interpolation-filters', 'color-interpolation-filters',
'color-profile', 'color-profile',
'color-rendering', 'color-rendering',
'cx',
'cy',
'd', 'd',
'dx',
'dy',
'diffuseConstant',
'direction', 'direction',
'display', 'display',
'divisor', 'dominant-baseline',
'dur', 'enable-background',
'edgeMode',
'elevation',
'end',
'fill', 'fill',
'fill-opacity', 'fill-opacity',
'fill-rule', 'fill-rule',
'filter', 'filter',
'filterUnits',
'flood-color', 'flood-color',
'flood-opacity', 'flood-opacity',
'font-family', 'font-family',
@@ -97,18 +91,103 @@ class Svg extends Xml
'font-style', 'font-style',
'font-variant', 'font-variant',
'font-weight', 'font-weight',
'image-rendering',
'kerning',
'letter-spacing',
'lighting-color',
'marker-end',
'marker-mid',
'marker-start',
'mask',
'opacity',
'overflow',
'paint-order',
'shape-rendering',
'stop-color',
'stop-opacity',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'text-anchor',
'text-decoration',
'text-rendering',
'transform',
'visibility',
'word-spacing',
'writing-mode',
// animation attribute target attributes
'attributeName',
'attributeType',
// animation timing attributes
'begin',
'dur',
'end',
'max',
'min',
'repeatCount',
'repeatDur',
'restart',
// animation value attributes
'by',
'from',
'keySplines',
'keyTimes',
'to',
'values',
// animation addition attributes
'accumulate',
'additive',
// filter primitive attributes
'height',
'result',
'width',
'x',
'y',
// transfer function attributes
'amplitude',
'exponent',
'intercept',
'offset',
'slope',
'tableValues',
'type',
// other attributes specific to one or multiple elements
'azimuth',
'baseFrequency',
'bias',
'clipPathUnits',
'cx',
'cy',
'diffuseConstant',
'divisor',
'dx',
'dy',
'edgeMode',
'elevation',
'filterUnits',
'fr',
'fx', 'fx',
'fy', 'fy',
'g1', 'g1',
'g2', 'g2',
'glyph-name', 'glyph-name',
'glyphRef', 'glyphRef',
'gradientUnits',
'gradientTransform', 'gradientTransform',
'height', 'gradientUnits',
'href', 'href',
'id', 'hreflang',
'image-rendering',
'in', 'in',
'in2', 'in2',
'k', 'k',
@@ -116,116 +195,73 @@ class Svg extends Xml
'k2', 'k2',
'k3', 'k3',
'k4', 'k4',
'kerning',
'keyPoints',
'keySplines',
'keyTimes',
'lang',
'lengthAdjust',
'letter-spacing',
'kernelMatrix', 'kernelMatrix',
'kernelUnitLength', 'kernelUnitLength',
'lighting-color', 'keyPoints',
'local', 'lengthAdjust',
'marker-end', 'limitingConeAngle',
'marker-mid',
'marker-start',
'markerHeight', 'markerHeight',
'markerUnits', 'markerUnits',
'markerWidth', 'markerWidth',
'maskContentUnits', 'maskContentUnits',
'maskUnits', 'maskUnits',
'max',
'mask',
'media', 'media',
'method', 'method',
'mode', 'mode',
'min',
'name',
'numOctaves', 'numOctaves',
'offset',
'operator', 'operator',
'opacity',
'order', 'order',
'orient', 'orient',
'orientation', 'orientation',
'origin',
'overflow',
'paint-order',
'path', 'path',
'pathLength', 'pathLength',
'patternContentUnits', 'patternContentUnits',
'patternTransform', 'patternTransform',
'patternUnits', 'patternUnits',
'points', 'points',
'pointsAtX',
'pointsAtY',
'pointsAtZ',
'preserveAlpha', 'preserveAlpha',
'preserveAspectRatio', 'preserveAspectRatio',
'primitiveUnits', 'primitiveUnits',
'r', 'r',
'rx',
'ry',
'radius', 'radius',
'refX', 'refX',
'refY', 'refY',
'repeatCount',
'repeatDur',
'restart',
'result',
'rotate', 'rotate',
'rx',
'ry',
'scale', 'scale',
'seed', 'seed',
'shape-rendering', 'side',
'spacing',
'specularConstant', 'specularConstant',
'specularExponent', 'specularExponent',
'spreadMethod', 'spreadMethod',
'startOffset', 'startOffset',
'stdDeviation', 'stdDeviation',
'stitchTiles', 'stitchTiles',
'stop-color',
'stop-opacity',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke',
'stroke-width',
'style',
'surfaceScale', 'surfaceScale',
'systemLanguage',
'tabindex',
'targetX', 'targetX',
'targetY', 'targetY',
'transform',
'text-anchor',
'text-decoration',
'text-rendering',
'textLength', 'textLength',
'type',
'u1', 'u1',
'u2', 'u2',
'unicode', 'unicode',
'values',
'viewBox',
'visibility',
'version', 'version',
'vert-adv-y', 'vert-adv-y',
'vert-origin-x', 'vert-origin-x',
'vert-origin-y', 'vert-origin-y',
'width', 'viewBox',
'word-spacing',
'wrap',
'writing-mode',
'xChannelSelector',
'yChannelSelector',
'x',
'x1', 'x1',
'x2', 'x2',
'xChannelSelector',
'xlink:href', 'xlink:href',
'y', 'xlink:title',
'y1', 'y1',
'y2', 'y2',
'yChannelSelector',
'z', 'z',
'zoomAndPan', 'zoomAndPan',
]; ];
@@ -247,12 +283,9 @@ class Svg extends Xml
* `allowedAttrs` list or `false` to allow the tag without * `allowedAttrs` list or `false` to allow the tag without
* any attributes * any attributes
* *
* @todo Move attributes from the global list to their tags
*
* @var array * @var array
*/ */
public static $allowedTags = [ public static $allowedTags = [
'svg' => true,
'a' => true, 'a' => true,
'altGlyph' => true, 'altGlyph' => true,
'altGlyphDef' => true, 'altGlyphDef' => true,
@@ -265,39 +298,6 @@ class Svg extends Xml
'defs' => true, 'defs' => true,
'desc' => true, 'desc' => true,
'ellipse' => true, 'ellipse' => true,
'filter' => true,
'font' => true,
'g' => true,
'glyph' => true,
'glyphRef' => true,
'hkern' => true,
'image' => true,
'line' => true,
'linearGradient' => true,
'marker' => true,
'mask' => true,
'metadata' => true,
'mpath' => true,
'path' => true,
'pattern' => true,
'polygon' => true,
'polyline' => true,
'radialGradient' => true,
'rect' => true,
'stop' => true,
'style' => true,
'switch' => true,
'symbol' => true,
'text' => true,
'textPath' => true,
'title' => true,
'tref' => true,
'tspan' => true,
'use' => true,
'view' => true,
'vkern' => true,
// filters
'feBlend' => true, 'feBlend' => true,
'feColorMatrix' => true, 'feColorMatrix' => true,
'feComponentTransfer' => true, 'feComponentTransfer' => true,
@@ -321,6 +321,38 @@ class Svg extends Xml
'feSpotLight' => true, 'feSpotLight' => true,
'feTile' => true, 'feTile' => true,
'feTurbulence' => true, 'feTurbulence' => true,
'filter' => true,
'font' => true,
'g' => true,
'glyph' => true,
'glyphRef' => true,
'hkern' => true,
'image' => true,
'line' => true,
'linearGradient' => true,
'marker' => true,
'mask' => true,
'metadata' => true,
'mpath' => true,
'path' => true,
'pattern' => true,
'polygon' => true,
'polyline' => true,
'radialGradient' => true,
'rect' => true,
'stop' => true,
'style' => true,
'svg' => true,
'switch' => true,
'symbol' => true,
'text' => true,
'textPath' => true,
'title' => true,
'tref' => true,
'tspan' => true,
'use' => true,
'view' => true,
'vkern' => true,
]; ];
/** /**

View File

@@ -467,10 +467,7 @@ class A
{ {
// convert a simple ignore list to a nested $key => true array // convert a simple ignore list to a nested $key => true array
if (isset($ignore[0]) === true) { if (isset($ignore[0]) === true) {
$ignore = array_map(function () { $ignore = array_map(fn () => true, array_flip($ignore));
return true;
}, array_flip($ignore));
$ignore = A::nest($ignore); $ignore = A::nest($ignore);
} }

View File

@@ -138,7 +138,7 @@ class Dom
*/ */
public function body() public function body()
{ {
return $this->body = $this->body ?? $this->query('/html/body')[0] ?? null; return $this->body ??= $this->query('/html/body')[0] ?? null;
} }
/** /**
@@ -455,11 +455,20 @@ class Dom
}; };
} }
if ($allowedNamespaces === true) { // if the configuration does not define namespace URIs or if the
// take the list as it is and only consider // currently checked node is from the special `xml:` namespace
// that has a fixed namespace according to the XML spec...
if ($allowedNamespaces === true || $node->namespaceURI === 'http://www.w3.org/XML/1998/namespace') {
// ...take the list as it is and only consider
// exact matches of the local name (which will // exact matches of the local name (which will
// contain a namespace if that namespace name // contain a namespace if that namespace name
// is not defined in the document) // is not defined in the document)
// the list contains the `xml:` prefix, so add it to the name as well
if ($node->namespaceURI === 'http://www.w3.org/XML/1998/namespace') {
$localName = 'xml:' . $localName;
}
foreach ($list as $item) { foreach ($list as $item) {
if ($compare($item, $localName) === true) { if ($compare($item, $localName) === true) {
return $item; return $item;

View File

@@ -81,7 +81,7 @@ class Escape
*/ */
protected static function escaper() protected static function escaper()
{ {
return static::$escaper = static::$escaper ?? new Escaper('utf-8'); return static::$escaper ??= new Escaper('utf-8');
} }
/** /**

View File

@@ -24,6 +24,34 @@ class Html extends Xml
*/ */
public static $entities; public static $entities;
/**
* List of HTML tags that can be used inline
*
* @var array
*/
public static $inlineList = [
'b',
'i',
'small',
'abbr',
'cite',
'code',
'dfn',
'em',
'kbd',
'strong',
'samp',
'var',
'a',
'bdo',
'br',
'img',
'q',
'span',
'sub',
'sup'
];
/** /**
* Closing string for void tags; * Closing string for void tags;
* can be used to switch to trailing slashes if required * can be used to switch to trailing slashes if required
@@ -117,6 +145,10 @@ class Html extends Xml
// all other cases can share the XML variant // all other cases can share the XML variant
$attr = parent::attr($name, $value); $attr = parent::attr($name, $value);
if ($attr === null) {
return null;
}
// HTML supports named entities // HTML supports named entities
$entities = parent::entities(); $entities = parent::entities();
$html = array_keys($entities); $html = array_keys($entities);
@@ -205,7 +237,7 @@ class Html extends Xml
*/ */
public static function entities(): array public static function entities(): array
{ {
return self::$entities = self::$entities ?? get_html_translation_table(HTML_ENTITIES); return self::$entities ??= get_html_translation_table(HTML_ENTITIES);
} }
/** /**
@@ -325,7 +357,7 @@ class Html extends Xml
*/ */
public static function rel(?string $rel = null, ?string $target = null): ?string public static function rel(?string $rel = null, ?string $target = null): ?string
{ {
$rel = trim($rel); $rel = trim($rel ?? '');
if ($target === '_blank') { if ($target === '_blank') {
if (empty($rel) === false) { if (empty($rel) === false) {

View File

@@ -115,7 +115,7 @@ class I18n
*/ */
public static function formatNumber($number, string $locale = null): string public static function formatNumber($number, string $locale = null): string
{ {
$locale = $locale ?? static::locale(); $locale ??= static::locale();
$formatter = static::decimalNumberFormatter($locale); $formatter = static::decimalNumberFormatter($locale);
if ($formatter !== null) { if ($formatter !== null) {
@@ -154,7 +154,7 @@ class I18n
*/ */
public static function translate($key, $fallback = null, string $locale = null) public static function translate($key, $fallback = null, string $locale = null)
{ {
$locale = $locale ?? static::locale(); $locale ??= static::locale();
if (is_array($key) === true) { if (is_array($key) === true) {
if (isset($key[$locale])) { if (isset($key[$locale])) {
@@ -224,7 +224,7 @@ class I18n
*/ */
public static function translation(string $locale = null): array public static function translation(string $locale = null): array
{ {
$locale = $locale ?? static::locale(); $locale ??= static::locale();
if (isset(static::$translations[$locale]) === true) { if (isset(static::$translations[$locale]) === true) {
return static::$translations[$locale]; return static::$translations[$locale];
@@ -283,8 +283,7 @@ class I18n
*/ */
public static function translateCount(string $key, int $count, string $locale = null, bool $formatNumber = true) public static function translateCount(string $key, int $count, string $locale = null, bool $formatNumber = true)
{ {
$locale = $locale ?? static::locale(); $locale ??= static::locale();
$translation = static::translate($key, null, $locale); $translation = static::translate($key, null, $locale);
if ($translation === null) { if ($translation === null) {

View File

@@ -40,7 +40,7 @@ class Iterator implements IteratorAggregate
* *
* @return \ArrayIterator * @return \ArrayIterator
*/ */
public function getIterator() public function getIterator(): ArrayIterator
{ {
return new ArrayIterator($this->data); return new ArrayIterator($this->data);
} }

View File

@@ -115,7 +115,7 @@ trait Properties
} }
// fetch the default value from the property // fetch the default value from the property
$value = $value ?? $this->$name ?? null; $value ??= $this->$name ?? null;
// store all original properties, to be able to clone them later // store all original properties, to be able to clone them later
$this->propertyData[$name] = $value; $this->propertyData[$name] = $value;

View File

@@ -3,6 +3,7 @@
namespace Kirby\Toolkit; namespace Kirby\Toolkit;
use Exception; use Exception;
use Kirby\Exception\InvalidArgumentException;
/** /**
* The String class provides a set * The String class provides a set
@@ -180,9 +181,9 @@ class Str
if ($position === false) { if ($position === false) {
return ''; return '';
} else {
return static::substr($string, $position + static::length($needle));
} }
return static::substr($string, $position + static::length($needle));
} }
/** /**
@@ -222,9 +223,9 @@ class Str
if ($position === false) { if ($position === false) {
return ''; return '';
} else {
return static::substr($string, 0, $position);
} }
return static::substr($string, 0, $position);
} }
/** /**
@@ -250,7 +251,40 @@ class Str
*/ */
public static function contains(string $string = null, string $needle, bool $caseInsensitive = false): bool public static function contains(string $string = null, string $needle, bool $caseInsensitive = false): bool
{ {
return call_user_func($caseInsensitive === true ? 'stripos' : 'strpos', $string, $needle) !== false; if ($needle === '') {
return true;
}
$method = $caseInsensitive === true ? 'stripos' : 'strpos';
return call_user_func($method, $string, $needle) !== false;
}
/**
* Convert timestamp to date string
* according to locale settings
*
* @param int|null $time
* @param string|null $format
* @param string $handler date or strftime
* @return string|int
*/
public static function date(?int $time = null, ?string $format = null, string $handler = 'date')
{
if (is_null($format) === true) {
return $time;
}
// separately handle strftime to be able
// to suppress deprecation warning
// TODO: remove strftime support for PHP 9.0
if ($handler === 'strftime') {
// make sure timezone is set correctly
date_default_timezone_get();
return @strftime($format, $time);
}
return $handler($format, $time);
} }
/** /**
@@ -373,6 +407,9 @@ class Str
*/ */
public static function float($value): string public static function float($value): string
{ {
// make sure $value is not null
$value ??= '';
// Convert exponential to decimal, 1e-8 as 0.00000001 // Convert exponential to decimal, 1e-8 as 0.00000001
if (strpos(strtolower($value), 'e') !== false) { if (strpos(strtolower($value), 'e') !== false) {
$value = rtrim(sprintf('%.16f', (float)$value), '0'); $value = rtrim(sprintf('%.16f', (float)$value), '0');
@@ -397,9 +434,9 @@ class Str
if ($position === false) { if ($position === false) {
return ''; return '';
} else {
return static::substr($string, $position);
} }
return static::substr($string, $position);
} }
/** /**
@@ -436,7 +473,7 @@ class Str
*/ */
public static function length(string $string = null): int public static function length(string $string = null): int
{ {
return mb_strlen($string, 'UTF-8'); return mb_strlen($string ?? '', 'UTF-8');
} }
/** /**
@@ -512,6 +549,10 @@ class Str
*/ */
public static function position(string $string = null, string $needle, bool $caseInsensitive = false) public static function position(string $string = null, string $needle, bool $caseInsensitive = false)
{ {
if ($needle === '') {
throw new InvalidArgumentException('The needle must not be empty');
}
if ($caseInsensitive === true) { if ($caseInsensitive === true) {
$string = static::lower($string); $string = static::lower($string);
$needle = static::lower($needle); $needle = static::lower($needle);
@@ -899,10 +940,10 @@ class Str
*/ */
public static function slug(string $string = null, string $separator = null, string $allowed = null, int $maxlength = 128): string public static function slug(string $string = null, string $separator = null, string $allowed = null, int $maxlength = 128): string
{ {
$separator = $separator ?? static::$defaults['slug']['separator']; $separator ??= static::$defaults['slug']['separator'];
$allowed = $allowed ?? static::$defaults['slug']['allowed']; $allowed ??= static::$defaults['slug']['allowed'];
$string = trim($string); $string = trim($string ?? '');
$string = static::lower($string); $string = static::lower($string);
$string = static::ascii($string); $string = static::ascii($string);
@@ -958,8 +999,11 @@ class Str
return $string; return $string;
} }
$parts = explode($separator, $string); // make sure $string is string
$out = []; $string ??= '';
$parts = explode($separator, $string);
$out = [];
foreach ($parts as $p) { foreach ($parts as $p) {
$p = trim($p); $p = trim($p);
@@ -1046,6 +1090,9 @@ class Str
$start = (string)($options['start'] ?? $start); $start = (string)($options['start'] ?? $start);
$end = (string)($options['end'] ?? $end); $end = (string)($options['end'] ?? $end);
// make sure $string is string
$string ??= '';
return preg_replace_callback('!' . $start . '(.*?)' . $end . '!', function ($match) use ($data, $fallback, $callback) { return preg_replace_callback('!' . $start . '(.*?)' . $end . '!', function ($match) use ($data, $fallback, $callback) {
$query = trim($match[1]); $query = trim($match[1]);
@@ -1084,8 +1131,12 @@ class Str
*/ */
public static function toBytes($size): int public static function toBytes($size): int
{ {
// TODO: remove in 3.7.0
// in favor of strict parameter type hint
$size ??= '';
$size = trim($size); $size = trim($size);
$last = strtolower($size[strlen($size)-1] ?? null); $last = strtolower($size[strlen($size)-1] ?? '');
$size = (int)$size; $size = (int)$size;
switch ($last) { switch ($last) {
@@ -1198,9 +1249,9 @@ class Str
if ($position === false) { if ($position === false) {
return ''; return '';
} else {
return static::substr($string, 0, $position + static::length($needle));
} }
return static::substr($string, 0, $position + static::length($needle));
} }
/** /**
@@ -1224,6 +1275,9 @@ class Str
*/ */
public static function widont(string $string = null): string public static function widont(string $string = null): string
{ {
// make sure $string is string
$string ??= '';
// Replace space between last word and punctuation // Replace space between last word and punctuation
$string = preg_replace_callback('|(\S)\s(\S?)$|u', function ($matches) { $string = preg_replace_callback('|(\S)\s(\S?)$|u', function ($matches) {
return $matches[1] . '&nbsp;' . $matches[2]; return $matches[1] . '&nbsp;' . $matches[2];

View File

@@ -249,6 +249,9 @@ V::$validators = [
* third argument to compare them. * third argument to compare them.
*/ */
'date' => function (?string $value, string $operator = null, string $test = null): bool { 'date' => function (?string $value, string $operator = null, string $test = null): bool {
// make sure $value is a string
$value ??= '';
$args = func_get_args(); $args = func_get_args();
// simple date validation // simple date validation
@@ -522,6 +525,6 @@ V::$validators = [
// In search for the perfect regular expression: https://mathiasbynens.be/demo/url-regex // In search for the perfect regular expression: https://mathiasbynens.be/demo/url-regex
// Added localhost support and removed 127.*.*.* ip restriction // Added localhost support and removed 127.*.*.* ip restriction
$regex = '_^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:localhost)|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$_iu'; $regex = '_^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:localhost)|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$_iu';
return preg_match($regex, $value) !== 0; return preg_match($regex, $value ?? '') !== 0;
} }
]; ];

View File

@@ -106,9 +106,10 @@ class Xml
if (isset($value['value'], $value['escape'])) { if (isset($value['value'], $value['escape'])) {
$value = $value['escape'] === true ? static::encode($value['value']) : $value['value']; $value = $value['escape'] === true ? static::encode($value['value']) : $value['value'];
} else { } else {
$value = implode(' ', array_filter($value, function ($value) { $value = implode(' ', array_filter(
return !empty($value) || is_numeric($value); $value,
})); fn ($value) => !empty($value) || is_numeric($value)
));
} }
} else { } else {
$value = static::encode($value); $value = static::encode($value);
@@ -420,7 +421,8 @@ class Xml
return $value; return $value;
} }
$encoded = htmlentities($value); // TODO: in 3.7.0 use ENT_NOQUOTES | ENT_XML1 instead
$encoded = htmlentities($value, ENT_COMPAT);
if ($encoded === $value) { if ($encoded === $value) {
// no CDATA block needed // no CDATA block needed
return $value; return $value;

View File

@@ -48,7 +48,7 @@ class SimpleImage {
* @param string $image An image file or a data URI to load. * @param string $image An image file or a data URI to load.
* @throws \Exception Thrown if the GD library is not found; file|URI or image data is invalid. * @throws \Exception Thrown if the GD library is not found; file|URI or image data is invalid.
*/ */
public function __construct($image = null) { public function __construct($image = '') {
// Check for the required GD extension // Check for the required GD extension
if(extension_loaded('gd')) { if(extension_loaded('gd')) {
// Ignore JPEG warnings that cause imagecreatefromjpeg() to fail // Ignore JPEG warnings that cause imagecreatefromjpeg() to fail
@@ -147,7 +147,7 @@ class SimpleImage {
// workaround to prevent imagepalettetruecolor() from borking transparency. // workaround to prevent imagepalettetruecolor() from borking transparency.
$width = imagesx($gif); $width = imagesx($gif);
$height = imagesy($gif); $height = imagesy($gif);
$this->image = imagecreatetruecolor($width, $height); $this->image = imagecreatetruecolor((int) $width, (int) $height);
$transparentColor = imagecolorallocatealpha($this->image, 0, 0, 0, 127); $transparentColor = imagecolorallocatealpha($this->image, 0, 0, 0, 127);
imagecolortransparent($this->image, $transparentColor); imagecolortransparent($this->image, $transparentColor);
imagefill($this->image, 0, 0, $transparentColor); imagefill($this->image, 0, 0, $transparentColor);
@@ -194,7 +194,7 @@ class SimpleImage {
* @return \claviska\SimpleImage * @return \claviska\SimpleImage
*/ */
public function fromNew($width, $height, $color = 'transparent') { public function fromNew($width, $height, $color = 'transparent') {
$this->image = imagecreatetruecolor($width, $height); $this->image = imagecreatetruecolor((int) $width, (int) $height);
// Use PNG for dynamically created images because it's lossless and supports transparency // Use PNG for dynamically created images because it's lossless and supports transparency
$this->mimeType = 'image/png'; $this->mimeType = 'image/png';
@@ -490,7 +490,7 @@ class SimpleImage {
imagefilter($srcIm, IMG_FILTER_COLORIZE, 0, 0, 0, 127 * ((100 - $pct) / 100)); imagefilter($srcIm, IMG_FILTER_COLORIZE, 0, 0, 0, 127 * ((100 - $pct) / 100));
} }
imagecopy($dstIm, $srcIm, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH); imagecopy($dstIm, $srcIm, (int) $dstX, (int) $dstY, (int) $srcX, (int) $srcY, (int) $srcW, (int) $srcH);
return true; return true;
} }
@@ -593,7 +593,7 @@ class SimpleImage {
// Avoid using native imagecrop() because of a bug with PNG transparency // Avoid using native imagecrop() because of a bug with PNG transparency
$dstW = abs($x2 - $x1); $dstW = abs($x2 - $x1);
$dstH = abs($y2 - $y1); $dstH = abs($y2 - $y1);
$newImage = imagecreatetruecolor($dstW, $dstH); $newImage = imagecreatetruecolor((int) $dstW, (int) $dstH);
$transparentColor = imagecolorallocatealpha($newImage, 0, 0, 0, 127); $transparentColor = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
imagecolortransparent($newImage, $transparentColor); imagecolortransparent($newImage, $transparentColor);
imagefill($newImage, 0, 0, $transparentColor); imagefill($newImage, 0, 0, $transparentColor);
@@ -603,10 +603,10 @@ class SimpleImage {
$newImage, $newImage,
$this->image, $this->image,
0, 0, min($x1, $x2), min($y1, $y2), 0, 0, min($x1, $x2), min($y1, $y2),
$dstW, (int) $dstW,
$dstH, (int) $dstH,
$dstW, (int) $dstW,
$dstH (int) $dstH
); );
// Swap out the new image // Swap out the new image
@@ -802,7 +802,7 @@ class SimpleImage {
// We can't use imagescale because it doesn't seem to preserve transparency properly. The // We can't use imagescale because it doesn't seem to preserve transparency properly. The
// workaround is to create a new truecolor image, allocate a transparent color, and copy the // workaround is to create a new truecolor image, allocate a transparent color, and copy the
// image over to it using imagecopyresampled. // image over to it using imagecopyresampled.
$newImage = imagecreatetruecolor($width, $height); $newImage = imagecreatetruecolor((int) $width, (int) $height);
$transparentColor = imagecolorallocatealpha($newImage, 0, 0, 0, 127); $transparentColor = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
imagecolortransparent($newImage, $transparentColor); imagecolortransparent($newImage, $transparentColor);
imagefill($newImage, 0, 0, $transparentColor); imagefill($newImage, 0, 0, $transparentColor);
@@ -810,8 +810,8 @@ class SimpleImage {
$newImage, $newImage,
$this->image, $this->image,
0, 0, 0, 0, 0, 0, 0, 0,
$width, (int) $width,
$height, (int) $height,
$this->getWidth(), $this->getWidth(),
$this->getHeight() $this->getHeight()
); );
@@ -1885,7 +1885,7 @@ class SimpleImage {
$color['red'], $color['red'],
$color['green'], $color['green'],
$color['blue'], $color['blue'],
127 - ($color['alpha'] * 127) (int) (127 - ($color['alpha'] * 127))
); );
if($index > -1) { if($index > -1) {
// Yes, return this color index // Yes, return this color index

View File

@@ -264,6 +264,7 @@ return array(
'League\\ColorExtractor\\Palette' => $vendorDir . '/league/color-extractor/src/League/ColorExtractor/Palette.php', 'League\\ColorExtractor\\Palette' => $vendorDir . '/league/color-extractor/src/League/ColorExtractor/Palette.php',
'Michelf\\SmartyPants' => $vendorDir . '/michelf/php-smartypants/Michelf/SmartyPants.php', 'Michelf\\SmartyPants' => $vendorDir . '/michelf/php-smartypants/Michelf/SmartyPants.php',
'Michelf\\SmartyPantsTypographer' => $vendorDir . '/michelf/php-smartypants/Michelf/SmartyPantsTypographer.php', 'Michelf\\SmartyPantsTypographer' => $vendorDir . '/michelf/php-smartypants/Michelf/SmartyPantsTypographer.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PHPMailer\\PHPMailer\\Exception' => $vendorDir . '/phpmailer/phpmailer/src/Exception.php', 'PHPMailer\\PHPMailer\\Exception' => $vendorDir . '/phpmailer/phpmailer/src/Exception.php',
'PHPMailer\\PHPMailer\\OAuth' => $vendorDir . '/phpmailer/phpmailer/src/OAuth.php', 'PHPMailer\\PHPMailer\\OAuth' => $vendorDir . '/phpmailer/phpmailer/src/OAuth.php',
'PHPMailer\\PHPMailer\\PHPMailer' => $vendorDir . '/phpmailer/phpmailer/src/PHPMailer.php', 'PHPMailer\\PHPMailer\\PHPMailer' => $vendorDir . '/phpmailer/phpmailer/src/PHPMailer.php',
@@ -279,11 +280,12 @@ return array(
'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
'Symfony\\Polyfill\\Intl\\Idn\\Idn' => $vendorDir . '/symfony/polyfill-intl-idn/Idn.php',
'Symfony\\Polyfill\\Intl\\Idn\\Info' => $vendorDir . '/symfony/polyfill-intl-idn/Info.php',
'Symfony\\Polyfill\\Intl\\Idn\\Resources\\unidata\\DisallowedRanges' => $vendorDir . '/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php',
'Symfony\\Polyfill\\Intl\\Idn\\Resources\\unidata\\Regex' => $vendorDir . '/symfony/polyfill-intl-idn/Resources/unidata/Regex.php',
'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php',
'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php',
'TrueBV\\Exception\\DomainOutOfBoundsException' => $vendorDir . '/true/punycode/src/Exception/DomainOutOfBoundsException.php',
'TrueBV\\Exception\\LabelOutOfBoundsException' => $vendorDir . '/true/punycode/src/Exception/LabelOutOfBoundsException.php',
'TrueBV\\Exception\\OutOfBoundsException' => $vendorDir . '/true/punycode/src/Exception/OutOfBoundsException.php',
'TrueBV\\Punycode' => $vendorDir . '/true/punycode/src/Punycode.php',
'Whoops\\Exception\\ErrorException' => $vendorDir . '/filp/whoops/src/Whoops/Exception/ErrorException.php', 'Whoops\\Exception\\ErrorException' => $vendorDir . '/filp/whoops/src/Whoops/Exception/ErrorException.php',
'Whoops\\Exception\\Formatter' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Formatter.php', 'Whoops\\Exception\\Formatter' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Formatter.php',
'Whoops\\Exception\\Frame' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Frame.php', 'Whoops\\Exception\\Frame' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Frame.php',

View File

@@ -6,8 +6,10 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php', '04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'f864ae44e8154e5ff6f4eec32f46d37f' => $baseDir . '/config/setup.php', 'f864ae44e8154e5ff6f4eec32f46d37f' => $baseDir . '/config/setup.php',
'87988fc7b1c1f093da22a1a3de972f3a' => $baseDir . '/config/helpers.php', '87988fc7b1c1f093da22a1a3de972f3a' => $baseDir . '/config/helpers.php',
); );

View File

@@ -7,8 +7,9 @@ $baseDir = dirname($vendorDir);
return array( return array(
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'), 'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
'TrueBV\\' => array($vendorDir . '/true/punycode/src'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'Laminas\\Escaper\\' => array($vendorDir . '/laminas/laminas-escaper/src'), 'Laminas\\Escaper\\' => array($vendorDir . '/laminas/laminas-escaper/src'),

Some files were not shown because too many files have changed in this diff Show More