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
+1 -1
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",
+1 -1
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",
+6 -2
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'
] ]
], ],
]; ];
+7 -3
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',
+2 -5
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(),
+7 -3
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'
], ],
] ]
]; ];
+11 -11
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",
+30 -30
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": "There’s an error in the \"{label}\" field:\n{message}", "error.object.validation": "There’s 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",
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
+14 -10
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();
} }
} }
+2 -7
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];
+1 -1
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);
} }
/** /**
+1 -3
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 (
+5 -7
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);
} }
/** /**
+3 -12
View File
@@ -85,26 +85,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()) {
if ($defaultTranslation = $parent->translation($defaultLanguage->code())) {
$default = $defaultTranslation->content();
}
$content = array_merge($default, $content); $content = array_merge($default, $content);
} }
}
return $content; return $content;
} }
+3 -12
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;
}
} }
/** /**
+1 -5
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');
} }
/** /**
+1 -5
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');
} }
/** /**
+53 -15
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();
+2 -8
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']);
+6 -21
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;
+45 -118
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
{ {
-2
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
+1 -3
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;
}
} }
} }
} }
+2 -2
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) {
+3 -3
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 = [];
+1 -1
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();
} }
/** /**
+4 -2
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
-2
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) {
+100
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;
}
}
+44
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
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
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);
}
}
+63
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;
}
}
+3 -1
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;
} }
+2 -8
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]);
} }
/** /**
+9 -4
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;
} }
} }
+4
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
{ {
+12 -13
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,24 +1183,19 @@ 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(
'!' . $start . '(.*?)' . $end . '!',
function ($match) use ($data, $fallback, $callback) {
$query = trim($match[1]); $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) {
@@ -1214,7 +1211,9 @@ class Str
// 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
);
} }
/** /**
+1 -1
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());
} }
+5
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',
+5
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',
+4 -4
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__ . '/../../',