Upgrade to 3.6.2
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -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])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -110,7 +110,7 @@ trait PageSiblings
|
||||
*/
|
||||
public function prevUnlisted($collection = null)
|
||||
{
|
||||
return $this->prevAll($collection)->unlisted()->first();
|
||||
return $this->prevAll($collection)->unlisted()->last();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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, '/');
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -142,9 +142,7 @@ class BlocksField extends FieldClass
|
||||
return [
|
||||
[
|
||||
'pattern' => 'uuid',
|
||||
'action' => function () {
|
||||
return ['uuid' => uuid()];
|
||||
}
|
||||
'action' => fn () => ['uuid' => uuid()]
|
||||
],
|
||||
[
|
||||
'pattern' => 'paste',
|
||||
|
@@ -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();
|
||||
},
|
||||
];
|
||||
|
@@ -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()
|
||||
];
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -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),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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'
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -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());
|
||||
|
@@ -49,6 +49,6 @@ class KirbyTags
|
||||
|
||||
return $match[0];
|
||||
}
|
||||
}, $text);
|
||||
}, $text ?? '');
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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
531
kirby/src/Toolkit/Date.php
Executable 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');
|
||||
}
|
||||
}
|
||||
}
|
@@ -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(
|
||||
|
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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 '';
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user