Upgrade to 4.3.0

This commit is contained in:
Bastian Allgeier
2024-06-13 12:13:00 +02:00
parent 4b55753c46
commit e50c0341fc
87 changed files with 643 additions and 430 deletions

View File

@@ -59,7 +59,11 @@ class Model
isset($schema['type']) === true &&
$this->data instanceof $schema['type'] === false
) {
throw new Exception(sprintf('Invalid model type "%s" expected: "%s"', get_class($this->data), $schema['type']));
$class = match ($this->data) {
null => 'null',
default => get_class($this->data),
};
throw new Exception(sprintf('Invalid model type "%s" expected: "%s"', $class, $schema['type']));
}
}

View File

@@ -30,6 +30,7 @@ use Kirby\Toolkit\A;
use Kirby\Toolkit\Config;
use Kirby\Toolkit\Controller;
use Kirby\Toolkit\LazyValue;
use Kirby\Toolkit\Locale;
use Kirby\Toolkit\Str;
use Kirby\Uuid\Uuid;
use Throwable;
@@ -170,7 +171,7 @@ class App
'roots' => $this->roots(),
'site' => $this->site(),
'urls' => $this->urls(),
'version' => $this->version(),
'version' => static::version(),
];
}
@@ -255,7 +256,7 @@ class App
foreach ($this->options as $key => $value) {
// detect option keys with the `vendor.plugin.option` format
if (preg_match('/^([a-z0-9-]+\.[a-z0-9-]+)\.(.*)$/i', $key, $matches) === 1) {
list(, $plugin, $option) = $matches;
[, $plugin, $option] = $matches;
// verify that it's really a plugin option
if (isset(static::$plugins[str_replace('.', '/', $plugin)]) !== true) {
@@ -538,6 +539,14 @@ class App
return false;
}
/**
* Returns the current language, if set by `static::setCurrentLanguage`
*/
public function currentLanguage(): Language|null
{
return $this->language ??= $this->defaultLanguage();
}
/**
* Returns the default language object
*/
@@ -623,7 +632,7 @@ class App
return Uuid::for($path, $parent?->files())->model();
}
$parent = $parent ?? $this->site();
$parent ??= $this->site();
$id = dirname($path);
$filename = basename($path);
@@ -879,7 +888,8 @@ class App
}
/**
* Returns the current language
* Returns the language by code or shortcut (`default`, `current`).
* Passing `null` is an alias for passing `current`
*/
public function language(string $code = null): Language|null
{
@@ -887,19 +897,11 @@ class App
return null;
}
if ($code === 'default') {
return $this->defaultLanguage();
}
// if requesting a non-default language,
// find it but don't cache it
if ($code !== null) {
return $this->languages()->find($code);
}
// otherwise return language set by `AppTranslation::setCurrentLanguage`
// or default language
return $this->language ??= $this->defaultLanguage();
return match ($code ?? 'current') {
'default' => $this->defaultLanguage(),
'current' => $this->currentLanguage(),
default => $this->languages()->find($code)
};
}
/**
@@ -1141,7 +1143,7 @@ class App
return null;
}
$parent = $parent ?? $this->site();
$parent ??= $this->site();
if ($page = $parent->find($id)) {
/**
@@ -1212,7 +1214,7 @@ class App
* @internal
* @throws \Kirby\Exception\NotFoundException if the home page cannot be found
*/
public function resolve(string $path = null, string $language = null): mixed
public function resolve(string|null $path = null, string|null $language = null): mixed
{
// set the current translation
$this->setCurrentTranslation($language);
@@ -1410,6 +1412,30 @@ class App
);
}
/**
* Load and set the current language if it exists
* Otherwise fall back to the default language
*
* @internal
*/
public function setCurrentLanguage(
string|null $languageCode = null
): Language|null {
if ($this->multilang() === false) {
Locale::set($this->option('locale', 'en_US.utf-8'));
return $this->language = null;
}
$this->language = $this->language($languageCode) ?? $this->defaultLanguage();
Locale::set($this->language->locale());
// add language slug rules to Str class
Str::$language = $this->language->rules();
return $this->language;
}
/**
* Create your own set of languages
*

View File

@@ -713,17 +713,25 @@ trait AppPlugins
*/
public static function plugin(
string $name,
array $extends = null
array $extends = null,
array $info = [],
string|null $root = null,
string|null $version = null
): PLugin|null {
if ($extends === null) {
return static::$plugins[$name] ?? null;
}
// get the correct root for the plugin
$extends['root'] = $extends['root'] ?? dirname(debug_backtrace()[0]['file']);
$plugin = new Plugin(
name: $name,
extends: $extends,
info: $info,
// TODO: Remove fallback to $extends in v7
root: $root ?? $extends['root'] ?? dirname(debug_backtrace()[0]['file']),
version: $version
);
$plugin = new Plugin($name, $extends);
$name = $plugin->name();
$name = $plugin->name();
if (isset(static::$plugins[$name]) === true) {
throw new DuplicateException('The plugin "' . $name . '" has already been registered');
@@ -792,7 +800,11 @@ trait AppPlugins
// register as anonymous plugin (without actual extensions)
// to be picked up by the Panel\Document class when
// rendering the Panel view
static::plugin('plugins/' . $dirname, ['root' => $dir]);
static::plugin(
name: 'plugins/' . $dirname,
extends: [],
root: $dir
);
} else {
continue;
}

View File

@@ -3,7 +3,6 @@
namespace Kirby\Cms;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Locale;
use Kirby\Toolkit\Str;
/**
@@ -104,33 +103,6 @@ trait AppTranslations
return $this->option('panel.language', $defaultCode);
}
/**
* Load and set the current language if it exists
* Otherwise fall back to the default language
*
* @internal
*/
public function setCurrentLanguage(
string $languageCode = null
): Language|null {
if ($this->multilang() === false) {
Locale::set($this->option('locale', 'en_US.utf-8'));
return $this->language = null;
}
$this->language = $this->language($languageCode);
$this->language ??= $this->defaultLanguage();
if ($this->language) {
Locale::set($this->language->locale());
}
// add language slug rules to Str class
Str::$language = $this->language->rules();
return $this->language;
}
/**
* Set the current translation
*
@@ -148,7 +120,7 @@ trait AppTranslations
*/
public function translation(string|null $locale = null): Translation
{
$locale = $locale ?? I18n::locale();
$locale ??= I18n::locale();
$locale = basename($locale);
// prefer loading them from the translations collection

View File

@@ -575,8 +575,8 @@ class Auth
}
// ensure that the category arrays are defined
$log['by-ip'] = $log['by-ip'] ?? [];
$log['by-email'] = $log['by-email'] ?? [];
$log['by-ip'] ??= [];
$log['by-email'] ??= [];
// remove all elements on the top level with different keys (old structure)
$log = array_intersect_key($log, array_flip(['by-ip', 'by-email']));

View File

@@ -65,7 +65,7 @@ class Blueprint
unset($props['model']);
// extend the blueprint in general
$props = $this->extend($props);
$props = static::extend($props);
// apply any blueprint preset
$props = $this->preset($props);
@@ -652,7 +652,7 @@ class Blueprint
}
// extend options if possible
$options = $this->extend($options);
$options = static::extend($options);
foreach ($options as $key => $value) {
$alias = $aliases[$key] ?? null;
@@ -686,7 +686,7 @@ class Blueprint
}
// inject all section extensions
$sectionProps = $this->extend($sectionProps);
$sectionProps = static::extend($sectionProps);
$sections[$sectionName] = $sectionProps = [
...$sectionProps,
@@ -699,14 +699,14 @@ class Blueprint
'name' => $sectionName,
'label' => 'Invalid section type for section "' . $sectionName . '"',
'type' => 'info',
'text' => 'The following section types are available: ' . $this->helpList(array_keys(Section::$types))
'text' => 'The following section types are available: ' . static::helpList(array_keys(Section::$types))
];
} elseif (isset(Section::$types[$type]) === false) {
$sections[$sectionName] = [
'name' => $sectionName,
'label' => 'Invalid section type ("' . $type . '")',
'type' => 'info',
'text' => 'The following section types are available: ' . $this->helpList(array_keys(Section::$types))
'text' => 'The following section types are available: ' . static::helpList(array_keys(Section::$types))
];
}
@@ -764,7 +764,7 @@ class Blueprint
}
// inject all tab extensions
$tabProps = $this->extend($tabProps);
$tabProps = static::extend($tabProps);
// inject a preset if available
$tabProps = $this->preset($tabProps);

View File

@@ -47,7 +47,9 @@ trait FileActions
string|null $extension = null
): static {
if ($sanitize === true) {
$name = F::safeName($name);
// sanitize the basename part only
// as the extension isn't included in $name
$name = F::safeBasename($name, false);
}
// if no extension is passed, make sure to maintain current one
@@ -139,8 +141,8 @@ trait FileActions
// rename and/or resize the file if configured by new blueprint
$create = $file->blueprint()->create();
$file = $file->manipulate($create);
$file = $file->changeExtension($file, $create['format'] ?? null);
$file->manipulate($create);
return $file;
});
@@ -266,7 +268,6 @@ trait FileActions
// we need to already rename it so that the correct file rules
// are applied
$create = $file->blueprint()->create();
$file = $file->changeExtension($file, $create['format'] ?? null);
// run the hook
$arguments = compact('file', 'upload');
@@ -284,6 +285,7 @@ trait FileActions
// resize the file on upload if configured
$file = $file->manipulate($create);
$file = $file->changeExtension($file, $create['format'] ?? null);
// store the content if necessary
// (always create files in the default language)
@@ -384,8 +386,8 @@ trait FileActions
// apply the resizing/crop options from the blueprint
$create = $file->blueprint()->create();
$file = $file->changeExtension($file, $create['format'] ?? null);
$file = $file->manipulate($create);
$file = $file->changeExtension($file, $create['format'] ?? null);
// return a fresh clone
return $file->clone();

View File

@@ -50,7 +50,7 @@ class FileVersion
// content fields
if ($this->original() instanceof File) {
return $this->original()->content()->get($method, $arguments);
return $this->original()->content()->get($method);
}
}

View File

@@ -29,14 +29,20 @@ class Helpers
* ```
*/
public static $deprecations = [
// The internal `$model->contentFile*()` methods have been deprecated
'model-content-file' => true,
// Passing an `info` array inside the `extends` array
// has been deprecated. Pass the individual entries (e.g. root, version)
// directly as named arguments.
// TODO: switch to true in v6
'plugin-extends-root' => false,
// Passing a single space as value to `Xml::attr()` has been
// deprecated. In a future version, passing a single space won't
// render an empty value anymore but a single space.
// To render an empty value, please pass an empty string.
'xml-attr-single-space' => true,
// The internal `$model->contentFile*()` methods have been deprecated
'model-content-file' => true,
];
/**

View File

@@ -42,7 +42,7 @@ class LanguageRules
/**
* Validates if the language can be updated
*/
public static function update(Language $language)
public static function update(Language $language): void
{
static::validLanguageCode($language);
static::validLanguageName($language);

View File

@@ -57,7 +57,12 @@ class Media
}
// try to generate a thumb for the file
return static::thumb($model, $hash, $filename);
try {
return static::thumb($model, $hash, $filename);
} catch (NotFoundException) {
// render the error page if there is no job for this filename
return false;
}
}
/**

View File

@@ -46,7 +46,7 @@ abstract class ModelWithContent implements Identifiable
public static App $kirby;
protected Site|null $site;
protected ContentStorage $storage;
public Collection|null $translations;
public Collection|null $translations = null;
/**
* Store values used to initilaize object

View File

@@ -22,7 +22,7 @@ class PagePicker extends Picker
// remove once our implementation is better
protected Pages|null $items = null;
protected Pages|null $itemsForQuery = null;
protected Page|Site|null $parent;
protected Page|Site|null $parent = null;
/**
* Extends the basic defaults

View File

@@ -26,12 +26,6 @@ use Throwable;
class Plugin
{
protected PluginAssets $assets;
protected array $extends;
protected string $name;
protected string $root;
// caches
protected array|null $info = null;
protected UpdateStatus|null $updateStatus = null;
/**
@@ -40,16 +34,44 @@ class Plugin
*
* @throws \Kirby\Exception\InvalidArgumentException If the plugin name has an invalid format
*/
public function __construct(string $name, array $extends = [])
{
public function __construct(
protected string $name,
protected array $extends = [],
protected array $info = [],
protected string|null $root = null,
protected string|null $version = null,
) {
static::validateName($name);
$this->name = $name;
$this->extends = $extends;
$this->root = $extends['root'] ?? dirname(debug_backtrace()[0]['file']);
$this->info = empty($extends['info']) === false && is_array($extends['info']) ? $extends['info'] : null;
// TODO: Remove in v7
if ($root = $extends['root'] ?? null) {
Helpers::deprecated('Plugin "' . $name . '": Passing the `root` inside the `extends` array has been deprecated. Pass it directly as named argument `root`.', 'plugin-extends-root');
$this->root ??= $root;
unset($this->extends['root']);
}
unset($this->extends['root'], $this->extends['info']);
$this->root ??= dirname(debug_backtrace()[0]['file']);
// TODO: Remove in v7
if ($info = $extends['info'] ?? null) {
Helpers::deprecated('Plugin "' . $name . '": Passing an `info` array inside the `extends` array has been deprecated. Pass the individual entries directly as named `info` argument.', 'plugin-extends-root');
if (empty($info) === false && is_array($info) === true) {
$this->info = [...$info, ...$this->info];
}
unset($this->extends['info']);
}
// read composer.json and use as info fallback
try {
$info = Data::read($this->manifest());
} catch (Exception) {
// there is no manifest file or it is invalid
$info = [];
}
$this->info = [...$info, ...$this->info];
}
/**
@@ -117,22 +139,11 @@ class Plugin
}
/**
* Returns the raw data from composer.json
* Returns the info data (from composer.json)
*/
public function info(): array
{
if (is_array($this->info) === true) {
return $this->info;
}
try {
$info = Data::read($this->manifest());
} catch (Exception) {
// there is no manifest file or it is invalid
$info = [];
}
return $this->info = $info;
return $this->info;
}
/**
@@ -295,17 +306,20 @@ class Plugin
*/
public function version(): string|null
{
$composerName = $this->info()['name'] ?? null;
$version = $this->info()['version'] ?? null;
$name = $this->info()['name'] ?? null;
try {
// if plugin doesn't have version key in composer.json file
// try to get version from "vendor/composer/installed.php"
$version ??= InstalledVersions::getPrettyVersion($composerName);
// try to get version from "vendor/composer/installed.php",
// this is the most reliable source for the version
$version = InstalledVersions::getPrettyVersion($name);
} catch (Throwable) {
return null;
$version = null;
}
// fallback to the version provided in the plugin's index.php: as named
// argument, entry in the info array or from the composer.json file
$version ??= $this->version ?? $this->info()['version'] ?? null;
if (
is_string($version) !== true ||
$version === '' ||

View File

@@ -162,6 +162,24 @@ class System
->toString();
}
/**
* Returns an array with relevant system information
* used for debugging
* @since 4.3.0
*/
public function info(): array
{
return [
'kirby' => $this->app->version(),
'php' => phpversion(),
'server' => $this->serverSoftware(),
'license' => $this->license()->label(),
'languages' => $this->app->languages()->values(
fn ($lang) => $lang->code()
)
];
}
/**
* Create the most important folders
* if they don't exist yet
@@ -379,32 +397,12 @@ class System
return true;
}
/**
* Check for a valid server environment
*/
public function server(): bool
{
return $this->serverSoftware() !== null;
}
/**
* Returns the detected server software
*/
public function serverSoftware(): string|null
public function serverSoftware(): string
{
$servers = $this->app->option('servers', [
'apache',
'caddy',
'litespeed',
'nginx',
'php'
]);
$software = $this->app->environment()->get('SERVER_SOFTWARE', '');
preg_match('!(' . implode('|', A::wrap($servers)) . ')!i', $software, $matches);
return $matches[0] ?? null;
return $this->app->environment()->get('SERVER_SOFTWARE', '');
}
/**
@@ -421,14 +419,13 @@ class System
public function status(): array
{
return [
'accounts' => $this->accounts(),
'content' => $this->content(),
'curl' => $this->curl(),
'sessions' => $this->sessions(),
'mbstring' => $this->mbstring(),
'media' => $this->media(),
'php' => $this->php(),
'server' => $this->server(),
'accounts' => $this->accounts(),
'content' => $this->content(),
'curl' => $this->curl(),
'sessions' => $this->sessions(),
'mbstring' => $this->mbstring(),
'media' => $this->media(),
'php' => $this->php()
];
}

View File

@@ -392,6 +392,46 @@ class UpdateStatus
});
}
/**
* Finds the maximum possible major update
* that is included with the current license
*
* @return string|null Version number of the update or
* `null` if no free update is possible
*/
protected function findMaximumFreeUpdate(): string|null
{
// get the timestamp of included updates
$renewal = $this->app->system()->license()->renewal();
if ($renewal === null || $this->data === null) {
return null;
}
foreach ($this->data['versions'] ?? [] as $entry) {
$initialRelease = $entry['initialRelease'] ?? null;
$latest = $entry['latest'] ?? '';
// skip entries of irrelevant releases
if (
is_string($initialRelease) !== true ||
version_compare($latest, $this->currentVersion, '<=') === true
) {
continue;
}
$timestamp = strtotime($initialRelease);
// update is free if the initial release was before the
// license renewal date
if (is_int($timestamp) === true && $timestamp < $renewal) {
return $latest;
}
}
return null;
}
/**
* Finds the minimum possible security update
* to fix all known vulnerabilities
@@ -655,7 +695,7 @@ class UpdateStatus
];
}
// check if free updates are possible from the current version
// check if updates within the same major version are possible
$latest = $versionEntry['latest'] ?? null;
if (is_string($latest) === true && $latest !== $this->currentVersion) {
return $this->targetData = [
@@ -665,6 +705,19 @@ class UpdateStatus
];
}
// check if the license includes updates to a newer major version
if ($version = $this->findMaximumFreeUpdate()) {
// extract the part before the first dot
// to find the major release page URL
preg_match('/^(\w+)\./', $version, $matches);
return $this->targetData = [
'status' => 'update',
'url' => $this->urlFor($matches[1] . '.0', 'changes'),
'version' => $version
];
}
// no free update is possible, but we are not on the latest version,
// so the overall latest version must be an upgrade
return $this->targetData = [

View File

@@ -112,7 +112,7 @@ abstract class Sql
public function columnName(string $table, string $column, bool $enforceQualified = false): string|null
{
// ensure we have clean $table and $column values without qualified identifiers
list($table, $column) = $this->splitIdentifier($table, $column);
[$table, $column] = $this->splitIdentifier($table, $column);
// combine the identifiers again
if ($this->database->validateColumn($table, $column) === true) {
@@ -624,7 +624,7 @@ abstract class Sql
$result = [];
foreach ($columns as $column) {
list($table, $columnPart) = $this->splitIdentifier($table, $column);
[$table, $columnPart] = $this->splitIdentifier($table, $column);
if ($this->validateColumn($table, $columnPart) === true) {
$result[] = $this->combineIdentifier($table, $columnPart);

View File

@@ -580,7 +580,7 @@ class F
}
// the math magic
$size = round($size / pow(1024, ($unit = floor(log($size, 1024)))), 2);
$size = round($size / 1024 ** ($unit = floor(log($size, 1024))), 2);
// format the number if requested
if ($locale !== false) {
@@ -756,20 +756,32 @@ class F
* Sanitize a file's name (without extension)
* @since 4.0.0
*/
public static function safeBasename(string $string): string
{
$name = static::name($string);
return Str::slug($name, '-', 'a-z0-9@._-');
public static function safeBasename(
string $string,
bool $extract = true
): string {
// extract only the name part from whole filename string
if ($extract === true) {
$string = static::name($string);
}
return Str::slug($string, '-', 'a-z0-9@._-');
}
/**
* Sanitize a file's extension
* @since 4.0.0
*/
public static function safeExtension(string $string): string
{
$extension = static::extension($string);
return Str::slug($extension);
public static function safeExtension(
string $string,
bool $extract = true
): string {
// extract only the extension part from whole filename string
if ($extract === true) {
$string = static::extension($string);
}
return Str::slug($string);
}
/**

View File

@@ -20,7 +20,7 @@ class Query
/**
* The Query data array
*/
protected array|null $data;
protected array|null $data = null;
/**
* Creates a new Query object.

View File

@@ -59,13 +59,15 @@ class QrCode
* @param int|null $size Image width/height in pixels, defaults to a size per module of 4x4
* @param string $color Foreground color in hex format
* @param string $back Background color in hex format
* @param int $border Border size in number of modules
*/
public function toDataUri(
int|null $size = null,
string $color = '#000000',
string $back = '#ffffff'
string $back = '#ffffff',
int $border = 4
): string {
$image = $this->toImage($size, $color, $back);
$image = $this->toImage($size, $color, $back, $border);
ob_start();
imagepng($image);
@@ -82,14 +84,16 @@ class QrCode
* @param int|null $size Image width/height in pixels, defaults to a size per module of 4x4
* @param string $color Foreground color in hex format
* @param string $back Background color in hex format
* @param int $border Border size in number of modules
*/
public function toImage(
int|null $size = null,
string $color = '#000000',
string $back = '#ffffff'
string $back = '#ffffff',
int $border = 4
): GdImage {
// get code and size measurements
$code = $this->encode();
$code = $this->encode($border);
[$width, $height] = $this->measure($code);
$size ??= ceil($width * 4);
$ws = $size / $width;
@@ -132,13 +136,15 @@ class QrCode
* @param int|string|null $size Optional CSS width of the `<svg>` element
* @param string $color Foreground color in hex format
* @param string $back Background color in hex format
* @param int $border Border size in number of modules
*/
public function toSvg(
int|string|null $size = null,
string $color = '#000000',
string $back = '#ffffff'
string $back = '#ffffff',
int $border = 4
): string {
$code = $this->encode();
$code = $this->encode($border);
[$vbw, $vbh] = $this->measure($code);
$modules = $this->eachModuleGroup(
@@ -167,15 +173,17 @@ class QrCode
* @param int|string|null $size Optional image width/height in pixels (defaults to a size per module of 4x4) or CSS width of the `<svg>` element
* @param string $color Foreground color in hex format
* @param string $back Background color in hex format
* @param int $border Border size in number of modules
*/
public function write(
string $file,
int|string|null $size = null,
string $color = '#000000',
string $back = '#ffffff'
string $back = '#ffffff',
int $border = 4
): void {
$format = F::extension($file);
$args = [$size, $color, $back];
$args = [$size, $color, $back, $border];
match ($format) {
'gif' => imagegif($this->toImage(...$args), $file),
@@ -395,7 +403,7 @@ class QrCode
return $result;
}
protected function encode(): array
protected function encode(int $q = 4): array
{
[$data, $version, $ecl, $ec] = $this->encodeData();
$data = $this->encodeErrorCorrection($data, $ec, $version);
@@ -404,7 +412,7 @@ class QrCode
$mtx = $this->finalizeMatrix($mtx, $size, $ecl, $mask, $version);
return [
'q' => [4, 4, 4, 4],
'q' => [$q, $q, $q, $q],
'size' => [$size, $size],
'bits' => $mtx
];

View File

@@ -56,13 +56,11 @@ class Assets
*/
public function css(): array
{
$css = A::merge(
[
'index' => $this->url . '/css/style.min.css',
'plugins' => $this->plugins->url('css')
],
$this->custom('panel.css')
);
$css = [
'index' => $this->url . '/css/style.min.css',
'plugins' => $this->plugins->url('css'),
...$this->custom('panel.css')
];
// during dev mode we do not need to load
// the general stylesheet (as styling will be inlined)
@@ -201,8 +199,8 @@ class Assets
*/
public function icons(): string
{
$dir = $this->kirby->root('panel') . '/';
$dir .= $this->dev ? 'public' : 'dist';
$dir = $this->kirby->root('panel') . '/';
$dir .= $this->dev ? 'public' : 'dist';
$icons = F::read($dir . '/img/icons.svg');
$icons = preg_replace('/<!--(.|\s)*?-->/', '', $icons);
return $icons;
@@ -213,64 +211,55 @@ class Assets
*/
public function js(): array
{
$js = A::merge(
[
'vue' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/vue.min.js'
],
'vendor' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/vendor.min.js',
'type' => 'module'
],
'pluginloader' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/plugins.js',
'type' => 'module'
],
'plugins' => [
'nonce' => $this->nonce,
'src' => $this->plugins->url('js'),
'defer' => true
]
$js = [
'vue' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/vue.min.js'
],
A::map($this->custom('panel.js'), fn ($src) => [
'vendor' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/vendor.min.js',
'type' => 'module'
],
'pluginloader' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/plugins.js',
'type' => 'module'
],
'plugins' => [
'nonce' => $this->nonce,
'src' => $this->plugins->url('js'),
'defer' => true
],
...A::map($this->custom('panel.js'), fn ($src) => [
'nonce' => $this->nonce,
'src' => $src,
'type' => 'module'
]),
[
'index' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/index.min.js',
'type' => 'module'
],
]
);
'index' => [
'nonce' => $this->nonce,
'src' => $this->url . '/js/index.min.js',
'type' => 'module'
],
];
// during dev mode, add vite client and adapt
// path to `index.js` - vendor does not need
// to be loaded in dev mode
if ($this->dev === true) {
// load the non-minified index.js, remove vendor script and
// development version of Vue
$js['vendor']['src'] = null;
$js['index']['src'] = $this->url . '/src/index.js';
$js['vue']['src'] = $this->url . '/node_modules/vue/dist/vue.js';
// add vite dev client
$js['vite'] = [
'nonce' => $this->nonce,
'src' => $this->url . '/@vite/client',
'type' => 'module'
];
$js['index'] = [
'nonce' => $this->nonce,
'src' => $this->url . '/src/index.js',
'type' => 'module'
];
// load the development version of Vue
$js['vue']['src'] = $this->url . '/node_modules/vue/dist/vue.js';
// remove the vendor script
$js['vendor']['src'] = null;
}
return array_filter($js, fn ($js) => empty($js['src']) === false);

View File

@@ -219,9 +219,11 @@ class Example
$tail = $matches[2][$key];
$code = $matches[3][$key];
$scriptId = trim(preg_replace_callback('!script="(.*?)"!', function ($match) {
return trim($match[1]);
}, $tail));
$scriptId = trim(preg_replace_callback(
'!script="(.*?)"!',
fn ($match) => trim($match[1]),
$tail
));
$scriptBlock = $scripts[$scriptId] ?? null;

View File

@@ -218,7 +218,7 @@ class Sessions
* @internal
* @param \Kirby\Session\Session $session Session instance to push to the cache
*/
public function updateCache(Session $session)
public function updateCache(Session $session): void
{
$this->cache[$session->token()] = $session;
}

View File

@@ -177,7 +177,7 @@ class KirbyTag
}
$pos = strpos($tag, ':');
$type = trim(substr($tag, 0, $pos ? $pos : null));
$type = trim(substr($tag, 0, $pos ?: null));
$type = strtolower($type);
$attr = static::$types[$type]['attr'] ?? [];

View File

@@ -76,7 +76,7 @@ class Component
}
$this->attrs = $attrs;
$this->options = $options = $this->setup($type);
$this->options = $options = static::setup($type);
$this->methods = $methods = $options['methods'] ?? [];
foreach ($attrs as $attrName => $attrValue) {

View File

@@ -464,7 +464,7 @@ class Dom
$namespaceUri = null;
$itemLocal = $item;
if (Str::contains($item, ':') === true) {
list($namespaceName, $itemLocal) = explode(':', $item);
[$namespaceName, $itemLocal] = explode(':', $item);
$namespaceUri = $allowedNamespaces[$namespaceName] ?? null;
} else {
// list items without namespace are from the default namespace

View File

@@ -26,7 +26,7 @@ class Escape
/**
* The internal singleton escaper instance
*/
protected static Escaper|null $escaper;
protected static Escaper|null $escaper = null;
/**
* Escape common HTML attributes data

View File

@@ -20,7 +20,7 @@ class Html extends Xml
/**
* An internal store for an HTML entities translation table
*/
public static array|null $entities;
public static array|null $entities = null;
/**
* List of HTML tags that can be used inline

View File

@@ -386,10 +386,13 @@ class Str
$encoded = '';
for ($i = 0; $i < static::length($string); $i++) {
$char = static::substr($string, $i, 1);
$char = mb_convert_encoding($char, 'UCS-4BE', 'UTF-8');
list(, $code) = unpack('N', $char);
$encoded .= rand(1, 2) === 1 ? '&#' . $code . ';' : '&#x' . dechex($code) . ';';
$char = static::substr($string, $i, 1);
$char = mb_convert_encoding($char, 'UCS-4BE', 'UTF-8');
[, $code] = unpack('N', $char);
$encoded .= match (random_int(1, 2)) {
1 => '&#' . $code . ';',
2 => '&#x' . dechex($code) . ';'
};
}
return $encoded;

View File

@@ -26,7 +26,7 @@ class BlockUuid extends FieldUuid
/**
* @var \Kirby\Cms\Block|null
*/
public Identifiable|null $model;
public Identifiable|null $model = null;
/**
* Converts content field to a Blocks collection

View File

@@ -47,7 +47,7 @@ abstract class FieldUuid extends Uuid
$parent = Uuid::for($value['parent'])->model();
if ($field = $parent?->content()->get($value['field'])) {
return $this->fieldToCollection($field)->get($value['id']);
return static::fieldToCollection($field)->get($value['id']);
}
}
}

View File

@@ -22,7 +22,7 @@ class FileUuid extends ModelUuid
/**
* @var \Kirby\Cms\File|null
*/
public Identifiable|null $model;
public Identifiable|null $model = null;
/**
* Looks up UUID in cache and resolves to file object;

View File

@@ -20,7 +20,7 @@ abstract class ModelUuid extends Uuid
/**
* @var \Kirby\Cms\ModelWithContent|null
*/
public Identifiable|null $model;
public Identifiable|null $model = null;
/**
* Looks up UUID in local and global index

View File

@@ -23,7 +23,7 @@ class PageUuid extends ModelUuid
/**
* @var \Kirby\Cms\Page|null
*/
public Identifiable|null $model;
public Identifiable|null $model = null;
/**
* Looks up UUID in cache and resolves

View File

@@ -23,7 +23,7 @@ class SiteUuid extends Uuid
/**
* @var \Kirby\Cms\Site|null
*/
public Identifiable|null $model;
public Identifiable|null $model = null;
/*
* Returns empty string since

View File

@@ -26,7 +26,7 @@ class StructureUuid extends FieldUuid
/**
* @var \Kirby\Cms\StructureObject|null
*/
public Identifiable|null $model;
public Identifiable|null $model = null;
/**
* Converts content field to a Structure collection

View File

@@ -23,7 +23,7 @@ class UserUuid extends Uuid
/**
* @var \Kirby\Cms\User|null
*/
public Identifiable|null $model;
public Identifiable|null $model = null;
/*
* Returns the user ID