Upgrade to 3.6.2

This commit is contained in:
Bastian Allgeier
2022-02-01 11:42:39 +01:00
parent f62d1a39ca
commit 848ea36dcf
108 changed files with 2887 additions and 2622 deletions

View File

@@ -771,10 +771,11 @@ class App
*/
public function kirbytags(string $text = null, array $data = []): string
{
$data['kirby'] = $data['kirby'] ?? $this;
$data['site'] = $data['site'] ?? $data['kirby']->site();
$data['parent'] = $data['parent'] ?? $data['site']->page();
$options = $this->options;
$data['kirby'] ??= $this;
$data['site'] ??= $data['kirby']->site();
$data['parent'] ??= $data['site']->page();
$options = $this->options;
$text = $this->apply('kirbytags:before', compact('text', 'data', 'options'), 'text');
$text = KirbyTags::parse($text, $data, $options);
@@ -789,14 +790,23 @@ class App
* @internal
* @param string|null $text
* @param array $data
* @param bool $inline
* @param bool $inline (deprecated: use $data['markdown']['inline'] instead)
* @return string
* @todo add deprecation warning for $inline parameter in 3.7.0
* @todo rename $data parameter to $options in 3.7.0
* @todo remove $inline parameter in in 3.8.0
*/
public function kirbytext(string $text = null, array $data = [], bool $inline = false): string
{
$options = A::merge([
'markdown' => [
'inline' => $inline
]
], $data);
$text = $this->apply('kirbytext:before', compact('text'), 'text');
$text = $this->kirbytags($text, $data);
$text = $this->markdown($text, $inline);
$text = $this->kirbytags($text, $options);
$text = $this->markdown($text, $options['markdown']);
if ($this->option('smartypants', false) !== false) {
$text = $this->smartypants($text);
@@ -890,12 +900,31 @@ class App
*
* @internal
* @param string|null $text
* @param bool $inline
* @param bool|array $options
* @return string
* @todo rename $inline parameter to $options in 3.7.0
* @todo add deprecation warning for boolean $options in 3.7.0
* @todo remove boolean $options in in 3.8.0
*/
public function markdown(string $text = null, bool $inline = false): string
public function markdown(string $text = null, $inline = null): string
{
return ($this->component('markdown'))($this, $text, $this->options['markdown'] ?? [], $inline);
// TODO: remove after renaming parameter
$options = $inline;
// support for the old syntax to enable inline mode as second argument
if (is_bool($options) === true) {
$options = [
'inline' => $options
];
}
// merge global options with local options
$options = array_merge(
$this->options['markdown'] ?? [],
(array)$options
);
return ($this->component('markdown'))($this, $text, $options);
}
/**

View File

@@ -3,6 +3,7 @@
namespace Kirby\Cms;
use Kirby\Http\Response;
use Kirby\Toolkit\I18n;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\Handler;
use Whoops\Handler\PlainTextHandler;
@@ -136,7 +137,7 @@ trait AppErrors
'status' => 'error',
'code' => $code,
'details' => $details,
'message' => 'An unexpected error occurred! Enable debug mode for more info: https://getkirby.com/docs/reference/system/options/debug',
'message' => I18n::translate('error.unexpected'),
], $httpCode);
}

View File

@@ -333,8 +333,6 @@ class Collection extends BaseCollection
*/
public function toArray(Closure $map = null): array
{
return parent::toArray($map ?? function ($object) {
return $object->toArray();
});
return parent::toArray($map ?? fn ($object) => $object->toArray());
}
}

View File

@@ -322,98 +322,32 @@ class Core
public function roots(): array
{
return $this->cache['roots'] ??= [
// kirby
'kirby' => function (array $roots) {
return dirname(__DIR__, 2);
},
'kirby' => fn (array $roots) => dirname(__DIR__, 2),
'i18n' => fn (array $roots) => $roots['kirby'] . '/i18n',
'i18n:translations' => fn (array $roots) => $roots['i18n'] . '/translations',
'i18n:rules' => fn (array $roots) => $roots['i18n'] . '/rules',
// i18n
'i18n' => function (array $roots) {
return $roots['kirby'] . '/i18n';
},
'i18n:translations' => function (array $roots) {
return $roots['i18n'] . '/translations';
},
'i18n:rules' => function (array $roots) {
return $roots['i18n'] . '/rules';
},
// index
'index' => function (array $roots) {
return dirname(__DIR__, 3);
},
// assets
'assets' => function (array $roots) {
return $roots['index'] . '/assets';
},
// content
'content' => function (array $roots) {
return $roots['index'] . '/content';
},
// media
'media' => function (array $roots) {
return $roots['index'] . '/media';
},
// panel
'panel' => function (array $roots) {
return $roots['kirby'] . '/panel';
},
// site
'site' => function (array $roots) {
return $roots['index'] . '/site';
},
'accounts' => function (array $roots) {
return $roots['site'] . '/accounts';
},
'blueprints' => function (array $roots) {
return $roots['site'] . '/blueprints';
},
'cache' => function (array $roots) {
return $roots['site'] . '/cache';
},
'collections' => function (array $roots) {
return $roots['site'] . '/collections';
},
'config' => function (array $roots) {
return $roots['site'] . '/config';
},
'controllers' => function (array $roots) {
return $roots['site'] . '/controllers';
},
'languages' => function (array $roots) {
return $roots['site'] . '/languages';
},
'license' => function (array $roots) {
return $roots['config'] . '/.license';
},
'logs' => function (array $roots) {
return $roots['site'] . '/logs';
},
'models' => function (array $roots) {
return $roots['site'] . '/models';
},
'plugins' => function (array $roots) {
return $roots['site'] . '/plugins';
},
'sessions' => function (array $roots) {
return $roots['site'] . '/sessions';
},
'snippets' => function (array $roots) {
return $roots['site'] . '/snippets';
},
'templates' => function (array $roots) {
return $roots['site'] . '/templates';
},
// blueprints
'roles' => function (array $roots) {
return $roots['blueprints'] . '/users';
},
'index' => fn (array $roots) => dirname(__DIR__, 3),
'assets' => fn (array $roots) => $roots['index'] . '/assets',
'content' => fn (array $roots) => $roots['index'] . '/content',
'media' => fn (array $roots) => $roots['index'] . '/media',
'panel' => fn (array $roots) => $roots['kirby'] . '/panel',
'site' => fn (array $roots) => $roots['index'] . '/site',
'accounts' => fn (array $roots) => $roots['site'] . '/accounts',
'blueprints' => fn (array $roots) => $roots['site'] . '/blueprints',
'cache' => fn (array $roots) => $roots['site'] . '/cache',
'collections' => fn (array $roots) => $roots['site'] . '/collections',
'config' => fn (array $roots) => $roots['site'] . '/config',
'controllers' => fn (array $roots) => $roots['site'] . '/controllers',
'languages' => fn (array $roots) => $roots['site'] . '/languages',
'license' => fn (array $roots) => $roots['config'] . '/.license',
'logs' => fn (array $roots) => $roots['site'] . '/logs',
'models' => fn (array $roots) => $roots['site'] . '/models',
'plugins' => fn (array $roots) => $roots['site'] . '/plugins',
'sessions' => fn (array $roots) => $roots['site'] . '/sessions',
'snippets' => fn (array $roots) => $roots['site'] . '/snippets',
'templates' => fn (array $roots) => $roots['site'] . '/templates',
'roles' => fn (array $roots) => $roots['blueprints'] . '/users',
];
}
@@ -518,12 +452,8 @@ class Core
public function urls(): array
{
return $this->cache['urls'] ??= [
'index' => function () {
return Url::index();
},
'base' => function (array $urls) {
return rtrim($urls['index'], '/');
},
'index' => fn () => Url::index(),
'base' => fn (array $urls) => rtrim($urls['index'], '/'),
'current' => function (array $urls) {
$path = trim($this->kirby->path(), '/');
@@ -533,18 +463,10 @@ class Core
return $urls['base'] . '/' . $path;
}
},
'assets' => function (array $urls) {
return $urls['base'] . '/assets';
},
'api' => function (array $urls) {
return $urls['base'] . '/' . $this->kirby->option('api.slug', 'api');
},
'media' => function (array $urls) {
return $urls['base'] . '/media';
},
'panel' => function (array $urls) {
return $urls['base'] . '/' . $this->kirby->option('panel.slug', 'panel');
}
'assets' => fn (array $urls) => $urls['base'] . '/assets',
'api' => fn (array $urls) => $urls['base'] . '/' . $this->kirby->option('api.slug', 'api'),
'media' => fn (array $urls) => $urls['base'] . '/media',
'panel' => fn (array $urls) => $urls['base'] . '/' . $this->kirby->option('panel.slug', 'panel')
];
}
}

View File

@@ -90,9 +90,11 @@ trait FileActions
*/
public function changeSort(int $sort)
{
return $this->commit('changeSort', ['file' => $this, 'position' => $sort], function ($file, $sort) {
return $file->save(['sort' => $sort]);
});
return $this->commit(
'changeSort',
['file' => $this, 'position' => $sort],
fn ($file, $sort) => $file->save(['sort' => $sort])
);
}
/**

View File

@@ -135,11 +135,14 @@ class Files extends Collection
* human-readable format
* @since 3.6.0
*
* @param string|null|false $locale Locale for number formatting,
* `null` for the current locale,
* `false` to disable number formatting
* @return string
*/
public function niceSize(): string
public function niceSize($locale = null): string
{
return F::niceSize($this->size());
return F::niceSize($this->size(), $locale);
}
/**
@@ -151,9 +154,7 @@ class Files extends Collection
*/
public function size(): int
{
return F::size($this->values(function ($file) {
return $file->root();
}));
return F::size($this->values(fn ($file) => $file->root()));
}
/**

View File

@@ -26,8 +26,6 @@ class NestCollection extends BaseCollection
*/
public function toArray(Closure $map = null): array
{
return parent::toArray($map ?? function ($object) {
return $object->toArray();
});
return parent::toArray($map ?? fn ($object) => $object->toArray());
}
}

View File

@@ -201,9 +201,11 @@ trait PageActions
protected function changeStatusToDraft()
{
$arguments = ['page' => $this, 'status' => 'draft', 'position' => null];
$page = $this->commit('changeStatus', $arguments, function ($page) {
return $page->unpublish();
});
$page = $this->commit(
'changeStatus',
$arguments,
fn ($page) => $page->unpublish()
);
return $page;
}
@@ -755,9 +757,7 @@ trait PageActions
->children()
->listed()
->append($this)
->filter(function ($page) {
return $page->blueprint()->num() === 'default';
});
->filter(fn ($page) => $page->blueprint()->num() === 'default');
// get a non-associative array of ids
$keys = $siblings->keys();
@@ -804,9 +804,7 @@ trait PageActions
->children()
->listed()
->not($this)
->filter(function ($page) {
return $page->blueprint()->num() === 'default';
});
->filter(fn ($page) => $page->blueprint()->num() === 'default');
if ($siblings->count() > 0) {
foreach ($siblings as $sibling) {

View File

@@ -110,7 +110,7 @@ trait PageSiblings
*/
public function prevUnlisted($collection = null)
{
return $this->prevAll($collection)->unlisted()->first();
return $this->prevAll($collection)->unlisted()->last();
}
/**

View File

@@ -205,6 +205,10 @@ class Pages extends Collection
*/
public function findById(string $id = null)
{
if ($id === null) {
return null;
}
// remove trailing or leading slashes
$id = trim($id, '/');

View File

@@ -482,11 +482,14 @@ class Dir
* Returns a nicely formatted size of all the contents of the folder
*
* @param string $dir The path of the directory
* @param string|null|false $locale Locale for number formatting,
* `null` for the current locale,
* `false` to disable number formatting
* @return mixed
*/
public static function niceSize(string $dir)
public static function niceSize(string $dir, $locale = null)
{
return F::niceSize(static::size($dir));
return F::niceSize(static::size($dir), $locale);
}
/**
@@ -513,9 +516,7 @@ class Dir
// add absolute paths
if ($absolute === true) {
$result = array_map(function ($item) use ($dir) {
return $dir . '/' . $item;
}, $result);
$result = array_map(fn ($item) => $dir . '/' . $item, $result);
}
return $result;

View File

@@ -756,9 +756,11 @@ class F
public static function size($file): int
{
if (is_array($file) === true) {
return array_reduce($file, function ($total, $file) {
return $total + F::size($file);
}, 0);
return array_reduce(
$file,
fn ($total, $file) => $total + F::size($file),
0
);
}
try {

View File

@@ -306,9 +306,11 @@ class File
// determine if any pattern matches the MIME type;
// once any pattern matches, `$carry` is `true` and the rest is skipped
$matches = array_reduce($rules['mime'], function ($carry, $pattern) use ($mime) {
return $carry || Mime::matches($mime, $pattern);
}, false);
$matches = array_reduce(
$rules['mime'],
fn ($carry, $pattern) => $carry || Mime::matches($mime, $pattern),
false
);
if ($matches !== true) {
throw new Exception([
@@ -416,11 +418,14 @@ class File
* Returns the file size in a
* human-readable format
*
* @param string|null|false $locale Locale for number formatting,
* `null` for the current locale,
* `false` to disable number formatting
* @return string
*/
public function niceSize(): string
public function niceSize($locale = null): string
{
return F::niceSize($this->root);
return F::niceSize($this->root, $locale);
}
/**

View File

@@ -142,9 +142,7 @@ class BlocksField extends FieldClass
return [
[
'pattern' => 'uuid',
'action' => function () {
return ['uuid' => uuid()];
}
'action' => fn () => ['uuid' => uuid()]
],
[
'pattern' => 'paste',

View File

@@ -84,13 +84,11 @@ class LayoutField extends BlocksField
return Layout::factory([
'attrs' => $attrs,
'columns' => array_map(function ($width) {
return [
'blocks' => [],
'id' => uuid(),
'width' => $width,
];
}, $columns)
'columns' => array_map(fn ($width) => [
'blocks' => [],
'id' => uuid(),
'width' => $width,
], $columns)
])->toArray();
},
];

View File

@@ -375,10 +375,8 @@ class Form
public function toArray(): array
{
$array = [
'errors' => $this->errors(),
'fields' => $this->fields->toArray(function ($item) {
return $item->toArray();
}),
'errors' => $this->errors(),
'fields' => $this->fields->toArray(fn ($item) => $item->toArray()),
'invalid' => $this->isInvalid()
];

View File

@@ -77,13 +77,13 @@ class Url
/**
* Tries to fix a broken url without protocol
*
* @param string $url
* @param string|null $url
* @return string
*/
public static function fix(string $url = null): string
{
// make sure to not touch absolute urls
return (!preg_match('!^(https|http|ftp)\:\/\/!i', $url)) ? 'http://' . $url : $url;
return (!preg_match('!^(https|http|ftp)\:\/\/!i', $url ?? '')) ? 'http://' . $url : $url;
}
/**
@@ -111,7 +111,7 @@ class Url
/**
* Checks if an URL is absolute
*
* @param string $url
* @param string|null $url
* @return bool
*/
public static function isAbsolute(string $url = null): bool
@@ -120,14 +120,14 @@ class Url
// //example.com/uri
// http://example.com/uri, https://example.com/uri, ftp://example.com/uri
// mailto:example@example.com, geo:49.0158,8.3239?z=11
return preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:|geo:)!i', $url) === 1;
return $url !== null && preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:|geo:)!i', $url) === 1;
}
/**
* Convert a relative path into an absolute URL
*
* @param string $path
* @param string $home
* @param string|null $path
* @param string|null $home
* @return string
*/
public static function makeAbsolute(string $path = null, string $home = null): string

View File

@@ -42,7 +42,7 @@ class ImageMagick extends Darkroom
protected function blur(string $file, array $options)
{
if ($options['blur'] !== false) {
return '-blur 0x' . $options['blur'];
return '-blur ' . escapeshellarg('0x' . $options['blur']);
}
}
@@ -69,7 +69,13 @@ class ImageMagick extends Darkroom
*/
protected function convert(string $file, array $options): string
{
return sprintf($options['bin'] . ' "%s"', $file);
$command = escapeshellarg($options['bin']);
// limit to single-threading to keep CPU usage sane
$command .= ' -limit thread 1';
// append input file
return $command . ' ' . escapeshellarg($file);
}
/**
@@ -162,7 +168,7 @@ class ImageMagick extends Darkroom
*/
protected function quality(string $file, array $options): string
{
return '-quality ' . $options['quality'];
return '-quality ' . escapeshellarg($options['quality']);
}
/**
@@ -177,7 +183,7 @@ class ImageMagick extends Darkroom
{
// simple resize
if ($options['crop'] === false) {
return sprintf('-thumbnail %sx%s!', $options['width'], $options['height']);
return '-thumbnail ' . escapeshellarg(sprintf('%sx%s!', $options['width'], $options['height']));
}
$gravities = [
@@ -195,15 +201,15 @@ class ImageMagick extends Darkroom
// translate the gravity option into something imagemagick understands
$gravity = $gravities[$options['crop']] ?? 'Center';
$command = sprintf('-thumbnail %sx%s^', $options['width'], $options['height']);
$command .= sprintf(' -gravity %s -crop %sx%s+0+0', $gravity, $options['width'], $options['height']);
$command = '-thumbnail ' . escapeshellarg(sprintf('%sx%s^', $options['width'], $options['height']));
$command .= ' -gravity ' . escapeshellarg($gravity);
$command .= ' -crop ' . escapeshellarg(sprintf('%sx%s+0+0', $options['width'], $options['height']));
return $command;
}
/**
* Makes sure to not process too many images at once
* which could crash the server
* Creates the option for the output file
*
* @param string $file
* @param array $options
@@ -215,7 +221,7 @@ class ImageMagick extends Darkroom
$file = pathinfo($file, PATHINFO_DIRNAME) . '/' . pathinfo($file, PATHINFO_FILENAME) . '.' . $options['format'];
}
return sprintf('-limit thread 1 "%s"', $file);
return escapeshellarg($file);
}
/**

View File

@@ -285,12 +285,12 @@ class Dimensions
if ($xml !== false) {
$attr = $xml->attributes();
$width = (float)($attr->width);
$height = (float)($attr->height);
if (($width === 0.0 || $height === 0.0) && empty($attr->viewBox) === false) {
$width = (int)($attr->width);
$height = (int)($attr->height);
if (($width === 0 || $height === 0) && empty($attr->viewBox) === false) {
$box = explode(' ', $attr->viewBox);
$width = (float)($box[2] ?? 0);
$height = (float)($box[3] ?? 0);
$width = (int)($box[2] ?? 0);
$height = (int)($box[3] ?? 0);
}
}

View File

@@ -3,6 +3,7 @@
namespace Kirby\Panel;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Filesystem\Dir;
use Kirby\Filesystem\F;
use Kirby\Http\Response;
@@ -65,22 +66,9 @@ class Document
'css' => [
'index' => $url . '/css/style.css',
'plugins' => $plugins->url('css'),
'custom' => static::customCss(),
'custom' => static::customAsset('panel.css'),
],
'icons' => $kirby->option('panel.favicon', [
'apple-touch-icon' => [
'type' => 'image/png',
'url' => $url . '/apple-touch-icon.png',
],
'shortcut icon' => [
'type' => 'image/svg+xml',
'url' => $url . '/favicon.svg',
],
'alternate icon' => [
'type' => 'image/png',
'url' => $url . '/favicon.png',
]
]),
'icons' => static::favicon($url),
'js' => [
'vendor' => [
'nonce' => $nonce,
@@ -99,7 +87,7 @@ class Document
],
'custom' => [
'nonce' => $nonce,
'src' => static::customJs(),
'src' => static::customAsset('panel.js'),
'type' => 'module'
],
'index' => [
@@ -139,15 +127,17 @@ class Document
}
/**
* Check for a custom css file from the
* config (panel.css)
* Check for a custom asset file from the
* config (e.g. panel.css or panel.js)
* @since 3.6.2
*
* @param string $option asset option name
* @return string|null
*/
public static function customCss(): ?string
public static function customAsset(string $option): ?string
{
if ($css = kirby()->option('panel.css')) {
$asset = asset($css);
if ($path = kirby()->option($option)) {
$asset = asset($path);
if ($asset->exists() === true) {
return $asset->url() . '?' . $asset->modified();
@@ -158,22 +148,64 @@ class Document
}
/**
* Check for a custom js file from the
* config (panel.js)
*
* @return string|null
* @deprecated 3.7.0 Use `Document::customAsset('panel.css)` instead
* @todo add deprecation warning in 3.7.0, remove in 3.8.0
*/
public static function customCss(): ?string
{
return static::customAsset('panel.css');
}
/**
* @deprecated 3.7.0 Use `Document::customAsset('panel.js)` instead
* @todo add deprecation warning in 3.7.0, remove in 3.8.0
*/
public static function customJs(): ?string
{
if ($js = kirby()->option('panel.js')) {
$asset = asset($js);
return static::customAsset('panel.js');
}
if ($asset->exists() === true) {
return $asset->url() . '?' . $asset->modified();
}
/**
* Returns array of favion icons
* based on config option
* @since 3.6.2
*
* @param string $url URL prefix for default icons
* @return array
*/
public static function favicon(string $url = ''): array
{
$kirby = kirby();
$icons = $kirby->option('panel.favicon', [
'apple-touch-icon' => [
'type' => 'image/png',
'url' => $url . '/apple-touch-icon.png',
],
'shortcut icon' => [
'type' => 'image/svg+xml',
'url' => $url . '/favicon.svg',
],
'alternate icon' => [
'type' => 'image/png',
'url' => $url . '/favicon.png',
]
]);
if (is_array($icons) === true) {
return $icons;
}
return null;
// make sure to convert favicon string to array
if (is_string($icons) === true) {
return [
'shortcut icon' => [
'type' => F::mime($icons),
'url' => $icons,
]
];
}
throw new InvalidArgumentException('Invalid panel.favicon option');
}
/**

View File

@@ -38,12 +38,10 @@ class File extends Model
}
break;
case 'page':
$breadcrumb = $this->model->parents()->flip()->values(function ($parent) {
return [
'label' => $parent->title()->toString(),
'link' => $parent->panel()->url(true),
];
});
$breadcrumb = $this->model->parents()->flip()->values(fn ($parent) => [
'label' => $parent->title()->toString(),
'link' => $parent->panel()->url(true),
]);
}
// add the file
@@ -459,13 +457,11 @@ class File extends Model
$file = $this->model;
return [
'breadcrumb' => function () use ($file): array {
return $file->panel()->breadcrumb();
},
'component' => 'k-file-view',
'props' => $this->props(),
'search' => 'files',
'title' => $file->filename(),
'breadcrumb' => fn (): array => $file->panel()->breadcrumb(),
'component' => 'k-file-view',
'props' => $this->props(),
'search' => 'files',
'title' => $file->filename(),
];
}
}

View File

@@ -22,12 +22,10 @@ class Page extends Model
public function breadcrumb(): array
{
$parents = $this->model->parents()->flip()->merge($this->model);
return $parents->values(function ($parent) {
return [
'label' => $parent->title()->toString(),
'link' => $parent->panel()->url(true),
];
});
return $parents->values(fn ($parent) => [
'label' => $parent->title()->toString(),
'link' => $parent->panel()->url(true),
]);
}
/**

View File

@@ -353,11 +353,9 @@ class Panel
[
'pattern' => 'browser',
'auth' => false,
'action' => function () use ($kirby) {
return new Response(
Tpl::load($kirby->root('kirby') . '/views/browser.php')
);
},
'action' => fn () => new Response(
Tpl::load($kirby->root('kirby') . '/views/browser.php')
),
]
];
@@ -382,17 +380,14 @@ class Panel
'installation',
'login',
],
'action' => function () {
Panel::go(Home::url());
}
'action' => fn () => Panel::go(Home::url()),
'auth' => false
];
// catch all route
$routes[] = [
'pattern' => '(:all)',
'action' => function () {
return 'The view could not be found';
}
'action' => fn () => 'The view could not be found'
];
return $routes;
@@ -420,9 +415,7 @@ class Panel
'pattern' => $pattern,
'type' => 'dialog',
'area' => $areaId,
'action' => $dialog['load'] ?? function () {
return 'The load handler for your dialog is missing';
},
'action' => $dialog['load'] ?? fn () => 'The load handler for your dialog is missing'
];
// submit event
@@ -431,9 +424,7 @@ class Panel
'type' => 'dialog',
'area' => $areaId,
'method' => 'POST',
'action' => $dialog['submit'] ?? function () {
return 'Your dialog does not define a submit handler';
}
'action' => $dialog['submit'] ?? fn () => 'Your dialog does not define a submit handler'
];
}

View File

@@ -178,15 +178,13 @@ class View
},
'$languages' => function () use ($kirby, $multilang): array {
if ($multilang === true) {
return $kirby->languages()->values(function ($language) {
return [
'code' => $language->code(),
'default' => $language->isDefault(),
'direction' => $language->direction(),
'name' => $language->name(),
'rules' => $language->rules(),
];
});
return $kirby->languages()->values(fn ($language) => [
'code' => $language->code(),
'default' => $language->isDefault(),
'direction' => $language->direction(),
'name' => $language->name(),
'rules' => $language->rules(),
]);
}
return [];
@@ -315,12 +313,10 @@ class View
'name' => $translation->name(),
];
},
'$urls' => function () use ($kirby) {
return [
'api' => $kirby->url('api'),
'site' => $kirby->url('index')
];
}
'$urls' => fn () => [
'api' => $kirby->url('api'),
'site' => $kirby->url('index')
]
];
}
@@ -401,10 +397,6 @@ class View
*/
public static function response($data, array $options = [])
{
$kirby = kirby();
$area = $options['area'] ?? [];
$areas = $options['areas'] ?? [];
// handle redirects
if (is_a($data, 'Kirby\Panel\Redirect') === true) {
return Response::redirect($data->location(), $data->code());

View File

@@ -49,6 +49,6 @@ class KirbyTags
return $match[0];
}
}, $text);
}, $text ?? '');
}
}

View File

@@ -37,8 +37,9 @@ class Markdown
public function defaults(): array
{
return [
'breaks' => true,
'extra' => false,
'breaks' => true
'safe' => false
];
}
@@ -69,6 +70,7 @@ class Markdown
}
$parser->setBreaksEnabled($this->options['breaks']);
$parser->setSafeMode($this->options['safe']);
if ($inline === true) {
return @$parser->line($text);

View File

@@ -121,7 +121,7 @@ class SmartyPants
public function parse(string $text = null): string
{
// prepare the text
$text = str_replace('"', '"', $text);
$text = str_replace('"', '"', $text ?? '');
// parse the text
return $this->parser->transform($text);

531
kirby/src/Toolkit/Date.php Executable file
View File

@@ -0,0 +1,531 @@
<?php
namespace Kirby\Toolkit;
use DateTime;
use DateTimeZone;
use Exception;
use Kirby\Exception\InvalidArgumentException;
/**
* Extension for PHP's `DateTime` class
* @since 3.6.2
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>,
* Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class Date extends DateTime
{
/**
* Class constructor
*
* @param string|int|\DateTimeInterface $datetime Datetime string, UNIX timestamp or object
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
*/
public function __construct($datetime = 'now', ?DateTimeZone $timezone = null)
{
if (is_int($datetime) === true) {
$datetime = date('r', $datetime);
}
if (is_a($datetime, 'DateTimeInterface') === true) {
$datetime = $datetime->format('r');
}
parent::__construct($datetime, $timezone);
}
/**
* Returns the datetime in `YYYY-MM-DD hh:mm:ss` format with timezone
*
* @return string
*/
public function __toString(): string
{
return $this->toString('datetime');
}
/**
* Rounds the datetime value up to next value of the specified unit
*
* @param string $unit `year`, `month`, `day`, `hour`, `minute` or `second`
* @return $this
*
* @throws \Kirby\Exception\InvalidArgumentException If the unit name is invalid
*/
public function ceil(string $unit)
{
static::validateUnit($unit);
$this->floor($unit);
$this->modify('+1 ' . $unit);
return $this;
}
/**
* Returns the interval between the provided and the object's datetime
*
* @param string|int|\DateTimeInterface $datetime
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
* @return \DateInterval
*/
public function compare($datetime = 'now', ?DateTimeZone $timezone = null)
{
return $this->diff(new static($datetime, $timezone));
}
/**
* Gets or sets the day value
*
* @param int|null $day
* @return int
*/
public function day(?int $day = null): int
{
if ($day === null) {
return (int)$this->format('d');
}
$this->setDate($this->year(), $this->month(), $day);
return $this->day();
}
/**
* Rounds the datetime value down to the specified unit
*
* @param string $unit `year`, `month`, `day`, `hour`, `minute` or `second`
* @return $this
*
* @throws \Kirby\Exception\InvalidArgumentException If the unit name is invalid
*/
public function floor(string $unit)
{
static::validateUnit($unit);
$formats = [
'year' => 'Y-01-01P',
'month' => 'Y-m-01P',
'day' => 'Y-m-dP',
'hour' => 'Y-m-d H:00:00P',
'minute' => 'Y-m-d H:i:00P',
'second' => 'Y-m-d H:i:sP'
];
$flooredDate = date($formats[$unit], $this->timestamp());
$this->set($flooredDate);
return $this;
}
/**
* Gets or sets the hour value
*
* @param int|null $hour
* @return int
*/
public function hour(?int $hour = null): int
{
if ($hour === null) {
return (int)$this->format('H');
}
$this->setTime($hour, $this->minute());
return $this->hour();
}
/**
* Checks if the object's datetime is the same as the given datetime
*
* @param string|int|\DateTimeInterface $datetime
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
* @return bool
*/
public function is($datetime = 'now', ?DateTimeZone $timezone = null): bool
{
return $this == new static($datetime, $timezone);
}
/**
* Checks if the object's datetime is after the given datetime
*
* @param string|int|\DateTimeInterface $datetime
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
* @return bool
*/
public function isAfter($datetime = 'now', ?DateTimeZone $timezone = null): bool
{
return $this > new static($datetime, $timezone);
}
/**
* Checks if the object's datetime is before the given datetime
*
* @param string|int|\DateTimeInterface $datetime
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
* @return bool
*/
public function isBefore($datetime = 'now', ?DateTimeZone $timezone = null): bool
{
return $this < new static($datetime, $timezone);
}
/**
* Checks if the object's datetime is between the given datetimes
*
* @param string|int|\DateTimeInterface $min
* @param string|int|\DateTimeInterface $max
* @return bool
*/
public function isBetween($min, $max): bool
{
return $this->isMin($min) === true && $this->isMax($max) === true;
}
/**
* Checks if the object's datetime is at or before the given datetime
*
* @param string|int|\DateTimeInterface $datetime
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
* @return bool
*/
public function isMax($datetime = 'now', ?DateTimeZone $timezone = null): bool
{
return $this <= new static($datetime, $timezone);
}
/**
* Checks if the object's datetime is at or after the given datetime
*
* @param string|int|\DateTimeInterface $datetime
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
* @return bool
*/
public function isMin($datetime = 'now', ?DateTimeZone $timezone = null): bool
{
return $this >= new static($datetime, $timezone);
}
/**
* Gets the microsecond value
*
* @return int
*/
public function microsecond(): int
{
return $this->format('u');
}
/**
* Gets the millisecond value
*
* @return int
*/
public function millisecond(): int
{
return $this->format('v');
}
/**
* Gets or sets the minute value
*
* @param int|null $minute
* @return int
*/
public function minute(?int $minute = null): int
{
if ($minute === null) {
return (int)$this->format('i');
}
$this->setTime($this->hour(), $minute);
return $this->minute();
}
/**
* Gets or sets the month value
*
* @param int|null $month
* @return int
*/
public function month(?int $month = null): int
{
if ($month === null) {
return (int)$this->format('m');
}
$this->setDate($this->year(), $month, $this->day());
return $this->month();
}
/**
* Returns the datetime which is nearest to the object's datetime
*
* @param string|int|\DateTimeInterface ...$datetime Datetime strings, UNIX timestamps or objects
* @return string|int|\DateTimeInterface
*/
public function nearest(...$datetime)
{
$timestamp = $this->timestamp();
$minDiff = PHP_INT_MAX;
$nearest = null;
foreach ($datetime as $item) {
$itemObject = new static($item, $this->timezone());
$itemTimestamp = $itemObject->timestamp();
$diff = abs($timestamp - $itemTimestamp);
if ($diff < $minDiff) {
$minDiff = $diff;
$nearest = $item;
}
}
return $nearest;
}
/**
* Returns an instance of the current datetime
*
* @param \DateTimeZone|null $timezone
* @return static
*/
public static function now(?DateTimeZone $timezone = null)
{
return new static('now', $timezone);
}
/**
* Tries to create an instance from the given string
* or fails silently by returning `null` on error
*
* @param string|null $datetime
* @param \DateTimeZone|null $timezone
* @return static|null
*/
public static function optional(?string $datetime = null, ?DateTimeZone $timezone = null)
{
if (empty($datetime) === true) {
return null;
}
try {
return new static($datetime, $timezone);
} catch (Exception $e) {
return null;
}
}
/**
* Rounds the date to the nearest value of the given unit
*
* @param string $unit `year`, `month`, `day`, `hour`, `minute` or `second`
* @param int $size Rounding step starting at `0` of the specified unit
* @return $this
*
* @throws \Kirby\Exception\InvalidArgumentException If the unit name or size is invalid
*/
public function round(string $unit, int $size = 1)
{
static::validateUnit($unit);
// round to a step of 1 first
$floor = (clone $this)->floor($unit);
$ceil = (clone $this)->ceil($unit);
$nearest = $this->nearest($floor, $ceil);
$this->set($nearest);
if ($size === 1) {
// we are already done
return $this;
}
// validate step size
if (
in_array($unit, ['day', 'month', 'year']) && $size !== 1 ||
$unit === 'hour' && 24 % $size !== 0 ||
in_array($unit, ['second', 'minute']) && 60 % $size !== 0
) {
throw new InvalidArgumentException('Invalid rounding size for ' . $unit);
}
// round to other rounding steps
$value = $this->{$unit}();
$value = round($value / $size) * $size;
$this->{$unit}($value);
return $this;
}
/**
* Gets or sets the second value
*
* @param int|null $second
* @return int
*/
public function second(?int $second = null): int
{
if ($second === null) {
return (int)$this->format('s');
}
$this->setTime($this->hour(), $this->minute(), $second);
return $this->second();
}
/**
* Overwrites the datetime value with a different one
*
* @param string|int|\DateTimeInterface $datetime Datetime string, UNIX timestamp or object
* @param \DateTimeZone|null $timezone Optional default timezone if `$datetime` is string
*/
public function set($datetime, ?DateTimeZone $timezone = null)
{
$datetime = new static($datetime, $timezone);
$this->setTimestamp($datetime->timestamp());
}
/**
* Normalizes the step configuration array for rounding
*
* @param array|string|int|null $input Full array with `size` and/or `unit` keys, `unit`
* string, `size` int or `null` for the default
* @param array|null $default Default values to use if one or both values are not provided
* @return array
*/
public static function stepConfig($input = null, ?array $default = null): array
{
$default ??= [
'size' => 1,
'unit' => 'day'
];
if ($input === null) {
return $default;
}
if (is_array($input) === true) {
$input = array_merge($default, $input);
$input['unit'] = strtolower($input['unit']);
return $input;
}
if (is_int($input) === true) {
return array_merge($default, ['size' => $input]);
}
if (is_string($input) === true) {
return array_merge($default, ['unit' => strtolower($input)]);
}
throw new InvalidArgumentException('Invalid input');
}
/**
* Returns the time in `hh:mm:ss` format
*
* @return string
*/
public function time(): string
{
return $this->format('H:i:s');
}
/**
* Returns the UNIX timestamp
*
* @return int
*/
public function timestamp(): int
{
return $this->getTimestamp();
}
/**
* Returns the timezone object
*
* @return \DateTimeZone
*/
public function timezone()
{
return $this->getTimezone();
}
/**
* Returns an instance of the beginning of the current day
*
* @param \DateTimeZone|null $timezone
* @return static
*/
public static function today(?DateTimeZone $timezone = null)
{
return new static('today', $timezone);
}
/**
* Returns the date, time or datetime in `YYYY-MM-DD hh:mm:ss` format
* with optional timezone
*
* @param string $mode `date`, `time` or `datetime`
* @param bool $timezone Whether the timezone is printed as well
* @return string
*
* @throws \Kirby\Exception\InvalidArgumentException If the mode is invalid
*/
public function toString(string $mode = 'datetime', bool $timezone = true): string
{
switch ($mode) {
case 'date':
$format = 'Y-m-d';
break;
case 'time':
$format = 'H:i:s';
break;
case 'datetime':
$format = 'Y-m-d H:i:s';
break;
default:
throw new InvalidArgumentException('Invalid mode');
}
if ($timezone === true) {
$format .= 'P';
}
return $this->format($format);
}
/**
* Gets or sets the year value
*
* @param int|null $year
* @return int
*/
public function year(?int $year = null): int
{
if ($year === null) {
return (int)$this->format('Y');
}
$this->setDate($year, $this->month(), $this->day());
return $this->year();
}
/**
* Ensures that the provided string is a valid unit name
*
* @param string $unit
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException
*/
protected static function validateUnit(string $unit): void
{
$units = ['year', 'month', 'day', 'hour', 'minute', 'second'];
if (in_array($unit, $units) === false) {
throw new InvalidArgumentException('Invalid rounding unit');
}
}
}

View File

@@ -247,9 +247,7 @@ class Dom
$options['allowedAttrPrefixes'],
$attr,
$options,
function ($expected, $real): bool {
return Str::startsWith($real, $expected);
}
fn ($expected, $real): bool => Str::startsWith($real, $expected)
) !== false
) {
return true;
@@ -450,9 +448,7 @@ class Dom
$localName = $node->localName;
if ($compare === null) {
$compare = function ($expected, $real): bool {
return $expected === $real;
};
$compare = fn ($expected, $real): bool => $expected === $real;
}
// if the configuration does not define namespace URIs or if the
@@ -831,9 +827,7 @@ class Dom
$options['disallowedTags'],
$element,
$options,
function ($expected, $real): bool {
return Str::lower($expected) === Str::lower($real);
}
fn ($expected, $real): bool => Str::lower($expected) === Str::lower($real)
) !== false
) {
$errors[] = new InvalidArgumentException(

View File

@@ -256,7 +256,7 @@ class Str
}
$method = $caseInsensitive === true ? 'stripos' : 'strpos';
return call_user_func($method, $string, $needle) !== false;
return call_user_func($method, $string ?? '', $needle) !== false;
}
/**
@@ -484,7 +484,7 @@ class Str
*/
public static function lower(string $string = null): string
{
return mb_strtolower($string, 'UTF-8');
return mb_strtolower($string ?? '', 'UTF-8');
}
/**
@@ -558,7 +558,7 @@ class Str
$needle = static::lower($needle);
}
return mb_strpos($string, $needle, 0, 'UTF-8');
return mb_strpos($string ?? '', $needle, 0, 'UTF-8');
}
/**
@@ -640,7 +640,7 @@ class Str
// without a limit we might as well use the built-in function
if ($limit === -1) {
return str_replace($search, $replace, $string);
return str_replace($search, $replace, $string ?? '');
}
// if the limit is zero, the result will be no replacements at all
@@ -1042,7 +1042,7 @@ class Str
*/
public static function substr(string $string = null, int $start = 0, int $length = null): string
{
return mb_substr($string, $start, $length, 'UTF-8');
return mb_substr($string ?? '', $start, $length, 'UTF-8');
}
/**
@@ -1214,7 +1214,7 @@ class Str
*/
public static function ucwords(string $string = null): string
{
return mb_convert_case($string, MB_CASE_TITLE, 'UTF-8');
return mb_convert_case($string ?? '', MB_CASE_TITLE, 'UTF-8');
}
/**
@@ -1262,7 +1262,7 @@ class Str
*/
public static function upper(string $string = null): string
{
return mb_strtoupper($string, 'UTF-8');
return mb_strtoupper($string ?? '', 'UTF-8');
}
/**

View File

@@ -19,13 +19,14 @@ class Tpl
/**
* Renders the template
*
* @param string $file
* @param string|null $file
* @param array $data
* @return string
* @throws Throwable
*/
public static function load(string $file = null, array $data = []): string
public static function load(?string $file = null, array $data = []): string
{
if (is_file($file) === false) {
if ($file === null || is_file($file) === false) {
return '';
}