Upgrade to 3.8.2

This commit is contained in:
Bastian Allgeier
2022-11-15 13:26:12 +01:00
parent fe2baa1b7a
commit f9e812cb0c
45 changed files with 760 additions and 346 deletions

View File

@@ -3,7 +3,7 @@
"description": "The Kirby 3 core", "description": "The Kirby 3 core",
"license": "proprietary", "license": "proprietary",
"type": "kirby-cms", "type": "kirby-cms",
"version": "3.8.1.1", "version": "3.8.2",
"keywords": [ "keywords": [
"kirby", "kirby",
"cms", "cms",

2
kirby/composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7e1f67ac895b8dc38cd8c53188565545", "content-hash": "7c0268af0d99aa02157532f2a6a9ed36",
"packages": [ "packages": [
{ {
"name": "claviska/simpleimage", "name": "claviska/simpleimage",

View File

@@ -57,6 +57,7 @@ return [
}, },
'type' => fn (File $file) => $file->type(), 'type' => fn (File $file) => $file->type(),
'url' => fn (File $file) => $file->url(), 'url' => fn (File $file) => $file->url(),
'uuid' => fn (File $file) => $file->uuid()?->toString()
], ],
'type' => 'Kirby\Cms\File', 'type' => 'Kirby\Cms\File',
'views' => [ 'views' => [
@@ -79,7 +80,8 @@ return [
'size', 'size',
'template', 'template',
'type', 'type',
'url' 'url',
'uuid'
], ],
'compact' => [ 'compact' => [
'filename', 'filename',
@@ -87,6 +89,7 @@ return [
'link', 'link',
'type', 'type',
'url', 'url',
'uuid'
], ],
'panel' => [ 'panel' => [
'blueprint', 'blueprint',
@@ -109,7 +112,8 @@ return [
'prevWithTemplate' => 'compact', 'prevWithTemplate' => 'compact',
'template', 'template',
'type', 'type',
'url' 'url',
'uuid'
] ]
], ],
]; ];

View File

@@ -38,6 +38,7 @@ return [
'template' => fn (Page $page) => $page->intendedTemplate()->name(), 'template' => fn (Page $page) => $page->intendedTemplate()->name(),
'title' => fn (Page $page) => $page->title()->value(), 'title' => fn (Page $page) => $page->title()->value(),
'url' => fn (Page $page) => $page->url(), 'url' => fn (Page $page) => $page->url(),
'uuid' => fn (Page $page) => $page->uuid()?->toString()
], ],
'type' => 'Kirby\Cms\Page', 'type' => 'Kirby\Cms\Page',
'views' => [ 'views' => [
@@ -45,7 +46,8 @@ return [
'id', 'id',
'title', 'title',
'url', 'url',
'num' 'num',
'uuid'
], ],
'default' => [ 'default' => [
'content', 'content',
@@ -57,7 +59,8 @@ return [
'slug', 'slug',
'template', 'template',
'title', 'title',
'url' 'url',
'uuid'
], ],
'panel' => [ 'panel' => [
'id', 'id',
@@ -71,7 +74,8 @@ return [
'previewUrl', 'previewUrl',
'slug', 'slug',
'title', 'title',
'url' 'url',
'uuid'
], ],
'selector' => [ 'selector' => [
'id', 'id',

View File

@@ -35,11 +35,8 @@ return [
$code = $this->user()?->language() ?? $code = $this->user()?->language() ??
$this->kirby()->panelLanguage(); $this->kirby()->panelLanguage();
if ($translation = $this->kirby()->translation($code)) { return $this->kirby()->translation($code) ??
return $translation; $this->kirby()->translation('en');
}
return $this->kirby()->translation('en');
}, },
'kirbytext' => fn () => $this->kirby()->option('panel.kirbytext') ?? true, 'kirbytext' => fn () => $this->kirby()->option('panel.kirbytext') ?? true,
'user' => fn () => $this->user(), 'user' => fn () => $this->user(),

View File

@@ -24,7 +24,8 @@ return [
'prev' => fn (User $user) => $user->prev(), 'prev' => fn (User $user) => $user->prev(),
'role' => fn (User $user) => $user->role(), 'role' => fn (User $user) => $user->role(),
'roles' => fn (User $user) => $user->roles(), 'roles' => fn (User $user) => $user->roles(),
'username' => fn (User $user) => $user->username() 'username' => fn (User $user) => $user->username(),
'uuid' => fn (User $user) => $user->uuid()?->toString()
], ],
'type' => 'Kirby\Cms\User', 'type' => 'Kirby\Cms\User',
'views' => [ 'views' => [
@@ -39,7 +40,8 @@ return [
'options', 'options',
'prev' => 'compact', 'prev' => 'compact',
'role', 'role',
'username' 'username',
'uuid'
], ],
'compact' => [ 'compact' => [
'avatar' => 'compact', 'avatar' => 'compact',
@@ -48,7 +50,8 @@ return [
'language', 'language',
'name', 'name',
'role' => 'compact', 'role' => 'compact',
'username' 'username',
'uuid'
], ],
'auth' => [ 'auth' => [
'avatar' => 'compact', 'avatar' => 'compact',
@@ -72,6 +75,7 @@ return [
'prev' => ['id', 'name'], 'prev' => ['id', 'name'],
'role', 'role',
'username', 'username',
'uuid'
], ],
] ]
]; ];

View File

@@ -454,7 +454,7 @@
"paste": "붙여넣기", "paste": "붙여넣기",
"paste.after": "뒤로 붙여넣기", "paste.after": "뒤로 붙여넣기",
"pixel": "픽셀", "pixel": "픽셀",
"plugin": "Plugin", "plugin": "플러그인",
"plugins": "플러그인", "plugins": "플러그인",
"prev": "이전", "prev": "이전",
"preview": "미리 보기", "preview": "미리 보기",
@@ -503,15 +503,15 @@
"system.issues.site": "<code>/site</code> 폴더의 권한을 확인하세요.", "system.issues.site": "<code>/site</code> 폴더의 권한을 확인하세요.",
"system.issues.vulnerability.kirby": "Your installation might be affected by the following vulnerability ({ severity } severity): { description }", "system.issues.vulnerability.kirby": "Your installation might be affected by the following vulnerability ({ severity } severity): { description }",
"system.issues.vulnerability.plugin": "Your installation might be affected by the following vulnerability in the { plugin } plugin ({ severity } severity): { description }", "system.issues.vulnerability.plugin": "Your installation might be affected by the following vulnerability in the { plugin } plugin ({ severity } severity): { description }",
"system.updateStatus": "Update status", "system.updateStatus": "업데이트 상태",
"system.updateStatus.error": "Could not check for updates", "system.updateStatus.error": "업데이트를 확인할 수 없습니다.",
"system.updateStatus.not-vulnerable": "No known vulnerabilities", "system.updateStatus.not-vulnerable": "알려진 취약성이 없습니다.",
"system.updateStatus.security-update": "Free security update { version } available", "system.updateStatus.security-update": "Free security update { version } available",
"system.updateStatus.security-upgrade": "Upgrade { version } with security fixes available", "system.updateStatus.security-upgrade": "Upgrade { version } with security fixes available",
"system.updateStatus.unreleased": "Unreleased version", "system.updateStatus.unreleased": "출시 전 버전",
"system.updateStatus.up-to-date": "Up to date", "system.updateStatus.up-to-date": "최신 버전입니다.",
"system.updateStatus.update": "Free update { version } available", "system.updateStatus.update": "{ version } 버전으로 무료 업데이트",
"system.updateStatus.upgrade": "Upgrade { version } available", "system.updateStatus.upgrade": "{ version } 버전으로 업그레이드",
"title": "제목", "title": "제목",
"template": "\ud15c\ud50c\ub9bf", "template": "\ud15c\ud50c\ub9bf",
@@ -578,9 +578,9 @@
"users": "사용자", "users": "사용자",
"version": "버전", "version": "버전",
"version.current": "Current version", "version.current": "현재 버전",
"version.latest": "Latest version", "version.latest": "최신 버전",
"versionInformation": "Version information", "versionInformation": "버전 정보",
"view.account": "계정", "view.account": "계정",
"view.installation": "\uc124\uce58", "view.installation": "\uc124\uce58",

View File

@@ -1,10 +1,10 @@
{ {
"account.changeName": "Change your name", "account.changeName": "Pakeisti savo vardą",
"account.delete": "Delete your account", "account.delete": "Panaikinti savo paskyrą",
"account.delete.confirm": "Do you really want to delete your account? You will be logged out immediately. Your account cannot be recovered.", "account.delete.confirm": "Ar tikrai norite panaikinti savo paskyrą? Jūs iš karto atsijungsite. Paskyros bus neįmanoma atstatyti.",
"add": "Pridėti", "add": "Pridėti",
"author": "Author", "author": "Autorius",
"avatar": "Profilio nuotrauka", "avatar": "Profilio nuotrauka",
"back": "Atgal", "back": "Atgal",
"cancel": "Atšaukti", "cancel": "Atšaukti",
@@ -49,10 +49,10 @@
"email": "El. paštas", "email": "El. paštas",
"email.placeholder": "mail@example.com", "email.placeholder": "mail@example.com",
"entries": "Entries", "entries": "Įrašai",
"entry": "Entry", "entry": "Įrašas",
"environment": "Environment", "environment": "Aplinka",
"error.access.code": "Neteisinas kodas", "error.access.code": "Neteisinas kodas",
"error.access.login": "Neteisingas prisijungimo vardas", "error.access.login": "Neteisingas prisijungimo vardas",
@@ -70,7 +70,7 @@
"error.blocks.max.singular": "Jūs galite pridėti daugiausiai vieną bloką", "error.blocks.max.singular": "Jūs galite pridėti daugiausiai vieną bloką",
"error.blocks.min.plural": "Minimalus blokų kiekis: {min}", "error.blocks.min.plural": "Minimalus blokų kiekis: {min}",
"error.blocks.min.singular": "Jūs turite pridėti bent vieną bloką", "error.blocks.min.singular": "Jūs turite pridėti bent vieną bloką",
"error.blocks.validation": "There's an error on the \"{field}\" field in block {index} using the \"{fieldset}\" block type", "error.blocks.validation": "Yra klaida laukelyje \"{field}\" bloke {index} naudojant bloko tipą \"{fieldset}\"",
"error.email.preset.notFound": "El. pašto paruoštukas \"{name}\" nerastas", "error.email.preset.notFound": "El. pašto paruoštukas \"{name}\" nerastas",
@@ -116,7 +116,7 @@
"error.object.validation": "Theres an error in the \"{label}\" field:\n{message}", "error.object.validation": "Theres an error in the \"{label}\" field:\n{message}",
"error.offline": "The Panel is currently offline", "error.offline": "Valdymo pultas dabar yra offline",
"error.page.changeSlug.permission": "Neturite teisės pakeisti \"{slug}\" URL", "error.page.changeSlug.permission": "Neturite teisės pakeisti \"{slug}\" URL",
"error.page.changeStatus.incomplete": "Puslapis turi klaidų ir negali būti paskelbtas", "error.page.changeStatus.incomplete": "Puslapis turi klaidų ir negali būti paskelbtas",
@@ -284,7 +284,7 @@
"field.layout.empty": "Dar nėra eilučių", "field.layout.empty": "Dar nėra eilučių",
"field.layout.select": "Pasirinkite išdėstymą", "field.layout.select": "Pasirinkite išdėstymą",
"field.object.empty": "No information yet", "field.object.empty": "Dar nėra informacijos",
"field.pages.empty": "Dar nėra puslapių", "field.pages.empty": "Dar nėra puslapių",
@@ -346,12 +346,12 @@
"license": "Licenzija", "license": "Licenzija",
"license.buy": "Pirkti licenziją", "license.buy": "Pirkti licenziją",
"license.register": "Registruoti", "license.register": "Registruoti",
"license.manage": "Manage your licenses", "license.manage": "Valdyti savo licencijas",
"license.register.help": "Licenzijos kodą gavote el. paštu po apmokėjimo. Prašome įterpti čia, kad sistema būtų užregistruota.", "license.register.help": "Licenzijos kodą gavote el. paštu po apmokėjimo. Prašome įterpti čia, kad sistema būtų užregistruota.",
"license.register.label": "Prašome įrašyti jūsų licenzijos kodą", "license.register.label": "Prašome įrašyti jūsų licenzijos kodą",
"license.register.success": "Ačiū, kad palaikote Kirby", "license.register.success": "Ačiū, kad palaikote Kirby",
"license.unregistered": "Tai neregistruota Kirby demo versija", "license.unregistered": "Tai neregistruota Kirby demo versija",
"license.unregistered.label": "Unregistered", "license.unregistered.label": "Neregistruota",
"link": "Nuoroda", "link": "Nuoroda",
"link.text": "Nuorodos tekstas", "link.text": "Nuorodos tekstas",
@@ -454,8 +454,8 @@
"paste": "Įterpti", "paste": "Įterpti",
"paste.after": "Įterpti po", "paste.after": "Įterpti po",
"pixel": "Pikselis", "pixel": "Pikselis",
"plugin": "Plugin", "plugin": "Įskiepas",
"plugins": "Plugins", "plugins": "Įskiepai",
"prev": "Ankstesnis", "prev": "Ankstesnis",
"preview": "Peržiūra", "preview": "Peržiūra",
"remove": "Pašalinti", "remove": "Pašalinti",
@@ -482,9 +482,9 @@
"section.required": "Sekcija privaloma", "section.required": "Sekcija privaloma",
"security": "Security", "security": "Saugumas",
"select": "Pasirinkti", "select": "Pasirinkti",
"server": "Server", "server": "Serveris",
"settings": "Nustatymai", "settings": "Nustatymai",
"show": "Rodyti", "show": "Rodyti",
"site.blueprint": "Svetainė neturi blueprint. Jūs galite nustatyti jį <strong>/site/blueprints/site.yml</strong>", "site.blueprint": "Svetainė neturi blueprint. Jūs galite nustatyti jį <strong>/site/blueprints/site.yml</strong>",
@@ -492,7 +492,7 @@
"slug": "URL pabaiga", "slug": "URL pabaiga",
"sort": "Rikiuoti", "sort": "Rikiuoti",
"stats.empty": "No reports", "stats.empty": "Nėra pranešimų",
"system.issues.content": "The content folder seems to be exposed", "system.issues.content": "The content folder seems to be exposed",
"system.issues.eol.kirby": "Your installed Kirby version has reached end-of-life and will not receive further security updates", "system.issues.eol.kirby": "Your installed Kirby version has reached end-of-life and will not receive further security updates",
"system.issues.eol.plugin": "Your installed version of the { plugin } plugin is has reached end-of-life and will not receive further security updates", "system.issues.eol.plugin": "Your installed version of the { plugin } plugin is has reached end-of-life and will not receive further security updates",
@@ -503,15 +503,15 @@
"system.issues.site": "The site folder seems to be exposed", "system.issues.site": "The site folder seems to be exposed",
"system.issues.vulnerability.kirby": "Your installation might be affected by the following vulnerability ({ severity } severity): { description }", "system.issues.vulnerability.kirby": "Your installation might be affected by the following vulnerability ({ severity } severity): { description }",
"system.issues.vulnerability.plugin": "Your installation might be affected by the following vulnerability in the { plugin } plugin ({ severity } severity): { description }", "system.issues.vulnerability.plugin": "Your installation might be affected by the following vulnerability in the { plugin } plugin ({ severity } severity): { description }",
"system.updateStatus": "Update status", "system.updateStatus": "Atnaujinimų statusas",
"system.updateStatus.error": "Could not check for updates", "system.updateStatus.error": "Nepavyko patikrinti atnaujinimų",
"system.updateStatus.not-vulnerable": "No known vulnerabilities", "system.updateStatus.not-vulnerable": "Nėra žinomų saugumo spragų",
"system.updateStatus.security-update": "Free security update { version } available", "system.updateStatus.security-update": "Prieinamas nemokamas saugumo atnaujinimas { version }",
"system.updateStatus.security-upgrade": "Upgrade { version } with security fixes available", "system.updateStatus.security-upgrade": "Prieinama nauja { version } versija su saugumo atnaujinimais",
"system.updateStatus.unreleased": "Unreleased version", "system.updateStatus.unreleased": "Neišleista versija",
"system.updateStatus.up-to-date": "Up to date", "system.updateStatus.up-to-date": "Naujausia versija",
"system.updateStatus.update": "Free update { version } available", "system.updateStatus.update": "Prieinamas nemokamas atnaujinimas { version }",
"system.updateStatus.upgrade": "Upgrade { version } available", "system.updateStatus.upgrade": "Prieinamas atnaujinimas { version }",
"title": "Pavadinimas", "title": "Pavadinimas",
"template": "Puslapio šablonas", "template": "Puslapio šablonas",
@@ -578,16 +578,16 @@
"users": "Vartotojai", "users": "Vartotojai",
"version": "Versija", "version": "Versija",
"version.current": "Current version", "version.current": "Dabartinė versija",
"version.latest": "Latest version", "version.latest": "Naujausia versija",
"versionInformation": "Version information", "versionInformation": "Versijos informacija",
"view.account": "Jūsų paskyra", "view.account": "Jūsų paskyra",
"view.installation": "Installation", "view.installation": "Installation",
"view.languages": "Kalbos", "view.languages": "Kalbos",
"view.resetPassword": "Sukurti naują slaptažodį", "view.resetPassword": "Sukurti naują slaptažodį",
"view.site": "Svetainė", "view.site": "Svetainė",
"view.system": "System", "view.system": "Sistema",
"view.users": "Vartotojai", "view.users": "Vartotojai",
"welcome": "Sveiki", "welcome": "Sveiki",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -383,7 +383,7 @@ class App
*/ */
public function collections() public function collections()
{ {
return $this->collections = $this->collections ?? new Collections(); return $this->collections ??= new Collections();
} }
/** /**
@@ -564,7 +564,7 @@ class App
*/ */
public function defaultLanguage() public function defaultLanguage()
{ {
return $this->defaultLanguage = $this->defaultLanguage ?? $this->languages()->default(); return $this->defaultLanguage ??= $this->languages()->default();
} }
/** /**
@@ -928,11 +928,15 @@ class App
return $this->defaultLanguage(); return $this->defaultLanguage();
} }
// if requesting a non-default language,
// find it but don't cache it
if ($code !== null) { if ($code !== null) {
return $this->languages()->find($code); return $this->languages()->find($code);
} }
return $this->language = $this->language ?? $this->defaultLanguage(); // otherwise return language set by `AppTranslation::setCurrentLanguage`
// or default language
return $this->language ??= $this->defaultLanguage();
} }
/** /**
@@ -1025,7 +1029,7 @@ class App
*/ */
public function nonce(): string public function nonce(): string
{ {
return $this->nonce = $this->nonce ?? base64_encode(random_bytes(20)); return $this->nonce ??= base64_encode(random_bytes(20));
} }
/** /**
@@ -1345,7 +1349,7 @@ class App
*/ */
public function response() public function response()
{ {
return $this->response = $this->response ?? new Responder(); return $this->response ??= new Responder();
} }
/** /**
@@ -1355,7 +1359,7 @@ class App
*/ */
public function roles() public function roles()
{ {
return $this->roles = $this->roles ?? Roles::load($this->root('roles')); return $this->roles ??= Roles::load($this->root('roles'));
} }
/** /**
@@ -1578,7 +1582,7 @@ class App
*/ */
public function site() public function site()
{ {
return $this->site = $this->site ?? new Site([ return $this->site ??= new Site([
'errorPageId' => $this->options['error'] ?? 'error', 'errorPageId' => $this->options['error'] ?? 'error',
'homePageId' => $this->options['home'] ?? 'home', 'homePageId' => $this->options['home'] ?? 'home',
'kirby' => $this, 'kirby' => $this,
@@ -1647,7 +1651,7 @@ class App
*/ */
public function system() public function system()
{ {
return $this->system = $this->system ?? new System($this); return $this->system ??= new System($this);
} }
/** /**
@@ -1771,7 +1775,7 @@ class App
public static function version(): string|null public static function version(): string|null
{ {
try { try {
return static::$version = static::$version ?? Data::read(dirname(__DIR__, 2) . '/composer.json')['version'] ?? null; return static::$version ??= Data::read(dirname(__DIR__, 2) . '/composer.json')['version'] ?? null;
} catch (Throwable) { } catch (Throwable) {
throw new LogicException('The Kirby version cannot be detected. The composer.json is probably missing or not readable.'); throw new LogicException('The Kirby version cannot be detected. The composer.json is probably missing or not readable.');
} }
@@ -1794,6 +1798,6 @@ class App
*/ */
public function visitor() public function visitor()
{ {
return $this->visitor = $this->visitor ?? new Visitor(); return $this->visitor ??= new Visitor();
} }
} }

View File

@@ -140,10 +140,7 @@ trait AppPlugins
protected function extendAreas(array $areas): array protected function extendAreas(array $areas): array
{ {
foreach ($areas as $id => $area) { foreach ($areas as $id => $area) {
if (isset($this->extensions['areas'][$id]) === false) { $this->extensions['areas'][$id] ??= [];
$this->extensions['areas'][$id] = [];
}
$this->extensions['areas'][$id][] = $area; $this->extensions['areas'][$id][] = $area;
} }
@@ -388,9 +385,7 @@ trait AppPlugins
protected function extendHooks(array $hooks): array protected function extendHooks(array $hooks): array
{ {
foreach ($hooks as $name => $callbacks) { foreach ($hooks as $name => $callbacks) {
if (isset($this->extensions['hooks'][$name]) === false) { $this->extensions['hooks'][$name] ??= [];
$this->extensions['hooks'][$name] = [];
}
if (is_array($callbacks) === false) { if (is_array($callbacks) === false) {
$callbacks = [$callbacks]; $callbacks = [$callbacks];

View File

@@ -31,7 +31,7 @@ trait AppUsers
*/ */
public function auth() public function auth()
{ {
return $this->auth = $this->auth ?? new Auth($this); return $this->auth ??= new Auth($this);
} }
/** /**

View File

@@ -63,9 +63,7 @@ class Collections
public function get(string $name, array $data = []) public function get(string $name, array $data = [])
{ {
// if not yet loaded // if not yet loaded
if (isset($this->collections[$name]) === false) { $this->collections[$name] ??= $this->load($name);
$this->collections[$name] = $this->load($name);
}
// if not yet cached // if not yet cached
if ( if (

View File

@@ -164,13 +164,11 @@ class Content
$key = strtolower($key); $key = strtolower($key);
if (isset($this->fields[$key])) { return $this->fields[$key] ??= new Field(
return $this->fields[$key]; $this->parent,
} $key,
$this->data()[$key] ?? null
$value = $this->data()[$key] ?? null; );
return $this->fields[$key] = new Field($this->parent, $key, $value);
} }
/** /**

View File

@@ -84,26 +84,17 @@ class ContentTranslation
*/ */
public function content(): array public function content(): array
{ {
$parent = $this->parent(); $parent = $this->parent();
$content = $this->content ??= $parent->readContent($this->code());
if ($this->content === null) {
$this->content = $parent->readContent($this->code());
}
$content = $this->content;
// merge with the default content // merge with the default content
if ( if (
$this->isDefault() === false && $this->isDefault() === false &&
$defaultLanguage = $parent->kirby()->defaultLanguage() $defaultLanguage = $parent->kirby()->defaultLanguage()
) { ) {
$default = []; if ($default = $parent->translation($defaultLanguage->code())?->content()) {
$content = array_merge($default, $content);
if ($defaultTranslation = $parent->translation($defaultLanguage->code())) {
$default = $defaultTranslation->content();
} }
$content = array_merge($default, $content);
} }
return $content; return $content;

View File

@@ -49,14 +49,10 @@ class Email
$this->props = array_merge($preset, $props); $this->props = array_merge($preset, $props);
// add transport settings // add transport settings
if (isset($this->props['transport']) === false) { $this->props['transport'] ??= $this->options['transport'] ?? [];
$this->props['transport'] = $this->options['transport'] ?? [];
}
// add predefined beforeSend option // add predefined beforeSend option
if (isset($this->props['beforeSend']) === false) { $this->props['beforeSend'] ??= $this->options['beforeSend'] ?? null;
$this->props['beforeSend'] = $this->options['beforeSend'] ?? null;
}
// transform model objects to values // transform model objects to values
$this->transformUserSingle('from', 'fromName'); $this->transformUserSingle('from', 'fromName');
@@ -235,12 +231,7 @@ class Email
$this->props[$addressProp] = $address; $this->props[$addressProp] = $address;
// only use the name from the user if no custom name was set // only use the name from the user if no custom name was set
if ( $this->props[$nameProp] ??= $name;
isset($this->props[$nameProp]) === false ||
$this->props[$nameProp] === null
) {
$this->props[$nameProp] = $name;
}
} }
/** /**

View File

@@ -295,11 +295,7 @@ class File extends ModelWithContent
$template = $this->template(); $template = $this->template();
if (isset($readable[$template]) === true) { return $readable[$template] ??= $this->permissions()->can('read');
return $readable[$template];
}
return $readable[$template] = $this->permissions()->can('read');
} }
/** /**

View File

@@ -777,11 +777,7 @@ class Page extends ModelWithContent
$template = $this->intendedTemplate()->name(); $template = $this->intendedTemplate()->name();
if (isset($readable[$template]) === true) { return $readable[$template] ??= $this->permissions()->can('read');
return $readable[$template];
}
return $readable[$template] = $this->permissions()->can('read');
} }
/** /**

View File

@@ -27,6 +27,55 @@ use Kirby\Uuid\Uuids;
*/ */
trait PageActions trait PageActions
{ {
/**
* Adapts necessary modifications which page uuid, page slug and files uuid
* of copy objects for single or multilang environments
*/
protected function adaptCopy(Page $copy, bool $files = false): Page
{
if ($this->kirby()->multilang() === true) {
foreach ($this->kirby()->languages() as $language) {
// overwrite with new UUID for the page and files
// for default language (remove old, add new)
if (
Uuids::enabled() === true &&
$language->isDefault() === true
) {
$copy = $copy->save(['uuid' => Uuid::generate()], $language->code());
if ($files !== false) {
foreach ($copy->files() as $file) {
$file->save(['uuid' => Uuid::generate()], $language->code());
}
}
}
// remove all translated slugs
if (
$language->isDefault() === false &&
$copy->translation($language)->exists() === true
) {
$copy = $copy->save(['slug' => null], $language->code());
}
}
return $copy;
}
// overwrite with new UUID for the page and files (remove old, add new)
if (Uuids::enabled() === true) {
$copy = $copy->save(['uuid' => Uuid::generate()]);
if ($files !== false) {
foreach ($copy->files() as $file) {
$file->save(['uuid' => Uuid::generate()]);
}
}
}
return $copy;
}
/** /**
* Changes the sorting number. * Changes the sorting number.
* The sorting number must already be correct * The sorting number must already be correct
@@ -434,19 +483,8 @@ trait PageActions
$copy = $parentModel->clone()->findPageOrDraft($slug); $copy = $parentModel->clone()->findPageOrDraft($slug);
// remove all translated slugs // normalize copy object
if ($this->kirby()->multilang() === true) { $copy = $this->adaptCopy($copy, $files);
foreach ($this->kirby()->languages() as $language) {
if ($language->isDefault() === false && $copy->translation($language)->exists() === true) {
$copy = $copy->save(['slug' => null], $language->code());
}
}
}
// overwrite with new UUID (remove old, add new)
if (Uuids::enabled() === true) {
$copy = $copy->save(['uuid' => Uuid::generate()]);
}
// add copy to siblings // add copy to siblings
static::updateParentCollections($copy, 'append', $parentModel); static::updateParentCollections($copy, 'append', $parentModel);
@@ -776,9 +814,9 @@ trait PageActions
foreach ($sorted as $key => $id) { foreach ($sorted as $key => $id) {
if ($id === $this->id()) { if ($id === $this->id()) {
continue; continue;
} elseif ($sibling = $siblings->get($id)) {
$sibling->changeNum($key + 1);
} }
$siblings->get($id)?->changeNum($key + 1);
} }
$parent = $this->parentModel(); $parent = $this->parentModel();

View File

@@ -79,11 +79,7 @@ class PageBlueprint extends Blueprint
'sort' => 'default', 'sort' => 'default',
]; ];
if (isset($aliases[$num]) === true) { return $aliases[$num] ?? $num;
return $aliases[$num];
}
return $num;
} }
/** /**
@@ -144,9 +140,7 @@ class PageBlueprint extends Blueprint
} }
// also make sure to have the text field set // also make sure to have the text field set
if (isset($status[$key]['text']) === false) { $status[$key]['text'] ??= null;
$status[$key]['text'] = null;
}
// translate text and label if necessary // translate text and label if necessary
$status[$key]['label'] = $this->i18n($status[$key]['label'], $status[$key]['label']); $status[$key]['label'] = $this->i18n($status[$key]['label'], $status[$key]['label']);

View File

@@ -19,20 +19,11 @@ class Body
{ {
use Properties; use Properties;
/** protected string|null $html = null;
* @var string protected string|null $text = null;
*/
protected $html;
/**
* @var string
*/
protected $text;
/** /**
* Email body constructor * Email body constructor
*
* @param array $props
*/ */
public function __construct(array $props = []) public function __construct(array $props = [])
{ {
@@ -41,20 +32,16 @@ class Body
/** /**
* Returns the HTML content of the email body * Returns the HTML content of the email body
*
* @return string
*/ */
public function html() public function html(): string
{ {
return $this->html ?? ''; return $this->html ?? '';
} }
/** /**
* Returns the plain text content of the email body * Returns the plain text content of the email body
*
* @return string
*/ */
public function text() public function text(): string
{ {
return $this->text ?? ''; return $this->text ?? '';
} }
@@ -62,10 +49,9 @@ class Body
/** /**
* Sets the HTML content for the email body * Sets the HTML content for the email body
* *
* @param string|null $html
* @return $this * @return $this
*/ */
protected function setHtml(string $html = null) protected function setHtml(string|null $html = null): static
{ {
$this->html = $html; $this->html = $html;
return $this; return $this;
@@ -74,10 +60,9 @@ class Body
/** /**
* Sets the plain text content for the email body * Sets the plain text content for the email body
* *
* @param string|null $text
* @return $this * @return $this
*/ */
protected function setText(string $text = null) protected function setText(string|null $text = null): static
{ {
$this->text = $text; $this->text = $text;
return $this; return $this;

View File

@@ -24,89 +24,61 @@ class Email
/** /**
* If set to `true`, the debug mode is enabled * If set to `true`, the debug mode is enabled
* for all emails * for all emails
*
* @var bool
*/ */
public static $debug = false; public static bool $debug = false;
/** /**
* Store for sent emails when `Email::$debug` * Store for sent emails when `Email::$debug`
* is set to `true` * is set to `true`
* */
public static array $emails = [];
/**
* @var array * @var array
*/ */
public static $emails = []; protected array|null $attachments = null;
protected Body|null $body = null;
/** /**
* @var array|null * @var array
*/ */
protected $attachments; protected array|null $bcc = null;
protected Closure|null $beforeSend = null;
/** /**
* @var \Kirby\Email\Body|null * @var array
*/ */
protected $body; protected array|null $cc = null;
/** /**
* @var array|null * @var string
*/ */
protected $bcc; protected string|null $from = null;
protected string|null $fromName = null;
protected bool $isSent = false;
/** /**
* @var \Closure|null * @var string
*/ */
protected $beforeSend; protected string|null $replyTo = null;
protected string|null $replyToName = null;
/** /**
* @var array|null * @var string
*/ */
protected $cc; protected string|null $subject = null;
/** /**
* @var string|null * @var array
*/ */
protected $from; protected array|null $to = null;
protected array|null $transport = null;
/**
* @var string|null
*/
protected $fromName;
/**
* @var string|null
*/
protected $replyTo;
/**
* @var string|null
*/
protected $replyToName;
/**
* @var bool
*/
protected $isSent = false;
/**
* @var string|null
*/
protected $subject;
/**
* @var array|null
*/
protected $to;
/**
* @var array|null
*/
protected $transport;
/** /**
* Email constructor * Email constructor
*
* @param array $props
* @param bool $debug
*/ */
public function __construct(array $props = [], bool $debug = false) public function __construct(array $props = [], bool $debug = false)
{ {
@@ -123,8 +95,6 @@ class Email
/** /**
* Returns the email attachments * Returns the email attachments
*
* @return array
*/ */
public function attachments(): array public function attachments(): array
{ {
@@ -133,18 +103,14 @@ class Email
/** /**
* Returns the email body * Returns the email body
*
* @return \Kirby\Email\Body|null
*/ */
public function body() public function body(): Body|null
{ {
return $this->body; return $this->body;
} }
/** /**
* Returns "bcc" recipients * Returns "bcc" recipients
*
* @return array
*/ */
public function bcc(): array public function bcc(): array
{ {
@@ -154,8 +120,6 @@ class Email
/** /**
* Returns the beforeSend callback closure, * Returns the beforeSend callback closure,
* which has access to the PHPMailer instance * which has access to the PHPMailer instance
*
* @return \Closure|null
*/ */
public function beforeSend(): Closure|null public function beforeSend(): Closure|null
{ {
@@ -164,8 +128,6 @@ class Email
/** /**
* Returns "cc" recipients * Returns "cc" recipients
*
* @return array
*/ */
public function cc(): array public function cc(): array
{ {
@@ -174,8 +136,6 @@ class Email
/** /**
* Returns default transport settings * Returns default transport settings
*
* @return array
*/ */
protected function defaultTransport(): array protected function defaultTransport(): array
{ {
@@ -186,8 +146,6 @@ class Email
/** /**
* Returns the "from" email address * Returns the "from" email address
*
* @return string
*/ */
public function from(): string public function from(): string
{ {
@@ -196,8 +154,6 @@ class Email
/** /**
* Returns the "from" name * Returns the "from" name
*
* @return string|null
*/ */
public function fromName(): string|null public function fromName(): string|null
{ {
@@ -206,18 +162,14 @@ class Email
/** /**
* Checks if the email has an HTML body * Checks if the email has an HTML body
*
* @return bool
*/ */
public function isHtml() public function isHtml(): bool
{ {
return empty($this->body()->html()) === false; return empty($this->body()->html()) === false;
} }
/** /**
* Checks if the email has been sent successfully * Checks if the email has been sent successfully
*
* @return bool
*/ */
public function isSent(): bool public function isSent(): bool
{ {
@@ -226,8 +178,6 @@ class Email
/** /**
* Returns the "reply to" email address * Returns the "reply to" email address
*
* @return string
*/ */
public function replyTo(): string public function replyTo(): string
{ {
@@ -236,8 +186,6 @@ class Email
/** /**
* Returns the "reply to" name * Returns the "reply to" name
*
* @return string|null
*/ */
public function replyToName(): string|null public function replyToName(): string|null
{ {
@@ -247,13 +195,12 @@ class Email
/** /**
* Converts single or multiple email addresses to a sanitized format * Converts single or multiple email addresses to a sanitized format
* *
* @param string|array|null $email
* @param bool $multiple
* @return array|mixed|string
* @throws \Exception * @throws \Exception
*/ */
protected function resolveEmail($email = null, bool $multiple = true) protected function resolveEmail(
{ string|array|null $email = null,
bool $multiple = true
): array|string {
if ($email === null) { if ($email === null) {
return $multiple === true ? [] : ''; return $multiple === true ? [] : '';
} }
@@ -284,8 +231,6 @@ class Email
/** /**
* Sends the email * Sends the email
*
* @return bool
*/ */
public function send(): bool public function send(): bool
{ {
@@ -295,10 +240,9 @@ class Email
/** /**
* Sets the email attachments * Sets the email attachments
* *
* @param array|null $attachments
* @return $this * @return $this
*/ */
protected function setAttachments($attachments = null) protected function setAttachments(array|null $attachments = null): static
{ {
$this->attachments = $attachments ?? []; $this->attachments = $attachments ?? [];
return $this; return $this;
@@ -307,10 +251,9 @@ class Email
/** /**
* Sets the email body * Sets the email body
* *
* @param string|array $body
* @return $this * @return $this
*/ */
protected function setBody($body) protected function setBody(string|array $body): static
{ {
if (is_string($body) === true) { if (is_string($body) === true) {
$body = ['text' => $body]; $body = ['text' => $body];
@@ -323,10 +266,9 @@ class Email
/** /**
* Sets "bcc" recipients * Sets "bcc" recipients
* *
* @param string|array|null $bcc
* @return $this * @return $this
*/ */
protected function setBcc($bcc = null) protected function setBcc(string|array|null $bcc = null): static
{ {
$this->bcc = $this->resolveEmail($bcc); $this->bcc = $this->resolveEmail($bcc);
return $this; return $this;
@@ -335,10 +277,9 @@ class Email
/** /**
* Sets the "beforeSend" callback * Sets the "beforeSend" callback
* *
* @param \Closure|null $beforeSend
* @return $this * @return $this
*/ */
protected function setBeforeSend(Closure|null $beforeSend = null) protected function setBeforeSend(Closure|null $beforeSend = null): static
{ {
$this->beforeSend = $beforeSend; $this->beforeSend = $beforeSend;
return $this; return $this;
@@ -347,10 +288,9 @@ class Email
/** /**
* Sets "cc" recipients * Sets "cc" recipients
* *
* @param string|array|null $cc
* @return $this * @return $this
*/ */
protected function setCc($cc = null) protected function setCc(string|array|null $cc = null): static
{ {
$this->cc = $this->resolveEmail($cc); $this->cc = $this->resolveEmail($cc);
return $this; return $this;
@@ -359,10 +299,9 @@ class Email
/** /**
* Sets the "from" email address * Sets the "from" email address
* *
* @param string $from
* @return $this * @return $this
*/ */
protected function setFrom(string $from) protected function setFrom(string $from): static
{ {
$this->from = $this->resolveEmail($from, false); $this->from = $this->resolveEmail($from, false);
return $this; return $this;
@@ -371,10 +310,9 @@ class Email
/** /**
* Sets the "from" name * Sets the "from" name
* *
* @param string|null $fromName
* @return $this * @return $this
*/ */
protected function setFromName(string $fromName = null) protected function setFromName(string|null $fromName = null): static
{ {
$this->fromName = $fromName; $this->fromName = $fromName;
return $this; return $this;
@@ -383,10 +321,9 @@ class Email
/** /**
* Sets the "reply to" email address * Sets the "reply to" email address
* *
* @param string|null $replyTo
* @return $this * @return $this
*/ */
protected function setReplyTo(string $replyTo = null) protected function setReplyTo(string|null $replyTo = null): static
{ {
$this->replyTo = $this->resolveEmail($replyTo, false); $this->replyTo = $this->resolveEmail($replyTo, false);
return $this; return $this;
@@ -395,10 +332,9 @@ class Email
/** /**
* Sets the "reply to" name * Sets the "reply to" name
* *
* @param string|null $replyToName
* @return $this * @return $this
*/ */
protected function setReplyToName(string $replyToName = null) protected function setReplyToName(string|null $replyToName = null): static
{ {
$this->replyToName = $replyToName; $this->replyToName = $replyToName;
return $this; return $this;
@@ -407,10 +343,9 @@ class Email
/** /**
* Sets the email subject * Sets the email subject
* *
* @param string $subject
* @return $this * @return $this
*/ */
protected function setSubject(string $subject) protected function setSubject(string $subject): static
{ {
$this->subject = $subject; $this->subject = $subject;
return $this; return $this;
@@ -419,10 +354,9 @@ class Email
/** /**
* Sets the recipients of the email * Sets the recipients of the email
* *
* @param string|array $to
* @return $this * @return $this
*/ */
protected function setTo($to) protected function setTo(string|array $to): static
{ {
$this->to = $this->resolveEmail($to); $this->to = $this->resolveEmail($to);
return $this; return $this;
@@ -431,10 +365,9 @@ class Email
/** /**
* Sets the email transport settings * Sets the email transport settings
* *
* @param array|null $transport
* @return $this * @return $this
*/ */
protected function setTransport($transport = null) protected function setTransport(array|null $transport = null): static
{ {
$this->transport = $transport; $this->transport = $transport;
return $this; return $this;
@@ -442,8 +375,6 @@ class Email
/** /**
* Returns the email subject * Returns the email subject
*
* @return string
*/ */
public function subject(): string public function subject(): string
{ {
@@ -452,8 +383,6 @@ class Email
/** /**
* Returns the email recipients * Returns the email recipients
*
* @return array
*/ */
public function to(): array public function to(): array
{ {
@@ -462,8 +391,6 @@ class Email
/** /**
* Returns the email transports settings * Returns the email transports settings
*
* @return array
*/ */
public function transport(): array public function transport(): array
{ {

View File

@@ -21,8 +21,6 @@ class PHPMailer extends Email
/** /**
* Sends email via PHPMailer library * Sends email via PHPMailer library
* *
* @param bool $debug
* @return bool
* @throws \Kirby\Exception\InvalidArgumentException * @throws \Kirby\Exception\InvalidArgumentException
*/ */
public function send(bool $debug = false): bool public function send(bool $debug = false): bool

View File

@@ -109,9 +109,7 @@ class Form
$input = array_merge($values, $input); $input = array_merge($values, $input);
foreach ($input as $key => $value) { foreach ($input as $key => $value) {
if (isset($this->values[$key]) === false) { $this->values[$key] ??= $value;
$this->values[$key] = $value;
}
} }
} }
} }

View File

@@ -7,8 +7,8 @@ use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\InvalidArgumentException;
use Kirby\Http\Remote; use Kirby\Http\Remote;
use Kirby\Http\Url; use Kirby\Http\Url;
use Kirby\Query\Query;
use Kirby\Toolkit\Properties; use Kirby\Toolkit\Properties;
use Kirby\Toolkit\Query;
use Kirby\Toolkit\Str; use Kirby\Toolkit\Str;
/** /**
@@ -134,7 +134,7 @@ class OptionsApi
throw new InvalidArgumentException('Invalid options format'); throw new InvalidArgumentException('Invalid options format');
} }
$result = (new Query($this->fetch(), Nest::create($data)))->result(); $result = (new Query($this->fetch()))->resolve(Nest::create($data));
$options = []; $options = [];
foreach ($result as $item) { foreach ($result as $item) {

View File

@@ -5,10 +5,10 @@ namespace Kirby\Form;
use Kirby\Cms\Field; use Kirby\Cms\Field;
use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException; use Kirby\Exception\NotFoundException;
use Kirby\Query\Query;
use Kirby\Toolkit\Collection; use Kirby\Toolkit\Collection;
use Kirby\Toolkit\Obj; use Kirby\Toolkit\Obj;
use Kirby\Toolkit\Properties; use Kirby\Toolkit\Properties;
use Kirby\Toolkit\Query;
use Kirby\Toolkit\Str; use Kirby\Toolkit\Str;
/** /**
@@ -117,8 +117,8 @@ class OptionsQuery
} }
$data = $this->data(); $data = $this->data();
$query = new Query($this->query(), $data); $query = new Query($this->query());
$result = $query->result(); $result = $query->resolve($data);
$result = $this->resultToCollection($result); $result = $this->resultToCollection($result);
$options = []; $options = [];

View File

@@ -47,7 +47,7 @@ class Url
*/ */
public static function current(): string public static function current(): string
{ {
return static::$current = static::$current ?? static::toObject()->toString(); return static::$current ??= static::toObject()->toString();
} }
/** /**

View File

@@ -8,7 +8,7 @@ use Kirby\Data\Json;
use Kirby\Exception\NotFoundException; use Kirby\Exception\NotFoundException;
use Kirby\Http\Remote; use Kirby\Http\Remote;
use Kirby\Http\Url; use Kirby\Http\Url;
use Kirby\Toolkit\Query; use Kirby\Query\Query;
/** /**
* Options fetched from any REST API * Options fetched from any REST API
@@ -108,7 +108,9 @@ class OptionsApi extends OptionsProvider
throw new NotFoundException('Options could not be loaded from API: ' . $model->toSafeString($this->url)); throw new NotFoundException('Options could not be loaded from API: ' . $model->toSafeString($this->url));
} }
$data = (new Query($this->query, Nest::create($data)))->result(); // turn data into Nest so that it can be queried
$data = Nest::create($data);
$data = Query::factory($this->query)->resolve($data);
// create options by resolving text and value query strings // create options by resolving text and value query strings
// for each item from the data // for each item from the data

View File

@@ -159,9 +159,7 @@ abstract class Model
} }
} }
if (isset($settings['query']) === true) { unset($settings['query']);
unset($settings['query']);
}
// resolve remaining options defined as query // resolve remaining options defined as query
return A::map($settings, function ($option) { return A::map($settings, function ($option) {

View File

@@ -0,0 +1,100 @@
<?php
namespace Kirby\Query;
use Closure;
use Kirby\Toolkit\Str;
/**
* The Argument class represents a single
* parameter passed to a method in a chained query
*
* @package Kirby Query
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
final class Argument
{
public function __construct(
public mixed $value
) {
}
/**
* Sanitizes argument string into
* PHP type/object as new Argument instance
*/
public static function factory(string $argument): static
{
$argument = trim($argument);
// string with single or double quotes
if (
(
Str::startsWith($argument, '"') &&
Str::endsWith($argument, '"')
) || (
Str::startsWith($argument, "'") &&
Str::endsWith($argument, "'")
)
) {
$string = substr($argument, 1, -1);
$string = str_replace(['\"', "\'"], ['"', "'"], $string);
return new static($string);
}
// array: split and recursive sanitizing
if (
Str::startsWith($argument, '[') &&
Str::endsWith($argument, ']')
) {
$array = substr($argument, 1, -1);
$array = Arguments::factory($array);
return new static($array);
}
// numeric
if (is_numeric($argument) === true) {
return new static((float)$argument);
}
// Closure
if (Str::startsWith($argument, '() =>')) {
$query = Str::after($argument, '() =>');
$query = trim($query);
return new static(fn () => $query);
}
return new static(match ($argument) {
'null' => null,
'true' => true,
'false' => false,
// resolve parameter for objects and methods itself
default => new Query($argument)
});
}
/**
* Return the argument value and
* resolves nested objects to scaler types
*/
public function resolve(array|object $data = []): mixed
{
// don't resolve the Closure immediately, instead
// resolve it to the sub-query and create a new Closure
// that resolves the sub-query with the same data set once called
if ($this->value instanceof Closure) {
$query = ($this->value)();
return fn () => static::factory($query)->resolve($data);
}
if (is_object($this->value) === true) {
return $this->value->resolve($data);
}
return $this->value;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Kirby\Query;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Collection;
/**
* The Argument class represents a single
* parameter passed to a method in a chained query
*
* @package Kirby Query
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
final class Arguments extends Collection
{
public const NO_PNTH = '\([^(]+\)(*SKIP)(*FAIL)';
public const NO_SQBR = '\[[^]]+\](*SKIP)(*FAIL)';
public const NO_DLQU = '\"(?:[^"\\\\]|\\\\.)*\"(*SKIP)(*FAIL)';
public const NO_SLQU = '\'(?:[^\'\\\\]|\\\\.)*\'(*SKIP)(*FAIL)';
public static function factory(string $arguments): static
{
$arguments = A::map(
// split by comma, but not inside skip groups
preg_split('!,|' . self::NO_PNTH . '|' . self::NO_SQBR . '|' .
self::NO_DLQU . '|' . self::NO_SLQU . '!', $arguments),
fn ($argument) => Argument::factory($argument)
);
return new static($arguments);
}
public function resolve(array|object $data = []): array
{
return A::map(
$this->data,
fn ($argument) => $argument->resolve($data)
);
}
}

131
kirby/src/Query/Query.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
namespace Kirby\Query;
use Closure;
use Kirby\Cms\App;
use Kirby\Cms\Collection;
use Kirby\Cms\File;
use Kirby\Cms\Page;
use Kirby\Cms\Site;
use Kirby\Cms\User;
use Kirby\Toolkit\I18n;
/**
* The Query class can be used to
* query arrays and objects, including their
* methods with a very simple string-based syntax.
*
* @package Kirby Query
* @author Bastian Allgeier <bastian@getkirby.com>,
* Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class Query
{
/**
* Default data entries
*/
public static array $entries = [];
/**
* Creates a new Query object
*/
public function __construct(
public string|null $query = null
) {
if ($query !== null) {
$this->query = trim($query);
}
}
/**
* Creates a new Query object
*/
public static function factory(string $query): static
{
return new static(query: $query);
}
/**
* Method to help classes that extend Query
* to intercept a segment's result.
*/
public function intercept(mixed $result): mixed
{
return $result;
}
/**
* Returns the query result if anything
* can be found, otherwise returns null
*
* @throws \Kirby\Exception\BadMethodCallException If an invalid method is accessed by the query
*/
public function resolve(array|object $data = []): mixed
{
if (empty($this->query) === true) {
return $data;
}
// merge data with default entries
if (is_array($data) === true) {
$data = array_merge(static::$entries, $data);
}
// direct data array access via key
if (
is_array($data) === true &&
array_key_exists($this->query, $data) === true
) {
$value = $data[$this->query];
if ($value instanceof Closure) {
$value = $value();
}
return $value;
}
// loop through all segments to resolve query
return Segments::factory($this->query, $this)->resolve($data);
}
}
/**
* Default entries/functions
*/
Query::$entries['kirby'] = function (): App {
return App::instance();
};
Query::$entries['collection'] = function (string $name): Collection|null {
return App::instance()->collection($name);
};
Query::$entries['file'] = function (string $id): File|null {
return App::instance()->file($id);
};
Query::$entries['page'] = function (string $id): Page|null {
return App::instance()->site()->find($id);
};
Query::$entries['site'] = function (): Site {
return App::instance()->site();
};
Query::$entries['t'] = function (
string $key,
string|array $fallback = null,
string $locale = null
): string|null {
return I18n::translate($key, $fallback, $locale);
};
Query::$entries['user'] = function (string $id = null): User|null {
return App::instance()->user($id);
};

144
kirby/src/Query/Segment.php Normal file
View File

@@ -0,0 +1,144 @@
<?php
namespace Kirby\Query;
use Closure;
use Kirby\Exception\BadMethodCallException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\Str;
/**
* The Segment class represents a single
* part of a chained query
*
* @package Kirby Query
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class Segment
{
public function __construct(
public string $method,
public int $position,
public Arguments|null $arguments = null,
) {
}
/**
* Throws an exception for an access to an invalid method
*
* @param mixed $data Variable on which the access was tried
* @param string $name Name of the method/property that was accessed
* @param string $label Type of the name (`method`, `property` or `method/property`)
*
* @throws \Kirby\Exception\BadMethodCallException
*/
public static function error(mixed $data, string $name, string $label): void
{
$type = strtolower(gettype($data));
if ($type === 'double') {
$type = 'float';
}
$nonExisting = in_array($type, ['array', 'object']) ? 'non-existing ' : '';
$error = 'Access to ' . $nonExisting . $label . ' ' . $name . ' on ' . $type;
throw new BadMethodCallException($error);
}
public static function factory(
string $segment,
int $position = 0
): static {
if (Str::endsWith($segment, ')') === false) {
return new static(method: $segment, position: $position);
}
// the args are everything inside the *outer* parentheses
$args = Str::substr($segment, Str::position($segment, '(') + 1, -1);
return new static(
method: Str::before($segment, '('),
position: $position,
arguments: Arguments::factory($args)
);
}
public function resolve(mixed $base = null, array|object $data = []): mixed
{
// resolve arguments to array
$args = $this->arguments?->resolve($data) ?? [];
// 1st segment, start from $data array
if ($this->position === 0) {
if (is_array($data) == true) {
return $this->resolveArray($data, $args);
}
return $this->resolveObject($data, $args);
}
if (is_array($base) === true) {
return $this->resolveArray($base, $args);
}
if (is_object($base) === true) {
return $this->resolveObject($base, $args);
}
// trying to access further segments on a scalar/null value
static::error($base, $this->method, 'method/property');
}
/**
* Resolves segment by calling the corresponding array key
*/
protected function resolveArray(array $array, array $args): mixed
{
if (array_key_exists($this->method, $array) === false) {
static::error($array, $this->method, 'property');
}
$value = $array[$this->method];
if ($value instanceof Closure) {
return $value(...$args);
}
if ($args !== []) {
throw new InvalidArgumentException('Cannot access array element ' . $this->method . ' with arguments');
}
return $value;
}
/**
* Resolves segment by calling the method/accessing the property
* on the base object
*/
protected function resolveObject(object $object, array $args): mixed
{
if (
method_exists($object, $this->method) === true ||
method_exists($object, '__call') === true
) {
return $object->{$this->method}(...$args);
}
if (
$args === [] && (
property_exists($object, $this->method) === true ||
method_exists($object, '__get') === true
)
) {
return $object->{$this->method};
}
$label = ($args === []) ? 'method/property' : 'method';
static::error($object, $this->method, $label);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Kirby\Query;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Collection;
/**
* The Segments class helps splitting a
* query string into processable segments
*
* @package Kirby Query
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
final class Segments extends Collection
{
public function __construct(
array $data = [],
protected Query|null $parent = null,
) {
parent::__construct($data);
}
/**
* Split query string into segments by dot
* but not inside (nested) parens
*/
public static function factory(string $query, Query $parent = null): static
{
$segments = preg_split(
'!\.|(\(([^()]+|(?1))*+\))(*SKIP)(*FAIL)!',
trim($query),
-1,
PREG_SPLIT_NO_EMPTY
);
$segments = A::map(
array_keys($segments),
fn ($index) => Segment::factory($segments[$index], $index)
);
return new static($segments, $parent);
}
public function resolve(array|object $data = [])
{
$value = null;
foreach ($this->data as $segment) {
// offer possibility to intercept on objects
if ($value !== null) {
$value = $this->parent?->intercept($value) ?? $value;
}
$value = $segment->resolve($value, $data);
}
return $value;
}
}

View File

@@ -157,7 +157,9 @@ class FileSessionStore extends SessionStore
// check if the file is already unlocked or doesn't exist // check if the file is already unlocked or doesn't exist
if (!isset($this->isLocked[$name])) { if (!isset($this->isLocked[$name])) {
return; return;
} elseif ($this->exists($expiryTime, $id) === false) { }
if ($this->exists($expiryTime, $id) === false) {
unset($this->isLocked[$name]); unset($this->isLocked[$name]);
return; return;
} }

View File

@@ -101,9 +101,7 @@ class Sessions
public function create(array $options = []) public function create(array $options = [])
{ {
// fall back to default mode // fall back to default mode
if (!isset($options['mode'])) { $options['mode'] ??= $this->mode;
$options['mode'] = $this->mode;
}
return new Session($this, null, $options); return new Session($this, null, $options);
} }
@@ -117,11 +115,7 @@ class Sessions
*/ */
public function get(string $token, string $mode = null) public function get(string $token, string $mode = null)
{ {
if (isset($this->cache[$token])) { return $this->cache[$token] ??= new Session($this, $token, ['mode' => $mode ?? $this->mode]);
return $this->cache[$token];
}
return $this->cache[$token] = new Session($this, $token, ['mode' => $mode ?? $this->mode]);
} }
/** /**

View File

@@ -602,14 +602,19 @@ class Html extends Xml
default: default:
// short URLs // short URLs
if (Str::contains($uri->host(), 'youtu.be') === true && $isYoutubeId($first) === true) { if (
Str::contains($uri->host(), 'youtu.be') === true &&
$isYoutubeId($first) === true
) {
$src = 'https://www.youtube.com/embed/' . $first; $src = 'https://www.youtube.com/embed/' . $first;
$query->start = $query->t; $query->start = $query->t;
unset($query->t); unset($query->t);
} elseif (
// embedded video URLs in_array($first, ['embed', 'shorts']) === true &&
} elseif ($first === 'embed' && $isYoutubeId($second) === true) { $isYoutubeId($second) === true
) {
// embedded and shorts video URLs
$src = $host . '/' . $second; $src = $host . '/' . $second;
} }
} }

View File

@@ -16,6 +16,10 @@ use Kirby\Exception\InvalidArgumentException;
* @link https://getkirby.com * @link https://getkirby.com
* @copyright Bastian Allgeier * @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT * @license https://opensource.org/licenses/MIT
*
* @deprecated 3.8.2 Use `Kirby\Query\Query` instead
* // TODO: throw warnings in 3.9.0
* // TODO: Remove in 3.10.0
*/ */
class Query class Query
{ {

View File

@@ -7,6 +7,8 @@ use DateTime;
use Exception; use Exception;
use IntlDateFormatter; use IntlDateFormatter;
use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\InvalidArgumentException;
use Kirby\Query\Query;
use Throwable;
/** /**
* The String class provides a set * The String class provides a set
@@ -665,7 +667,7 @@ class Str
*/ */
public static function query(string $query, array $data = []) public static function query(string $query, array $data = [])
{ {
return (new Query($query, $data))->result(); return Query::factory($query)->resolve($data);
} }
/** /**
@@ -1181,40 +1183,37 @@ class Str
// make sure $string is string // make sure $string is string
$string ??= ''; $string ??= '';
return preg_replace_callback('!' . $start . '(.*?)' . $end . '!', function ($match) use ($data, $fallback, $callback) { return preg_replace_callback(
$query = trim($match[1]); '!' . $start . '(.*?)' . $end . '!',
function ($match) use ($data, $fallback, $callback) {
$query = trim($match[1]);
// if the placeholder contains a dot, it is a query
if (strpos($query, '.') !== false) {
try { try {
$result = (new Query($match[1], $data))->result(); $result = Query::factory($query)->resolve($data);
} catch (Exception) { } catch (Throwable) {
$result = null; $result = null;
} }
} else {
$result = $data[$query] ?? null;
}
// if we don't have a result, use the fallback if given // if we don't have a result, use the fallback if given
if ($result === null && $fallback !== null) { $result ??= $fallback;
$result = $fallback;
}
// callback on result if given // callback on result if given
if ($callback !== null) { if ($callback !== null) {
$callbackResult = $callback((string)$result, $query, $data); $callbackResult = $callback((string)$result, $query, $data);
if ($result === null && $callbackResult === '') { if ($result === null && $callbackResult === '') {
// the empty string came just from string casting, // the empty string came just from string casting,
// keep the null value and ignore the callback result // keep the null value and ignore the callback result
} else { } else {
$result = $callbackResult; $result = $callbackResult;
}
} }
}
// if we still don't have a result, keep the original placeholder // if we still don't have a result, keep the original placeholder
return $result ?? $match[0]; return $result ?? $match[0];
}, $string); },
$string
);
} }
/** /**

View File

@@ -38,7 +38,7 @@ class UserUuid extends Uuid
/** /**
* Returns the user object * Returns the user object
*/ */
public function model(bool $lazy = false): User public function model(bool $lazy = false): User|null
{ {
return $this->model ??= App::instance()->user($this->id()); return $this->model ??= App::instance()->user($this->id());
} }

View File

@@ -252,6 +252,11 @@ return array(
'Kirby\\Parsley\\Schema' => $baseDir . '/src/Parsley/Schema.php', 'Kirby\\Parsley\\Schema' => $baseDir . '/src/Parsley/Schema.php',
'Kirby\\Parsley\\Schema\\Blocks' => $baseDir . '/src/Parsley/Schema/Blocks.php', 'Kirby\\Parsley\\Schema\\Blocks' => $baseDir . '/src/Parsley/Schema/Blocks.php',
'Kirby\\Parsley\\Schema\\Plain' => $baseDir . '/src/Parsley/Schema/Plain.php', 'Kirby\\Parsley\\Schema\\Plain' => $baseDir . '/src/Parsley/Schema/Plain.php',
'Kirby\\Query\\Argument' => $baseDir . '/src/Query/Argument.php',
'Kirby\\Query\\Arguments' => $baseDir . '/src/Query/Arguments.php',
'Kirby\\Query\\Query' => $baseDir . '/src/Query/Query.php',
'Kirby\\Query\\Segment' => $baseDir . '/src/Query/Segment.php',
'Kirby\\Query\\Segments' => $baseDir . '/src/Query/Segments.php',
'Kirby\\Sane\\DomHandler' => $baseDir . '/src/Sane/DomHandler.php', 'Kirby\\Sane\\DomHandler' => $baseDir . '/src/Sane/DomHandler.php',
'Kirby\\Sane\\Handler' => $baseDir . '/src/Sane/Handler.php', 'Kirby\\Sane\\Handler' => $baseDir . '/src/Sane/Handler.php',
'Kirby\\Sane\\Html' => $baseDir . '/src/Sane/Html.php', 'Kirby\\Sane\\Html' => $baseDir . '/src/Sane/Html.php',

View File

@@ -352,6 +352,11 @@ class ComposerStaticInita8011b477bb239488e5d139cdeb7b31e
'Kirby\\Parsley\\Schema' => __DIR__ . '/../..' . '/src/Parsley/Schema.php', 'Kirby\\Parsley\\Schema' => __DIR__ . '/../..' . '/src/Parsley/Schema.php',
'Kirby\\Parsley\\Schema\\Blocks' => __DIR__ . '/../..' . '/src/Parsley/Schema/Blocks.php', 'Kirby\\Parsley\\Schema\\Blocks' => __DIR__ . '/../..' . '/src/Parsley/Schema/Blocks.php',
'Kirby\\Parsley\\Schema\\Plain' => __DIR__ . '/../..' . '/src/Parsley/Schema/Plain.php', 'Kirby\\Parsley\\Schema\\Plain' => __DIR__ . '/../..' . '/src/Parsley/Schema/Plain.php',
'Kirby\\Query\\Argument' => __DIR__ . '/../..' . '/src/Query/Argument.php',
'Kirby\\Query\\Arguments' => __DIR__ . '/../..' . '/src/Query/Arguments.php',
'Kirby\\Query\\Query' => __DIR__ . '/../..' . '/src/Query/Query.php',
'Kirby\\Query\\Segment' => __DIR__ . '/../..' . '/src/Query/Segment.php',
'Kirby\\Query\\Segments' => __DIR__ . '/../..' . '/src/Query/Segments.php',
'Kirby\\Sane\\DomHandler' => __DIR__ . '/../..' . '/src/Sane/DomHandler.php', 'Kirby\\Sane\\DomHandler' => __DIR__ . '/../..' . '/src/Sane/DomHandler.php',
'Kirby\\Sane\\Handler' => __DIR__ . '/../..' . '/src/Sane/Handler.php', 'Kirby\\Sane\\Handler' => __DIR__ . '/../..' . '/src/Sane/Handler.php',
'Kirby\\Sane\\Html' => __DIR__ . '/../..' . '/src/Sane/Html.php', 'Kirby\\Sane\\Html' => __DIR__ . '/../..' . '/src/Sane/Html.php',

View File

@@ -1,8 +1,8 @@
<?php return array( <?php return array(
'root' => array( 'root' => array(
'name' => 'getkirby/cms', 'name' => 'getkirby/cms',
'pretty_version' => '3.8.1.1', 'pretty_version' => '3.8.2',
'version' => '3.8.1.1', 'version' => '3.8.2.0',
'reference' => NULL, 'reference' => NULL,
'type' => 'kirby-cms', 'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
@@ -38,8 +38,8 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'getkirby/cms' => array( 'getkirby/cms' => array(
'pretty_version' => '3.8.1.1', 'pretty_version' => '3.8.2',
'version' => '3.8.1.1', 'version' => '3.8.2.0',
'reference' => NULL, 'reference' => NULL,
'type' => 'kirby-cms', 'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',