Upgrade to 3.8.0
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
||||
* stop at older or too recent versions
|
||||
*/
|
||||
if (
|
||||
version_compare(PHP_VERSION, '7.4.0', '>=') === false ||
|
||||
version_compare(PHP_VERSION, '8.0.0', '>=') === false ||
|
||||
version_compare(PHP_VERSION, '8.2.0', '<') === false
|
||||
) {
|
||||
die(include __DIR__ . '/views/php.php');
|
||||
|
||||
+4
-3
@@ -3,7 +3,7 @@
|
||||
"description": "The Kirby 3 core",
|
||||
"license": "proprietary",
|
||||
"type": "kirby-cms",
|
||||
"version": "3.7.5",
|
||||
"version": "3.8.0",
|
||||
"keywords": [
|
||||
"kirby",
|
||||
"cms",
|
||||
@@ -24,7 +24,7 @@
|
||||
"source": "https://github.com/getkirby/kirby"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0 <8.2.0",
|
||||
"php": ">=8.0.0 <8.2.0",
|
||||
"ext-SimpleXML": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
@@ -37,6 +37,7 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"claviska/simpleimage": "3.7.0",
|
||||
"composer/semver": "3.3.2",
|
||||
"filp/whoops": "2.14.5",
|
||||
"getkirby/composer-installer": "^1.2.1",
|
||||
"laminas/laminas-escaper": "2.10.0",
|
||||
@@ -76,7 +77,7 @@
|
||||
},
|
||||
"optimize-autoloader": true,
|
||||
"platform": {
|
||||
"php": "7.4.0"
|
||||
"php": "8.0.0"
|
||||
},
|
||||
"platform-check": false
|
||||
},
|
||||
|
||||
Generated
+93
-12
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "fb087946fb5ac5910e25a4d263905d99",
|
||||
"content-hash": "1ee4c8241ce2584bc60acda5c8a62771",
|
||||
"packages": [
|
||||
{
|
||||
"name": "claviska/simpleimage",
|
||||
@@ -55,6 +55,87 @@
|
||||
],
|
||||
"time": "2022-07-05T13:18:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9",
|
||||
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.2 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.4",
|
||||
"symfony/phpunit-bridge": "^4.2 || ^5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\Semver\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nils Adermann",
|
||||
"email": "naderman@naderman.de",
|
||||
"homepage": "http://www.naderman.de"
|
||||
},
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
},
|
||||
{
|
||||
"name": "Rob Bast",
|
||||
"email": "rob.bast@gmail.com",
|
||||
"homepage": "http://robbast.nl"
|
||||
}
|
||||
],
|
||||
"description": "Semver library that offers utilities, version constraint parsing and validation.",
|
||||
"keywords": [
|
||||
"semantic",
|
||||
"semver",
|
||||
"validation",
|
||||
"versioning"
|
||||
],
|
||||
"support": {
|
||||
"irc": "irc://irc.freenode.org/composer",
|
||||
"issues": "https://github.com/composer/semver/issues",
|
||||
"source": "https://github.com/composer/semver/tree/3.3.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-01T19:23:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
"version": "2.14.5",
|
||||
@@ -427,30 +508,30 @@
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.4",
|
||||
"version": "3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
|
||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "Psr/Log/"
|
||||
"Psr\\Log\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@@ -471,9 +552,9 @@
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/1.1.4"
|
||||
"source": "https://github.com/php-fig/log/tree/3.0.0"
|
||||
},
|
||||
"time": "2021-05-03T11:20:27+00:00"
|
||||
"time": "2021-07-14T16:46:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
@@ -737,7 +818,7 @@
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.4.0 <8.2.0",
|
||||
"php": ">=8.0.0 <8.2.0",
|
||||
"ext-simplexml": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
@@ -752,7 +833,7 @@
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "7.4.0"
|
||||
"php": "8.0.0"
|
||||
},
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ return [
|
||||
'cookie' => 'Kirby\Http\Cookie',
|
||||
'header' => 'Kirby\Http\Header',
|
||||
'remote' => 'Kirby\Http\Remote',
|
||||
'server' => 'Kirby\Http\Server',
|
||||
|
||||
// image classes
|
||||
'dimensions' => 'Kirby\Image\Dimensions',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Cms\Helpers;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Form\Form;
|
||||
|
||||
@@ -21,43 +20,13 @@ return [
|
||||
'hasFiles' => fn (Page $page) => $page->hasFiles(),
|
||||
'id' => fn (Page $page) => $page->id(),
|
||||
'isSortable' => fn (Page $page) => $page->isSortable(),
|
||||
/**
|
||||
* @deprecated 3.6.0
|
||||
* @todo Remove in 3.8.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
'next' => function (Page $page) {
|
||||
Helpers::deprecated('The API field page.next has been deprecated and will be removed in 3.8.0.');
|
||||
|
||||
return $page
|
||||
->nextAll()
|
||||
->filter('intendedTemplate', $page->intendedTemplate())
|
||||
->filter('status', $page->status())
|
||||
->filter('isReadable', true)
|
||||
->first();
|
||||
},
|
||||
'num' => fn (Page $page) => $page->num(),
|
||||
'options' => fn (Page $page) => $page->panel()->options(['preview']),
|
||||
'panelImage' => fn (Page $page) => $page->panel()->image(),
|
||||
'parent' => fn (Page $page) => $page->parent(),
|
||||
'parents' => fn (Page $page) => $page->parents()->flip(),
|
||||
/**
|
||||
* @deprecated 3.6.0
|
||||
* @todo Remove in 3.8.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
'prev' => function (Page $page) {
|
||||
Helpers::deprecated('The API field page.prev has been deprecated and will be removed in 3.8.0.');
|
||||
|
||||
return $page
|
||||
->prevAll()
|
||||
->filter('intendedTemplate', $page->intendedTemplate())
|
||||
->filter('status', $page->status())
|
||||
->filter('isReadable', true)
|
||||
->last();
|
||||
},
|
||||
'previewUrl' => fn (Page $page) => $page->previewUrl(),
|
||||
'siblings' => function (Page $page) {
|
||||
'num' => fn (Page $page) => $page->num(),
|
||||
'options' => fn (Page $page) => $page->panel()->options(['preview']),
|
||||
'panelImage' => fn (Page $page) => $page->panel()->image(),
|
||||
'parent' => fn (Page $page) => $page->parent(),
|
||||
'parents' => fn (Page $page) => $page->parents()->flip(),
|
||||
'previewUrl' => fn (Page $page) => $page->previewUrl(),
|
||||
'siblings' => function (Page $page) {
|
||||
if ($page->isDraft() === true) {
|
||||
return $page->parentModel()->children()->not($page);
|
||||
} else {
|
||||
|
||||
@@ -32,28 +32,23 @@ return [
|
||||
'slugs' => fn () => Str::$language,
|
||||
'title' => fn () => $this->site()->title()->value(),
|
||||
'translation' => function () {
|
||||
if ($user = $this->user()) {
|
||||
$translationCode = $user->language();
|
||||
} else {
|
||||
$translationCode = $this->kirby()->panelLanguage();
|
||||
$code = $this->user()?->language() ??
|
||||
$this->kirby()->panelLanguage();
|
||||
|
||||
if ($translation = $this->kirby()->translation($code)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
if ($translation = $this->kirby()->translation($translationCode)) {
|
||||
return $translation;
|
||||
} else {
|
||||
return $this->kirby()->translation('en');
|
||||
}
|
||||
return $this->kirby()->translation('en');
|
||||
},
|
||||
'kirbytext' => fn () => $this->kirby()->option('panel.kirbytext') ?? true,
|
||||
'user' => fn () => $this->user(),
|
||||
'version' => function () {
|
||||
$user = $this->user();
|
||||
|
||||
if ($user && $user->role()->permissions()->for('access', 'system') === true) {
|
||||
if ($this->user()?->role()->permissions()->for('access', 'system') === true) {
|
||||
return $this->kirby()->version();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
],
|
||||
'type' => 'Kirby\Cms\System',
|
||||
|
||||
@@ -87,13 +87,13 @@ return [
|
||||
'status' => 'ok',
|
||||
'user' => $this->resolve($user)->view('auth')->toArray()
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'code' => 200,
|
||||
'status' => 'ok',
|
||||
'challenge' => $status->challenge()
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'status' => 'ok',
|
||||
'challenge' => $status->challenge()
|
||||
];
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -105,4 +105,14 @@ return [
|
||||
return true;
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'auth/ping',
|
||||
'method' => 'POST',
|
||||
'auth' => false,
|
||||
'action' => function () {
|
||||
// refresh the session timeout
|
||||
$this->kirby()->session();
|
||||
return true;
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
@@ -12,9 +12,7 @@ return [
|
||||
'pattern' => $pattern . '/files/(:any)/sections/(:any)',
|
||||
'method' => 'GET',
|
||||
'action' => function (string $path, string $filename, string $sectionName) {
|
||||
if ($section = $this->file($path, $filename)->blueprint()->section($sectionName)) {
|
||||
return $section->toResponse();
|
||||
}
|
||||
return $this->file($path, $filename)->blueprint()->section($sectionName)?->toResponse();
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -60,9 +58,9 @@ return [
|
||||
|
||||
if ($this->requestMethod() === 'GET') {
|
||||
return $files->search($this->requestQuery('q'));
|
||||
} else {
|
||||
return $files->query($this->requestBody());
|
||||
}
|
||||
|
||||
return $files->query($this->requestBody());
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -86,16 +84,20 @@ return [
|
||||
'pattern' => $pattern . '/files/(:any)',
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $path, string $filename) {
|
||||
return $this->file($path, $filename)->update($this->requestBody(), $this->language(), true);
|
||||
return $this->file($path, $filename)->update(
|
||||
$this->requestBody(),
|
||||
$this->language(),
|
||||
true
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)',
|
||||
'method' => 'POST',
|
||||
'action' => function (string $path, string $filename) {
|
||||
return $this->upload(function ($source) use ($path, $filename) {
|
||||
return $this->file($path, $filename)->replace($source);
|
||||
});
|
||||
return $this->upload(
|
||||
fn ($source) => $this->file($path, $filename)->replace($source)
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -124,9 +126,9 @@ return [
|
||||
|
||||
if ($this->requestMethod() === 'GET') {
|
||||
return $files->search($this->requestQuery('q'));
|
||||
} else {
|
||||
return $files->query($this->requestBody());
|
||||
}
|
||||
|
||||
return $files->query($this->requestBody());
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
@@ -29,18 +29,14 @@ return [
|
||||
'pattern' => 'languages/(:any)',
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $code) {
|
||||
if ($language = $this->kirby()->languages()->find($code)) {
|
||||
return $language->update($this->requestBody());
|
||||
}
|
||||
return $this->kirby()->languages()->find($code)?->update($this->requestBody());
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'languages/(:any)',
|
||||
'method' => 'DELETE',
|
||||
'action' => function (string $code) {
|
||||
if ($language = $this->kirby()->languages()->find($code)) {
|
||||
return $language->delete();
|
||||
}
|
||||
return $this->kirby()->languages()->find($code)?->delete();
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
@@ -5,40 +5,41 @@
|
||||
* Content Lock Routes
|
||||
*/
|
||||
return [
|
||||
[
|
||||
'pattern' => '(:all)/lock',
|
||||
'method' => 'GET',
|
||||
'action' => function (string $path) {
|
||||
return [
|
||||
'lock' => $this->parent($path)->lock()?->toArray() ?? false
|
||||
];
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/lock',
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $path) {
|
||||
if ($lock = $this->parent($path)->lock()) {
|
||||
return $lock->create();
|
||||
}
|
||||
return $this->parent($path)->lock()?->create();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/lock',
|
||||
'method' => 'DELETE',
|
||||
'action' => function (string $path) {
|
||||
if ($lock = $this->parent($path)->lock()) {
|
||||
return $lock->remove();
|
||||
}
|
||||
return $this->parent($path)->lock()?->remove();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/unlock',
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $path) {
|
||||
if ($lock = $this->parent($path)->lock()) {
|
||||
return $lock->unlock();
|
||||
}
|
||||
return $this->parent($path)->lock()?->unlock();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/unlock',
|
||||
'method' => 'DELETE',
|
||||
'action' => function (string $path) {
|
||||
if ($lock = $this->parent($path)->lock()) {
|
||||
return $lock->resolve();
|
||||
}
|
||||
return $this->parent($path)->lock()?->resolve();
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
@@ -104,9 +104,7 @@ return [
|
||||
'pattern' => 'pages/(:any)/sections/(:any)',
|
||||
'method' => 'GET',
|
||||
'action' => function (string $id, string $sectionName) {
|
||||
if ($section = $this->page($id)->blueprint()->section($sectionName)) {
|
||||
return $section->toResponse();
|
||||
}
|
||||
return $this->page($id)->blueprint()->section($sectionName)?->toResponse();
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -10,14 +10,11 @@ return [
|
||||
'action' => function () {
|
||||
$kirby = $this->kirby();
|
||||
|
||||
switch ($kirby->request()->get('canBe')) {
|
||||
case 'changed':
|
||||
return $kirby->roles()->canBeChanged();
|
||||
case 'created':
|
||||
return $kirby->roles()->canBeCreated();
|
||||
default:
|
||||
return $kirby->roles();
|
||||
}
|
||||
return match ($kirby->request()->get('canBe')) {
|
||||
'changed' => $kirby->roles()->canBeChanged(),
|
||||
'created' => $kirby->roles()->canBeCreated(),
|
||||
default => $kirby->roles()
|
||||
};
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -79,18 +79,16 @@ return [
|
||||
|
||||
if ($this->requestMethod() === 'GET') {
|
||||
return $pages->search($this->requestQuery('q'));
|
||||
} else {
|
||||
return $pages->query($this->requestBody());
|
||||
}
|
||||
|
||||
return $pages->query($this->requestBody());
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'site/sections/(:any)',
|
||||
'method' => 'GET',
|
||||
'action' => function (string $sectionName) {
|
||||
if ($section = $this->site()->blueprint()->section($sectionName)) {
|
||||
return $section->toResponse();
|
||||
}
|
||||
return $this->site()->blueprint()->section($sectionName)?->toResponse();
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -17,19 +17,18 @@ return [
|
||||
|
||||
if ($this->kirby()->user()) {
|
||||
return $system;
|
||||
} else {
|
||||
if ($system->isOk() === true) {
|
||||
$info = $this->resolve($system)->view('login')->toArray();
|
||||
} else {
|
||||
$info = $this->resolve($system)->view('troubleshooting')->toArray();
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'data' => $info,
|
||||
'type' => 'model'
|
||||
];
|
||||
}
|
||||
|
||||
$info = match ($system->isOk()) {
|
||||
true => $this->resolve($system)->view('login')->toArray(),
|
||||
false => $this->resolve($system)->view('troubleshooting')->toArray()
|
||||
};
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'data' => $info,
|
||||
'type' => 'model'
|
||||
];
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -26,9 +26,9 @@ return [
|
||||
'action' => function () {
|
||||
if ($this->requestMethod() === 'GET') {
|
||||
return $this->users()->search($this->requestQuery('q'));
|
||||
} else {
|
||||
return $this->users()->query($this->requestBody());
|
||||
}
|
||||
|
||||
return $this->users()->query($this->requestBody());
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -79,17 +79,16 @@ return [
|
||||
],
|
||||
'method' => 'POST',
|
||||
'action' => function (string $id) {
|
||||
if ($avatar = $this->user($id)->avatar()) {
|
||||
$avatar->delete();
|
||||
}
|
||||
$this->user($id)->avatar()?->delete();
|
||||
|
||||
return $this->upload(function ($source, $filename) use ($id) {
|
||||
return $this->user($id)->createFile([
|
||||
return $this->upload(
|
||||
fn ($source, $filename) => $this->user($id)->createFile([
|
||||
'filename' => 'profile.' . F::extension($filename),
|
||||
'template' => 'avatar',
|
||||
'source' => $source
|
||||
]);
|
||||
}, $single = true);
|
||||
]),
|
||||
single: true
|
||||
);
|
||||
}
|
||||
],
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
@@ -1,48 +1,92 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'system' => [
|
||||
'pattern' => 'system',
|
||||
'action' => function () {
|
||||
$kirby = App::instance();
|
||||
$system = $kirby->system();
|
||||
$license = $system->license();
|
||||
$kirby = App::instance();
|
||||
$system = $kirby->system();
|
||||
$updateStatus = $system->updateStatus();
|
||||
$license = $system->license();
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($license === true) {
|
||||
// valid license, but user is not admin
|
||||
$license = 'Kirby 3';
|
||||
} elseif ($license === false) {
|
||||
// no valid license
|
||||
$license = null;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$environment = [
|
||||
[
|
||||
'label' => $license ? I18n::translate('license') : I18n::translate('license.register.label'),
|
||||
'value' => $license ? 'Kirby 3' : I18n::translate('license.unregistered.label'),
|
||||
'theme' => $license ? null : 'negative',
|
||||
'dialog' => $license ? 'license' : 'registration'
|
||||
],
|
||||
[
|
||||
'label' => $updateStatus?->label() ?? I18n::translate('version'),
|
||||
'value' => $kirby->version(),
|
||||
'link' => (
|
||||
$updateStatus ?
|
||||
$updateStatus->url() :
|
||||
'https://github.com/getkirby/kirby/releases/tag/' . $kirby->version()
|
||||
),
|
||||
'theme' => $updateStatus?->theme()
|
||||
],
|
||||
[
|
||||
'label' => 'PHP',
|
||||
'value' => phpversion()
|
||||
],
|
||||
[
|
||||
'label' => I18n::translate('server'),
|
||||
'value' => $system->serverSoftware() ?? '?'
|
||||
]
|
||||
];
|
||||
|
||||
$exceptions = $updateStatus?->exceptionMessages() ?? [];
|
||||
|
||||
$plugins = $system->plugins()->values(function ($plugin) use (&$exceptions) {
|
||||
$authors = $plugin->authorsNames();
|
||||
$updateStatus = $plugin->updateStatus();
|
||||
$version = $updateStatus?->toArray() ?? $plugin->version() ?? '–';
|
||||
|
||||
if ($updateStatus !== null) {
|
||||
$exceptions = array_merge($exceptions, $updateStatus->exceptionMessages());
|
||||
}
|
||||
|
||||
$plugins = $system->plugins()->values(function ($plugin) {
|
||||
return [
|
||||
'author' => $plugin->authorsNames(),
|
||||
'license' => $plugin->license(),
|
||||
'author' => empty($authors) ? '–' : $authors,
|
||||
'license' => $plugin->license() ?? '–',
|
||||
'name' => [
|
||||
'text' => $plugin->name(),
|
||||
'text' => $plugin->name() ?? '–',
|
||||
'href' => $plugin->link(),
|
||||
],
|
||||
'version' => $plugin->version(),
|
||||
'version' => $version,
|
||||
];
|
||||
});
|
||||
|
||||
$security = $updateStatus?->messages() ?? [];
|
||||
|
||||
if ($kirby->option('debug', false) === true) {
|
||||
$security[] = [
|
||||
'id' => 'debug',
|
||||
'text' => I18n::translate('system.issues.debug'),
|
||||
'link' => 'https://getkirby.com/security/debug'
|
||||
];
|
||||
}
|
||||
|
||||
if ($kirby->environment()->https() !== true) {
|
||||
$security[] = [
|
||||
'id' => 'https',
|
||||
'text' => I18n::translate('system.issues.https'),
|
||||
'link' => 'https://getkirby.com/security/https'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-system-view',
|
||||
'props' => [
|
||||
'debug' => $kirby->option('debug', false),
|
||||
'license' => $license,
|
||||
'plugins' => $plugins,
|
||||
'php' => phpversion(),
|
||||
'server' => $system->serverSoftware(),
|
||||
'https' => $kirby->environment()->https(),
|
||||
'version' => $kirby->version(),
|
||||
'urls' => [
|
||||
'environment' => $environment,
|
||||
'exceptions' => $kirby->option('debug') === true ? $exceptions : [],
|
||||
'plugins' => $plugins,
|
||||
'security' => $security,
|
||||
'urls' => [
|
||||
'content' => $system->exposedFileUrl('content'),
|
||||
'git' => $system->exposedFileUrl('git'),
|
||||
'kirby' => $system->exposedFileUrl('kirby'),
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?php /** @var \Kirby\Cms\Block $block */ ?>
|
||||
<figure>
|
||||
<?php
|
||||
/** @var \Kirby\Cms\Block $block */
|
||||
$caption = $block->caption();
|
||||
$crop = $block->crop()->isTrue();
|
||||
$ratio = $block->ratio()->or('auto');
|
||||
?>
|
||||
<figure<?= Html::attr(['data-ratio' => $ratio, 'data-crop' => $crop], null, ' ') ?>>
|
||||
<ul>
|
||||
<?php foreach ($block->images()->toFiles() as $image): ?>
|
||||
<li>
|
||||
@@ -7,4 +12,9 @@
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php if ($caption->isNotEmpty()): ?>
|
||||
<figcaption>
|
||||
<?= $caption ?>
|
||||
</figcaption>
|
||||
<?php endif ?>
|
||||
</figure>
|
||||
|
||||
@@ -14,3 +14,27 @@ fields:
|
||||
template: blocks/image
|
||||
image:
|
||||
ratio: 1/1
|
||||
caption:
|
||||
label: field.blocks.image.caption
|
||||
type: writer
|
||||
icon: text
|
||||
inline: true
|
||||
ratio:
|
||||
label: field.blocks.image.ratio
|
||||
type: select
|
||||
placeholder: Auto
|
||||
width: 1/2
|
||||
options:
|
||||
1/1: "1:1"
|
||||
16/9: "16:9"
|
||||
10/8: "10:8"
|
||||
21/9: "21:9"
|
||||
7/5: "7:5"
|
||||
4/3: "4:3"
|
||||
5/3: "5:3"
|
||||
3/2: "3:2"
|
||||
3/1: "3:1"
|
||||
crop:
|
||||
label: field.blocks.image.crop
|
||||
type: toggle
|
||||
width: 1/2
|
||||
|
||||
+16
-46
@@ -4,8 +4,9 @@ use Kirby\Cms\App;
|
||||
use Kirby\Cms\Collection;
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\FileVersion;
|
||||
use Kirby\Cms\Helpers;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Template;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Email\PHPMailer as Emailer;
|
||||
use Kirby\Filesystem\F;
|
||||
@@ -30,33 +31,6 @@ return [
|
||||
*/
|
||||
'css' => fn (App $kirby, string $url, $options = null): string => $url,
|
||||
|
||||
|
||||
/**
|
||||
* Object and variable dumper
|
||||
* to help with debugging.
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param mixed $variable
|
||||
* @param bool $echo
|
||||
* @return string
|
||||
*
|
||||
* @deprecated 3.7.0 Disable `dump()` via `KIRBY_HELPER_DUMP` instead and create your own function
|
||||
* @todo move to `Helpers::dump()`, remove component in 3.8.0
|
||||
*/
|
||||
'dump' => function (App $kirby, $variable, bool $echo = true) {
|
||||
if ($kirby->environment()->cli() === true) {
|
||||
$output = print_r($variable, true) . PHP_EOL;
|
||||
} else {
|
||||
$output = '<pre>' . print_r($variable, true) . '</pre>';
|
||||
}
|
||||
|
||||
if ($echo === true) {
|
||||
echo $output;
|
||||
}
|
||||
|
||||
return $output;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add your own email provider
|
||||
*
|
||||
@@ -108,7 +82,7 @@ return [
|
||||
Data::write($job, array_merge($options, [
|
||||
'filename' => $file->filename()
|
||||
]));
|
||||
} catch (Throwable $e) {
|
||||
} catch (Throwable) {
|
||||
// if thumb doesn't exist yet and job file cannot
|
||||
// be created, return
|
||||
return $file;
|
||||
@@ -138,24 +112,16 @@ return [
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string $text Text to parse
|
||||
* @param array $options Markdown options
|
||||
* @param bool $inline Whether to wrap the text in `<p>` tags (deprecated: set via $options['inline'] instead)
|
||||
* @return string
|
||||
* @todo remove $inline parameter in in 3.8.0
|
||||
*/
|
||||
'markdown' => function (App $kirby, string $text = null, array $options = [], bool $inline = false): string {
|
||||
'markdown' => function (
|
||||
App $kirby,
|
||||
string $text = null,
|
||||
array $options = []
|
||||
): string {
|
||||
static $markdown;
|
||||
static $config;
|
||||
|
||||
// warning for deprecated fourth parameter
|
||||
if (func_num_args() === 4 && isset($options['inline']) === false) {
|
||||
// @codeCoverageIgnoreStart
|
||||
Helpers::deprecated('markdown component: the $inline parameter is deprecated and will be removed in Kirby 3.8.0. Use $options[\'inline\'] instead.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// support for the deprecated fourth argument
|
||||
$options['inline'] ??= $inline;
|
||||
|
||||
// if the config options have changed or the component is called for the first time,
|
||||
// (re-)initialize the parser object
|
||||
if ($config !== $options) {
|
||||
@@ -206,17 +172,22 @@ return [
|
||||
return $options['words'] ? '\b' . preg_quote($value) . '\b' : preg_quote($value);
|
||||
}, $searchWords);
|
||||
|
||||
// returns an empty collection if there is no search word
|
||||
if (empty($searchWords) === true) {
|
||||
return $collection->limit(0);
|
||||
}
|
||||
|
||||
$preg = '!(' . implode('|', $searchWords) . ')!i';
|
||||
$results = $collection->filter(function ($item) use ($query, $preg, $options, $lowerQuery, $exactQuery) {
|
||||
$data = $item->content()->toArray();
|
||||
$keys = array_keys($data);
|
||||
$keys[] = 'id';
|
||||
|
||||
if (is_a($item, 'Kirby\Cms\User') === true) {
|
||||
if ($item instanceof User) {
|
||||
$keys[] = 'name';
|
||||
$keys[] = 'email';
|
||||
$keys[] = 'role';
|
||||
} elseif (is_a($item, 'Kirby\Cms\Page') === true) {
|
||||
} elseif ($item instanceof Page) {
|
||||
// apply the default score for pages
|
||||
$options['score'] = array_merge([
|
||||
'id' => 64,
|
||||
@@ -295,9 +266,8 @@ return [
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string|array $name Snippet name
|
||||
* @param array $data Data array for the snippet
|
||||
* @return string|null
|
||||
*/
|
||||
'snippet' => function (App $kirby, $name, array $data = []): ?string {
|
||||
'snippet' => function (App $kirby, $name, array $data = []): string {
|
||||
$snippets = A::wrap($name);
|
||||
|
||||
foreach ($snippets as $name) {
|
||||
|
||||
@@ -46,13 +46,13 @@ return [
|
||||
/**
|
||||
* Latest date, which can be selected/saved (Y-m-d)
|
||||
*/
|
||||
'max' => function (string $max = null): ?string {
|
||||
'max' => function (string $max = null): string|null {
|
||||
return Date::optional($max);
|
||||
},
|
||||
/**
|
||||
* Earliest date, which can be selected/saved (Y-m-d)
|
||||
*/
|
||||
'min' => function (string $min = null): ?string {
|
||||
'min' => function (string $min = null): string|null {
|
||||
return Date::optional($min);
|
||||
},
|
||||
|
||||
|
||||
@@ -34,7 +34,10 @@ return [
|
||||
],
|
||||
'computed' => [
|
||||
'parentModel' => function () {
|
||||
if (is_string($this->parent) === true && $model = $this->model()->query($this->parent, 'Kirby\Cms\Model')) {
|
||||
if (
|
||||
is_string($this->parent) === true &&
|
||||
$model = $this->model()->query($this->parent, 'Kirby\Cms\Model')
|
||||
) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
@@ -68,10 +71,13 @@ return [
|
||||
|
||||
foreach (Data::decode($value, 'yaml') as $id) {
|
||||
if (is_array($id) === true) {
|
||||
$id = $id['id'] ?? null;
|
||||
$id = $id['uuid'] ?? $id['id'] ?? null;
|
||||
}
|
||||
|
||||
if ($id !== null && ($file = $this->kirby()->file($id, $this->model()))) {
|
||||
if (
|
||||
$id !== null &&
|
||||
($file = $this->kirby()->file($id, $this->model()))
|
||||
) {
|
||||
$files[] = $this->fileResponse($file);
|
||||
}
|
||||
}
|
||||
@@ -122,7 +128,7 @@ return [
|
||||
];
|
||||
},
|
||||
'save' => function ($value = null) {
|
||||
return A::pluck($value, 'uuid');
|
||||
return A::pluck($value, $this->store);
|
||||
},
|
||||
'validations' => [
|
||||
'max',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Form\Options;
|
||||
use Kirby\Field\FieldOptions;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
@@ -30,19 +30,17 @@ return [
|
||||
],
|
||||
'methods' => [
|
||||
'getOptions' => function () {
|
||||
return Options::factory(
|
||||
$this->options(),
|
||||
$this->props,
|
||||
$this->model()
|
||||
);
|
||||
$props = FieldOptions::polyfill($this->props);
|
||||
$options = FieldOptions::factory($props['options']);
|
||||
return $options->render($this->model());
|
||||
},
|
||||
'sanitizeOption' => function ($option) {
|
||||
$allowed = array_column($this->options(), 'value');
|
||||
return in_array($option, $allowed, true) === true ? $option : null;
|
||||
'sanitizeOption' => function ($value) {
|
||||
$options = array_column($this->options(), 'value');
|
||||
return in_array($value, $options, true) === true ? $value : null;
|
||||
},
|
||||
'sanitizeOptions' => function ($options) {
|
||||
$allowed = array_column($this->options(), 'value');
|
||||
return array_intersect($options, $allowed);
|
||||
'sanitizeOptions' => function ($values) {
|
||||
$options = array_column($this->options(), 'value');
|
||||
return array_intersect($values, $options);
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
@@ -67,12 +67,21 @@ return [
|
||||
return $search;
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether to store UUID or path in
|
||||
* the content file
|
||||
*
|
||||
* @param string $store 'uuid'|'id'
|
||||
*/
|
||||
'store' => function (string $store = 'uuid') {
|
||||
return $store;
|
||||
},
|
||||
|
||||
/**
|
||||
* Main text for each item
|
||||
*/
|
||||
'text' => function (string $text = null) {
|
||||
return $text;
|
||||
},
|
||||
|
||||
],
|
||||
];
|
||||
|
||||
@@ -51,7 +51,7 @@ return [
|
||||
$parent = $this->model();
|
||||
}
|
||||
|
||||
if (is_a($parent, 'Kirby\Cms\File') === true) {
|
||||
if ($parent instanceof File) {
|
||||
$parent = $parent->parent();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ return [
|
||||
'filename' => $filename,
|
||||
]);
|
||||
|
||||
if (is_a($file, 'Kirby\Cms\File') === false) {
|
||||
if ($file instanceof File === false) {
|
||||
throw new Exception('The file could not be uploaded');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
return [
|
||||
'extends' => 'tags',
|
||||
'props' => [
|
||||
@@ -28,5 +30,18 @@ return [
|
||||
'sort' => function (bool $sort = false) {
|
||||
return $sort;
|
||||
},
|
||||
]
|
||||
],
|
||||
'methods' => [
|
||||
'toValues' => function ($value) {
|
||||
if (is_null($value) === true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($value) === false) {
|
||||
$value = Str::split($value, $this->separator());
|
||||
}
|
||||
|
||||
return $this->sanitizeOptions($value);
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
/**
|
||||
* Unset inherited props
|
||||
*/
|
||||
'after' => null,
|
||||
'before' => null,
|
||||
'autofocus' => null,
|
||||
'icon' => null,
|
||||
'placeholder' => null,
|
||||
|
||||
/**
|
||||
* Set the default values for the object
|
||||
*/
|
||||
'default' => function ($default = null) {
|
||||
return $default;
|
||||
},
|
||||
|
||||
/**
|
||||
* The placeholder text if no information has been added yet
|
||||
*/
|
||||
'empty' => function ($empty = null) {
|
||||
return I18n::translate($empty, $empty);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fields setup for the object form. Works just like fields in regular forms.
|
||||
*/
|
||||
'fields' => function (array $fields = []) {
|
||||
return $fields;
|
||||
}
|
||||
],
|
||||
'computed' => [
|
||||
'default' => function () {
|
||||
if (empty($this->default) === true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->form($this->default)->values();
|
||||
},
|
||||
'fields' => function () {
|
||||
if (empty($this->fields) === true) {
|
||||
throw new Exception('Please provide some fields for the object');
|
||||
}
|
||||
|
||||
return $this->form()->fields()->toArray();
|
||||
},
|
||||
'value' => function () {
|
||||
$data = Data::decode($this->value, 'yaml');
|
||||
|
||||
if (empty($data) === true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->form($data)->values();
|
||||
}
|
||||
],
|
||||
'methods' => [
|
||||
'form' => function (array $values = []) {
|
||||
return new Form([
|
||||
'fields' => $this->attrs['fields'],
|
||||
'values' => $values,
|
||||
'model' => $this->model
|
||||
]);
|
||||
},
|
||||
],
|
||||
'save' => function ($value) {
|
||||
if (empty($value) === true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->form($value)->content();
|
||||
},
|
||||
'validations' => [
|
||||
'object' => function ($value) {
|
||||
if (empty($value) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$errors = $this->form($value)->errors();
|
||||
|
||||
if (empty($errors) === false) {
|
||||
// use the first error for details
|
||||
$name = array_key_first($errors);
|
||||
$error = $errors[$name];
|
||||
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'object.validation',
|
||||
'data' => [
|
||||
'label' => $error['label'] ?? $name,
|
||||
'message' => implode("\n", $error['message'])
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
@@ -67,7 +67,7 @@ return [
|
||||
|
||||
foreach (Data::decode($value, 'yaml') as $id) {
|
||||
if (is_array($id) === true) {
|
||||
$id = $id['id'] ?? null;
|
||||
$id = $id['uuid'] ?? $id['id'] ?? null;
|
||||
}
|
||||
|
||||
if ($id !== null && ($page = $kirby->page($id))) {
|
||||
@@ -102,7 +102,7 @@ return [
|
||||
];
|
||||
},
|
||||
'save' => function ($value = null) {
|
||||
return A::pluck($value, 'id');
|
||||
return A::pluck($value, $this->store);
|
||||
},
|
||||
'validations' => [
|
||||
'max',
|
||||
|
||||
@@ -108,7 +108,7 @@ return [
|
||||
$columns = [];
|
||||
$mobile = 0;
|
||||
|
||||
if (empty($this->columns)) {
|
||||
if (empty($this->columns) === true) {
|
||||
foreach ($this->fields as $field) {
|
||||
// Skip hidden and unsaveable fields
|
||||
// They should never be included as column
|
||||
@@ -129,7 +129,10 @@ return [
|
||||
|
||||
$field = $this->fields[$columnName] ?? null;
|
||||
|
||||
if (empty($field) === true || $field['saveable'] === false) {
|
||||
if (
|
||||
empty($field) === true ||
|
||||
$field['saveable'] === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -137,10 +140,10 @@ return [
|
||||
$mobile++;
|
||||
}
|
||||
|
||||
$columns[$columnName] = array_merge($columnProps, [
|
||||
$columns[$columnName] = array_merge([
|
||||
'type' => $field['type'],
|
||||
'label' => $field['label'] ?? $field['name']
|
||||
]);
|
||||
], $columnProps);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ return [
|
||||
* Set to `list` to display each tag with 100% width,
|
||||
* otherwise the tags are displayed inline
|
||||
*/
|
||||
'layout' => function (?string $layout = null) {
|
||||
'layout' => function (string|null $layout = null) {
|
||||
return $layout;
|
||||
},
|
||||
/**
|
||||
@@ -55,43 +55,32 @@ return [
|
||||
],
|
||||
'computed' => [
|
||||
'default' => function (): array {
|
||||
return $this->toTags($this->default);
|
||||
return $this->toValues($this->default);
|
||||
},
|
||||
'value' => function (): array {
|
||||
return $this->toTags($this->value);
|
||||
return $this->toValues($this->value);
|
||||
}
|
||||
],
|
||||
'methods' => [
|
||||
'toTags' => function ($value) {
|
||||
'toValues' => function ($value) {
|
||||
if (is_null($value) === true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = $this->options();
|
||||
if (is_array($value) === false) {
|
||||
$value = Str::split($value, $this->separator());
|
||||
}
|
||||
|
||||
// transform into value-text objects
|
||||
return array_map(function ($option) use ($options) {
|
||||
// already a valid object
|
||||
if (is_array($option) === true && isset($option['value'], $option['text']) === true) {
|
||||
return $option;
|
||||
}
|
||||
if ($this->accept === 'options') {
|
||||
$value = $this->sanitizeOptions($value);
|
||||
}
|
||||
|
||||
$index = array_search($option, array_column($options, 'value'));
|
||||
|
||||
if ($index !== false) {
|
||||
return $options[$index];
|
||||
}
|
||||
|
||||
return [
|
||||
'value' => $option,
|
||||
'text' => $option,
|
||||
];
|
||||
}, Str::split($value, $this->separator()));
|
||||
return $value;
|
||||
}
|
||||
],
|
||||
'save' => function (array $value = null): string {
|
||||
return A::join(
|
||||
A::pluck($value, 'value'),
|
||||
$value,
|
||||
$this->separator() . ' '
|
||||
);
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ return [
|
||||
/**
|
||||
* Sets the default time when a new page/file/user is created
|
||||
*/
|
||||
'default' => function ($default = null): ?string {
|
||||
'default' => function ($default = null): string|null {
|
||||
return $default;
|
||||
},
|
||||
|
||||
@@ -36,13 +36,13 @@ return [
|
||||
/**
|
||||
* Latest time, which can be selected/saved (H:i or H:i:s)
|
||||
*/
|
||||
'max' => function (string $max = null): ?string {
|
||||
'max' => function (string $max = null): string|null {
|
||||
return Date::optional($max);
|
||||
},
|
||||
/**
|
||||
* Earliest time, which can be selected/saved (H:i or H:i:s)
|
||||
*/
|
||||
'min' => function (string $min = null): ?string {
|
||||
'min' => function (string $min = null): string|null {
|
||||
return Date::optional($min);
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@ return [
|
||||
'unit' => 'minute',
|
||||
]);
|
||||
},
|
||||
'value' => function ($value = null): ?string {
|
||||
'value' => function ($value = null): string|null {
|
||||
return $value;
|
||||
}
|
||||
],
|
||||
@@ -80,7 +80,7 @@ return [
|
||||
'format' => function () {
|
||||
return $this->props['format'] ?? 'H:i:s';
|
||||
},
|
||||
'value' => function (): ?string {
|
||||
'value' => function (): string|null {
|
||||
return $this->toDatetime($this->value, 'H:i:s') ?? '';
|
||||
}
|
||||
],
|
||||
|
||||
@@ -96,7 +96,7 @@ return [
|
||||
];
|
||||
},
|
||||
'save' => function ($value = null) {
|
||||
return A::pluck($value, 'id');
|
||||
return A::pluck($value, $this->store);
|
||||
},
|
||||
'validations' => [
|
||||
'max',
|
||||
|
||||
+30
-29
@@ -35,7 +35,7 @@ if (Helpers::hasOverride('attr') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $after An optional string that will be appended if the result is not empty
|
||||
* @return string|null
|
||||
*/
|
||||
function attr(?array $attr = null, ?string $before = null, ?string $after = null): ?string
|
||||
function attr(array|null $attr = null, string|null $before = null, string|null $after = null): string|null
|
||||
{
|
||||
return Html::attr($attr, null, $before, $after);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ if (Helpers::hasOverride('csrf') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $check Pass a token here to compare it to the one in the session
|
||||
* @return string|bool Either the token or a boolean check result
|
||||
*/
|
||||
function csrf(?string $check = null)
|
||||
function csrf(string|null $check = null)
|
||||
{
|
||||
// check explicitly if there have been no arguments at all;
|
||||
// checking for null introduces a security issue because null could come
|
||||
@@ -82,7 +82,7 @@ if (Helpers::hasOverride('css') === false) { // @codeCoverageIgnore
|
||||
* @param string|array $options Pass an array of attributes for the link tag or a media attribute string
|
||||
* @return string|null
|
||||
*/
|
||||
function css($url, $options = null): ?string
|
||||
function css($url, $options = null): string|null
|
||||
{
|
||||
return Html::css($url, $options);
|
||||
}
|
||||
@@ -167,7 +167,7 @@ if (Helpers::hasOverride('gist') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $file
|
||||
* @return string
|
||||
*/
|
||||
function gist(string $url, ?string $file = null): string
|
||||
function gist(string $url, string|null $file = null): string
|
||||
{
|
||||
return App::instance()->kirbytag([
|
||||
'gist' => $url,
|
||||
@@ -199,7 +199,7 @@ if (Helpers::hasOverride('h') === false) { // @codeCoverageIgnore
|
||||
* @param bool $keepTags
|
||||
* @return string
|
||||
*/
|
||||
function h(?string $string, bool $keepTags = false): string
|
||||
function h(string|null $string, bool $keepTags = false): string
|
||||
{
|
||||
return Html::encode($string, $keepTags);
|
||||
}
|
||||
@@ -213,7 +213,7 @@ if (Helpers::hasOverride('html') === false) { // @codeCoverageIgnore
|
||||
* @param bool $keepTags
|
||||
* @return string
|
||||
*/
|
||||
function html(?string $string, bool $keepTags = false): string
|
||||
function html(string|null $string, bool $keepTags = false): string
|
||||
{
|
||||
return Html::encode($string, $keepTags);
|
||||
}
|
||||
@@ -230,7 +230,7 @@ if (Helpers::hasOverride('image') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $path
|
||||
* @return \Kirby\Cms\File|null
|
||||
*/
|
||||
function image(?string $path = null)
|
||||
function image(string|null $path = null)
|
||||
{
|
||||
return App::instance()->image($path);
|
||||
}
|
||||
@@ -259,7 +259,7 @@ if (Helpers::hasOverride('js') === false) { // @codeCoverageIgnore
|
||||
* @param string|array $options
|
||||
* @return string|null
|
||||
*/
|
||||
function js($url, $options = null): ?string
|
||||
function js($url, $options = null): string|null
|
||||
{
|
||||
return Html::js($url, $options);
|
||||
}
|
||||
@@ -287,7 +287,7 @@ if (Helpers::hasOverride('kirbytag') === false) { // @codeCoverageIgnore
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
function kirbytag($type, ?string $value = null, array $attr = [], array $data = []): string
|
||||
function kirbytag($type, string|null $value = null, array $attr = [], array $data = []): string
|
||||
{
|
||||
return App::instance()->kirbytag($type, $value, $attr, $data);
|
||||
}
|
||||
@@ -302,7 +302,7 @@ if (Helpers::hasOverride('kirbytags') === false) { // @codeCoverageIgnore
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
function kirbytags(?string $text = null, array $data = []): string
|
||||
function kirbytags(string|null $text = null, array $data = []): string
|
||||
{
|
||||
return App::instance()->kirbytags($text, $data);
|
||||
}
|
||||
@@ -317,7 +317,7 @@ if (Helpers::hasOverride('kirbytext') === false) { // @codeCoverageIgnore
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
function kirbytext(?string $text = null, array $data = []): string
|
||||
function kirbytext(string|null $text = null, array $data = []): string
|
||||
{
|
||||
return App::instance()->kirbytext($text, $data);
|
||||
}
|
||||
@@ -333,7 +333,7 @@ if (Helpers::hasOverride('kirbytextinline') === false) { // @codeCoverageIgnore
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
function kirbytextinline(?string $text = null, array $options = []): string
|
||||
function kirbytextinline(string|null $text = null, array $options = []): string
|
||||
{
|
||||
$options['markdown']['inline'] = true;
|
||||
return App::instance()->kirbytext($text, $options);
|
||||
@@ -348,7 +348,7 @@ if (Helpers::hasOverride('kt') === false) { // @codeCoverageIgnore
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
function kt(?string $text = null, array $data = []): string
|
||||
function kt(string|null $text = null, array $data = []): string
|
||||
{
|
||||
return App::instance()->kirbytext($text, $data);
|
||||
}
|
||||
@@ -363,7 +363,7 @@ if (Helpers::hasOverride('kti') === false) { // @codeCoverageIgnore
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
function kti(?string $text = null, array $options = []): string
|
||||
function kti(string|null $text = null, array $options = []): string
|
||||
{
|
||||
$options['markdown']['inline'] = true;
|
||||
return App::instance()->kirbytext($text, $options);
|
||||
@@ -378,7 +378,7 @@ if (Helpers::hasOverride('load') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $base
|
||||
* @return void
|
||||
*/
|
||||
function load(array $classmap, ?string $base = null): void
|
||||
function load(array $classmap, string|null $base = null): void
|
||||
{
|
||||
F::loadClasses($classmap, $base);
|
||||
}
|
||||
@@ -393,7 +393,7 @@ if (Helpers::hasOverride('markdown') === false) { // @codeCoverageIgnore
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
function markdown(?string $text = null, array $options = []): string
|
||||
function markdown(string|null $text = null, array $options = []): string
|
||||
{
|
||||
return App::instance()->markdown($text, $options);
|
||||
}
|
||||
@@ -421,7 +421,7 @@ if (Helpers::hasOverride('page') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $id
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
function page(?string $id = null)
|
||||
function page(string|null $id = null)
|
||||
{
|
||||
if (empty($id) === true) {
|
||||
return App::instance()->site()->page();
|
||||
@@ -459,8 +459,9 @@ if (Helpers::hasOverride('param') === false) { // @codeCoverageIgnore
|
||||
* @param string $key
|
||||
* @param string|null $fallback
|
||||
* @return string|null
|
||||
* @psalm-return ($fallback is string ? string : string|null)
|
||||
*/
|
||||
function param(string $key, ?string $fallback = null): ?string
|
||||
function param(string $key, string|null $fallback = null): string|null
|
||||
{
|
||||
return App::instance()->request()->url()->params()->$key ?? $fallback;
|
||||
}
|
||||
@@ -505,7 +506,7 @@ if (Helpers::hasOverride('router') === false) { // @codeCoverageIgnore
|
||||
* @param \Closure|null $callback
|
||||
* @return mixed
|
||||
*/
|
||||
function router(?string $path = null, string $method = 'GET', array $routes = [], ?Closure $callback = null)
|
||||
function router(string|null $path = null, string $method = 'GET', array $routes = [], Closure|null $callback = null)
|
||||
{
|
||||
return Router::execute($path, $method, $routes, $callback);
|
||||
}
|
||||
@@ -545,7 +546,7 @@ if (Helpers::hasOverride('smartypants') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $text
|
||||
* @return string
|
||||
*/
|
||||
function smartypants(?string $text = null): string
|
||||
function smartypants(string|null $text = null): string
|
||||
{
|
||||
return App::instance()->smartypants($text);
|
||||
}
|
||||
@@ -560,7 +561,7 @@ if (Helpers::hasOverride('snippet') === false) { // @codeCoverageIgnore
|
||||
* @param bool $return
|
||||
* @return string|null
|
||||
*/
|
||||
function snippet($name, $data = [], bool $return = false): ?string
|
||||
function snippet($name, $data = [], bool $return = false): string|null
|
||||
{
|
||||
return App::instance()->snippet($name, $data, $return);
|
||||
}
|
||||
@@ -624,7 +625,7 @@ if (Helpers::hasOverride('timestamp') === false) { // @codeCoverageIgnore
|
||||
* @param int|array|null $step array of `unit` and `size` to round to nearest
|
||||
* @return int|null
|
||||
*/
|
||||
function timestamp(?string $date = null, $step = null): ?int
|
||||
function timestamp(string|null $date = null, $step = null): int|null
|
||||
{
|
||||
return Date::roundedTimestamp($date, $step);
|
||||
}
|
||||
@@ -641,7 +642,7 @@ if (Helpers::hasOverride('tt') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $locale
|
||||
* @return string
|
||||
*/
|
||||
function tt(string $key, $fallback = null, ?array $replace = null, ?string $locale = null): string
|
||||
function tt(string $key, $fallback = null, array|null $replace = null, string|null $locale = null): string
|
||||
{
|
||||
return I18n::template($key, $fallback, $replace, $locale);
|
||||
}
|
||||
@@ -657,7 +658,7 @@ if (Helpers::hasOverride('twitter') === false) { // @codeCoverageIgnore
|
||||
* @param string|null $class
|
||||
* @return string
|
||||
*/
|
||||
function twitter(string $username, ?string $text = null, ?string $title = null, ?string $class = null): string
|
||||
function twitter(string $username, string|null $text = null, string|null $title = null, string|null $class = null): string
|
||||
{
|
||||
return App::instance()->kirbytag([
|
||||
'twitter' => $username,
|
||||
@@ -676,7 +677,7 @@ if (Helpers::hasOverride('u') === false) { // @codeCoverageIgnore
|
||||
* @param array|string|null $options
|
||||
* @return string
|
||||
*/
|
||||
function u(?string $path = null, $options = null): string
|
||||
function u(string|null $path = null, $options = null): string
|
||||
{
|
||||
return Url::to($path, $options);
|
||||
}
|
||||
@@ -690,7 +691,7 @@ if (Helpers::hasOverride('url') === false) { // @codeCoverageIgnore
|
||||
* @param array|string|null $options
|
||||
* @return string
|
||||
*/
|
||||
function url(?string $path = null, $options = null): string
|
||||
function url(string|null $path = null, $options = null): string
|
||||
{
|
||||
return Url::to($path, $options);
|
||||
}
|
||||
@@ -719,7 +720,7 @@ if (Helpers::hasOverride('video') === false) { // @codeCoverageIgnore
|
||||
* @param array $attr
|
||||
* @return string|null
|
||||
*/
|
||||
function video(string $url, array $options = [], array $attr = []): ?string
|
||||
function video(string $url, array $options = [], array $attr = []): string|null
|
||||
{
|
||||
return Html::video($url, $options, $attr);
|
||||
}
|
||||
@@ -734,7 +735,7 @@ if (Helpers::hasOverride('vimeo') === false) { // @codeCoverageIgnore
|
||||
* @param array $attr
|
||||
* @return string|null
|
||||
*/
|
||||
function vimeo(string $url, array $options = [], array $attr = []): ?string
|
||||
function vimeo(string $url, array $options = [], array $attr = []): string|null
|
||||
{
|
||||
return Html::vimeo($url, $options, $attr);
|
||||
}
|
||||
@@ -764,7 +765,7 @@ if (Helpers::hasOverride('youtube') === false) { // @codeCoverageIgnore
|
||||
* @param array $attr
|
||||
* @return string|null
|
||||
*/
|
||||
function youtube(string $url, array $options = [], array $attr = []): ?string
|
||||
function youtube(string $url, array $options = [], array $attr = []): string|null
|
||||
{
|
||||
return Html::youtube($url, $options, $attr);
|
||||
}
|
||||
|
||||
+32
-22
@@ -2,6 +2,7 @@
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Blocks;
|
||||
use Kirby\Cms\Content;
|
||||
use Kirby\Cms\Field;
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Html;
|
||||
@@ -63,16 +64,16 @@ return function (App $app) {
|
||||
*/
|
||||
'toBlocks' => function (Field $field) {
|
||||
try {
|
||||
$blocks = Blocks::factory(Blocks::parse($field->value()), [
|
||||
'parent' => $field->parent(),
|
||||
$blocks = Blocks::parse($field->value());
|
||||
$blocks = Blocks::factory($blocks, [
|
||||
'parent' => $field->parent()
|
||||
]);
|
||||
|
||||
return $blocks->filter('isHidden', false);
|
||||
} catch (Throwable $e) {
|
||||
if ($field->parent() === null) {
|
||||
$message = 'Invalid blocks data for "' . $field->key() . '" field';
|
||||
} else {
|
||||
$message = 'Invalid blocks data for "' . $field->key() . '" field on parent "' . $field->parent()->title() . '"';
|
||||
} catch (Throwable) {
|
||||
$message = 'Invalid blocks data for "' . $field->key() . '" field';
|
||||
|
||||
if ($parent = $field->parent()) {
|
||||
$message .= ' on parent "' . $parent->title() . '"';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($message);
|
||||
@@ -99,13 +100,10 @@ return function (App $app) {
|
||||
* @return array
|
||||
*/
|
||||
'toData' => function (Field $field, string $method = ',') {
|
||||
switch ($method) {
|
||||
case 'yaml':
|
||||
case 'json':
|
||||
return Data::decode($field->value, $method);
|
||||
default:
|
||||
return $field->split($method);
|
||||
}
|
||||
return match ($method) {
|
||||
'yaml', 'json' => Data::decode($field->value, $method),
|
||||
default => $field->split($method)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -222,6 +220,17 @@ return function (App $app) {
|
||||
return Html::a($href, $field->value, $attr ?? []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse yaml data and convert it to a
|
||||
* content object
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Content
|
||||
*/
|
||||
'toObject' => function (Field $field) {
|
||||
return new Content($field->yaml(), $field->parent(), true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a page object from a page id in the field
|
||||
*
|
||||
@@ -252,11 +261,11 @@ return function (App $app) {
|
||||
'toStructure' => function (Field $field) {
|
||||
try {
|
||||
return new Structure(Data::decode($field->value, 'yaml'), $field->parent());
|
||||
} catch (Exception $e) {
|
||||
if ($field->parent() === null) {
|
||||
$message = 'Invalid structure data for "' . $field->key() . '" field';
|
||||
} else {
|
||||
$message = 'Invalid structure data for "' . $field->key() . '" field on parent "' . $field->parent()->title() . '"';
|
||||
} catch (Exception) {
|
||||
$message = 'Invalid structure data for "' . $field->key() . '" field';
|
||||
|
||||
if ($parent = $field->parent()) {
|
||||
$message .= ' on parent "' . $parent->title() . '"';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($message);
|
||||
@@ -500,10 +509,11 @@ return function (App $app) {
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param array $data
|
||||
* @param string $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* @param string|null $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* (`null` to keep the original token)
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'replace' => function (Field $field, array $data = [], string $fallback = '') use ($app) {
|
||||
'replace' => function (Field $field, array $data = [], string|null $fallback = '') use ($app) {
|
||||
if ($parent = $field->parent()) {
|
||||
// never pass `null` as the $template to avoid the fallback to the model ID
|
||||
$field->value = $parent->toString($field->value ?? '', $data, $fallback);
|
||||
|
||||
@@ -5,7 +5,7 @@ use Kirby\Toolkit\I18n;
|
||||
return function (array $props) {
|
||||
$props['sections'] = [
|
||||
'files' => [
|
||||
'headline' => $props['headline'] ?? I18n::translate('files'),
|
||||
'label' => $props['label'] ?? $props['headline'] ?? I18n::translate('files'),
|
||||
'type' => 'files',
|
||||
'layout' => $props['layout'] ?? 'cards',
|
||||
'template' => $props['template'] ?? null,
|
||||
@@ -17,6 +17,7 @@ return function (array $props) {
|
||||
// remove global options
|
||||
unset(
|
||||
$props['headline'],
|
||||
$props['label'],
|
||||
$props['layout'],
|
||||
$props['template'],
|
||||
$props['image']
|
||||
|
||||
@@ -10,7 +10,7 @@ return function ($props) {
|
||||
|
||||
if (is_string($props) === true) {
|
||||
$props = [
|
||||
'headline' => $props
|
||||
'label' => $props
|
||||
];
|
||||
}
|
||||
|
||||
@@ -27,18 +27,18 @@ return function ($props) {
|
||||
|
||||
if ($pages !== false) {
|
||||
$sidebar['pages'] = $section([
|
||||
'headline' => I18n::translate('pages'),
|
||||
'type' => 'pages',
|
||||
'status' => 'all',
|
||||
'layout' => 'list',
|
||||
'label' => I18n::translate('pages'),
|
||||
'type' => 'pages',
|
||||
'status' => 'all',
|
||||
'layout' => 'list',
|
||||
], $pages);
|
||||
}
|
||||
|
||||
if ($files !== false) {
|
||||
$sidebar['files'] = $section([
|
||||
'headline' => I18n::translate('files'),
|
||||
'type' => 'files',
|
||||
'layout' => 'list'
|
||||
'label' => I18n::translate('files'),
|
||||
'type' => 'files',
|
||||
'layout' => 'list'
|
||||
], $files);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ return function (array $props) {
|
||||
// load the general templates setting for all sections
|
||||
$templates = $props['templates'] ?? null;
|
||||
|
||||
$section = function ($headline, $status, $props) use ($templates) {
|
||||
$section = function ($label, $status, $props) use ($templates) {
|
||||
$defaults = [
|
||||
'headline' => $headline,
|
||||
'type' => 'pages',
|
||||
'layout' => 'list',
|
||||
'status' => $status
|
||||
'label' => $label,
|
||||
'type' => 'pages',
|
||||
'layout' => 'list',
|
||||
'status' => $status
|
||||
];
|
||||
|
||||
if ($props === true) {
|
||||
@@ -20,7 +20,7 @@ return function (array $props) {
|
||||
|
||||
if (is_string($props) === true) {
|
||||
$props = [
|
||||
'headline' => $props
|
||||
'label' => $props
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use Kirby\Cms\PluginAssets;
|
||||
use Kirby\Panel\Panel;
|
||||
use Kirby\Panel\Plugins;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Uuid;
|
||||
|
||||
return function ($kirby) {
|
||||
$api = $kirby->option('api.slug', 'api');
|
||||
@@ -101,6 +102,26 @@ return function ($kirby) {
|
||||
return Panel::router($path);
|
||||
}
|
||||
],
|
||||
// permalinks for page/file UUIDs
|
||||
[
|
||||
'pattern' => '@/(page|file)/(:all)',
|
||||
'method' => 'ALL',
|
||||
'env' => 'site',
|
||||
'action' => function (string $type, string $id) use ($kirby) {
|
||||
// try to resolve to model, but only from UUID cache;
|
||||
// this ensures that only existing UUIDs can be queried
|
||||
// and attackers can't force Kirby to go through the whole
|
||||
// site index with a non-existing UUID
|
||||
if ($model = Uuid::for($type . '://' . $id)->model(true)) {
|
||||
return $kirby
|
||||
->response()
|
||||
->redirect($model->url());
|
||||
}
|
||||
|
||||
// render the error page
|
||||
return false;
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
// Multi-language setup
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Form\Form;
|
||||
|
||||
return [
|
||||
@@ -31,7 +33,10 @@ return [
|
||||
'fields' => function () {
|
||||
$fields = $this->form->fields()->toArray();
|
||||
|
||||
if (is_a($this->model, 'Kirby\Cms\Page') === true || is_a($this->model, 'Kirby\Cms\Site') === true) {
|
||||
if (
|
||||
$this->model instanceof Page ||
|
||||
$this->model instanceof Site
|
||||
) {
|
||||
// the title should never be updated directly via
|
||||
// fields section to avoid conflicts with the rename dialog
|
||||
unset($fields['title']);
|
||||
|
||||
@@ -25,9 +25,9 @@ return [
|
||||
],
|
||||
'toArray' => function () {
|
||||
return [
|
||||
'headline' => $this->headline,
|
||||
'text' => $this->text,
|
||||
'theme' => $this->theme
|
||||
'label' => $this->headline,
|
||||
'text' => $this->text,
|
||||
'theme' => $this->theme
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Cms\Helpers;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
/**
|
||||
* The headline for the section. This can be a simple string or a template with additional info from the parent page.
|
||||
* @todo remove in 3.9.0
|
||||
* @deprecated 3.8.0 Use `label` instead
|
||||
*/
|
||||
'headline' => function ($headline = null) {
|
||||
// TODO: add deprecation notive in 3.8.0
|
||||
// if ($headline !== null) {
|
||||
// Helpers::deprecated('`headline` prop for sections has been deprecated and will be removed in Kirby 3.9.0. Use `label` instead.');
|
||||
// }
|
||||
|
||||
return I18n::translate($headline, $headline);
|
||||
},
|
||||
/**
|
||||
@@ -28,14 +22,14 @@ return [
|
||||
],
|
||||
'computed' => [
|
||||
'headline' => function () {
|
||||
if ($this->headline) {
|
||||
return $this->model()->toString($this->headline);
|
||||
}
|
||||
|
||||
if ($this->label) {
|
||||
return $this->model()->toString($this->label);
|
||||
}
|
||||
|
||||
if ($this->headline) {
|
||||
return $this->model()->toString($this->headline);
|
||||
}
|
||||
|
||||
return ucfirst($this->name);
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Exception\Exception;
|
||||
|
||||
return [
|
||||
@@ -24,10 +28,10 @@ return [
|
||||
}
|
||||
|
||||
if (
|
||||
is_a($parent, 'Kirby\Cms\Page') === false &&
|
||||
is_a($parent, 'Kirby\Cms\Site') === false &&
|
||||
is_a($parent, 'Kirby\Cms\File') === false &&
|
||||
is_a($parent, 'Kirby\Cms\User') === false
|
||||
$parent instanceof Page === false &&
|
||||
$parent instanceof Site === false &&
|
||||
$parent instanceof File === false &&
|
||||
$parent instanceof User === false
|
||||
) {
|
||||
throw new Exception('The parent for the section "' . $this->name() . '" has to be a page, site or user object');
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ return [
|
||||
}
|
||||
],
|
||||
'methods' => [
|
||||
'searchterm' => function (): ?string {
|
||||
'searchterm' => function (): string|null {
|
||||
return App::instance()->request()->get('searchterm');
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Kirby\Cms\Blueprint;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\I18n;
|
||||
@@ -53,8 +55,8 @@ return [
|
||||
$parent = $this->parentModel();
|
||||
|
||||
if (
|
||||
is_a($parent, 'Kirby\Cms\Site') === false &&
|
||||
is_a($parent, 'Kirby\Cms\Page') === false
|
||||
$parent instanceof Site === false &&
|
||||
$parent instanceof Page === false
|
||||
) {
|
||||
throw new InvalidArgumentException('The parent is invalid. You must choose the site or a page as parent.');
|
||||
}
|
||||
@@ -62,22 +64,13 @@ return [
|
||||
return $parent;
|
||||
},
|
||||
'pages' => function () {
|
||||
switch ($this->status) {
|
||||
case 'draft':
|
||||
$pages = $this->parent->drafts();
|
||||
break;
|
||||
case 'listed':
|
||||
$pages = $this->parent->children()->listed();
|
||||
break;
|
||||
case 'published':
|
||||
$pages = $this->parent->children();
|
||||
break;
|
||||
case 'unlisted':
|
||||
$pages = $this->parent->children()->unlisted();
|
||||
break;
|
||||
default:
|
||||
$pages = $this->parent->childrenAndDrafts();
|
||||
}
|
||||
$pages = match ($this->status) {
|
||||
'draft' => $this->parent->drafts(),
|
||||
'listed' => $this->parent->children()->listed(),
|
||||
'published' => $this->parent->children(),
|
||||
'unlisted' => $this->parent->children()->unlisted(),
|
||||
default => $this->parent->childrenAndDrafts()
|
||||
};
|
||||
|
||||
// filters pages that are protected and not in the templates list
|
||||
// internal `filter()` method used instead of foreach loop that previously included `unset()`
|
||||
@@ -228,7 +221,7 @@ return [
|
||||
'name' => basename($props['name']),
|
||||
'title' => $props['title'],
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
} catch (Throwable) {
|
||||
$blueprints[] = [
|
||||
'name' => basename($template),
|
||||
'title' => ucfirst($template),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
/**
|
||||
* Constants
|
||||
* @deprecated 3.8.0 Use `/` instead
|
||||
*/
|
||||
define('DS', '/');
|
||||
|
||||
|
||||
+12
-5
@@ -3,6 +3,7 @@
|
||||
use Kirby\Cms\Html;
|
||||
use Kirby\Cms\Url;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Uuid;
|
||||
|
||||
/**
|
||||
* Default KirbyTags definition
|
||||
@@ -117,11 +118,8 @@ return [
|
||||
return $img;
|
||||
}
|
||||
|
||||
if ($link = $tag->file($tag->link)) {
|
||||
$link = $link->url();
|
||||
} else {
|
||||
$link = $tag->link === 'self' ? $tag->src : $tag->link;
|
||||
}
|
||||
$link = $tag->file($tag->link)?->url();
|
||||
$link ??= $tag->link === 'self' ? $tag->src : $tag->link;
|
||||
|
||||
return Html::a($link, [$img], [
|
||||
'rel' => $tag->rel,
|
||||
@@ -173,6 +171,15 @@ return [
|
||||
$tag->value = Url::to($tag->value, $tag->lang);
|
||||
}
|
||||
|
||||
// if value is a UUID, resolve to page/file model
|
||||
// and use the URL as value
|
||||
if (
|
||||
Uuid::is($tag->value, 'page') === true ||
|
||||
Uuid::is($tag->value, 'file') === true
|
||||
) {
|
||||
$tag->value = Uuid::for($tag->value)->model()->url();
|
||||
}
|
||||
|
||||
return Html::a($tag->value, $tag->text, [
|
||||
'rel' => $tag->rel,
|
||||
'class' => $tag->class,
|
||||
|
||||
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
+594
-571
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+83
-180
@@ -4,10 +4,14 @@ namespace Kirby\Api;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Exception\Exception as ExceptionException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Http\Route;
|
||||
use Kirby\Http\Router;
|
||||
use Kirby\Toolkit\Collection as BaseCollection;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Pagination;
|
||||
use Kirby\Toolkit\Properties;
|
||||
@@ -32,82 +36,59 @@ class Api
|
||||
|
||||
/**
|
||||
* Authentication callback
|
||||
*
|
||||
* @var \Closure
|
||||
*/
|
||||
protected $authentication;
|
||||
protected Closure|null $authentication = null;
|
||||
|
||||
/**
|
||||
* Debugging flag
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $debug = false;
|
||||
protected bool $debug = false;
|
||||
|
||||
/**
|
||||
* Collection definition
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $collections = [];
|
||||
protected array $collections = [];
|
||||
|
||||
/**
|
||||
* Injected data/dependencies
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* Model definitions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $models = [];
|
||||
protected array $models = [];
|
||||
|
||||
/**
|
||||
* The current route
|
||||
*
|
||||
* @var \Kirby\Http\Route
|
||||
*/
|
||||
protected $route;
|
||||
protected Route|null $route = null;
|
||||
|
||||
/**
|
||||
* The Router instance
|
||||
*
|
||||
* @var \Kirby\Http\Router
|
||||
*/
|
||||
protected $router;
|
||||
protected Router|null $router = null;
|
||||
|
||||
/**
|
||||
* Route definition
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $routes = [];
|
||||
protected array $routes = [];
|
||||
|
||||
/**
|
||||
* Request data
|
||||
* [query, body, files]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $requestData = [];
|
||||
protected array $requestData = [];
|
||||
|
||||
/**
|
||||
* The applied request method
|
||||
* (GET, POST, PATCH, etc.)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $requestMethod;
|
||||
protected string|null $requestMethod = null;
|
||||
|
||||
/**
|
||||
* Magic accessor for any given data
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
*/
|
||||
public function __call(string $method, array $args = [])
|
||||
@@ -117,8 +98,6 @@ class Api
|
||||
|
||||
/**
|
||||
* Creates a new API instance
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
@@ -128,16 +107,10 @@ class Api
|
||||
/**
|
||||
* Runs the authentication method
|
||||
* if set
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
if ($auth = $this->authentication()) {
|
||||
return $auth->call($this);
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->authentication()?->call($this) ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +118,7 @@ class Api
|
||||
*
|
||||
* @return \Closure|null
|
||||
*/
|
||||
public function authentication()
|
||||
public function authentication(): Closure|null
|
||||
{
|
||||
return $this->authentication;
|
||||
}
|
||||
@@ -154,14 +127,10 @@ class Api
|
||||
* Execute an API call for the given path,
|
||||
* request method and optional request data
|
||||
*
|
||||
* @param string|null $path
|
||||
* @param string $method
|
||||
* @param array $requestData
|
||||
* @return mixed
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function call(string $path = null, string $method = 'GET', array $requestData = [])
|
||||
public function call(string|null $path = null, string $method = 'GET', array $requestData = [])
|
||||
{
|
||||
$path = rtrim($path ?? '', '/');
|
||||
|
||||
@@ -170,19 +139,18 @@ class Api
|
||||
|
||||
$this->router = new Router($this->routes());
|
||||
$this->route = $this->router->find($path, $method);
|
||||
$auth = $this->route->attributes()['auth'] ?? true;
|
||||
$auth = $this->route?->attributes()['auth'] ?? true;
|
||||
|
||||
if ($auth !== false) {
|
||||
$user = $this->authenticate();
|
||||
|
||||
// set PHP locales based on *user* language
|
||||
// so that e.g. strftime() gets formatted correctly
|
||||
if (is_a($user, 'Kirby\Cms\User') === true) {
|
||||
if ($user instanceof User) {
|
||||
$language = $user->language();
|
||||
|
||||
// get the locale from the translation
|
||||
$translation = $user->kirby()->translation($language);
|
||||
$locale = ($translation !== null) ? $translation->locale() : $language;
|
||||
$locale = $user->kirby()->translation($language)->locale();
|
||||
|
||||
// provide some variants as fallbacks to be
|
||||
// compatible with as many systems as possible
|
||||
@@ -208,14 +176,17 @@ class Api
|
||||
$validate = Pagination::$validate;
|
||||
Pagination::$validate = false;
|
||||
|
||||
$output = $this->route->action()->call($this, ...$this->route->arguments());
|
||||
$output = $this->route?->action()->call(
|
||||
$this,
|
||||
...$this->route->arguments()
|
||||
);
|
||||
|
||||
// restore old pagination validation mode
|
||||
Pagination::$validate = $validate;
|
||||
|
||||
if (
|
||||
is_object($output) === true &&
|
||||
is_a($output, 'Kirby\\Http\\Response') !== true
|
||||
$output instanceof Response === false
|
||||
) {
|
||||
return $this->resolve($output)->toResponse();
|
||||
}
|
||||
@@ -226,13 +197,10 @@ class Api
|
||||
/**
|
||||
* Setter and getter for an API collection
|
||||
*
|
||||
* @param string $name
|
||||
* @param array|null $collection
|
||||
* @return \Kirby\Api\Collection
|
||||
* @throws \Kirby\Exception\NotFoundException If no collection for `$name` exists
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function collection(string $name, $collection = null)
|
||||
public function collection(string $name, array|BaseCollection|null $collection = null): Collection
|
||||
{
|
||||
if (isset($this->collections[$name]) === false) {
|
||||
throw new NotFoundException(sprintf('The collection "%s" does not exist', $name));
|
||||
@@ -243,8 +211,6 @@ class Api
|
||||
|
||||
/**
|
||||
* Returns the collections definition
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function collections(): array
|
||||
{
|
||||
@@ -255,13 +221,9 @@ class Api
|
||||
* Returns the injected data array
|
||||
* or certain parts of it by key
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param mixed ...$args
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Kirby\Exception\NotFoundException If no data for `$key` exists
|
||||
*/
|
||||
public function data($key = null, ...$args)
|
||||
public function data(string|null $key = null, ...$args)
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->data;
|
||||
@@ -272,7 +234,7 @@ class Api
|
||||
}
|
||||
|
||||
// lazy-load data wrapped in Closures
|
||||
if (is_a($this->data[$key], 'Closure') === true) {
|
||||
if ($this->data[$key] instanceof Closure) {
|
||||
return $this->data[$key]->call($this, ...$args);
|
||||
}
|
||||
|
||||
@@ -281,8 +243,6 @@ class Api
|
||||
|
||||
/**
|
||||
* Returns the debugging flag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function debug(): bool
|
||||
{
|
||||
@@ -291,9 +251,6 @@ class Api
|
||||
|
||||
/**
|
||||
* Checks if injected data exists for the given key
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function hasData(string $key): bool
|
||||
{
|
||||
@@ -305,39 +262,31 @@ class Api
|
||||
* based on the `type` field
|
||||
*
|
||||
* @param array models or collections
|
||||
* @param mixed $object
|
||||
*
|
||||
* @return string key of match
|
||||
* @return string|null key of match
|
||||
*/
|
||||
protected function match(array $array, $object = null)
|
||||
protected function match(array $array, $object = null): string|null
|
||||
{
|
||||
foreach ($array as $definition => $model) {
|
||||
if (is_a($object, $model['type']) === true) {
|
||||
if ($object instanceof $model['type']) {
|
||||
return $definition;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an API model instance by name
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param mixed $object
|
||||
* @return \Kirby\Api\Model
|
||||
*
|
||||
* @throws \Kirby\Exception\NotFoundException If no model for `$name` exists
|
||||
*/
|
||||
public function model(string $name = null, $object = null)
|
||||
public function model(string|null $name = null, $object = null): Model
|
||||
{
|
||||
// Try to auto-match object with API models
|
||||
if ($name === null) {
|
||||
if ($model = $this->match($this->models, $object)) {
|
||||
$name = $model;
|
||||
}
|
||||
}
|
||||
$name ??= $this->match($this->models, $object);
|
||||
|
||||
if (isset($this->models[$name]) === false) {
|
||||
throw new NotFoundException(sprintf('The model "%s" does not exist', $name));
|
||||
throw new NotFoundException(sprintf('The model "%s" does not exist', $name ?? 'NULL'));
|
||||
}
|
||||
|
||||
return new Model($this, $object, $this->models[$name]);
|
||||
@@ -345,8 +294,6 @@ class Api
|
||||
|
||||
/**
|
||||
* Returns all model definitions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function models(): array
|
||||
{
|
||||
@@ -363,8 +310,11 @@ class Api
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function requestData(string $type = null, string $key = null, $default = null)
|
||||
{
|
||||
public function requestData(
|
||||
string|null $type = null,
|
||||
string|null $key = null,
|
||||
$default = null
|
||||
) {
|
||||
if ($type === null) {
|
||||
return $this->requestData;
|
||||
}
|
||||
@@ -381,58 +331,40 @@ class Api
|
||||
|
||||
/**
|
||||
* Returns the request body if available
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function requestBody(string $key = null, $default = null)
|
||||
public function requestBody(string|null $key = null, $default = null)
|
||||
{
|
||||
return $this->requestData('body', $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the files from the request if available
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function requestFiles(string $key = null, $default = null)
|
||||
public function requestFiles(string|null $key = null, $default = null)
|
||||
{
|
||||
return $this->requestData('files', $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all headers from the request if available
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function requestHeaders(string $key = null, $default = null)
|
||||
public function requestHeaders(string|null $key = null, $default = null)
|
||||
{
|
||||
return $this->requestData('headers', $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request method
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function requestMethod(): string
|
||||
public function requestMethod(): string|null
|
||||
{
|
||||
return $this->requestMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request query if available
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function requestQuery(string $key = null, $default = null)
|
||||
public function requestQuery(string|null $key = null, $default = null)
|
||||
{
|
||||
return $this->requestData('query', $key, $default);
|
||||
}
|
||||
@@ -441,14 +373,14 @@ class Api
|
||||
* Turns a Kirby object into an
|
||||
* API model or collection representation
|
||||
*
|
||||
* @param mixed $object
|
||||
* @return \Kirby\Api\Model|\Kirby\Api\Collection
|
||||
*
|
||||
* @throws \Kirby\Exception\NotFoundException If `$object` cannot be resolved
|
||||
*/
|
||||
public function resolve($object)
|
||||
public function resolve($object): Model|Collection
|
||||
{
|
||||
if (is_a($object, 'Kirby\Api\Model') === true || is_a($object, 'Kirby\Api\Collection') === true) {
|
||||
if (
|
||||
$object instanceof Model ||
|
||||
$object instanceof Collection
|
||||
) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
@@ -465,8 +397,6 @@ class Api
|
||||
|
||||
/**
|
||||
* Returns all defined routes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function routes(): array
|
||||
{
|
||||
@@ -475,11 +405,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the authentication callback
|
||||
*
|
||||
* @param \Closure|null $authentication
|
||||
* @return $this
|
||||
*/
|
||||
protected function setAuthentication(Closure $authentication = null)
|
||||
protected function setAuthentication(Closure|null $authentication = null): static
|
||||
{
|
||||
$this->authentication = $authentication;
|
||||
return $this;
|
||||
@@ -487,11 +415,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the collections definition
|
||||
*
|
||||
* @param array|null $collections
|
||||
* @return $this
|
||||
*/
|
||||
protected function setCollections(array $collections = null)
|
||||
protected function setCollections(array|null $collections = null): static
|
||||
{
|
||||
if ($collections !== null) {
|
||||
$this->collections = array_change_key_case($collections);
|
||||
@@ -501,11 +427,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the injected data
|
||||
*
|
||||
* @param array|null $data
|
||||
* @return $this
|
||||
*/
|
||||
protected function setData(array $data = null)
|
||||
protected function setData(array|null $data = null): static
|
||||
{
|
||||
$this->data = $data ?? [];
|
||||
return $this;
|
||||
@@ -513,11 +437,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the debug flag
|
||||
*
|
||||
* @param bool $debug
|
||||
* @return $this
|
||||
*/
|
||||
protected function setDebug(bool $debug = false)
|
||||
protected function setDebug(bool $debug = false): static
|
||||
{
|
||||
$this->debug = $debug;
|
||||
return $this;
|
||||
@@ -525,11 +447,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the model definitions
|
||||
*
|
||||
* @param array|null $models
|
||||
* @return $this
|
||||
*/
|
||||
protected function setModels(array $models = null)
|
||||
protected function setModels(array|null $models = null): static
|
||||
{
|
||||
if ($models !== null) {
|
||||
$this->models = array_change_key_case($models);
|
||||
@@ -540,11 +460,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the request data
|
||||
*
|
||||
* @param array|null $requestData
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRequestData(array $requestData = null)
|
||||
protected function setRequestData(array|null $requestData = null): static
|
||||
{
|
||||
$defaults = [
|
||||
'query' => [],
|
||||
@@ -558,11 +476,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the request method
|
||||
*
|
||||
* @param string|null $requestMethod
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRequestMethod(string $requestMethod = null)
|
||||
protected function setRequestMethod(string|null $requestMethod = null): static
|
||||
{
|
||||
$this->requestMethod = $requestMethod ?? 'GET';
|
||||
return $this;
|
||||
@@ -570,11 +486,9 @@ class Api
|
||||
|
||||
/**
|
||||
* Setter for the route definitions
|
||||
*
|
||||
* @param array|null $routes
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRoutes(array $routes = null)
|
||||
protected function setRoutes(array|null $routes = null): static
|
||||
{
|
||||
$this->routes = $routes ?? [];
|
||||
return $this;
|
||||
@@ -582,13 +496,8 @@ class Api
|
||||
|
||||
/**
|
||||
* Renders the API call
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $method
|
||||
* @param array $requestData
|
||||
* @return mixed
|
||||
*/
|
||||
public function render(string $path, $method = 'GET', array $requestData = [])
|
||||
public function render(string $path, string $method = 'GET', array $requestData = [])
|
||||
{
|
||||
try {
|
||||
$result = $this->call($path, $method, $requestData);
|
||||
@@ -596,13 +505,12 @@ class Api
|
||||
$result = $this->responseForException($e);
|
||||
}
|
||||
|
||||
if ($result === null) {
|
||||
$result = $this->responseFor404();
|
||||
} elseif ($result === false) {
|
||||
$result = $this->responseFor400();
|
||||
} elseif ($result === true) {
|
||||
$result = $this->responseFor200();
|
||||
}
|
||||
$result = match ($result) {
|
||||
null => $this->responseFor404(),
|
||||
false => $this->responseFor400(),
|
||||
true => $this->responseFor200(),
|
||||
default => $result
|
||||
};
|
||||
|
||||
if (is_array($result) === false) {
|
||||
return $result;
|
||||
@@ -628,8 +536,6 @@ class Api
|
||||
/**
|
||||
* Returns a 200 - ok
|
||||
* response array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function responseFor200(): array
|
||||
{
|
||||
@@ -643,8 +549,6 @@ class Api
|
||||
/**
|
||||
* Returns a 400 - bad request
|
||||
* response array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function responseFor400(): array
|
||||
{
|
||||
@@ -658,8 +562,6 @@ class Api
|
||||
/**
|
||||
* Returns a 404 - not found
|
||||
* response array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function responseFor404(): array
|
||||
{
|
||||
@@ -674,9 +576,6 @@ class Api
|
||||
* Creates the response array for
|
||||
* an exception. Kirby exceptions will
|
||||
* have more information
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return array
|
||||
*/
|
||||
public function responseForException(Throwable $e): array
|
||||
{
|
||||
@@ -696,11 +595,11 @@ class Api
|
||||
'file' => F::relativepath($e->getFile(), $docRoot),
|
||||
'line' => $e->getLine(),
|
||||
'details' => [],
|
||||
'route' => $this->route ? $this->route->pattern() : null
|
||||
'route' => $this->route?->pattern()
|
||||
];
|
||||
|
||||
// extend the information for Kirby Exceptions
|
||||
if (is_a($e, 'Kirby\Exception\Exception') === true) {
|
||||
if ($e instanceof ExceptionException) {
|
||||
$result['key'] = $e->getKey();
|
||||
$result['details'] = $e->getDetails();
|
||||
$result['code'] = $e->getHttpCode();
|
||||
@@ -726,14 +625,9 @@ class Api
|
||||
* move_uploaded_file() not working with unit test
|
||||
* Added debug parameter for testing purposes as we did in the Email class
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @param bool $single
|
||||
* @param bool $debug
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception If request has no files or there was an error with the upload
|
||||
*/
|
||||
public function upload(Closure $callback, $single = false, $debug = false): array
|
||||
public function upload(Closure $callback, bool $single = false, bool $debug = false): array
|
||||
{
|
||||
$trials = 0;
|
||||
$uploads = [];
|
||||
@@ -757,13 +651,16 @@ class Api
|
||||
|
||||
if ($postMaxSize < $uploadMaxFileSize) {
|
||||
throw new Exception(I18n::translate('upload.error.iniPostSize'));
|
||||
} else {
|
||||
throw new Exception(I18n::translate('upload.error.noFiles'));
|
||||
}
|
||||
|
||||
throw new Exception(I18n::translate('upload.error.noFiles'));
|
||||
}
|
||||
|
||||
foreach ($files as $upload) {
|
||||
if (isset($upload['tmp_name']) === false && is_array($upload)) {
|
||||
if (
|
||||
isset($upload['tmp_name']) === false &&
|
||||
is_array($upload) === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -780,7 +677,10 @@ class Api
|
||||
|
||||
// try to detect the correct mime and add the extension
|
||||
// accordingly. This will avoid .tmp filenames
|
||||
if (empty($extension) === true || in_array($extension, ['tmp', 'temp'])) {
|
||||
if (
|
||||
empty($extension) === true ||
|
||||
in_array($extension, ['tmp', 'temp']) === true
|
||||
) {
|
||||
$mime = F::mime($upload['tmp_name']);
|
||||
$extension = F::mimeToExtension($mime);
|
||||
$filename = F::name($upload['name']) . '.' . $extension;
|
||||
@@ -792,7 +692,10 @@ class Api
|
||||
|
||||
// move the file to a location including the extension,
|
||||
// for better mime detection
|
||||
if ($debug === false && move_uploaded_file($upload['tmp_name'], $source) === false) {
|
||||
if (
|
||||
$debug === false &&
|
||||
move_uploaded_file($upload['tmp_name'], $source) === false
|
||||
) {
|
||||
throw new Exception(I18n::translate('upload.error.cantMove'));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Api;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
@@ -19,48 +20,27 @@ use Kirby\Toolkit\Str;
|
||||
*/
|
||||
class Collection
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Api\Api
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected Api $api;
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $select;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* Collection constructor
|
||||
*
|
||||
* @param \Kirby\Api\Api $api
|
||||
* @param mixed|null $data
|
||||
* @param array $schema
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(Api $api, $data, array $schema)
|
||||
{
|
||||
$this->api = $api;
|
||||
$this->data = $data;
|
||||
$this->model = $schema['model'] ?? null;
|
||||
$this->view = $schema['view'] ?? null;
|
||||
$this->api = $api;
|
||||
$this->data = $data;
|
||||
$this->model = $schema['model'] ?? null;
|
||||
$this->select = null;
|
||||
$this->view = $schema['view'] ?? null;
|
||||
|
||||
if ($data === null) {
|
||||
if (is_a($schema['default'] ?? null, 'Closure') === false) {
|
||||
if (($schema['default'] ?? null) instanceof Closure === false) {
|
||||
throw new Exception('Missing collection data');
|
||||
}
|
||||
|
||||
@@ -69,18 +49,17 @@ class Collection
|
||||
|
||||
if (
|
||||
isset($schema['type']) === true &&
|
||||
is_a($this->data, $schema['type']) === false
|
||||
$this->data instanceof $schema['type'] === false
|
||||
) {
|
||||
throw new Exception('Invalid collection type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array|null $keys
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function select($keys = null)
|
||||
public function select($keys = null): static
|
||||
{
|
||||
if ($keys === false) {
|
||||
return $this;
|
||||
@@ -99,7 +78,6 @@ class Collection
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -125,7 +103,6 @@ class Collection
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -167,10 +144,9 @@ class Collection
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $view
|
||||
* @return $this
|
||||
*/
|
||||
public function view(string $view)
|
||||
public function view(string $view): static
|
||||
{
|
||||
$this->view = $view;
|
||||
return $this;
|
||||
|
||||
+16
-36
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Api;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
@@ -21,37 +22,15 @@ use Kirby\Toolkit\Str;
|
||||
*/
|
||||
class Model
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Api\Api
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected Api $api;
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var array|mixed
|
||||
*/
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $select;
|
||||
|
||||
/**
|
||||
* @var array|mixed
|
||||
*/
|
||||
protected $views;
|
||||
|
||||
/**
|
||||
* Model constructor
|
||||
*
|
||||
* @param \Kirby\Api\Api $api
|
||||
* @param mixed $data
|
||||
* @param array $schema
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(Api $api, $data, array $schema)
|
||||
@@ -62,12 +41,15 @@ class Model
|
||||
$this->select = $schema['select'] ?? null;
|
||||
$this->views = $schema['views'] ?? [];
|
||||
|
||||
if ($this->select === null && array_key_exists('default', $this->views)) {
|
||||
if (
|
||||
$this->select === null &&
|
||||
array_key_exists('default', $this->views)
|
||||
) {
|
||||
$this->view('default');
|
||||
}
|
||||
|
||||
if ($data === null) {
|
||||
if (is_a($schema['default'] ?? null, 'Closure') === false) {
|
||||
if (($schema['default'] ?? null) instanceof Closure === false) {
|
||||
throw new Exception('Missing model data');
|
||||
}
|
||||
|
||||
@@ -76,18 +58,17 @@ class Model
|
||||
|
||||
if (
|
||||
isset($schema['type']) === true &&
|
||||
is_a($this->data, $schema['type']) === false
|
||||
$this->data instanceof $schema['type'] === false
|
||||
) {
|
||||
throw new Exception(sprintf('Invalid model type "%s" expected: "%s"', get_class($this->data), $schema['type']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $keys
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function select($keys = null)
|
||||
public function select($keys = null): static
|
||||
{
|
||||
if ($keys === false) {
|
||||
return $this;
|
||||
@@ -106,7 +87,6 @@ class Model
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function selection(): array
|
||||
@@ -153,7 +133,6 @@ class Model
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -163,7 +142,10 @@ class Model
|
||||
$result = [];
|
||||
|
||||
foreach ($this->fields as $key => $resolver) {
|
||||
if (array_key_exists($key, $select) === false || is_a($resolver, 'Closure') === false) {
|
||||
if (
|
||||
array_key_exists($key, $select) === false ||
|
||||
$resolver instanceof Closure === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -174,8 +156,8 @@ class Model
|
||||
}
|
||||
|
||||
if (
|
||||
is_a($value, 'Kirby\Api\Collection') === true ||
|
||||
is_a($value, 'Kirby\Api\Model') === true
|
||||
$value instanceof Collection ||
|
||||
$value instanceof self
|
||||
) {
|
||||
$selection = $select[$key];
|
||||
|
||||
@@ -199,7 +181,6 @@ class Model
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -224,11 +205,10 @@ class Model
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function view(string $name)
|
||||
public function view(string $name): static
|
||||
{
|
||||
if ($name === 'any') {
|
||||
return $this->select(null);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use Kirby\Cms\Collection as BaseCollection;
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Toolkit\A;
|
||||
use TypeError;
|
||||
|
||||
/**
|
||||
* Typed collection
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Collection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* The expected object type
|
||||
*/
|
||||
public const TYPE = Node::class;
|
||||
|
||||
public function __construct(array $objects = [])
|
||||
{
|
||||
foreach ($objects as $object) {
|
||||
$this->__set($object->id, $object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Kirby Collection class only shows the key to
|
||||
* avoid huge tress with dump, but for the blueprint
|
||||
* collections this is really not useful
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return A::map($this->data, fn ($item) => (array)$item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the type of every item that is being
|
||||
* added to the collection. They cneed to have
|
||||
* the class defined by static::TYPE.
|
||||
*/
|
||||
public function __set(string $key, $value): void
|
||||
{
|
||||
if (
|
||||
is_a($value, static::TYPE) === false
|
||||
) {
|
||||
throw new TypeError('Each value in the collection must be an instance of ' . static::TYPE);
|
||||
}
|
||||
|
||||
parent::__set($key, $value);
|
||||
}
|
||||
|
||||
public static function factory(array $items)
|
||||
{
|
||||
$collection = new static();
|
||||
$className = static::TYPE;
|
||||
|
||||
foreach ($items as $id => $item) {
|
||||
if (is_array($item) === true) {
|
||||
$item['id'] ??= $id;
|
||||
$item = $className::factory($item);
|
||||
$collection->__set($item->id, $item);
|
||||
} else {
|
||||
$collection->__set($id, $className::factory($item));
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function render(ModelWithContent $model)
|
||||
{
|
||||
$props = [];
|
||||
|
||||
foreach ($this->data as $key => $item) {
|
||||
$props[$key] = $item->render($model);
|
||||
}
|
||||
|
||||
return $props;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Data\Yaml;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* Config
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
public string $file;
|
||||
public string $id;
|
||||
public string|array|Closure|null $plugin;
|
||||
public string $root;
|
||||
|
||||
public function __construct(
|
||||
public string $path
|
||||
) {
|
||||
$kirby = App::instance();
|
||||
|
||||
$this->id = basename($this->path);
|
||||
$this->root = $kirby->root('blueprints');
|
||||
$this->file = $this->root . '/' . $this->path . '.yml';
|
||||
$this->plugin = $kirby->extension('blueprints', $this->path);
|
||||
}
|
||||
|
||||
public function read(): array
|
||||
{
|
||||
if (F::exists($this->file, $this->root) === true) {
|
||||
return $this->unpack($this->file);
|
||||
}
|
||||
|
||||
return $this->unpack($this->plugin);
|
||||
}
|
||||
|
||||
public function write(array $props): bool
|
||||
{
|
||||
return Yaml::write($this->file, $props);
|
||||
}
|
||||
|
||||
public function unpack(string|array|Closure|null $extension): array
|
||||
{
|
||||
return match (true) {
|
||||
// extension does not exist
|
||||
is_null($extension)
|
||||
=> throw new NotFoundException('"' . $this->path . '" could not be found'),
|
||||
|
||||
// extension is stored as a file path
|
||||
is_string($extension)
|
||||
=> Yaml::read($extension),
|
||||
|
||||
// extension is a callback to be resolved
|
||||
is_callable($extension)
|
||||
=> $extension(App::instance()),
|
||||
|
||||
// extension is already defined as array
|
||||
default
|
||||
=> $extension
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
/**
|
||||
* Extension
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Extension
|
||||
{
|
||||
public function __construct(
|
||||
public string $path
|
||||
) {
|
||||
}
|
||||
|
||||
public static function apply(array $props): array
|
||||
{
|
||||
if (isset($props['extends']) === false) {
|
||||
return $props;
|
||||
}
|
||||
|
||||
// already extended
|
||||
if (is_a($props['extends'], Extension::class) === true) {
|
||||
return $props;
|
||||
}
|
||||
|
||||
$extension = new static($props['extends']);
|
||||
return $extension->extend($props);
|
||||
}
|
||||
|
||||
public function extend(array $props): array
|
||||
{
|
||||
$props = array_replace_recursive(
|
||||
$this->read(),
|
||||
$props
|
||||
);
|
||||
|
||||
$props['extends'] = $this;
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
public static function factory(string|array $path): static
|
||||
{
|
||||
if (is_string($path) === true) {
|
||||
return new static(path: $path);
|
||||
}
|
||||
|
||||
return new static(...$path);
|
||||
}
|
||||
|
||||
public function read(): array
|
||||
{
|
||||
$config = new Config($this->path);
|
||||
return $config->read();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use ReflectionException;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
use ReflectionUnionType;
|
||||
|
||||
/**
|
||||
* Factory
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* Resolves the properties by
|
||||
* applying a map of factories (propName => class)
|
||||
*/
|
||||
public static function apply(array $properties, array $factories): array
|
||||
{
|
||||
foreach ($factories as $property => $class) {
|
||||
// skip non-existing properties, empty properties
|
||||
// or properties that are matching objects
|
||||
if (
|
||||
isset($properties[$property]) === false ||
|
||||
$properties[$property] === null ||
|
||||
is_a($properties[$property], $class) === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$properties[$property] = $class::factory($properties[$property]);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
public static function forNamedType(ReflectionNamedType|null $type, $value)
|
||||
{
|
||||
// get the class name for the single type
|
||||
$className = $type->getName();
|
||||
|
||||
// check if there's a factory for the value
|
||||
if (method_exists($className, 'factory') === true) {
|
||||
return $className::factory($value);
|
||||
}
|
||||
|
||||
// try to assign the value directly and trust
|
||||
// in PHP's type system.
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function forProperties(string $class, array $properties): array
|
||||
{
|
||||
foreach ($properties as $property => $value) {
|
||||
try {
|
||||
$properties[$property] = static::forProperty($class, $property, $value);
|
||||
} catch (ReflectionException $e) {
|
||||
// the property does not exist
|
||||
unset($properties[$property]);
|
||||
}
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
public static function forProperty(string $class, string $property, $value)
|
||||
{
|
||||
if (is_null($value) === true) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// instantly assign objects
|
||||
// PHP's type system will find issues automatically
|
||||
if (is_object($value) === true) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// get the type for the property
|
||||
$reflection = new ReflectionProperty($class, $property);
|
||||
$propType = $reflection->getType();
|
||||
|
||||
// no type given
|
||||
if ($propType === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// union types
|
||||
if (is_a($propType, ReflectionUnionType::class) === true) {
|
||||
return static::forUnionType($propType, $value);
|
||||
}
|
||||
|
||||
return static::forNamedType($propType, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For properties with union types,
|
||||
* the first named type is used to create
|
||||
* the factory or pass a built-in value
|
||||
*/
|
||||
public static function forUnionType(ReflectionUnionType $type, $value)
|
||||
{
|
||||
return static::forNamedType($type->getTypes()[0], $value);
|
||||
}
|
||||
|
||||
public static function make(string $class, array $properties): object
|
||||
{
|
||||
return new $class(...static::forProperties($class, $properties));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
|
||||
/**
|
||||
* A node of the blueprint
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Node
|
||||
{
|
||||
public const TYPE = 'node';
|
||||
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public Extension|null $extends = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic getter for properties
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$this->defaults();
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply default values
|
||||
*/
|
||||
public function defaults(): static
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance by a set of array properties.
|
||||
*/
|
||||
public static function factory(array $props): static
|
||||
{
|
||||
$props = Extension::apply($props);
|
||||
$props = static::polyfill($props);
|
||||
return Factory::make(static::class, $props);
|
||||
}
|
||||
|
||||
|
||||
public static function load(string|array $props): static
|
||||
{
|
||||
// load by path
|
||||
if (is_string($props) === true) {
|
||||
$props = static::loadProps($props);
|
||||
}
|
||||
|
||||
return static::factory($props);
|
||||
}
|
||||
|
||||
public static function loadProps(string $path): array
|
||||
{
|
||||
$config = new Config($path);
|
||||
$props = $config->read();
|
||||
|
||||
// add the id if it's not set yet
|
||||
$props['id'] ??= basename($path);
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional method that runs before static::factory sends
|
||||
* its properties to the instance. This is perfect to clean
|
||||
* up props or keep deprecated props compatible.
|
||||
*/
|
||||
public static function polyfill(array $props): array
|
||||
{
|
||||
return $props;
|
||||
}
|
||||
|
||||
public function render(ModelWithContent $model)
|
||||
{
|
||||
// apply default values
|
||||
$this->defaults();
|
||||
|
||||
$array = [];
|
||||
|
||||
// go through all public properties
|
||||
foreach (get_object_vars($this) as $key => $value) {
|
||||
if (is_object($value) === false && is_resource($value) === false) {
|
||||
$array[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method_exists($value, 'render') === true) {
|
||||
$array[$key] = $value->render($model);
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal setter for properties
|
||||
*/
|
||||
public function set(string $property, $value): static
|
||||
{
|
||||
$this->$property = Factory::forProperty(static::class, $property, $value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
* Translatable node property
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeI18n extends NodeProperty
|
||||
{
|
||||
public function __construct(
|
||||
public array $translations,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function factory($value = null): static|null
|
||||
{
|
||||
if ($value === false || $value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_array($value) === false) {
|
||||
$value = ['en' => $value];
|
||||
}
|
||||
|
||||
return new static($value);
|
||||
}
|
||||
|
||||
public function render(ModelWithContent $model): string|null
|
||||
{
|
||||
return I18n::translate($this->translations, $this->translations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
/**
|
||||
* Custom emoji or icon from the Kirby iconset
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeIcon extends NodeString
|
||||
{
|
||||
public static function field()
|
||||
{
|
||||
$field = parent::field();
|
||||
$field->id = 'icon';
|
||||
$field->label->translations = ['en' => 'Icon'];
|
||||
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
|
||||
/**
|
||||
* Represents a property for a node
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract class NodeProperty
|
||||
{
|
||||
abstract public static function factory($value = null): static|null;
|
||||
|
||||
public function render(ModelWithContent $model)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
|
||||
/**
|
||||
* Simple string blueprint node
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeString extends NodeProperty
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function factory($value = null): static|null
|
||||
{
|
||||
if ($value === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return new static($value);
|
||||
}
|
||||
|
||||
public function render(ModelWithContent $model): string|null
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Blueprint;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
|
||||
/**
|
||||
* The text node is translatable
|
||||
* and will parse query template strings
|
||||
*
|
||||
* @package Kirby Blueprint
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.9
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeText extends NodeI18n
|
||||
{
|
||||
public function render(ModelWithContent $model): ?string
|
||||
{
|
||||
if ($text = parent::render($model)) {
|
||||
return $model->toSafeString($text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,17 @@ use APCUIterator;
|
||||
*/
|
||||
class ApcuCache extends Cache
|
||||
{
|
||||
/**
|
||||
* Returns whether the cache is ready to
|
||||
* store values
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return apcu_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an item exists in the cache
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(string $key): bool
|
||||
{
|
||||
@@ -29,24 +35,19 @@ class ApcuCache extends Cache
|
||||
/**
|
||||
* Flushes the entire cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush(): bool
|
||||
{
|
||||
if (empty($this->options['prefix']) === false) {
|
||||
return apcu_delete(new APCUIterator('!^' . preg_quote($this->options['prefix']) . '!'));
|
||||
} else {
|
||||
return apcu_clear_cache();
|
||||
}
|
||||
|
||||
return apcu_clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function remove(string $key): bool
|
||||
{
|
||||
@@ -56,13 +57,11 @@ class ApcuCache extends Cache
|
||||
/**
|
||||
* Internal method to retrieve the raw cache value;
|
||||
* needs to return a Value object or null if not found
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cache\Value|null
|
||||
*/
|
||||
public function retrieve(string $key)
|
||||
public function retrieve(string $key): Value|null
|
||||
{
|
||||
return Value::fromJson(apcu_fetch($this->key($key)));
|
||||
$value = apcu_fetch($this->key($key));
|
||||
return Value::fromJson($value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,14 +72,12 @@ class ApcuCache extends Cache
|
||||
* // put an item in the cache for 15 minutes
|
||||
* $cache->set('value', 'my value', 15);
|
||||
* </code>
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $minutes
|
||||
* @return bool
|
||||
*/
|
||||
public function set(string $key, $value, int $minutes = 0): bool
|
||||
{
|
||||
return apcu_store($this->key($key), (new Value($value, $minutes))->toJson(), $this->expiration($minutes));
|
||||
$key = $this->key($key);
|
||||
$value = (new Value($value, $minutes))->toJson();
|
||||
$expires = $this->expiration($minutes);
|
||||
return apcu_store($key, $value, $expires);
|
||||
}
|
||||
}
|
||||
|
||||
+120
-124
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Kirby\Cache;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Cache foundation
|
||||
* This abstract class is used as
|
||||
@@ -18,14 +20,11 @@ abstract class Cache
|
||||
{
|
||||
/**
|
||||
* Stores all options for the driver
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
protected array $options = [];
|
||||
|
||||
/**
|
||||
* Sets all parameters which are needed to connect to the cache storage
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
@@ -33,87 +32,47 @@ abstract class Cache
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an item to the cache for a given number of minutes and
|
||||
* returns whether the operation was successful;
|
||||
* this needs to be defined by the driver
|
||||
*
|
||||
* <code>
|
||||
* // put an item in the cache for 15 minutes
|
||||
* $cache->set('value', 'my value', 15);
|
||||
* </code>
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $minutes
|
||||
* @return bool
|
||||
* Checks when the cache has been created;
|
||||
* returns the creation timestamp on success
|
||||
* and false if the item does not exist
|
||||
*/
|
||||
abstract public function set(string $key, $value, int $minutes = 0): bool;
|
||||
|
||||
/**
|
||||
* Adds the prefix to the key if given
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function key(string $key): string
|
||||
public function created(string $key): int|false
|
||||
{
|
||||
if (empty($this->options['prefix']) === false) {
|
||||
$key = $this->options['prefix'] . '/' . $key;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to retrieve the raw cache value;
|
||||
* needs to return a Value object or null if not found;
|
||||
* this needs to be defined by the driver
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cache\Value|null
|
||||
*/
|
||||
abstract public function retrieve(string $key);
|
||||
|
||||
/**
|
||||
* Gets an item from the cache
|
||||
*
|
||||
* <code>
|
||||
* // get an item from the cache driver
|
||||
* $value = $cache->get('value');
|
||||
*
|
||||
* // return a default value if the requested item isn't cached
|
||||
* $value = $cache->get('value', 'default value');
|
||||
* </code>
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key, $default = null)
|
||||
{
|
||||
// get the Value
|
||||
// get the Value object
|
||||
$value = $this->retrieve($key);
|
||||
|
||||
// check for a valid cache value
|
||||
if (!is_a($value, 'Kirby\Cache\Value')) {
|
||||
return $default;
|
||||
// check for a valid Value object
|
||||
if ($value instanceof Value === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove the item if it is expired
|
||||
if ($value->expires() > 0 && time() >= $value->expires()) {
|
||||
$this->remove($key);
|
||||
return $default;
|
||||
}
|
||||
|
||||
// return the pure value
|
||||
return $value->value();
|
||||
// return the expires timestamp
|
||||
return $value->created();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cache is ready to
|
||||
* store values
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
// TODO: Make this method abstract in a future
|
||||
// release to ensure that cache drivers override it;
|
||||
// until then, we assume that the cache is enabled
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an item exists in the cache
|
||||
*/
|
||||
public function exists(string $key): bool
|
||||
{
|
||||
return $this->expired($key) === false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the expiration timestamp
|
||||
*
|
||||
* @param int $minutes
|
||||
* @return int
|
||||
*/
|
||||
protected function expiration(int $minutes = 0): int
|
||||
{
|
||||
@@ -130,17 +89,14 @@ abstract class Cache
|
||||
* Checks when an item in the cache expires;
|
||||
* returns the expiry timestamp on success, null if the
|
||||
* item never expires and false if the item does not exist
|
||||
*
|
||||
* @param string $key
|
||||
* @return int|null|false
|
||||
*/
|
||||
public function expires(string $key)
|
||||
public function expires(string $key): int|false|null
|
||||
{
|
||||
// get the Value object
|
||||
$value = $this->retrieve($key);
|
||||
|
||||
// check for a valid Value object
|
||||
if (!is_a($value, 'Kirby\Cache\Value')) {
|
||||
if ($value instanceof Value === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -150,9 +106,6 @@ abstract class Cache
|
||||
|
||||
/**
|
||||
* Checks if an item in the cache is expired
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function expired(string $key): bool
|
||||
{
|
||||
@@ -160,83 +113,126 @@ abstract class Cache
|
||||
|
||||
if ($expires === null) {
|
||||
return false;
|
||||
} elseif (!is_int($expires)) {
|
||||
return true;
|
||||
} else {
|
||||
return time() >= $expires;
|
||||
}
|
||||
|
||||
if (is_int($expires) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return time() >= $expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks when the cache has been created;
|
||||
* returns the creation timestamp on success
|
||||
* and false if the item does not exist
|
||||
*
|
||||
* @param string $key
|
||||
* @return int|false
|
||||
* Flushes the entire cache and returns
|
||||
* whether the operation was successful;
|
||||
* this needs to be defined by the driver
|
||||
*/
|
||||
public function created(string $key)
|
||||
abstract public function flush(): bool;
|
||||
|
||||
/**
|
||||
* Gets an item from the cache
|
||||
*
|
||||
* <code>
|
||||
* // get an item from the cache driver
|
||||
* $value = $cache->get('value');
|
||||
*
|
||||
* // return a default value if the requested item isn't cached
|
||||
* $value = $cache->get('value', 'default value');
|
||||
* </code>
|
||||
*/
|
||||
public function get(string $key, $default = null)
|
||||
{
|
||||
// get the Value object
|
||||
// get the Value
|
||||
$value = $this->retrieve($key);
|
||||
|
||||
// check for a valid Value object
|
||||
if (!is_a($value, 'Kirby\Cache\Value')) {
|
||||
return false;
|
||||
// check for a valid cache value
|
||||
if ($value instanceof Value === false) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
// return the expires timestamp
|
||||
return $value->created();
|
||||
// remove the item if it is expired
|
||||
if ($value->expires() > 0 && time() >= $value->expires()) {
|
||||
$this->remove($key);
|
||||
return $default;
|
||||
}
|
||||
|
||||
// return the pure value
|
||||
return $value->value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value by either getting it from the cache
|
||||
* or via the callback function which then is stored in
|
||||
* the cache for future retrieval. This method cannot be
|
||||
* used for `null` as value to be cached.
|
||||
* @since 3.8.0
|
||||
*/
|
||||
public function getOrSet(
|
||||
string $key,
|
||||
Closure $result,
|
||||
int $minutes = 0
|
||||
) {
|
||||
$value = $this->get($key);
|
||||
$result = $value ?? $result();
|
||||
|
||||
if ($value === null) {
|
||||
$this->set($key, $result, $minutes);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the prefix to the key if given
|
||||
*/
|
||||
protected function key(string $key): string
|
||||
{
|
||||
if (empty($this->options['prefix']) === false) {
|
||||
$key = $this->options['prefix'] . '/' . $key;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternate version for Cache::created($key)
|
||||
*
|
||||
* @param string $key
|
||||
* @return int|false
|
||||
*/
|
||||
public function modified(string $key)
|
||||
public function modified(string $key): int|false
|
||||
{
|
||||
return static::created($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an item exists in the cache
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
* Returns all passed cache options
|
||||
*/
|
||||
public function exists(string $key): bool
|
||||
public function options(): array
|
||||
{
|
||||
return $this->expired($key) === false;
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the cache and returns
|
||||
* whether the operation was successful;
|
||||
* this needs to be defined by the driver
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function remove(string $key): bool;
|
||||
|
||||
/**
|
||||
* Flushes the entire cache and returns
|
||||
* whether the operation was successful;
|
||||
* Internal method to retrieve the raw cache value;
|
||||
* needs to return a Value object or null if not found;
|
||||
* this needs to be defined by the driver
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function flush(): bool;
|
||||
abstract public function retrieve(string $key): Value|null;
|
||||
|
||||
/**
|
||||
* Returns all passed cache options
|
||||
* Writes an item to the cache for a given number of minutes and
|
||||
* returns whether the operation was successful;
|
||||
* this needs to be defined by the driver
|
||||
*
|
||||
* @return array
|
||||
* <code>
|
||||
* // put an item in the cache for 15 minutes
|
||||
* $cache->set('value', 'my value', 15);
|
||||
* </code>
|
||||
*/
|
||||
public function options(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
abstract public function set(string $key, $value, int $minutes = 0): bool;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,8 @@ class FileCache extends Cache
|
||||
{
|
||||
/**
|
||||
* Full root including prefix
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
protected string $root;
|
||||
|
||||
/**
|
||||
* Sets all parameters which are needed for the file cache
|
||||
@@ -44,6 +42,7 @@ class FileCache extends Cache
|
||||
|
||||
// build the full root including prefix
|
||||
$this->root = $this->options['root'];
|
||||
|
||||
if (empty($this->options['prefix']) === false) {
|
||||
$this->root .= '/' . $this->options['prefix'];
|
||||
}
|
||||
@@ -52,10 +51,17 @@ class FileCache extends Cache
|
||||
Dir::make($this->root, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cache is ready to
|
||||
* store values
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return is_writable($this->root) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full root including prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function root(): string
|
||||
{
|
||||
@@ -64,9 +70,6 @@ class FileCache extends Cache
|
||||
|
||||
/**
|
||||
* Returns the full path to a file for a given key
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function file(string $key): string
|
||||
{
|
||||
@@ -108,9 +111,9 @@ class FileCache extends Cache
|
||||
|
||||
if (isset($this->options['extension'])) {
|
||||
return $file . '.' . $this->options['extension'];
|
||||
} else {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,11 +124,6 @@ class FileCache extends Cache
|
||||
* // put an item in the cache for 15 minutes
|
||||
* $cache->set('value', 'my value', 15);
|
||||
* </code>
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $minutes
|
||||
* @return bool
|
||||
*/
|
||||
public function set(string $key, $value, int $minutes = 0): bool
|
||||
{
|
||||
@@ -137,11 +135,8 @@ class FileCache extends Cache
|
||||
/**
|
||||
* Internal method to retrieve the raw cache value;
|
||||
* needs to return a Value object or null if not found
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cache\Value|null
|
||||
*/
|
||||
public function retrieve(string $key)
|
||||
public function retrieve(string $key): Value|null
|
||||
{
|
||||
$file = $this->file($key);
|
||||
$value = F::read($file);
|
||||
@@ -153,11 +148,8 @@ class FileCache extends Cache
|
||||
* Checks when the cache has been created;
|
||||
* returns the creation timestamp on success
|
||||
* and false if the item does not exist
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function created(string $key)
|
||||
public function created(string $key): int|false
|
||||
{
|
||||
// use the modification timestamp
|
||||
// as indicator when the cache has been created/overwritten
|
||||
@@ -165,15 +157,12 @@ class FileCache extends Cache
|
||||
|
||||
// get the file for this cache key
|
||||
$file = $this->file($key);
|
||||
return file_exists($file) ? filemtime($this->file($key)) : false;
|
||||
return file_exists($file) ? filemtime($file) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function remove(string $key): bool
|
||||
{
|
||||
@@ -190,9 +179,6 @@ class FileCache extends Cache
|
||||
/**
|
||||
* Removes empty directories safely by checking each directory
|
||||
* up to the root directory
|
||||
*
|
||||
* @param string $dir
|
||||
* @return void
|
||||
*/
|
||||
protected function removeEmptyDirectories(string $dir): void
|
||||
{
|
||||
@@ -202,7 +188,13 @@ class FileCache extends Cache
|
||||
|
||||
// checks all directory segments until reaching the root directory
|
||||
while (Str::startsWith($dir, $this->root()) === true && $dir !== $this->root()) {
|
||||
$files = array_diff(scandir($dir) ?? [], ['.', '..']);
|
||||
$files = scandir($dir);
|
||||
|
||||
if ($files === false) {
|
||||
$files = []; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$files = array_diff($files, ['.', '..']);
|
||||
|
||||
if (empty($files) === true && Dir::remove($dir) === true) {
|
||||
// continue with the next level up
|
||||
@@ -212,7 +204,7 @@ class FileCache extends Cache
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) { // @codeCoverageIgnore
|
||||
} catch (Exception) { // @codeCoverageIgnore
|
||||
// silently stops the process
|
||||
}
|
||||
}
|
||||
@@ -220,12 +212,13 @@ class FileCache extends Cache
|
||||
/**
|
||||
* Flushes the entire cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush(): bool
|
||||
{
|
||||
if (Dir::remove($this->root) === true && Dir::make($this->root) === true) {
|
||||
if (
|
||||
Dir::remove($this->root) === true &&
|
||||
Dir::make($this->root) === true
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,14 @@ use Memcached as MemcachedExt;
|
||||
class MemCached extends Cache
|
||||
{
|
||||
/**
|
||||
* store for the memcache connection
|
||||
* @var \Memcached
|
||||
* Store for the memcache connection
|
||||
*/
|
||||
protected $connection;
|
||||
protected MemcachedExt $connection;
|
||||
|
||||
/**
|
||||
* Stores whether the connection was successful
|
||||
*/
|
||||
protected bool $enabled;
|
||||
|
||||
/**
|
||||
* Sets all parameters which are needed to connect to Memcached
|
||||
@@ -39,7 +43,19 @@ class MemCached extends Cache
|
||||
parent::__construct(array_merge($defaults, $options));
|
||||
|
||||
$this->connection = new MemcachedExt();
|
||||
$this->connection->addServer($this->options['host'], $this->options['port']);
|
||||
$this->enabled = $this->connection->addServer(
|
||||
$this->options['host'],
|
||||
$this->options['port']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cache is ready to
|
||||
* store values
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,35 +66,28 @@ class MemCached extends Cache
|
||||
* // put an item in the cache for 15 minutes
|
||||
* $cache->set('value', 'my value', 15);
|
||||
* </code>
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $minutes
|
||||
* @return bool
|
||||
*/
|
||||
public function set(string $key, $value, int $minutes = 0): bool
|
||||
{
|
||||
return $this->connection->set($this->key($key), (new Value($value, $minutes))->toJson(), $this->expiration($minutes));
|
||||
$key = $this->key($key);
|
||||
$value = (new Value($value, $minutes))->toJson();
|
||||
$expires = $this->expiration($minutes);
|
||||
return $this->connection->set($key, $value, $expires);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to retrieve the raw cache value;
|
||||
* needs to return a Value object or null if not found
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cache\Value|null
|
||||
*/
|
||||
public function retrieve(string $key)
|
||||
public function retrieve(string $key): Value|null
|
||||
{
|
||||
return Value::fromJson($this->connection->get($this->key($key)));
|
||||
$value = $this->connection->get($this->key($key));
|
||||
return Value::fromJson($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function remove(string $key): bool
|
||||
{
|
||||
@@ -89,8 +98,6 @@ class MemCached extends Cache
|
||||
* Flushes the entire cache and returns
|
||||
* whether the operation was successful;
|
||||
* WARNING: Memcached only supports flushing the whole cache at once!
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush(): bool
|
||||
{
|
||||
|
||||
@@ -15,9 +15,17 @@ class MemoryCache extends Cache
|
||||
{
|
||||
/**
|
||||
* Cache data
|
||||
* @var array
|
||||
*/
|
||||
protected $store = [];
|
||||
protected array $store = [];
|
||||
|
||||
/**
|
||||
* Returns whether the cache is ready to
|
||||
* store values
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an item to the cache for a given number of minutes and
|
||||
@@ -27,11 +35,6 @@ class MemoryCache extends Cache
|
||||
* // put an item in the cache for 15 minutes
|
||||
* $cache->set('value', 'my value', 15);
|
||||
* </code>
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $minutes
|
||||
* @return bool
|
||||
*/
|
||||
public function set(string $key, $value, int $minutes = 0): bool
|
||||
{
|
||||
@@ -42,11 +45,8 @@ class MemoryCache extends Cache
|
||||
/**
|
||||
* Internal method to retrieve the raw cache value;
|
||||
* needs to return a Value object or null if not found
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cache\Value|null
|
||||
*/
|
||||
public function retrieve(string $key)
|
||||
public function retrieve(string $key): Value|null
|
||||
{
|
||||
return $this->store[$key] ?? null;
|
||||
}
|
||||
@@ -54,25 +54,20 @@ class MemoryCache extends Cache
|
||||
/**
|
||||
* Removes an item from the cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function remove(string $key): bool
|
||||
{
|
||||
if (isset($this->store[$key])) {
|
||||
unset($this->store[$key]);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the entire cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush(): bool
|
||||
{
|
||||
|
||||
@@ -13,6 +13,15 @@ namespace Kirby\Cache;
|
||||
*/
|
||||
class NullCache extends Cache
|
||||
{
|
||||
/**
|
||||
* Returns whether the cache is ready to
|
||||
* store values
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an item to the cache for a given number of minutes and
|
||||
* returns whether the operation was successful
|
||||
@@ -21,11 +30,6 @@ class NullCache extends Cache
|
||||
* // put an item in the cache for 15 minutes
|
||||
* $cache->set('value', 'my value', 15);
|
||||
* </code>
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $minutes
|
||||
* @return bool
|
||||
*/
|
||||
public function set(string $key, $value, int $minutes = 0): bool
|
||||
{
|
||||
@@ -35,11 +39,8 @@ class NullCache extends Cache
|
||||
/**
|
||||
* Internal method to retrieve the raw cache value;
|
||||
* needs to return a Value object or null if not found
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cache\Value|null
|
||||
*/
|
||||
public function retrieve(string $key)
|
||||
public function retrieve(string $key): Value|null
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -47,9 +48,6 @@ class NullCache extends Cache
|
||||
/**
|
||||
* Removes an item from the cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function remove(string $key): bool
|
||||
{
|
||||
@@ -59,8 +57,6 @@ class NullCache extends Cache
|
||||
/**
|
||||
* Flushes the entire cache and returns
|
||||
* whether the operation was successful
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush(): bool
|
||||
{
|
||||
|
||||
+16
-32
@@ -19,7 +19,6 @@ class Value
|
||||
{
|
||||
/**
|
||||
* Cached value
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
@@ -27,35 +26,30 @@ class Value
|
||||
* the number of minutes until the value expires
|
||||
* @todo Rename this property to $expiry to reflect
|
||||
* both minutes and absolute timestamps
|
||||
* @var int
|
||||
*/
|
||||
protected $minutes;
|
||||
protected int $minutes;
|
||||
|
||||
/**
|
||||
* Creation timestamp
|
||||
* @var int
|
||||
*/
|
||||
protected $created;
|
||||
protected int $created;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param int $minutes the number of minutes until the value expires
|
||||
* or an absolute UNIX timestamp
|
||||
* @param int $created the UNIX timestamp when the value has been created
|
||||
*/
|
||||
public function __construct($value, int $minutes = 0, int $created = null)
|
||||
public function __construct($value, int $minutes = 0, int|null $created = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->minutes = $minutes ?? 0;
|
||||
$this->minutes = $minutes;
|
||||
$this->created = $created ?? time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the creation date as UNIX timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function created(): int
|
||||
{
|
||||
@@ -65,10 +59,8 @@ class Value
|
||||
/**
|
||||
* Returns the expiration date as UNIX timestamp or
|
||||
* null if the value never expires
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function expires(): ?int
|
||||
public function expires(): int|null
|
||||
{
|
||||
// 0 = keep forever
|
||||
if ($this->minutes === 0) {
|
||||
@@ -85,41 +77,37 @@ class Value
|
||||
|
||||
/**
|
||||
* Creates a value object from an array
|
||||
*
|
||||
* @param array $array
|
||||
* @return static
|
||||
*/
|
||||
public static function fromArray(array $array)
|
||||
public static function fromArray(array $array): static
|
||||
{
|
||||
return new static($array['value'] ?? null, $array['minutes'] ?? 0, $array['created'] ?? null);
|
||||
return new static(
|
||||
$array['value'] ?? null,
|
||||
$array['minutes'] ?? 0,
|
||||
$array['created'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a value object from a JSON string;
|
||||
* returns null on error
|
||||
*
|
||||
* @param string $json
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromJson(string $json)
|
||||
public static function fromJson(string $json): static|null
|
||||
{
|
||||
try {
|
||||
$array = json_decode($json, true);
|
||||
|
||||
if (is_array($array)) {
|
||||
if (is_array($array) === true) {
|
||||
return static::fromArray($array);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
|
||||
return null;
|
||||
} catch (Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the object to a JSON string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson(): string
|
||||
{
|
||||
@@ -128,8 +116,6 @@ class Value
|
||||
|
||||
/**
|
||||
* Converts the object to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -142,8 +128,6 @@ class Value
|
||||
|
||||
/**
|
||||
* Returns the pure value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function value()
|
||||
{
|
||||
|
||||
+30
-65
@@ -5,6 +5,7 @@ namespace Kirby\Cms;
|
||||
use Kirby\Api\Api as BaseApi;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Session\Session;
|
||||
|
||||
/**
|
||||
* Api
|
||||
@@ -25,38 +26,30 @@ class Api extends BaseApi
|
||||
/**
|
||||
* Execute an API call for the given path,
|
||||
* request method and optional request data
|
||||
*
|
||||
* @param string|null $path
|
||||
* @param string $method
|
||||
* @param array $requestData
|
||||
* @return mixed
|
||||
*/
|
||||
public function call(string $path = null, string $method = 'GET', array $requestData = [])
|
||||
{
|
||||
public function call(
|
||||
string|null $path = null,
|
||||
string $method = 'GET',
|
||||
array $requestData = []
|
||||
) {
|
||||
$this->setRequestMethod($method);
|
||||
$this->setRequestData($requestData);
|
||||
|
||||
$this->kirby->setCurrentLanguage($this->language());
|
||||
|
||||
$allowImpersonation = $this->kirby()->option('api.allowImpersonation', false);
|
||||
if ($user = $this->kirby->user(null, $allowImpersonation)) {
|
||||
$translation = $user->language();
|
||||
} else {
|
||||
$translation = $this->kirby->panelLanguage();
|
||||
}
|
||||
|
||||
$translation = $this->kirby->user(null, $allowImpersonation)?->language();
|
||||
$translation ??= $this->kirby->panelLanguage();
|
||||
$this->kirby->setCurrentTranslation($translation);
|
||||
|
||||
return parent::call($path, $method, $requestData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $model
|
||||
* @param string $name
|
||||
* @param string|null $path
|
||||
* @return mixed
|
||||
* @throws \Kirby\Exception\NotFoundException if the field type cannot be found or the field cannot be loaded
|
||||
*/
|
||||
public function fieldApi($model, string $name, string $path = null)
|
||||
public function fieldApi($model, string $name, string|null $path = null)
|
||||
{
|
||||
$field = Form::for($model)->field($name);
|
||||
|
||||
@@ -75,11 +68,9 @@ class Api extends BaseApi
|
||||
* parent path and filename
|
||||
*
|
||||
* @param string|null $path Path to file's parent model
|
||||
* @param string $filename Filename
|
||||
* @return \Kirby\Cms\File|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the file cannot be found
|
||||
*/
|
||||
public function file(string $path = null, string $filename)
|
||||
public function file(string|null $path = null, string $filename): File|null
|
||||
{
|
||||
return Find::file($path, $filename);
|
||||
}
|
||||
@@ -88,31 +79,26 @@ class Api extends BaseApi
|
||||
* Returns the model's object for the given path
|
||||
*
|
||||
* @param string $path Path to parent model
|
||||
* @return \Kirby\Cms\Model|null
|
||||
* @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid
|
||||
* @throws \Kirby\Exception\NotFoundException if the model cannot be found
|
||||
*/
|
||||
public function parent(string $path)
|
||||
public function parent(string $path): Model|null
|
||||
{
|
||||
return Find::parent($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kirby instance
|
||||
*
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
public function kirby()
|
||||
public function kirby(): App
|
||||
{
|
||||
return $this->kirby;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language request header
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function language(): ?string
|
||||
public function language(): string|null
|
||||
{
|
||||
return $this->requestQuery('language') ?? $this->requestHeaders('x-language');
|
||||
}
|
||||
@@ -121,10 +107,9 @@ class Api extends BaseApi
|
||||
* Returns the page object for the given id
|
||||
*
|
||||
* @param string $id Page's id
|
||||
* @return \Kirby\Cms\Page|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the page cannot be found
|
||||
*/
|
||||
public function page(string $id)
|
||||
public function page(string $id): Page|null
|
||||
{
|
||||
return Find::page($id);
|
||||
}
|
||||
@@ -133,39 +118,26 @@ class Api extends BaseApi
|
||||
* Returns the subpages for the given
|
||||
* parent. The subpages can be filtered
|
||||
* by status (draft, listed, unlisted, published, all)
|
||||
*
|
||||
* @param string|null $parentId
|
||||
* @param string|null $status
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function pages(string $parentId = null, string $status = null)
|
||||
public function pages(string|null $parentId = null, string|null $status = null): Pages
|
||||
{
|
||||
$parent = $parentId === null ? $this->site() : $this->page($parentId);
|
||||
|
||||
switch ($status) {
|
||||
case 'all':
|
||||
return $parent->childrenAndDrafts();
|
||||
case 'draft':
|
||||
case 'drafts':
|
||||
return $parent->drafts();
|
||||
case 'listed':
|
||||
return $parent->children()->listed();
|
||||
case 'unlisted':
|
||||
return $parent->children()->unlisted();
|
||||
case 'published':
|
||||
default:
|
||||
return $parent->children();
|
||||
}
|
||||
return match ($status) {
|
||||
'all' => $parent->childrenAndDrafts(),
|
||||
'draft', 'drafts' => $parent->drafts(),
|
||||
'listed' => $parent->children()->listed(),
|
||||
'unlisted' => $parent->children()->unlisted(),
|
||||
'published' => $parent->children(),
|
||||
default => $parent->children()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for direct subpages of the
|
||||
* given parent
|
||||
*
|
||||
* @param string|null $parent
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function searchPages(string $parent = null)
|
||||
public function searchPages(string|null $parent = null): Pages
|
||||
{
|
||||
$pages = $this->pages($parent, $this->requestQuery('status'));
|
||||
|
||||
@@ -180,9 +152,8 @@ class Api extends BaseApi
|
||||
* Returns the current Session instance
|
||||
*
|
||||
* @param array $options Additional options, see the session component
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function session(array $options = [])
|
||||
public function session(array $options = []): Session
|
||||
{
|
||||
return $this->kirby->session(array_merge([
|
||||
'detect' => true
|
||||
@@ -192,10 +163,9 @@ class Api extends BaseApi
|
||||
/**
|
||||
* Setter for the parent Kirby instance
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return $this
|
||||
*/
|
||||
protected function setKirby(App $kirby)
|
||||
protected function setKirby(App $kirby): static
|
||||
{
|
||||
$this->kirby = $kirby;
|
||||
return $this;
|
||||
@@ -203,10 +173,8 @@ class Api extends BaseApi
|
||||
|
||||
/**
|
||||
* Returns the site object
|
||||
*
|
||||
* @return \Kirby\Cms\Site
|
||||
*/
|
||||
public function site()
|
||||
public function site(): Site
|
||||
{
|
||||
return $this->kirby->site();
|
||||
}
|
||||
@@ -217,10 +185,9 @@ class Api extends BaseApi
|
||||
* id is passed
|
||||
*
|
||||
* @param string|null $id User's id
|
||||
* @return \Kirby\Cms\User|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the user for the given id cannot be found
|
||||
*/
|
||||
public function user(string $id = null)
|
||||
public function user(string|null $id = null): User|null
|
||||
{
|
||||
try {
|
||||
return Find::user($id);
|
||||
@@ -235,10 +202,8 @@ class Api extends BaseApi
|
||||
|
||||
/**
|
||||
* Returns the users collection
|
||||
*
|
||||
* @return \Kirby\Cms\Users
|
||||
*/
|
||||
public function users()
|
||||
public function users(): Users
|
||||
{
|
||||
return $this->kirby->users();
|
||||
}
|
||||
|
||||
+98
-107
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\ErrorPageException;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
@@ -23,6 +25,7 @@ use Kirby\Toolkit\Config;
|
||||
use Kirby\Toolkit\Controller;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Uuid;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -435,7 +438,7 @@ class App
|
||||
|
||||
$salt = $this->option('content.salt', $default);
|
||||
|
||||
if (is_a($salt, 'Closure') === true) {
|
||||
if ($salt instanceof Closure) {
|
||||
$salt = $salt($model);
|
||||
}
|
||||
|
||||
@@ -496,7 +499,11 @@ class App
|
||||
|
||||
// registry controller
|
||||
if ($controller = $this->extension('controllers', $name)) {
|
||||
return is_a($controller, 'Kirby\Toolkit\Controller') ? $controller : new Controller($controller);
|
||||
if ($controller instanceof Controller) {
|
||||
return $controller;
|
||||
}
|
||||
|
||||
return new Controller($controller);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -520,7 +527,7 @@ class App
|
||||
* @param string|null $check Pass a token here to compare it to the one in the session
|
||||
* @return string|bool Either the token or a boolean check result
|
||||
*/
|
||||
public function csrf(?string $check = null)
|
||||
public function csrf(string|null $check = null)
|
||||
{
|
||||
$session = $this->session();
|
||||
|
||||
@@ -633,28 +640,26 @@ class App
|
||||
*/
|
||||
public function file(string $path, $parent = null, bool $drafts = true)
|
||||
{
|
||||
// find by global UUID
|
||||
if (Uuid::is($path, 'file') === true) {
|
||||
// prefer files of parent, when parent given
|
||||
return Uuid::for($path, $parent?->files())->model();
|
||||
}
|
||||
|
||||
$parent = $parent ?? $this->site();
|
||||
$id = dirname($path);
|
||||
$filename = basename($path);
|
||||
|
||||
if (is_a($parent, 'Kirby\Cms\User') === true) {
|
||||
if ($parent instanceof User) {
|
||||
return $parent->file($filename);
|
||||
}
|
||||
|
||||
if (is_a($parent, 'Kirby\Cms\File') === true) {
|
||||
if ($parent instanceof File) {
|
||||
$parent = $parent->parent();
|
||||
}
|
||||
|
||||
if ($id === '.') {
|
||||
if ($file = $parent->file($filename)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
if ($file = $this->site()->file($filename)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return null;
|
||||
return $parent->file($filename) ?? $this->site()->file($filename);
|
||||
}
|
||||
|
||||
if ($page = $this->page($id, $parent, $drafts)) {
|
||||
@@ -680,7 +685,7 @@ class App
|
||||
*
|
||||
* @todo merge with App::file()
|
||||
*/
|
||||
public function image(?string $path = null)
|
||||
public function image(string|null $path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
return $this->site()->page()->image();
|
||||
@@ -693,23 +698,13 @@ class App
|
||||
$uri = null;
|
||||
}
|
||||
|
||||
switch ($uri) {
|
||||
case '/':
|
||||
$parent = $this->site();
|
||||
break;
|
||||
case null:
|
||||
$parent = $this->site()->page();
|
||||
break;
|
||||
default:
|
||||
$parent = $this->site()->page($uri);
|
||||
break;
|
||||
}
|
||||
$parent = match ($uri) {
|
||||
'/' => $this->site(),
|
||||
null => $this->site()->page(),
|
||||
default => $this->site()->page($uri)
|
||||
};
|
||||
|
||||
if ($parent) {
|
||||
return $parent->image($filename);
|
||||
}
|
||||
|
||||
return null;
|
||||
return $parent?->image($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -718,18 +713,19 @@ class App
|
||||
* @param \Kirby\Cms\App|null $instance
|
||||
* @param bool $lazy If `true`, the instance is only returned if already existing
|
||||
* @return static|null
|
||||
* @psalm-return ($lazy is false ? static : static|null)
|
||||
*/
|
||||
public static function instance(self $instance = null, bool $lazy = false)
|
||||
{
|
||||
if ($instance === null) {
|
||||
if ($lazy === true) {
|
||||
return static::$instance;
|
||||
} else {
|
||||
return static::$instance ?? new static();
|
||||
}
|
||||
if ($instance !== null) {
|
||||
return static::$instance = $instance;
|
||||
}
|
||||
|
||||
return static::$instance = $instance;
|
||||
if ($lazy === true) {
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
return static::$instance ?? new static();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -746,8 +742,8 @@ class App
|
||||
$response = $this->response();
|
||||
|
||||
// any direct exception will be turned into an error page
|
||||
if (is_a($input, 'Throwable') === true) {
|
||||
if (is_a($input, 'Kirby\Exception\Exception') === true) {
|
||||
if ($input instanceof Throwable) {
|
||||
if ($input instanceof Exception) {
|
||||
$code = $input->getHttpCode();
|
||||
} else {
|
||||
$code = $input->getCode();
|
||||
@@ -778,7 +774,7 @@ class App
|
||||
}
|
||||
|
||||
// (Modified) global response configuration, e.g. in routes
|
||||
if (is_a($input, 'Kirby\Cms\Responder') === true) {
|
||||
if ($input instanceof Responder) {
|
||||
// return the passed object unmodified (without injecting headers
|
||||
// from the global object) to allow a complete response override
|
||||
// https://github.com/getkirby/kirby/pull/4144#issuecomment-1034766726
|
||||
@@ -786,37 +782,41 @@ class App
|
||||
}
|
||||
|
||||
// Responses
|
||||
if (is_a($input, 'Kirby\Http\Response') === true) {
|
||||
if ($input instanceof Response) {
|
||||
$data = $input->toArray();
|
||||
|
||||
// inject headers from the global response configuration
|
||||
// lazily (only if they are not already set);
|
||||
// the case-insensitive nature of headers will be
|
||||
// handled by PHP's `header()` function
|
||||
$data['headers'] = array_merge($response->headers(), $data['headers']);
|
||||
$data['headers'] = array_merge(
|
||||
$response->headers(),
|
||||
$data['headers']
|
||||
);
|
||||
|
||||
return new Response($data);
|
||||
}
|
||||
|
||||
// Pages
|
||||
if (is_a($input, 'Kirby\Cms\Page')) {
|
||||
if ($input instanceof Page) {
|
||||
try {
|
||||
$html = $input->render();
|
||||
} catch (ErrorPageException $e) {
|
||||
return $this->io($e);
|
||||
}
|
||||
|
||||
if ($input->isErrorPage() === true) {
|
||||
if ($response->code() === null) {
|
||||
$response->code(404);
|
||||
}
|
||||
if (
|
||||
$input->isErrorPage() === true &&
|
||||
$response->code() === null
|
||||
) {
|
||||
$response->code(404);
|
||||
}
|
||||
|
||||
return $response->send($html);
|
||||
}
|
||||
|
||||
// Files
|
||||
if (is_a($input, 'Kirby\Cms\File')) {
|
||||
if ($input instanceof File) {
|
||||
return $response->redirect($input->mediaUrl(), 307)->send();
|
||||
}
|
||||
|
||||
@@ -844,7 +844,7 @@ class App
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function kirbytag($type, ?string $value = null, array $attr = [], array $data = []): string
|
||||
public function kirbytag($type, string|null $value = null, array $attr = [], array $data = []): string
|
||||
{
|
||||
if (is_array($type) === true) {
|
||||
$kirbytag = $type;
|
||||
@@ -895,24 +895,13 @@ class App
|
||||
* @internal
|
||||
* @param string|null $text
|
||||
* @param array $options
|
||||
* @param bool $inline (deprecated: use $options['markdown']['inline'] instead)
|
||||
* @return string
|
||||
* @todo remove $inline parameter in in 3.8.0
|
||||
*/
|
||||
public function kirbytext(string $text = null, array $options = [], bool $inline = false): string
|
||||
public function kirbytext(string $text = null, array $options = []): string
|
||||
{
|
||||
// warning for deprecated fourth parameter
|
||||
// @codeCoverageIgnoreStart
|
||||
if (func_num_args() === 3) {
|
||||
Helpers::deprecated('Cms\App::kirbytext(): the $inline parameter is deprecated and will be removed in Kirby 3.8.0. Use $options[\'markdown\'][\'inline\'] instead.');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$options['markdown']['inline'] ??= $inline;
|
||||
|
||||
$text = $this->apply('kirbytext:before', compact('text'), 'text');
|
||||
$text = $this->kirbytags($text, $options);
|
||||
$text = $this->markdown($text, $options['markdown']);
|
||||
$text = $this->markdown($text, $options['markdown'] ?? []);
|
||||
|
||||
if ($this->option('smartypants', false) !== false) {
|
||||
$text = $this->smartypants($text);
|
||||
@@ -953,22 +942,15 @@ class App
|
||||
* @param string|null $languageCode
|
||||
* @return string|null
|
||||
*/
|
||||
public function languageCode(string $languageCode = null): ?string
|
||||
public function languageCode(string $languageCode = null): string|null
|
||||
{
|
||||
if ($language = $this->language($languageCode)) {
|
||||
return $language->code();
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->language($languageCode)?->code();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available site languages
|
||||
*
|
||||
* @param bool
|
||||
* @return \Kirby\Cms\Languages
|
||||
*/
|
||||
public function languages(bool $clone = true)
|
||||
public function languages(bool $clone = true): Languages
|
||||
{
|
||||
if ($this->languages !== null) {
|
||||
return $clone === true ? clone $this->languages : $this->languages;
|
||||
@@ -1006,34 +988,18 @@ class App
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $text
|
||||
* @param bool|array $options Boolean inline value is deprecated, use `['inline' => true]` instead
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @todo remove boolean $options in in 3.8.0
|
||||
*/
|
||||
public function markdown(string $text = null, $options = null): string
|
||||
public function markdown(string $text = null, array $options = null): string
|
||||
{
|
||||
// support for the old syntax to enable inline mode as second argument
|
||||
// @codeCoverageIgnoreStart
|
||||
if (is_bool($options) === true) {
|
||||
Helpers::deprecated('Cms\App::markdown(): Passing a boolean as second parameter has been deprecated and won\'t be supported anymore in Kirby 3.8.0. Instead pass array with the key "inline" set to true or false.');
|
||||
|
||||
$options = [
|
||||
'inline' => $options
|
||||
];
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// merge global options with local options
|
||||
$options = array_merge(
|
||||
$this->options['markdown'] ?? [],
|
||||
(array)$options
|
||||
);
|
||||
|
||||
// TODO: remove passing the $inline parameter in 3.8.0
|
||||
// $options['inline'] is set to `false` to avoid the deprecation
|
||||
// warning in the component; this can also be removed in 3.8.0
|
||||
$inline = $options['inline'] ??= false;
|
||||
return ($this->component('markdown'))($this, $text, $options, $inline);
|
||||
return ($this->component('markdown'))($this, $text, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1111,19 +1077,27 @@ class App
|
||||
*/
|
||||
protected function optionsFromEnvironment(array $props = []): array
|
||||
{
|
||||
$globalUrl = $this->options['url'] ?? null;
|
||||
$root = $this->root('config');
|
||||
|
||||
// create the environment based on the URL setup
|
||||
// first load `config/env.php` to access its `url` option
|
||||
$envOptions = F::load($root . '/env.php', []);
|
||||
|
||||
// use the option from the main `config.php`,
|
||||
// but allow the `env.php` to override it
|
||||
$globalUrl = $envOptions['url'] ?? $this->options['url'] ?? null;
|
||||
|
||||
// create the URL setup based on hostname and server IP address
|
||||
$this->environment = new Environment([
|
||||
'allowed' => $globalUrl,
|
||||
'cli' => $props['cli'] ?? null,
|
||||
], $props['server'] ?? null);
|
||||
|
||||
// merge into one clean options array
|
||||
$options = $this->environment()->options($this->root('config'));
|
||||
$this->options = array_replace_recursive($this->options, $options);
|
||||
// merge into one clean options array;
|
||||
// the `env.php` options always override everything else
|
||||
$hostAddrOptions = $this->environment()->options($root);
|
||||
$this->options = array_replace_recursive($this->options, $hostAddrOptions, $envOptions);
|
||||
|
||||
// reload the environment if the environment config has overridden
|
||||
// reload the environment if the host/address config has overridden
|
||||
// the `url` option; this ensures that the base URL is correct
|
||||
$envUrl = $this->options['url'] ?? null;
|
||||
if ($envUrl !== $globalUrl) {
|
||||
@@ -1201,12 +1175,17 @@ class App
|
||||
* @param bool $drafts
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function page(?string $id = null, $parent = null, bool $drafts = true)
|
||||
public function page(string|null $id = null, $parent = null, bool $drafts = true)
|
||||
{
|
||||
if ($id === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// find by global UUID
|
||||
if (Uuid::is($id, 'page') === true) {
|
||||
return Uuid::for($id, $parent?->childrenAndDrafts())->model();
|
||||
}
|
||||
|
||||
$parent = $parent ?? $this->site();
|
||||
|
||||
if ($page = $parent->find($id)) {
|
||||
@@ -1252,6 +1231,10 @@ class App
|
||||
*/
|
||||
public function render(string $path = null, string $method = null)
|
||||
{
|
||||
if (($_ENV['KIRBY_RENDER'] ?? true) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->io($this->call($path, $method));
|
||||
}
|
||||
|
||||
@@ -1308,7 +1291,10 @@ class App
|
||||
|
||||
// search for a draft if the page cannot be found
|
||||
if (!$page && $draft = $site->draft($path)) {
|
||||
if ($this->user() || $draft->isVerified($this->request()->get('token'))) {
|
||||
if (
|
||||
$this->user() ||
|
||||
$draft->isVerified($this->request()->get('token'))
|
||||
) {
|
||||
$page = $draft;
|
||||
}
|
||||
}
|
||||
@@ -1335,7 +1321,7 @@ class App
|
||||
}
|
||||
|
||||
return $response->body($output);
|
||||
} catch (NotFoundException $e) {
|
||||
} catch (NotFoundException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1378,7 +1364,7 @@ class App
|
||||
* @param string $type
|
||||
* @return string|null
|
||||
*/
|
||||
public function root(string $type = 'index'): ?string
|
||||
public function root(string $type = 'index'): string|null
|
||||
{
|
||||
return $this->roots->__get($type);
|
||||
}
|
||||
@@ -1572,12 +1558,16 @@ class App
|
||||
* @deprecated 3.7.0 Use `$kirby->environment()` instead
|
||||
*
|
||||
* @return \Kirby\Http\Environment
|
||||
* @todo Start throwing deprecation warnings in 3.8.0
|
||||
* @deprecated Will be removed in Kirby 3.9.0
|
||||
* @todo Remove in 3.9.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function server()
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
Helpers::deprecated('$kirby->server() has been deprecated and will be removed in Kirby 3.9.0. Use $kirby->environment() instead.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return $this->environment();
|
||||
}
|
||||
|
||||
@@ -1628,13 +1618,13 @@ class App
|
||||
* Uses the snippet component to create
|
||||
* and return a template snippet
|
||||
*
|
||||
* @internal
|
||||
* @param mixed $name
|
||||
* @param array|object $data Variables or an object that becomes `$item`
|
||||
* @param bool $return On `false`, directly echo the snippet
|
||||
* @return string|null
|
||||
* @psalm-return ($return is true ? string : null)
|
||||
*/
|
||||
public function snippet($name, $data = [], bool $return = true): ?string
|
||||
public function snippet($name, $data = [], bool $return = true): string|null
|
||||
{
|
||||
if (is_object($data) === true) {
|
||||
$data = ['item' => $data];
|
||||
@@ -1740,6 +1730,7 @@ class App
|
||||
* @param string $type
|
||||
* @param bool $object If set to `true`, the URL is converted to an object
|
||||
* @return string|\Kirby\Http\Uri|null
|
||||
* @psalm-return ($object is false ? string|null : \Kirby\Http\Uri)
|
||||
*/
|
||||
public function url(string $type = 'index', bool $object = false)
|
||||
{
|
||||
@@ -1777,11 +1768,11 @@ class App
|
||||
* @return string|null
|
||||
* @throws \Kirby\Exception\LogicException if the Kirby version cannot be detected
|
||||
*/
|
||||
public static function version(): ?string
|
||||
public static function version(): string|null
|
||||
{
|
||||
try {
|
||||
return static::$version = static::$version ?? Data::read(dirname(__DIR__, 2) . '/composer.json')['version'] ?? null;
|
||||
} catch (Throwable $e) {
|
||||
} catch (Throwable) {
|
||||
throw new LogicException('The Kirby version cannot be detected. The composer.json is probably missing or not readable.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Cache\Cache;
|
||||
use Kirby\Cache\NullCache;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
|
||||
@@ -54,7 +55,7 @@ trait AppCaches
|
||||
$cache = new $className($options);
|
||||
|
||||
// check if it is a usable cache object
|
||||
if (is_a($cache, 'Kirby\Cache\Cache') !== true) {
|
||||
if ($cache instanceof Cache === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'app.invalid.cacheType',
|
||||
'data' => ['type' => $type]
|
||||
@@ -72,7 +73,8 @@ trait AppCaches
|
||||
*/
|
||||
protected function cacheOptions(string $key): array
|
||||
{
|
||||
$options = $this->option($this->cacheOptionsKey($key), false);
|
||||
$options = $this->option($this->cacheOptionsKey($key), null);
|
||||
$options ??= $this->core()->caches()[$key] ?? false;
|
||||
|
||||
if ($options === false) {
|
||||
return [
|
||||
@@ -94,9 +96,9 @@ trait AppCaches
|
||||
|
||||
if ($options === true) {
|
||||
return $defaults;
|
||||
} else {
|
||||
return array_merge($defaults, $options);
|
||||
}
|
||||
|
||||
return array_merge($defaults, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Throwable;
|
||||
use Whoops\Handler\CallbackHandler;
|
||||
use Whoops\Handler\Handler;
|
||||
use Whoops\Handler\PlainTextHandler;
|
||||
@@ -84,7 +87,7 @@ trait AppErrors
|
||||
$handler = new CallbackHandler(function ($exception, $inspector, $run) {
|
||||
$fatal = $this->option('fatal');
|
||||
|
||||
if (is_a($fatal, 'Closure') === true) {
|
||||
if ($fatal instanceof Closure) {
|
||||
echo $fatal($this, $exception);
|
||||
} else {
|
||||
include $this->root('kirby') . '/views/fatal.php';
|
||||
@@ -109,11 +112,11 @@ trait AppErrors
|
||||
protected function handleJsonErrors(): void
|
||||
{
|
||||
$handler = new CallbackHandler(function ($exception, $inspector, $run) {
|
||||
if (is_a($exception, 'Kirby\Exception\Exception') === true) {
|
||||
if ($exception instanceof Exception) {
|
||||
$httpCode = $exception->getHttpCode();
|
||||
$code = $exception->getCode();
|
||||
$details = $exception->getDetails();
|
||||
} elseif (is_a($exception, '\Throwable') === true) {
|
||||
} elseif ($exception instanceof Throwable) {
|
||||
$httpCode = 500;
|
||||
$code = $exception->getCode();
|
||||
$details = null;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user