Update to Kirby 5
This commit is contained in:
parent
5d9979fca8
commit
0fefc5e2e1
472 changed files with 30853 additions and 10301 deletions
|
@ -75,7 +75,6 @@ return [
|
|||
// Any of these might be removed at any point in the future
|
||||
'kirby\cms\asset' => 'Kirby\Filesystem\Asset',
|
||||
'kirby\cms\content' => 'Kirby\Content\Content',
|
||||
'kirby\cms\contenttranslation' => 'Kirby\Content\ContentTranslation',
|
||||
'kirby\cms\dir' => 'Kirby\Filesystem\Dir',
|
||||
'kirby\cms\filename' => 'Kirby\Filesystem\Filename',
|
||||
'kirby\cms\filefoundation' => 'Kirby\Filesystem\IsFile',
|
||||
|
@ -83,6 +82,9 @@ return [
|
|||
'kirby\cms\form' => 'Kirby\Form\Form',
|
||||
'kirby\cms\kirbytag' => 'Kirby\Text\KirbyTag',
|
||||
'kirby\cms\kirbytags' => 'Kirby\Text\KirbyTags',
|
||||
'kirby\cms\plugin' => 'Kirby\Plugin\Plugin',
|
||||
'kirby\cms\pluginasset' => 'Kirby\Plugin\Asset',
|
||||
'kirby\cms\pluginassets' => 'Kirby\Plugin\Assets',
|
||||
'kirby\cms\template' => 'Kirby\Template\Template',
|
||||
'kirby\form\options' => 'Kirby\Option\Options',
|
||||
'kirby\form\optionsapi' => 'Kirby\Option\OptionsApi',
|
||||
|
|
|
@ -11,17 +11,17 @@ return function () {
|
|||
$auth->type($allowImpersonation) === 'session' &&
|
||||
$auth->csrf() === false
|
||||
) {
|
||||
throw new AuthException('Unauthenticated');
|
||||
throw new AuthException(message: 'Unauthenticated');
|
||||
}
|
||||
|
||||
// get user from session or basic auth
|
||||
if ($user = $auth->user(null, $allowImpersonation)) {
|
||||
if ($user->role()->permissions()->for('access', 'panel') === false) {
|
||||
throw new AuthException(['key' => 'access.panel']);
|
||||
throw new AuthException(key: 'access.panel');
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
throw new AuthException('Unauthenticated');
|
||||
throw new AuthException(message: 'Unauthenticated');
|
||||
};
|
||||
|
|
|
@ -4,25 +4,25 @@
|
|||
* Api Routes Definitions
|
||||
*/
|
||||
return function ($kirby) {
|
||||
$routes = array_merge(
|
||||
include __DIR__ . '/routes/auth.php',
|
||||
include __DIR__ . '/routes/pages.php',
|
||||
include __DIR__ . '/routes/roles.php',
|
||||
include __DIR__ . '/routes/site.php',
|
||||
include __DIR__ . '/routes/users.php',
|
||||
include __DIR__ . '/routes/files.php',
|
||||
include __DIR__ . '/routes/lock.php',
|
||||
include __DIR__ . '/routes/system.php',
|
||||
include __DIR__ . '/routes/translations.php'
|
||||
);
|
||||
$routes = [
|
||||
...include __DIR__ . '/routes/auth.php',
|
||||
...include __DIR__ . '/routes/changes.php',
|
||||
...include __DIR__ . '/routes/pages.php',
|
||||
...include __DIR__ . '/routes/roles.php',
|
||||
...include __DIR__ . '/routes/site.php',
|
||||
...include __DIR__ . '/routes/users.php',
|
||||
...include __DIR__ . '/routes/files.php',
|
||||
...include __DIR__ . '/routes/system.php',
|
||||
...include __DIR__ . '/routes/translations.php'
|
||||
];
|
||||
|
||||
// only add the language routes if the
|
||||
// multi language setup is activated
|
||||
if ($kirby->option('languages', false) !== false) {
|
||||
$routes = array_merge(
|
||||
$routes,
|
||||
include __DIR__ . '/routes/languages.php'
|
||||
);
|
||||
$routes = [
|
||||
...$routes,
|
||||
...include __DIR__ . '/routes/languages.php'
|
||||
];
|
||||
}
|
||||
|
||||
return $routes;
|
||||
|
|
|
@ -15,7 +15,9 @@ return [
|
|||
return $this->resolve($user)->view('auth');
|
||||
}
|
||||
|
||||
throw new NotFoundException('The user cannot be found');
|
||||
throw new NotFoundException(
|
||||
message: 'The user cannot be found'
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
|
@ -27,7 +29,9 @@ return [
|
|||
|
||||
// csrf token check
|
||||
if ($auth->type() === 'session' && $auth->csrf() === false) {
|
||||
throw new InvalidArgumentException('Invalid CSRF token');
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Invalid CSRF token'
|
||||
);
|
||||
}
|
||||
|
||||
$user = $auth->verifyChallenge($this->requestBody('code'));
|
||||
|
@ -49,7 +53,9 @@ return [
|
|||
|
||||
// csrf token check
|
||||
if ($auth->type() === 'session' && $auth->csrf() === false) {
|
||||
throw new InvalidArgumentException('Invalid CSRF token');
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Invalid CSRF token'
|
||||
);
|
||||
}
|
||||
|
||||
$email = $this->requestBody('email');
|
||||
|
@ -58,7 +64,9 @@ return [
|
|||
|
||||
if ($password) {
|
||||
if (isset($methods['password']) !== true) {
|
||||
throw new InvalidArgumentException('Login with password is not enabled');
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Login with password is not enabled'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -73,7 +81,9 @@ return [
|
|||
$mode = match (true) {
|
||||
isset($methods['code']) => 'login',
|
||||
isset($methods['password-reset']) => 'password-reset',
|
||||
default => throw new InvalidArgumentException('Login without password is not enabled')
|
||||
default => throw new InvalidArgumentException(
|
||||
message: 'Login without password is not enabled'
|
||||
)
|
||||
};
|
||||
|
||||
$status = $auth->createChallenge($email, $long, $mode);
|
||||
|
|
37
kirby/config/api/routes/changes.php
Normal file
37
kirby/config/api/routes/changes.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Api\Controller\Changes;
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
|
||||
return [
|
||||
[
|
||||
'pattern' => '(:all)/changes/discard',
|
||||
'method' => 'POST',
|
||||
'action' => function (string $path) {
|
||||
return Changes::discard(
|
||||
model: Find::parent($path),
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/changes/publish',
|
||||
'method' => 'POST',
|
||||
'action' => function (string $path) {
|
||||
return Changes::publish(
|
||||
model: Find::parent($path),
|
||||
input: App::instance()->request()->get()
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/changes/save',
|
||||
'method' => 'POST',
|
||||
'action' => function (string $path) {
|
||||
return Changes::save(
|
||||
model: Find::parent($path),
|
||||
input: App::instance()->request()->get()
|
||||
);
|
||||
}
|
||||
],
|
||||
];
|
|
@ -47,7 +47,7 @@ return [
|
|||
// move_uploaded_file() not working with unit test
|
||||
// @codeCoverageIgnoreStart
|
||||
return $this->upload(function ($source, $filename) use ($path) {
|
||||
// move the source file from the temp dir
|
||||
// move the source file to the content folder
|
||||
return $this->parent($path)->createFile([
|
||||
'content' => [
|
||||
'sort' => $this->requestBody('sort')
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Content Lock Routes
|
||||
*/
|
||||
|
||||
use Kirby\Exception\NotFoundException;
|
||||
|
||||
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) {
|
||||
return $this->parent($path)->lock()?->create();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/lock',
|
||||
'method' => 'DELETE',
|
||||
'action' => function (string $path) {
|
||||
try {
|
||||
return $this->parent($path)->lock()?->remove();
|
||||
} catch (NotFoundException) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/unlock',
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $path) {
|
||||
return $this->parent($path)->lock()?->unlock();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => '(:all)/unlock',
|
||||
'method' => 'DELETE',
|
||||
'action' => function (string $path) {
|
||||
try {
|
||||
return $this->parent($path)->lock()?->resolve();
|
||||
} catch (NotFoundException) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
],
|
||||
];
|
|
@ -31,18 +31,6 @@ return [
|
|||
];
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'system/method-test',
|
||||
'method' => 'PATCH',
|
||||
'action' => function () {
|
||||
return [
|
||||
'status' => match ($this->kirby()->request()->method()) {
|
||||
'PATCH' => 'ok',
|
||||
default => 'fail'
|
||||
}
|
||||
];
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'system/register',
|
||||
'method' => 'POST',
|
||||
|
@ -60,19 +48,27 @@ return [
|
|||
|
||||
// csrf token check
|
||||
if ($auth->type() === 'session' && $auth->csrf() === false) {
|
||||
throw new InvalidArgumentException('Invalid CSRF token');
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Invalid CSRF token'
|
||||
);
|
||||
}
|
||||
|
||||
if ($system->isOk() === false) {
|
||||
throw new Exception('The server is not setup correctly');
|
||||
throw new Exception(
|
||||
message: 'The server is not setup correctly'
|
||||
);
|
||||
}
|
||||
|
||||
if ($system->isInstallable() === false) {
|
||||
throw new Exception('The Panel cannot be installed');
|
||||
throw new Exception(
|
||||
message: 'The Panel cannot be installed'
|
||||
);
|
||||
}
|
||||
|
||||
if ($system->isInstalled() === true) {
|
||||
throw new Exception('The Panel is already installed');
|
||||
throw new Exception(
|
||||
message: 'The Panel is already installed'
|
||||
);
|
||||
}
|
||||
|
||||
// create the first user
|
||||
|
|
|
@ -86,18 +86,18 @@ return [
|
|||
function ($source, $filename) use ($id) {
|
||||
$type = F::type($filename);
|
||||
if ($type !== 'image') {
|
||||
throw new Exception([
|
||||
'key' => 'file.type.invalid',
|
||||
'data' => compact('type')
|
||||
]);
|
||||
throw new Exception(
|
||||
key: 'file.type.invalid',
|
||||
data: compact('type')
|
||||
);
|
||||
}
|
||||
|
||||
$mime = F::mime($source);
|
||||
if (Str::startsWith($mime, 'image/') !== true) {
|
||||
throw new Exception([
|
||||
'key' => 'file.mime.invalid',
|
||||
'data' => compact('mime')
|
||||
]);
|
||||
throw new Exception(
|
||||
key: 'file.mime.invalid',
|
||||
data: compact('mime')
|
||||
);
|
||||
}
|
||||
|
||||
// delete the old avatar
|
||||
|
@ -184,7 +184,23 @@ return [
|
|||
],
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $id) {
|
||||
return $this->user($id)->changePassword($this->requestBody('password'));
|
||||
$user = $this->user($id);
|
||||
|
||||
// validate password of acting user unless they have logged in to reset it;
|
||||
// always validate password of acting user when changing password of other users
|
||||
if ($this->session()->get('kirby.resetPassword') !== true || $this->user()->is($user) !== true) {
|
||||
$this->user()->validatePassword($this->requestBody('currentPassword'));
|
||||
}
|
||||
|
||||
$result = $user->changePassword($this->requestBody('password'));
|
||||
|
||||
// if we changed the password of the current user…
|
||||
if ($user->isLoggedIn() === true) {
|
||||
// …don't allow additional resets (now the password is known again)
|
||||
$this->session()->remove('kirby.resetPassword');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
|
@ -7,6 +7,7 @@ return function () {
|
|||
'icon' => 'account',
|
||||
'label' => I18n::translate('view.account'),
|
||||
'search' => 'users',
|
||||
'buttons' => require __DIR__ . '/account/buttons.php',
|
||||
'dialogs' => require __DIR__ . '/account/dialogs.php',
|
||||
'drawers' => require __DIR__ . '/account/drawers.php',
|
||||
'dropdowns' => require __DIR__ . '/account/dropdowns.php',
|
||||
|
|
13
kirby/config/areas/account/buttons.php
Normal file
13
kirby/config/areas/account/buttons.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButton;
|
||||
|
||||
return [
|
||||
'user.theme' => function (App $kirby, User $user) {
|
||||
if ($kirby->user()->is($user) === true) {
|
||||
return new ViewButton(component: 'k-theme-view-button');
|
||||
}
|
||||
}
|
||||
];
|
|
@ -5,7 +5,6 @@ use Kirby\Panel\UserTotpEnableDialog;
|
|||
$dialogs = require __DIR__ . '/../users/dialogs.php';
|
||||
|
||||
return [
|
||||
|
||||
// change email
|
||||
'account.changeEmail' => [
|
||||
'pattern' => '(account)/changeEmail',
|
||||
|
|
|
@ -7,8 +7,16 @@ return [
|
|||
'pattern' => '(account)',
|
||||
'options' => $dropdowns['user']['options']
|
||||
],
|
||||
'account.languages' => [
|
||||
'pattern' => '(account)/languages',
|
||||
'options' => $dropdowns['user.languages']['options']
|
||||
],
|
||||
'account.file' => [
|
||||
'pattern' => '(account)/files/(:any)',
|
||||
'options' => $dropdowns['user.file']['options']
|
||||
],
|
||||
'account.file.languages' => [
|
||||
'pattern' => '(account)/files/(:any)/languages',
|
||||
'options' => $files['language']
|
||||
]
|
||||
];
|
||||
|
|
|
@ -26,6 +26,9 @@ return [
|
|||
[
|
||||
'label' => I18n::translate('view.resetPassword')
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'requirePassword' => App::instance()->session()->get('kirby.resetPassword') !== true
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
14
kirby/config/areas/files/buttons.php
Normal file
14
kirby/config/areas/files/buttons.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Panel\Ui\Buttons\OpenButton;
|
||||
use Kirby\Panel\Ui\Buttons\SettingsButton;
|
||||
|
||||
return [
|
||||
'file.open' => function (File $file) {
|
||||
return new OpenButton(link: $file->previewUrl());
|
||||
},
|
||||
'file.settings' => function (File $file) {
|
||||
return new SettingsButton(model: $file);
|
||||
}
|
||||
];
|
|
@ -45,13 +45,7 @@ return [
|
|||
$oldUrl = $file->panel()->url(true);
|
||||
$newUrl = $renamed->panel()->url(true);
|
||||
$response = [
|
||||
'event' => 'file.changeName',
|
||||
'dispatch' => [
|
||||
'content/move' => [
|
||||
$oldUrl,
|
||||
$newUrl
|
||||
]
|
||||
],
|
||||
'event' => 'file.changeName'
|
||||
];
|
||||
|
||||
// check for a necessary redirect after the filename has changed
|
||||
|
@ -163,7 +157,6 @@ return [
|
|||
|
||||
return [
|
||||
'event' => 'file.delete',
|
||||
'dispatch' => ['content/remove' => [$url]],
|
||||
'redirect' => $redirect
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Ui\Buttons\LanguagesDropdown;
|
||||
|
||||
return [
|
||||
'file' => function (string $parent, string $filename) {
|
||||
return Find::file($parent, $filename)->panel()->dropdown();
|
||||
},
|
||||
'language' => function (string $parent, string $filename) {
|
||||
$file = Find::file($parent, $filename);
|
||||
return (new LanguagesDropdown($file))->options();
|
||||
}
|
||||
];
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Panel\Lab\Doc;
|
||||
use Kirby\Panel\Lab\Docs;
|
||||
|
||||
return [
|
||||
'lab.docs' => [
|
||||
'pattern' => 'lab/docs/(:any)',
|
||||
'load' => function (string $component) {
|
||||
if (Docs::installed() === false) {
|
||||
if (Docs::isInstalled() === false) {
|
||||
return [
|
||||
'component' => 'k-text-drawer',
|
||||
'props' => [
|
||||
|
@ -15,14 +16,12 @@ return [
|
|||
];
|
||||
}
|
||||
|
||||
$docs = new Docs($component);
|
||||
|
||||
return [
|
||||
'component' => 'k-lab-docs-drawer',
|
||||
'props' => [
|
||||
'icon' => 'book',
|
||||
'title' => $component,
|
||||
'docs' => $docs->toArray()
|
||||
'docs' => Doc::factory($component)->toArray()
|
||||
]
|
||||
];
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Panel\Lab\Category;
|
||||
use Kirby\Panel\Lab\Doc;
|
||||
use Kirby\Panel\Lab\Docs;
|
||||
|
||||
return [
|
||||
|
@ -12,7 +13,7 @@ return [
|
|||
'component' => 'k-lab-index-view',
|
||||
'props' => [
|
||||
'categories' => Category::all(),
|
||||
'info' => Category::installed() ? null : 'The default Lab examples are not installed.',
|
||||
'info' => Category::isInstalled() ? null : 'The default Lab examples are not installed.',
|
||||
'tab' => 'examples',
|
||||
],
|
||||
];
|
||||
|
@ -21,18 +22,7 @@ return [
|
|||
'lab.docs' => [
|
||||
'pattern' => 'lab/docs',
|
||||
'action' => function () {
|
||||
$props = match (Docs::installed()) {
|
||||
true => [
|
||||
'categories' => [['examples' => Docs::all()]],
|
||||
'tab' => 'docs',
|
||||
],
|
||||
false => [
|
||||
'info' => 'The UI docs are not installed.',
|
||||
'tab' => 'docs',
|
||||
]
|
||||
};
|
||||
|
||||
return [
|
||||
$view = [
|
||||
'component' => 'k-lab-index-view',
|
||||
'title' => 'Docs',
|
||||
'breadcrumb' => [
|
||||
|
@ -40,8 +30,28 @@ return [
|
|||
'label' => 'Docs',
|
||||
'link' => 'lab/docs'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// if docs are not installed, show info message
|
||||
if (Docs::isInstalled() === false) {
|
||||
return [
|
||||
...$view,
|
||||
'props' => [
|
||||
'info' => 'The UI docs are not installed.',
|
||||
'tab' => 'docs',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...$view,
|
||||
'props' => [
|
||||
'categories' => [
|
||||
['examples' => Docs::all()]
|
||||
],
|
||||
'tab' => 'docs',
|
||||
],
|
||||
'props' => $props,
|
||||
];
|
||||
}
|
||||
],
|
||||
|
@ -59,7 +69,7 @@ return [
|
|||
]
|
||||
];
|
||||
|
||||
if (Docs::installed() === false) {
|
||||
if (Docs::isInstalled() === false) {
|
||||
return [
|
||||
'component' => 'k-lab-index-view',
|
||||
'title' => $component,
|
||||
|
@ -71,16 +81,50 @@ return [
|
|||
];
|
||||
}
|
||||
|
||||
$docs = new Docs($component);
|
||||
$doc = Doc::factory($component);
|
||||
|
||||
if ($doc === null) {
|
||||
return [
|
||||
'component' => 'k-lab-index-view',
|
||||
'title' => $component,
|
||||
'breadcrumb' => $crumbs,
|
||||
'props' => [
|
||||
'info' => 'No UI docs found for ' . $component . '.',
|
||||
'tab' => 'docs',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// header buttons
|
||||
$buttons = [];
|
||||
|
||||
if ($lab = $doc->lab()) {
|
||||
$buttons[] = [
|
||||
'props' => [
|
||||
'text' => 'Lab examples',
|
||||
'icon' => 'lab',
|
||||
'link' => '/lab/' . $lab
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$buttons[] = [
|
||||
'props' => [
|
||||
'icon' => 'github',
|
||||
'link' => $doc->source(),
|
||||
'target' => '_blank'
|
||||
]
|
||||
];
|
||||
|
||||
return [
|
||||
'component' => 'k-lab-docs-view',
|
||||
'title' => $component,
|
||||
'breadcrumb' => $crumbs,
|
||||
'props' => [
|
||||
'buttons' => $buttons,
|
||||
'component' => $component,
|
||||
'docs' => $docs->toArray(),
|
||||
'lab' => $docs->lab()
|
||||
'docs' => $doc->toArray(),
|
||||
'lab' => $lab
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -111,16 +155,39 @@ return [
|
|||
$vue = $example->vue();
|
||||
$compiler = App::instance()->option('panel.vue.compiler', true);
|
||||
|
||||
if (Docs::installed() === true && $docs = $props['docs'] ?? null) {
|
||||
$docs = new Docs($docs);
|
||||
if ($doc = $props['docs'] ?? null) {
|
||||
$doc = Doc::factory($doc);
|
||||
}
|
||||
|
||||
$github = $docs?->github();
|
||||
$github = $doc?->source();
|
||||
|
||||
if ($source = $props['source'] ?? null) {
|
||||
$github ??= 'https://github.com/getkirby/kirby/tree/main/' . $source;
|
||||
}
|
||||
|
||||
// header buttons
|
||||
$buttons = [];
|
||||
|
||||
if ($doc) {
|
||||
$buttons[] = [
|
||||
'props' => [
|
||||
'text' => $doc->name,
|
||||
'icon' => 'book',
|
||||
'drawer' => 'lab/docs/' . $doc->name
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if ($github) {
|
||||
$buttons[] = [
|
||||
'props' => [
|
||||
'icon' => 'github',
|
||||
'link' => $github,
|
||||
'target' => '_blank'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-lab-playground-view',
|
||||
'breadcrumb' => [
|
||||
|
@ -133,8 +200,9 @@ return [
|
|||
]
|
||||
],
|
||||
'props' => [
|
||||
'buttons' => $buttons,
|
||||
'compiler' => $compiler,
|
||||
'docs' => $docs?->name(),
|
||||
'docs' => $doc?->name,
|
||||
'examples' => $vue['examples'],
|
||||
'file' => $example->module(),
|
||||
'github' => $github,
|
||||
|
|
|
@ -7,6 +7,7 @@ return function ($kirby) {
|
|||
'icon' => 'translate',
|
||||
'label' => I18n::translate('view.languages'),
|
||||
'menu' => true,
|
||||
'buttons' => require __DIR__ . '/languages/buttons.php',
|
||||
'dialogs' => require __DIR__ . '/languages/dialogs.php',
|
||||
'views' => require __DIR__ . '/languages/views.php'
|
||||
];
|
||||
|
|
21
kirby/config/areas/languages/buttons.php
Normal file
21
kirby/config/areas/languages/buttons.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\Language;
|
||||
use Kirby\Panel\Ui\Buttons\LanguageCreateButton;
|
||||
use Kirby\Panel\Ui\Buttons\LanguageDeleteButton;
|
||||
use Kirby\Panel\Ui\Buttons\LanguageSettingsButton;
|
||||
use Kirby\Panel\Ui\Buttons\OpenButton;
|
||||
|
||||
return [
|
||||
'languages.create' => fn () =>
|
||||
new LanguageCreateButton(),
|
||||
'language.open' => fn (Language $language) =>
|
||||
new OpenButton(link: $language->url()),
|
||||
'language.settings' => fn (Language $language) =>
|
||||
new LanguageSettingsButton($language),
|
||||
'language.delete' => function (Language $language) {
|
||||
if ($language->isDeletable() === true) {
|
||||
return new LanguageDeleteButton($language);
|
||||
}
|
||||
}
|
||||
];
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Cms\Language;
|
||||
use Kirby\Cms\LanguageVariable;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\A;
|
||||
|
@ -49,16 +50,34 @@ $translationDialogFields = [
|
|||
'label' => I18n::translate('language.variable.key'),
|
||||
'type' => 'text'
|
||||
],
|
||||
'multiple' => [
|
||||
'label' => I18n::translate('language.variable.multiple'),
|
||||
'text' => I18n::translate('language.variable.multiple.text'),
|
||||
'help' => I18n::translate('language.variable.multiple.help'),
|
||||
'type' => 'toggle'
|
||||
],
|
||||
'value' => [
|
||||
'buttons' => false,
|
||||
'counter' => false,
|
||||
'label' => I18n::translate('language.variable.value'),
|
||||
'type' => 'textarea'
|
||||
'type' => 'textarea',
|
||||
'when' => [
|
||||
'multiple' => false
|
||||
]
|
||||
],
|
||||
'entries' => [
|
||||
'field' => ['type' => 'text'],
|
||||
'label' => I18n::translate('language.variable.entries'),
|
||||
'help' => I18n::translate('language.variable.entries.help'),
|
||||
'type' => 'entries',
|
||||
'min' => 1,
|
||||
'when' => [
|
||||
'multiple' => true
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
// create language
|
||||
'language.create' => [
|
||||
'pattern' => 'languages/create',
|
||||
|
@ -184,6 +203,9 @@ return [
|
|||
'props' => [
|
||||
'fields' => $translationDialogFields,
|
||||
'size' => 'large',
|
||||
'value' => [
|
||||
'multiple' => false,
|
||||
]
|
||||
],
|
||||
];
|
||||
},
|
||||
|
@ -191,8 +213,13 @@ return [
|
|||
$request = App::instance()->request();
|
||||
$language = Find::language($languageCode);
|
||||
|
||||
$key = $request->get('key', '');
|
||||
$value = $request->get('value', '');
|
||||
$key = $request->get('key', '');
|
||||
$multiple = $request->get('multiple', false);
|
||||
|
||||
$value = match ($multiple) {
|
||||
true => $request->get('entries', []),
|
||||
default => $request->get('value', '')
|
||||
};
|
||||
|
||||
LanguageVariable::create($key, $value);
|
||||
|
||||
|
@ -209,9 +236,9 @@ return [
|
|||
$variable = Find::language($languageCode)->variable($translationKey, true);
|
||||
|
||||
if ($variable->exists() === false) {
|
||||
throw new NotFoundException([
|
||||
'key' => 'language.variable.notFound'
|
||||
]);
|
||||
throw new NotFoundException(
|
||||
key: 'language.variable.notFound'
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -230,48 +257,65 @@ return [
|
|||
'language.translation.update' => [
|
||||
'pattern' => 'languages/(:any)/translations/(:any)/update',
|
||||
'load' => function (string $languageCode, string $translationKey) use ($translationDialogFields) {
|
||||
$variable = Find::language($languageCode)->variable($translationKey, true);
|
||||
$language = Find::language($languageCode);
|
||||
$variable = $language->variable($translationKey, true);
|
||||
|
||||
if ($variable->exists() === false) {
|
||||
throw new NotFoundException([
|
||||
'key' => 'language.variable.notFound'
|
||||
]);
|
||||
throw new NotFoundException(
|
||||
key: 'language.variable.notFound'
|
||||
);
|
||||
}
|
||||
|
||||
$fields = $translationDialogFields;
|
||||
$fields['key']['disabled'] = true;
|
||||
$fields['value']['autofocus'] = true;
|
||||
|
||||
// shows info text when variable is an array
|
||||
// TODO: 5.0: use entries field instead showing info text
|
||||
$isVariableArray = is_array($variable->value()) === true;
|
||||
// the key field cannot be changed
|
||||
// the multiple field is hidden
|
||||
$fields['key']['disabled'] = true;
|
||||
$fields['multiple']['type'] = 'hidden';
|
||||
|
||||
// check if the variable has multiple values;
|
||||
// ensure to use the default language for this check because
|
||||
// the variable might not exist in the current language but
|
||||
// already be defined in the default language with multiple values
|
||||
$isVariableArray = Language::ensure('default')->variable($translationKey, true)->hasMultipleValues();
|
||||
|
||||
// set the correct value field
|
||||
// when value is string, set value for value field
|
||||
// when value is array, set value for entries field
|
||||
if ($isVariableArray === true) {
|
||||
$fields['value'] = [
|
||||
'label' => I18n::translate('info'),
|
||||
'type' => 'info',
|
||||
'text' => 'You are using an array variable for this key. Please modify it in the language file in /site/languages',
|
||||
$fields['entries']['autofocus'] = true;
|
||||
$value = [
|
||||
'entries' => $variable->value(),
|
||||
'key' => $variable->key(),
|
||||
'multiple' => true
|
||||
];
|
||||
} else {
|
||||
$fields['value']['autofocus'] = true;
|
||||
$value = [
|
||||
'key' => $variable->key(),
|
||||
'multiple' => false,
|
||||
'value' => $variable->value()
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'cancelButton' => $isVariableArray === false,
|
||||
'fields' => $fields,
|
||||
'size' => 'large',
|
||||
'submitButton' => $isVariableArray === false,
|
||||
'value' => [
|
||||
'key' => $variable->key(),
|
||||
'value' => $variable->value()
|
||||
]
|
||||
],
|
||||
'fields' => $fields,
|
||||
'size' => 'large',
|
||||
'value' => $value
|
||||
]
|
||||
];
|
||||
},
|
||||
'submit' => function (string $languageCode, string $translationKey) {
|
||||
Find::language($languageCode)->variable($translationKey, true)->update(
|
||||
App::instance()->request()->get('value', '')
|
||||
);
|
||||
$request = App::instance()->request();
|
||||
$multiple = $request->get('multiple', false);
|
||||
$value = match ($multiple) {
|
||||
true => $request->get('entries', []),
|
||||
default => $request->get('value', '')
|
||||
};
|
||||
|
||||
Find::language($languageCode)->variable($translationKey, true)->update($value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
|
@ -19,9 +20,9 @@ return [
|
|||
$foundation = $kirby->defaultLanguage()->translations();
|
||||
$translations = $language->translations();
|
||||
|
||||
// TODO: update following line and adapt for update and delete options
|
||||
// when new `languageVariables.*` permissions available
|
||||
$canUpdate = $kirby->user()?->role()->permissions()->for('languages', 'update') === true;
|
||||
// TODO: update following line and adapt for update and
|
||||
// delete options when `languageVariables.*` permissions available
|
||||
$canUpdate = $kirby->role()?->permissions()->for('languages', 'update') === true;
|
||||
|
||||
ksort($foundation);
|
||||
|
||||
|
@ -73,6 +74,10 @@ return [
|
|||
]
|
||||
],
|
||||
'props' => [
|
||||
'buttons' => fn () =>
|
||||
ViewButtons::view('language', model: $language)
|
||||
->defaults('open', 'settings', 'delete')
|
||||
->render(),
|
||||
'deletable' => $language->isDeletable(),
|
||||
'code' => Escape::html($language->code()),
|
||||
'default' => $language->isDefault(),
|
||||
|
@ -113,6 +118,10 @@ return [
|
|||
return [
|
||||
'component' => 'k-languages-view',
|
||||
'props' => [
|
||||
'buttons' => fn () =>
|
||||
ViewButtons::view('languages')
|
||||
->defaults('create')
|
||||
->render(),
|
||||
'languages' => $kirby->languages()->values(fn ($language) => [
|
||||
'deletable' => $language->isDeletable(),
|
||||
'default' => $language->isDefault(),
|
||||
|
|
|
@ -12,6 +12,7 @@ return function ($kirby) {
|
|||
'icon' => $blueprint->icon() ?? 'home',
|
||||
'label' => $blueprint->title() ?? I18n::translate('view.site'),
|
||||
'menu' => true,
|
||||
'buttons' => require __DIR__ . '/site/buttons.php',
|
||||
'dialogs' => require __DIR__ . '/site/dialogs.php',
|
||||
'drawers' => require __DIR__ . '/site/drawers.php',
|
||||
'dropdowns' => require __DIR__ . '/site/dropdowns.php',
|
||||
|
|
72
kirby/config/areas/site/buttons.php
Normal file
72
kirby/config/areas/site/buttons.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Panel\Ui\Buttons\LanguagesDropdown;
|
||||
use Kirby\Panel\Ui\Buttons\OpenButton;
|
||||
use Kirby\Panel\Ui\Buttons\PageStatusButton;
|
||||
use Kirby\Panel\Ui\Buttons\PreviewButton;
|
||||
use Kirby\Panel\Ui\Buttons\SettingsButton;
|
||||
use Kirby\Panel\Ui\Buttons\VersionsButton;
|
||||
|
||||
return [
|
||||
'site.open' => function (Site $site, string $versionId = 'latest') {
|
||||
$versionId = $versionId === 'compare' ? 'changes' : $versionId;
|
||||
$link = $site->previewUrl($versionId);
|
||||
|
||||
if ($link !== null) {
|
||||
return new OpenButton(
|
||||
link: $link,
|
||||
);
|
||||
}
|
||||
},
|
||||
'site.preview' => function (Site $site) {
|
||||
if ($site->previewUrl() !== null) {
|
||||
return new PreviewButton(
|
||||
link: $site->panel()->url(true) . '/preview/changes',
|
||||
);
|
||||
}
|
||||
},
|
||||
'site.versions' => function (Site $site, string $versionId = 'latest') {
|
||||
return new VersionsButton(
|
||||
model: $site,
|
||||
versionId: $versionId
|
||||
);
|
||||
},
|
||||
'page.open' => function (Page $page, string $versionId = 'latest') {
|
||||
$versionId = $versionId === 'compare' ? 'changes' : $versionId;
|
||||
$link = $page->previewUrl($versionId);
|
||||
|
||||
if ($link !== null) {
|
||||
return new OpenButton(
|
||||
link: $link,
|
||||
);
|
||||
}
|
||||
},
|
||||
'page.preview' => function (Page $page) {
|
||||
if ($page->previewUrl() !== null) {
|
||||
return new PreviewButton(
|
||||
link: $page->panel()->url(true) . '/preview/changes',
|
||||
);
|
||||
}
|
||||
},
|
||||
'page.versions' => function (Page $page, string $versionId = 'latest') {
|
||||
return new VersionsButton(
|
||||
model: $page,
|
||||
versionId: $versionId
|
||||
);
|
||||
},
|
||||
'page.settings' => fn (Page $page) => new SettingsButton(model: $page),
|
||||
'page.status' => fn (Page $page) => new PageStatusButton($page),
|
||||
|
||||
// `languages` button needs to be in site area,
|
||||
// as the languages might be not loaded even in
|
||||
// multilang mode when the `languages` option is deactivated
|
||||
// (but content languages to switch between still can exist)
|
||||
'languages' => fn (ModelWithContent $model) =>
|
||||
new LanguagesDropdown($model),
|
||||
|
||||
// file buttons
|
||||
...require __DIR__ . '/../files/buttons.php'
|
||||
];
|
|
@ -28,12 +28,10 @@ return [
|
|||
$page = Find::page($id);
|
||||
|
||||
if ($page->blueprint()->num() !== 'default') {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.sort.permission',
|
||||
'data' => [
|
||||
'slug' => $page->slug()
|
||||
]
|
||||
]);
|
||||
throw new PermissionException(
|
||||
key: 'page.sort.permission',
|
||||
data: ['slug' => $page->slug()]
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -150,12 +148,10 @@ return [
|
|||
$blueprints = $page->blueprints();
|
||||
|
||||
if (count($blueprints) <= 1) {
|
||||
throw new Exception([
|
||||
'key' => 'page.changeTemplate.invalid',
|
||||
'data' => [
|
||||
'slug' => $id
|
||||
]
|
||||
]);
|
||||
throw new Exception(
|
||||
key: 'page.changeTemplate.invalid',
|
||||
data: ['slug' => $id]
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -264,20 +260,17 @@ return [
|
|||
|
||||
// the page title changed
|
||||
if ($page->title()->value() !== $title) {
|
||||
$page->changeTitle($title);
|
||||
$page = $page->changeTitle($title);
|
||||
$response['event'][] = 'page.changeTitle';
|
||||
}
|
||||
|
||||
// the slug changed
|
||||
if ($page->slug() !== $slug) {
|
||||
$newPage = $page->changeSlug($slug);
|
||||
$response['event'][] = 'page.changeSlug';
|
||||
$response['dispatch'] = [
|
||||
'content/move' => [
|
||||
$oldUrl = $page->panel()->url(true),
|
||||
$newUrl = $newPage->panel()->url(true)
|
||||
]
|
||||
];
|
||||
|
||||
$newPage = $page->changeSlug($slug);
|
||||
$oldUrl = $page->panel()->url(true);
|
||||
$newUrl = $newPage->panel()->url(true);
|
||||
|
||||
// check for a necessary redirect after the slug has changed
|
||||
if (Panel::referrer() === $oldUrl && $oldUrl !== $newUrl) {
|
||||
|
@ -372,7 +365,9 @@ return [
|
|||
$page->childrenAndDrafts()->count() > 0 &&
|
||||
$request->get('check') !== $page->title()->value()
|
||||
) {
|
||||
throw new InvalidArgumentException(['key' => 'page.delete.confirm']);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'page.delete.confirm'
|
||||
);
|
||||
}
|
||||
|
||||
$page->delete(true);
|
||||
|
@ -385,7 +380,6 @@ return [
|
|||
|
||||
return [
|
||||
'event' => 'page.delete',
|
||||
'dispatch' => ['content/remove' => [$url]],
|
||||
'redirect' => $redirect
|
||||
];
|
||||
}
|
||||
|
@ -416,19 +410,17 @@ return [
|
|||
|
||||
if ($hasFiles === true) {
|
||||
$fields['files'] = [
|
||||
'label' => I18n::translate('page.duplicate.files'),
|
||||
'type' => 'toggle',
|
||||
'required' => true,
|
||||
'width' => $toggleWidth
|
||||
'label' => I18n::translate('page.duplicate.files'),
|
||||
'type' => 'toggle',
|
||||
'width' => $toggleWidth
|
||||
];
|
||||
}
|
||||
|
||||
if ($hasChildren === true) {
|
||||
$fields['children'] = [
|
||||
'label' => I18n::translate('page.duplicate.pages'),
|
||||
'type' => 'toggle',
|
||||
'required' => true,
|
||||
'width' => $toggleWidth
|
||||
'label' => I18n::translate('page.duplicate.pages'),
|
||||
'type' => 'toggle',
|
||||
'width' => $toggleWidth
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -440,11 +432,11 @@ return [
|
|||
$duplicateSlug = $page->slug() . '-' . $slugAppendix;
|
||||
$siblingKeys = $page->parentModel()->childrenAndDrafts()->pluck('uid');
|
||||
|
||||
if (in_array($duplicateSlug, $siblingKeys) === true) {
|
||||
if (in_array($duplicateSlug, $siblingKeys, true) === true) {
|
||||
$suffixCounter = 2;
|
||||
$newSlug = $duplicateSlug . $suffixCounter;
|
||||
|
||||
while (in_array($newSlug, $siblingKeys) === true) {
|
||||
while (in_array($newSlug, $siblingKeys, true) === true) {
|
||||
$newSlug = $duplicateSlug . ++$suffixCounter;
|
||||
}
|
||||
|
||||
|
@ -556,13 +548,7 @@ return [
|
|||
|
||||
return [
|
||||
'event' => 'page.move',
|
||||
'redirect' => $newPage->panel()->url(true),
|
||||
'dispatch' => [
|
||||
'content/move' => [
|
||||
$oldPage->panel()->url(true),
|
||||
$newPage->panel()->url(true)
|
||||
]
|
||||
],
|
||||
'redirect' => $newPage->panel()->url(true)
|
||||
];
|
||||
}
|
||||
],
|
||||
|
@ -643,13 +629,7 @@ return [
|
|||
'changes' => [
|
||||
'pattern' => 'changes',
|
||||
'load' => function () {
|
||||
$dialog = new ChangesDialog();
|
||||
return $dialog->load();
|
||||
return (new ChangesDialog())->load();
|
||||
},
|
||||
'submit' => function () {
|
||||
$dialog = new ChangesDialog();
|
||||
$ids = App::instance()->request()->get('ids');
|
||||
return $dialog->submit($ids);
|
||||
}
|
||||
],
|
||||
];
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Ui\Buttons\LanguagesDropdown;
|
||||
|
||||
$files = require __DIR__ . '/../files/dropdowns.php';
|
||||
|
||||
|
@ -10,12 +13,34 @@ return [
|
|||
return Find::page($path)->panel()->dropdown();
|
||||
}
|
||||
],
|
||||
'page.languages' => [
|
||||
'pattern' => 'pages/(:any)/languages',
|
||||
'options' => function (string $path) {
|
||||
$page = Find::page($path);
|
||||
return (new LanguagesDropdown($page))->options();
|
||||
}
|
||||
],
|
||||
'page.file' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)',
|
||||
'options' => $files['file']
|
||||
],
|
||||
'page.file.languages' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/languages',
|
||||
'options' => $files['language']
|
||||
],
|
||||
'site.languages' => [
|
||||
'pattern' => 'site/languages',
|
||||
'options' => function () {
|
||||
$site = App::instance()->site();
|
||||
return (new LanguagesDropdown($site))->options();
|
||||
}
|
||||
],
|
||||
'site.file' => [
|
||||
'pattern' => '(site)/files/(:any)',
|
||||
'options' => $files['file']
|
||||
],
|
||||
'site.file.languages' => [
|
||||
'pattern' => '(site)/files/(:any)/languages',
|
||||
'options' => $files['language']
|
||||
]
|
||||
];
|
||||
|
|
|
@ -1,90 +1,25 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Panel\Controller\PageTree;
|
||||
|
||||
return [
|
||||
// @codeCoverageIgnoreStart
|
||||
// TODO: move to controller class and add unit tests
|
||||
'tree' => [
|
||||
'pattern' => 'site/tree',
|
||||
'action' => function () {
|
||||
$kirby = App::instance();
|
||||
$request = $kirby->request();
|
||||
$move = $request->get('move');
|
||||
$move = $move ? Find::parent($move) : null;
|
||||
$parent = $request->get('parent');
|
||||
|
||||
if ($parent === null) {
|
||||
$site = $kirby->site();
|
||||
$panel = $site->panel();
|
||||
$uuid = $site->uuid()?->toString();
|
||||
$url = $site->url();
|
||||
$value = $uuid ?? '/';
|
||||
|
||||
return [
|
||||
[
|
||||
'children' => $panel->url(true),
|
||||
'disabled' => $move?->isMovableTo($site) === false,
|
||||
'hasChildren' => true,
|
||||
'icon' => 'home',
|
||||
'id' => '/',
|
||||
'label' => I18n::translate('view.site'),
|
||||
'open' => false,
|
||||
'url' => $url,
|
||||
'uuid' => $uuid,
|
||||
'value' => $value
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$parent = Find::parent($parent);
|
||||
$pages = [];
|
||||
|
||||
foreach ($parent->childrenAndDrafts()->filterBy('isListable', true) as $child) {
|
||||
$panel = $child->panel();
|
||||
$uuid = $child->uuid()?->toString();
|
||||
$url = $child->url();
|
||||
$value = $uuid ?? $child->id();
|
||||
|
||||
$pages[] = [
|
||||
'children' => $panel->url(true),
|
||||
'disabled' => $move?->isMovableTo($child) === false,
|
||||
'hasChildren' => $child->hasChildren() === true || $child->hasDrafts() === true,
|
||||
'icon' => $panel->image()['icon'] ?? null,
|
||||
'id' => $child->id(),
|
||||
'open' => false,
|
||||
'label' => $child->title()->value(),
|
||||
'url' => $url,
|
||||
'uuid' => $uuid,
|
||||
'value' => $value
|
||||
];
|
||||
}
|
||||
|
||||
return $pages;
|
||||
return (new PageTree())->children(
|
||||
parent: App::instance()->request()->get('parent'),
|
||||
moving: App::instance()->request()->get('move')
|
||||
);
|
||||
}
|
||||
],
|
||||
'tree.parents' => [
|
||||
'pattern' => 'site/tree/parents',
|
||||
'action' => function () {
|
||||
$kirby = App::instance();
|
||||
$request = $kirby->request();
|
||||
$root = $request->get('root');
|
||||
$page = $kirby->page($request->get('page'));
|
||||
$parents = $page?->parents()->flip()->values(
|
||||
fn ($parent) => $parent->uuid()?->toString() ?? $parent->id()
|
||||
) ?? [];
|
||||
|
||||
// if root is included, add the site as top-level parent
|
||||
if ($root === 'true') {
|
||||
array_unshift($parents, $kirby->site()->uuid()?->toString() ?? '/');
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => $parents
|
||||
];
|
||||
return (new PageTree())->parents(
|
||||
page: App::instance()->request()->get('page'),
|
||||
includeSite: App::instance()->request()->get('root') === 'true',
|
||||
);
|
||||
}
|
||||
]
|
||||
// @codeCoverageIgnoreEnd
|
||||
];
|
||||
|
|
|
@ -1,56 +1,17 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Panel\Controller\Search;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'pages' => [
|
||||
'label' => I18n::translate('pages'),
|
||||
'icon' => 'page',
|
||||
'query' => function (string|null $query, int $limit, int $page) {
|
||||
$kirby = App::instance();
|
||||
$pages = $kirby->site()
|
||||
->index(true)
|
||||
->search($query)
|
||||
->filter('isListable', true)
|
||||
->paginate($limit, $page);
|
||||
|
||||
return [
|
||||
'results' => $pages->values(fn ($page) => [
|
||||
'image' => $page->panel()->image(),
|
||||
'text' => Escape::html($page->title()->value()),
|
||||
'link' => $page->panel()->url(true),
|
||||
'info' => Escape::html($page->id()),
|
||||
'uuid' => $page->uuid()?->toString(),
|
||||
]),
|
||||
'pagination' => $pages->pagination()->toArray()
|
||||
];
|
||||
}
|
||||
'query' => fn (string|null $query, int $limit, int $page) => Search::pages($query, $limit, $page)
|
||||
],
|
||||
'files' => [
|
||||
'label' => I18n::translate('files'),
|
||||
'icon' => 'image',
|
||||
'query' => function (string|null $query, int $limit, int $page) {
|
||||
$kirby = App::instance();
|
||||
$files = $kirby->site()
|
||||
->index(true)
|
||||
->filter('isListable', true)
|
||||
->files()
|
||||
->filter('isListable', true)
|
||||
->search($query)
|
||||
->paginate($limit, $page);
|
||||
|
||||
return [
|
||||
'results' => $files->values(fn ($file) => [
|
||||
'image' => $file->panel()->image(),
|
||||
'text' => Escape::html($file->filename()),
|
||||
'link' => $file->panel()->url(true),
|
||||
'info' => Escape::html($file->id()),
|
||||
'uuid' => $file->uuid()->toString(),
|
||||
]),
|
||||
'pagination' => $files->pagination()->toArray()
|
||||
];
|
||||
}
|
||||
'query' => fn (string|null $query, int $limit, int $page) => Search::files($query, $limit, $page)
|
||||
]
|
||||
];
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'page' => [
|
||||
|
@ -14,6 +17,40 @@ return [
|
|||
return Find::file('pages/' . $id, $filename)->panel()->view();
|
||||
}
|
||||
],
|
||||
'page.preview' => [
|
||||
'pattern' => 'pages/(:any)/preview/(changes|latest|compare)',
|
||||
'action' => function (string $path, string $versionId) {
|
||||
$page = Find::page($path);
|
||||
$view = $page->panel()->view();
|
||||
$src = [
|
||||
'latest' => $page->previewUrl('latest'),
|
||||
'changes' => $page->previewUrl('changes'),
|
||||
];
|
||||
|
||||
if ($src['latest'] === null) {
|
||||
throw new PermissionException('The preview is not available');
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-preview-view',
|
||||
'props' => [
|
||||
...$view['props'],
|
||||
'back' => $view['props']['link'],
|
||||
'buttons' => fn () =>
|
||||
ViewButtons::view('page.preview', model: $page)
|
||||
->defaults(
|
||||
'page.versions',
|
||||
'languages',
|
||||
)
|
||||
->bind(['versionId' => $versionId])
|
||||
->render(),
|
||||
'src' => $src,
|
||||
'versionId' => $versionId,
|
||||
],
|
||||
'title' => $view['props']['title'] . ' | ' . I18n::translate('preview'),
|
||||
];
|
||||
}
|
||||
],
|
||||
'site' => [
|
||||
'pattern' => 'site',
|
||||
'action' => fn () => App::instance()->site()->panel()->view()
|
||||
|
@ -24,4 +61,38 @@ return [
|
|||
return Find::file('site', $filename)->panel()->view();
|
||||
}
|
||||
],
|
||||
'site.preview' => [
|
||||
'pattern' => 'site/preview/(changes|latest|compare)',
|
||||
'action' => function (string $versionId) {
|
||||
$site = App::instance()->site();
|
||||
$view = $site->panel()->view();
|
||||
$src = [
|
||||
'latest' => $site->previewUrl('latest'),
|
||||
'changes' => $site->previewUrl('changes'),
|
||||
];
|
||||
|
||||
if ($src['latest'] === null) {
|
||||
throw new PermissionException('The preview is not available');
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-preview-view',
|
||||
'props' => [
|
||||
...$view['props'],
|
||||
'back' => $view['props']['link'],
|
||||
'buttons' => fn () =>
|
||||
ViewButtons::view('site.preview', model: $site)
|
||||
->defaults(
|
||||
'site.versions',
|
||||
'languages'
|
||||
)
|
||||
->bind(['versionId' => $versionId])
|
||||
->render(),
|
||||
'src' => $src,
|
||||
'versionId' => $versionId
|
||||
],
|
||||
'title' => I18n::translate('view.site') . ' | ' . I18n::translate('preview'),
|
||||
];
|
||||
}
|
||||
],
|
||||
];
|
||||
|
|
|
@ -53,7 +53,7 @@ return [
|
|||
];
|
||||
}
|
||||
|
||||
throw new LogicException('The upgrade failed');
|
||||
throw new LogicException(message: 'The upgrade failed');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
|
@ -59,11 +60,12 @@ return [
|
|||
|
||||
return [
|
||||
'author' => empty($authors) ? '–' : $authors,
|
||||
'license' => $plugin->license() ?? '–',
|
||||
'license' => $plugin->license()->toArray(),
|
||||
'name' => [
|
||||
'text' => $plugin->name() ?? '–',
|
||||
'href' => $plugin->link(),
|
||||
],
|
||||
'status' => $plugin->license()->status()->toArray(),
|
||||
'version' => $version,
|
||||
];
|
||||
});
|
||||
|
@ -122,12 +124,14 @@ return [
|
|||
return [
|
||||
'component' => 'k-system-view',
|
||||
'props' => [
|
||||
'buttons' => fn () =>
|
||||
ViewButtons::view('system')->render(),
|
||||
'environment' => $environment,
|
||||
'exceptions' => $debugMode ? $exceptions : [],
|
||||
'info' => $system->info(),
|
||||
'plugins' => $plugins,
|
||||
'security' => $security,
|
||||
'urls' => $sensitive ?? null
|
||||
'urls' => $sensitive ?? []
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ return function ($kirby) {
|
|||
'label' => I18n::translate('view.users'),
|
||||
'search' => 'users',
|
||||
'menu' => true,
|
||||
'buttons' => require __DIR__ . '/users/buttons.php',
|
||||
'dialogs' => require __DIR__ . '/users/dialogs.php',
|
||||
'drawers' => require __DIR__ . '/users/drawers.php',
|
||||
'dropdowns' => require __DIR__ . '/users/dropdowns.php',
|
||||
|
|
20
kirby/config/areas/users/buttons.php
Normal file
20
kirby/config/areas/users/buttons.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Panel\Ui\Buttons\SettingsButton;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButton;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'users.create' => function (User $user, string|null $role = null) {
|
||||
return new ViewButton(
|
||||
dialog: 'users/create?role=' . $role,
|
||||
disabled: $user->kirby()->roles()->canBeCreated()->count() < 1,
|
||||
icon: 'add',
|
||||
text: I18n::translate('user.create'),
|
||||
);
|
||||
},
|
||||
'user.settings' => function (User $user) {
|
||||
return new SettingsButton(model: $user);
|
||||
}
|
||||
];
|
|
@ -57,7 +57,7 @@ return [
|
|||
'email' => '',
|
||||
'password' => '',
|
||||
'translation' => $kirby->panelLanguage(),
|
||||
'role' => $role ?? $roles['options'][0]['value'] ?? null
|
||||
'role' => $role ?: $roles['options'][0]['value'] ?? null
|
||||
]
|
||||
]
|
||||
];
|
||||
|
@ -231,9 +231,9 @@ return [
|
|||
|
||||
// compare passwords
|
||||
if ($password !== $passwordConfirmation) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'user.password.notSame'
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'user.password.notSame'
|
||||
);
|
||||
}
|
||||
|
||||
// change password if everything's fine
|
||||
|
@ -319,7 +319,6 @@ return [
|
|||
|
||||
return [
|
||||
'event' => 'user.delete',
|
||||
'dispatch' => ['content/remove' => [$url]],
|
||||
'redirect' => $redirect
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Ui\Buttons\LanguagesDropdown;
|
||||
|
||||
$files = require __DIR__ . '/../files/dropdowns.php';
|
||||
|
||||
return [
|
||||
'user' => [
|
||||
'pattern' => 'users/(:any)',
|
||||
'options' => fn (string $id) =>
|
||||
Find::user($id)->panel()->dropdown()
|
||||
],
|
||||
'user.languages' => [
|
||||
'pattern' => 'users/(:any)/languages',
|
||||
'options' => function (string $id) {
|
||||
return Find::user($id)->panel()->dropdown();
|
||||
$user = Find::user($id);
|
||||
return (new LanguagesDropdown($user))->options();
|
||||
}
|
||||
],
|
||||
'user.file' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)',
|
||||
'options' => $files['file']
|
||||
],
|
||||
'user.file.languages' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/languages',
|
||||
'options' => $files['language']
|
||||
]
|
||||
];
|
||||
|
|
|
@ -1,29 +1,12 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Panel\Controller\Search;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'users' => [
|
||||
'label' => I18n::translate('users'),
|
||||
'icon' => 'users',
|
||||
'query' => function (string|null $query, int $limit, int $page) {
|
||||
$kirby = App::instance();
|
||||
$users = $kirby->users()
|
||||
->search($query)
|
||||
->paginate($limit, $page);
|
||||
|
||||
return [
|
||||
'results' => $users->values(fn ($user) => [
|
||||
'image' => $user->panel()->image(),
|
||||
'text' => Escape::html($user->username()),
|
||||
'link' => $user->panel()->url(true),
|
||||
'info' => Escape::html($user->role()->title()),
|
||||
'uuid' => $user->uuid()->toString(),
|
||||
]),
|
||||
'pagination' => $users->pagination()->toArray()
|
||||
];
|
||||
}
|
||||
'query' => fn (string|null $query, int $limit, int $page) => Search::users($query, $limit, $page)
|
||||
]
|
||||
];
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Toolkit\Escape;
|
||||
|
||||
return [
|
||||
|
@ -18,7 +19,11 @@ return [
|
|||
return [
|
||||
'component' => 'k-users-view',
|
||||
'props' => [
|
||||
'canCreate' => $kirby->roles()->canBeCreated()->count() > 0,
|
||||
'buttons' => fn () =>
|
||||
ViewButtons::view('users')
|
||||
->defaults('create')
|
||||
->bind(['role' => $role])
|
||||
->render(),
|
||||
'role' => function () use ($roles, $role) {
|
||||
if ($role) {
|
||||
return $roles[$role] ?? null;
|
||||
|
|
|
@ -4,11 +4,15 @@ use Kirby\Cms\App;
|
|||
use Kirby\Cms\Collection;
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\FileVersion;
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Content\PlainTextStorage;
|
||||
use Kirby\Content\Storage;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Email\PHPMailer as Emailer;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Filesystem\Filename;
|
||||
use Kirby\Http\Uri;
|
||||
|
@ -59,22 +63,20 @@ return [
|
|||
/**
|
||||
* Adapt file characteristics
|
||||
*
|
||||
* @param \Kirby\Cms\File|\Kirby\Filesystem\Asset $file The file object
|
||||
* @param array $options All thumb options (width, height, crop, blur, grayscale)
|
||||
* @return \Kirby\Cms\File|\Kirby\Cms\FileVersion|\Kirby\Filesystem\Asset
|
||||
*/
|
||||
'file::version' => function (
|
||||
App $kirby,
|
||||
$file,
|
||||
File|Asset $file,
|
||||
array $options = []
|
||||
) {
|
||||
): File|Asset|FileVersion {
|
||||
// if file is not resizable, return
|
||||
if ($file->isResizable() === false) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
// create url and root
|
||||
$mediaRoot = dirname($file->mediaRoot());
|
||||
$mediaRoot = $file->mediaDir();
|
||||
$template = $mediaRoot . '/{{ name }}{{ attributes }}.{{ extension }}';
|
||||
$thumbRoot = (new Filename($file->root(), $template, $options))->toString();
|
||||
$thumbName = basename($thumbRoot);
|
||||
|
@ -85,9 +87,10 @@ return [
|
|||
$job = $mediaRoot . '/.jobs/' . $thumbName . '.json';
|
||||
|
||||
try {
|
||||
Data::write($job, array_merge($options, [
|
||||
'filename' => $file->filename()
|
||||
]));
|
||||
Data::write(
|
||||
$job,
|
||||
[...$options, 'filename' => $file->filename()]
|
||||
);
|
||||
} catch (Throwable) {
|
||||
// if thumb doesn't exist yet and job file cannot
|
||||
// be created, return
|
||||
|
@ -99,7 +102,7 @@ return [
|
|||
'modifications' => $options,
|
||||
'original' => $file,
|
||||
'root' => $thumbRoot,
|
||||
'url' => dirname($file->mediaUrl()) . '/' . $thumbName,
|
||||
'url' => $file->mediaUrl($thumbName),
|
||||
]);
|
||||
},
|
||||
|
||||
|
@ -150,17 +153,16 @@ return [
|
|||
$params = ['fields' => Str::split($params, '|')];
|
||||
}
|
||||
|
||||
$defaults = [
|
||||
$collection = clone $collection;
|
||||
$query = trim($query ?? '');
|
||||
$options = [
|
||||
'fields' => [],
|
||||
'minlength' => 2,
|
||||
'score' => [],
|
||||
'words' => false,
|
||||
...$params
|
||||
];
|
||||
|
||||
$collection = clone $collection;
|
||||
$options = array_merge($defaults, $params);
|
||||
$query = trim($query ?? '');
|
||||
|
||||
// empty or too short search query
|
||||
if (Str::length($query) < $options['minlength']) {
|
||||
return $collection->limit(0);
|
||||
|
@ -204,10 +206,11 @@ return [
|
|||
$keys[] = 'role';
|
||||
} elseif ($item instanceof Page) {
|
||||
// apply the default score for pages
|
||||
$options['score'] = array_merge(
|
||||
['id' => 64, 'title' => 64],
|
||||
$options['score']
|
||||
);
|
||||
$options['score'] = [
|
||||
'id' => 64,
|
||||
'title' => 64,
|
||||
...$options['score']
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($options['fields']) === false) {
|
||||
|
@ -231,7 +234,7 @@ return [
|
|||
$scoring['score'] += 16 * $score;
|
||||
$scoring['hits'] += 1;
|
||||
|
||||
// check for exact beginning matches
|
||||
// check for exact beginning matches
|
||||
} elseif (
|
||||
$options['words'] === false &&
|
||||
Str::startsWith($lowerValue, $query) === true
|
||||
|
@ -239,7 +242,7 @@ return [
|
|||
$scoring['score'] += 8 * $score;
|
||||
$scoring['hits'] += 1;
|
||||
|
||||
// check for exact query matches
|
||||
// check for exact query matches
|
||||
} elseif ($matches = preg_match_all('!' . $exact . '!ui', $value, $r)) {
|
||||
$scoring['score'] += 2 * $score;
|
||||
$scoring['hits'] += $matches;
|
||||
|
@ -309,6 +312,16 @@ return [
|
|||
return Snippet::factory($name, $data, $slots);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new storage object for the given model
|
||||
*/
|
||||
'storage' => function (
|
||||
App $kirby,
|
||||
ModelWithContent $model
|
||||
): Storage {
|
||||
return new PlainTextStorage(model: $model);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add your own template engine
|
||||
*
|
||||
|
@ -332,7 +345,6 @@ return [
|
|||
* @param string $src Root of the original file
|
||||
* @param string $dst Template string for the root to the desired destination
|
||||
* @param array $options All thumb options that should be applied: `width`, `height`, `crop`, `blur`, `grayscale`
|
||||
* @return string
|
||||
*/
|
||||
'thumb' => function (
|
||||
App $kirby,
|
||||
|
@ -401,7 +413,7 @@ return [
|
|||
// keep relative urls
|
||||
if (
|
||||
$path !== null &&
|
||||
(substr($path, 0, 2) === './' || substr($path, 0, 3) === '../')
|
||||
(str_starts_with($path, './') || str_starts_with($path, '../'))
|
||||
) {
|
||||
return $path;
|
||||
}
|
||||
|
@ -417,7 +429,9 @@ return [
|
|||
$model = Uuid::for($path)->model();
|
||||
|
||||
if ($model === null) {
|
||||
throw new NotFoundException('The model could not be found for "' . $path . '" uuid');
|
||||
throw new NotFoundException(
|
||||
message: 'The model could not be found for "' . $path . '" uuid'
|
||||
);
|
||||
}
|
||||
|
||||
$path = $model->url();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\Helpers;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Field\FieldOptions;
|
||||
use Kirby\Toolkit\A;
|
||||
|
@ -24,8 +25,10 @@ return [
|
|||
* The CSS format (hex, rgb, hsl) to display and store the value
|
||||
*/
|
||||
'format' => function (string $format = 'hex'): string {
|
||||
if (in_array($format, ['hex', 'hsl', 'rgb']) === false) {
|
||||
throw new InvalidArgumentException('Unsupported format for color field (supported: hex, rgb, hsl)');
|
||||
if (in_array($format, ['hex', 'hsl', 'rgb'], true) === false) {
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Unsupported format for color field (supported: hex, rgb, hsl)'
|
||||
);
|
||||
}
|
||||
|
||||
return $format;
|
||||
|
@ -35,8 +38,10 @@ return [
|
|||
* show the `options` as toggles
|
||||
*/
|
||||
'mode' => function (string $mode = 'picker'): string {
|
||||
if (in_array($mode, ['picker', 'input', 'options']) === false) {
|
||||
throw new InvalidArgumentException('Unsupported mode for color field (supported: picker, input, options)');
|
||||
if (in_array($mode, ['picker', 'input', 'options'], true) === false) {
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Unsupported mode for color field (supported: picker, input, options)'
|
||||
);
|
||||
}
|
||||
|
||||
return $mode;
|
||||
|
@ -69,30 +74,33 @@ return [
|
|||
return [];
|
||||
}
|
||||
|
||||
$options = match (true) {
|
||||
// simple array of values
|
||||
// or value=text (from Options class)
|
||||
if (
|
||||
is_numeric($options[0]['value']) ||
|
||||
$options[0]['value'] === $options[0]['text']
|
||||
=> A::map($options, fn ($option) => [
|
||||
'value' => $option['text']
|
||||
]),
|
||||
) {
|
||||
// simple array of values
|
||||
// or value=text (from Options class)
|
||||
$options = A::map($options, fn ($option) => [
|
||||
'value' => $option['text']
|
||||
]);
|
||||
|
||||
// deprecated: name => value, flipping
|
||||
// TODO: start throwing in warning in v5
|
||||
$this->isColor($options[0]['text'])
|
||||
=> A::map($options, fn ($option) => [
|
||||
'value' => $option['text'],
|
||||
// ensure that any HTML in the new text is escaped
|
||||
'text' => Escape::html($option['value'])
|
||||
]),
|
||||
} elseif ($this->isColor($options[0]['text'])) {
|
||||
// @deprecated 4.0.0
|
||||
// TODO: Remove in Kirby 6
|
||||
|
||||
default
|
||||
=> A::map($options, fn ($option) => [
|
||||
Helpers::deprecated('Color field "' . $this->name . '": the text => value notation for options has been deprecated and will be removed in Kirby 6. Please rewrite your options as value => text.');
|
||||
|
||||
$options = A::map($options, fn ($option) => [
|
||||
'value' => $option['text'],
|
||||
// ensure that any HTML in the new text is escaped
|
||||
'text' => Escape::html($option['value'])
|
||||
]);
|
||||
} else {
|
||||
$options = A::map($options, fn ($option) => [
|
||||
'value' => $option['value'],
|
||||
'text' => $option['text']
|
||||
]),
|
||||
};
|
||||
]);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
@ -121,24 +129,24 @@ return [
|
|||
}
|
||||
|
||||
if ($this->format === 'hex' && $this->isHex($value) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.color',
|
||||
'data' => ['format' => 'hex']
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.color',
|
||||
data: ['format' => 'hex']
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->format === 'rgb' && $this->isRgb($value) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.color',
|
||||
'data' => ['format' => 'rgb']
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.color',
|
||||
data: ['format' => 'rgb']
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->format === 'hsl' && $this->isHsl($value) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.color',
|
||||
'data' => ['format' => 'hsl']
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.color',
|
||||
data: ['format' => 'hsl']
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -125,27 +125,27 @@ return [
|
|||
$format = $this->time === false ? 'd.m.Y' : 'd.m.Y H:i';
|
||||
|
||||
if ($min && $max && $value->isBetween($min, $max) === false) {
|
||||
throw new Exception([
|
||||
'key' => 'validation.date.between',
|
||||
'data' => [
|
||||
throw new Exception(
|
||||
key: 'validation.date.between',
|
||||
data: [
|
||||
'min' => $min->format($format),
|
||||
'max' => $max->format($format)
|
||||
]
|
||||
]);
|
||||
} elseif ($min && $value->isMin($min) === false) {
|
||||
throw new Exception([
|
||||
'key' => 'validation.date.after',
|
||||
'data' => [
|
||||
'date' => $min->format($format),
|
||||
]
|
||||
]);
|
||||
} elseif ($max && $value->isMax($max) === false) {
|
||||
throw new Exception([
|
||||
'key' => 'validation.date.before',
|
||||
'data' => [
|
||||
'date' => $max->format($format),
|
||||
]
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
if ($min && $value->isMin($min) === false) {
|
||||
throw new Exception(
|
||||
key: 'validation.date.after',
|
||||
data: ['date' => $min->format($format)]
|
||||
);
|
||||
}
|
||||
|
||||
if ($max && $value->isMax($max) === false) {
|
||||
throw new Exception(
|
||||
key: 'validation.date.before',
|
||||
data: ['date' => $max->format($format)]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -48,7 +48,7 @@ return [
|
|||
'activeTypes' => function () {
|
||||
return array_filter(
|
||||
$this->availableTypes(),
|
||||
fn (string $type) => in_array($type, $this->props['options']),
|
||||
fn (string $type) => in_array($type, $this->props['options'], true),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
},
|
||||
|
@ -153,17 +153,17 @@ return [
|
|||
$detected = true;
|
||||
|
||||
if ($options['validate']($link) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.' . $type
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.' . $type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// none of the configured types has been detected
|
||||
if ($detected === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.linkType'
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.linkType'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -7,8 +7,11 @@ return [
|
|||
* Available layouts: `list`, `cardlets`, `cards`
|
||||
*/
|
||||
'layout' => function (string $layout = 'list') {
|
||||
$layouts = ['list', 'cardlets', 'cards'];
|
||||
return in_array($layout, $layouts) ? $layout : 'list';
|
||||
return match ($layout) {
|
||||
'cards' => 'cards',
|
||||
'cardlets' => 'cardlets',
|
||||
default => 'list'
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,7 @@ return [
|
|||
},
|
||||
'sanitizeOption' => function ($value) {
|
||||
$options = array_column($this->options(), 'value');
|
||||
return in_array($value, $options) === true ? $value : null;
|
||||
return in_array($value, $options) ? $value : null;
|
||||
},
|
||||
'sanitizeOptions' => function ($values) {
|
||||
$options = array_column($this->options(), 'value');
|
||||
|
|
|
@ -34,7 +34,9 @@ return [
|
|||
$parent = $this->uploadParent($uploads['parent'] ?? null);
|
||||
|
||||
if ($parent === null) {
|
||||
throw new InvalidArgumentException('"' . $uploads['parent'] . '" could not be resolved as a valid parent for the upload');
|
||||
throw new InvalidArgumentException(
|
||||
message: '"' . $uploads['parent'] . '" could not be resolved as a valid parent for the upload'
|
||||
);
|
||||
}
|
||||
|
||||
$file = new File([
|
||||
|
@ -52,7 +54,9 @@ return [
|
|||
'methods' => [
|
||||
'upload' => function (Api $api, $params, Closure $map) {
|
||||
if ($params === false) {
|
||||
throw new Exception('Uploads are disabled for this field');
|
||||
throw new Exception(
|
||||
message: 'Uploads are disabled for this field'
|
||||
);
|
||||
}
|
||||
|
||||
$parent = $this->uploadParent($params['parent'] ?? null);
|
||||
|
@ -68,7 +72,9 @@ return [
|
|||
$file = $parent->createFile($props, true);
|
||||
|
||||
if ($file instanceof File === false) {
|
||||
throw new Exception('The file could not be uploaded');
|
||||
throw new Exception(
|
||||
message: 'The file could not be uploaded'
|
||||
);
|
||||
}
|
||||
|
||||
return $map($file, $parent);
|
||||
|
|
|
@ -38,7 +38,7 @@ return [
|
|||
],
|
||||
'methods' => [
|
||||
'toNumber' => function ($value): float|null {
|
||||
if ($this->isEmpty($value) === true) {
|
||||
if ($this->isEmptyValue($value) === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,13 +91,13 @@ return [
|
|||
$name = array_key_first($errors);
|
||||
$error = $errors[$name];
|
||||
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'object.validation',
|
||||
'data' => [
|
||||
throw new InvalidArgumentException(
|
||||
key: 'object.validation',
|
||||
data: [
|
||||
'label' => $error['label'] ?? $name,
|
||||
'message' => implode("\n", $error['message'])
|
||||
]
|
||||
]);
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -20,7 +20,8 @@ return [
|
|||
],
|
||||
'computed' => [
|
||||
'default' => function () {
|
||||
return $this->sanitizeOption($this->default);
|
||||
$default = $this->model()->toString($this->default);
|
||||
return $this->sanitizeOption($default);
|
||||
},
|
||||
'value' => function () {
|
||||
return $this->sanitizeOption($this->value) ?? '';
|
||||
|
|
|
@ -18,7 +18,7 @@ return [
|
|||
return $icon;
|
||||
},
|
||||
/**
|
||||
* Custom placeholder string for empty option.
|
||||
* Text shown when no option is selected yet
|
||||
*/
|
||||
'placeholder' => function (string|array $placeholder = '—') {
|
||||
return I18n::translate($placeholder, $placeholder);
|
||||
|
|
|
@ -149,7 +149,7 @@ return [
|
|||
|
||||
// make the first column visible on mobile
|
||||
// if no other mobile columns are defined
|
||||
if (in_array(true, array_column($columns, 'mobile')) === false) {
|
||||
if (in_array(true, array_column($columns, 'mobile'), true) === false) {
|
||||
$columns[array_key_first($columns)]['mobile'] = true;
|
||||
}
|
||||
|
||||
|
@ -166,24 +166,37 @@ return [
|
|||
continue;
|
||||
}
|
||||
|
||||
$value[] = $this->form($row)->values();
|
||||
$value[] = $this->form()->fill(input: $row, passthrough: true)->toFormValues();
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
'form' => function (array $values = []) {
|
||||
return new Form([
|
||||
'fields' => $this->attrs['fields'] ?? [],
|
||||
'values' => $values,
|
||||
'model' => $this->model
|
||||
]);
|
||||
},
|
||||
'form' => function () {
|
||||
$this->form ??= new Form(
|
||||
fields: $this->attrs['fields'] ?? [],
|
||||
model: $this->model,
|
||||
language: 'current'
|
||||
);
|
||||
|
||||
return $this->form->reset();
|
||||
}
|
||||
],
|
||||
'save' => function ($value) {
|
||||
$data = [];
|
||||
$data = [];
|
||||
$form = $this->form();
|
||||
$defaults = $form->defaults();
|
||||
|
||||
foreach ($value as $row) {
|
||||
$row = $this->form($row)->content();
|
||||
foreach ($value as $index => $row) {
|
||||
$row = $form
|
||||
->reset()
|
||||
->fill(
|
||||
input: $defaults,
|
||||
)
|
||||
->submit(
|
||||
input: $row,
|
||||
passthrough: true
|
||||
)
|
||||
->toStoredValues();
|
||||
|
||||
// remove frontend helper id
|
||||
unset($row['_id']);
|
||||
|
@ -204,19 +217,20 @@ return [
|
|||
$values = A::wrap($value);
|
||||
|
||||
foreach ($values as $index => $value) {
|
||||
$form = $this->form($value);
|
||||
$form = $this->form();
|
||||
$form->fill(input: $value);
|
||||
|
||||
foreach ($form->fields() as $field) {
|
||||
$errors = $field->errors();
|
||||
|
||||
if (empty($errors) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'structure.validation',
|
||||
'data' => [
|
||||
throw new InvalidArgumentException(
|
||||
key: 'structure.validation',
|
||||
data: [
|
||||
'field' => $field->label() ?? Str::ucfirst($field->name()),
|
||||
'index' => $index + 1
|
||||
]
|
||||
]);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,14 @@ return [
|
|||
* The field value will be converted with the selected converter before the value gets saved. Available converters: `lower`, `upper`, `ucfirst`, `slug`
|
||||
*/
|
||||
'converter' => function ($value = null) {
|
||||
if ($value !== null && array_key_exists($value, $this->converters()) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'field.converter.invalid',
|
||||
'data' => ['converter' => $value]
|
||||
]);
|
||||
if (
|
||||
$value !== null &&
|
||||
array_key_exists($value, $this->converters()) === false
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
key: 'field.converter.invalid',
|
||||
data: ['converter' => $value]
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
|
|
@ -89,12 +89,11 @@ return [
|
|||
[
|
||||
'pattern' => 'files',
|
||||
'action' => function () {
|
||||
$params = array_merge($this->field()->files(), [
|
||||
return $this->field()->filepicker([
|
||||
...$this->field()->files(),
|
||||
'page' => $this->requestQuery('page'),
|
||||
'search' => $this->requestQuery('search')
|
||||
]);
|
||||
|
||||
return $this->field()->filepicker($params);
|
||||
}
|
||||
],
|
||||
[
|
||||
|
@ -104,14 +103,12 @@ return [
|
|||
$field = $this->field();
|
||||
$uploads = $field->uploads();
|
||||
|
||||
return $this->field()->upload($this, $uploads, function ($file, $parent) use ($field) {
|
||||
$absolute = $field->model()->is($parent) === false;
|
||||
|
||||
return [
|
||||
'filename' => $file->filename(),
|
||||
'dragText' => $file->panel()->dragText('auto', $absolute),
|
||||
];
|
||||
});
|
||||
return $this->field()->upload($this, $uploads, fn ($file, $parent) => [
|
||||
'filename' => $file->filename(),
|
||||
'dragText' => $file->panel()->dragText(
|
||||
absolute: $field->model()->is($parent) === false
|
||||
),
|
||||
]);
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\Date;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
|
@ -97,27 +97,27 @@ return [
|
|||
$format = 'H:i:s';
|
||||
|
||||
if ($min && $max && $value->isBetween($min, $max) === false) {
|
||||
throw new Exception([
|
||||
'key' => 'validation.time.between',
|
||||
'data' => [
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.time.between',
|
||||
data: [
|
||||
'min' => $min->format($format),
|
||||
'max' => $min->format($format)
|
||||
]
|
||||
]);
|
||||
} elseif ($min && $value->isMin($min) === false) {
|
||||
throw new Exception([
|
||||
'key' => 'validation.time.after',
|
||||
'data' => [
|
||||
'time' => $min->format($format),
|
||||
]
|
||||
]);
|
||||
} elseif ($max && $value->isMax($max) === false) {
|
||||
throw new Exception([
|
||||
'key' => 'validation.time.before',
|
||||
'data' => [
|
||||
'time' => $max->format($format),
|
||||
]
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
if ($min && $value->isMin($min) === false) {
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.time.after',
|
||||
data: ['time' => $min->format($format)]
|
||||
);
|
||||
}
|
||||
|
||||
if ($max && $value->isMax($max) === false) {
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.time.before',
|
||||
data: ['time' => $max->format($format)]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -65,8 +65,13 @@ return [
|
|||
'validations' => [
|
||||
'boolean',
|
||||
'required' => function ($value) {
|
||||
if ($this->isRequired() && ($value === false || $this->isEmpty($value))) {
|
||||
throw new InvalidArgumentException(I18n::translate('field.required'));
|
||||
if (
|
||||
$this->isRequired() &&
|
||||
($value === false || $this->isEmptyValue($value))
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
message: I18n::translate('field.required')
|
||||
);
|
||||
}
|
||||
},
|
||||
]
|
||||
|
|
|
@ -79,10 +79,10 @@ return [
|
|||
$this->minlength &&
|
||||
V::minLength(strip_tags($value), $this->minlength) === false
|
||||
) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.minlength',
|
||||
'data' => ['min' => $this->minlength]
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.minlength',
|
||||
data: ['min' => $this->minlength]
|
||||
);
|
||||
}
|
||||
},
|
||||
'maxlength' => function ($value) {
|
||||
|
@ -90,10 +90,10 @@ return [
|
|||
$this->maxlength &&
|
||||
V::maxLength(strip_tags($value), $this->maxlength) === false
|
||||
) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.maxlength',
|
||||
'data' => ['max' => $this->maxlength]
|
||||
]);
|
||||
throw new InvalidArgumentException(
|
||||
key: 'validation.maxlength',
|
||||
data: ['max' => $this->maxlength]
|
||||
);
|
||||
}
|
||||
},
|
||||
]
|
||||
|
|
|
@ -55,7 +55,7 @@ if (Helpers::hasOverride('collection') === false) { // @codeCoverageIgnore
|
|||
* Returns the result of a collection by name
|
||||
*
|
||||
* @return \Kirby\Toolkit\Collection|null
|
||||
* @todo 5.0 Add return type declaration
|
||||
* @todo 6.0 Add return type declaration
|
||||
*/
|
||||
function collection(string $name, array $options = [])
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Blocks;
|
||||
use Kirby\Cms\Collection;
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Html;
|
||||
|
@ -80,7 +81,9 @@ return function (App $app) {
|
|||
$message .= ' on parent "' . $parent->title() . '"';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($message);
|
||||
throw new InvalidArgumentException(
|
||||
message: $message
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -130,6 +133,18 @@ return function (App $app) {
|
|||
return Str::date($time, $format);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse yaml entries data and convert it to a
|
||||
* collection of field objects
|
||||
*/
|
||||
'toEntries' => function (Field $field): Collection {
|
||||
$entries = new Collection(parent: $field->parent());
|
||||
foreach ($field->yaml() as $index => $entry) {
|
||||
$entries->append(new Field($field->parent(), $index, $entry));
|
||||
}
|
||||
return $entries;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a file object from a filename in the field
|
||||
*/
|
||||
|
@ -266,7 +281,9 @@ return function (App $app) {
|
|||
$message .= ' on parent "' . $parent->id() . '"';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($message);
|
||||
throw new InvalidArgumentException(
|
||||
message: $message
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -40,19 +40,36 @@ return function (array $props) {
|
|||
|
||||
|
||||
if ($drafts !== false) {
|
||||
$sections['drafts'] = $section(I18n::translate('pages.status.draft'), 'drafts', $drafts);
|
||||
$sections['drafts'] = $section(
|
||||
I18n::translate('pages.status.draft'),
|
||||
'drafts',
|
||||
$drafts
|
||||
);
|
||||
}
|
||||
|
||||
if ($unlisted !== false) {
|
||||
$sections['unlisted'] = $section(I18n::translate('pages.status.unlisted'), 'unlisted', $unlisted);
|
||||
$sections['unlisted'] = $section(
|
||||
I18n::translate('pages.status.unlisted'),
|
||||
'unlisted',
|
||||
$unlisted
|
||||
);
|
||||
}
|
||||
|
||||
if ($listed !== false) {
|
||||
$sections['listed'] = $section(I18n::translate('pages.status.listed'), 'listed', $listed);
|
||||
$sections['listed'] = $section(
|
||||
I18n::translate('pages.status.listed'),
|
||||
'listed',
|
||||
$listed
|
||||
);
|
||||
}
|
||||
|
||||
// cleaning up
|
||||
unset($props['drafts'], $props['unlisted'], $props['listed'], $props['templates']);
|
||||
unset(
|
||||
$props['drafts'],
|
||||
$props['unlisted'],
|
||||
$props['listed'],
|
||||
$props['templates']
|
||||
);
|
||||
|
||||
return array_merge($props, ['sections' => $sections]);
|
||||
return [...$props, 'sections' => $sections];
|
||||
};
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\LanguageRoutes;
|
||||
use Kirby\Cms\Media;
|
||||
use Kirby\Cms\PluginAssets;
|
||||
use Kirby\Panel\Panel;
|
||||
use Kirby\Panel\Plugins;
|
||||
use Kirby\Plugin\Assets;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Uuid;
|
||||
|
||||
|
@ -71,7 +71,7 @@ return function (App $kirby) {
|
|||
string $hash,
|
||||
string $path
|
||||
) {
|
||||
return PluginAssets::resolve(
|
||||
return Assets::resolve(
|
||||
$provider . '/' . $pluginName,
|
||||
$hash,
|
||||
$path
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Form\Form;
|
||||
|
||||
return [
|
||||
|
@ -12,45 +10,19 @@ return [
|
|||
],
|
||||
'computed' => [
|
||||
'form' => function () {
|
||||
$fields = $this->fields;
|
||||
$disabled = $this->model->permissions()->update() === false;
|
||||
$lang = $this->model->kirby()->languageCode();
|
||||
$content = $this->model->content($lang)->toArray();
|
||||
|
||||
if ($disabled === true) {
|
||||
foreach ($fields as $key => $props) {
|
||||
$fields[$key]['disabled'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new Form([
|
||||
'fields' => $fields,
|
||||
'values' => $content,
|
||||
'model' => $this->model,
|
||||
'strict' => true
|
||||
]);
|
||||
return new Form(
|
||||
fields: $this->fields,
|
||||
model: $this->model,
|
||||
language: 'current'
|
||||
);
|
||||
},
|
||||
'fields' => function () {
|
||||
$fields = $this->form->fields()->toArray();
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
foreach ($fields as $index => $props) {
|
||||
unset($fields[$index]['value']);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
return $this->form->fields()->toProps();
|
||||
}
|
||||
],
|
||||
'methods' => [
|
||||
'errors' => function () {
|
||||
$this->form->fill($this->model->content('current')->toArray());
|
||||
return $this->form->errors();
|
||||
}
|
||||
],
|
||||
|
|
|
@ -6,6 +6,7 @@ use Kirby\Toolkit\I18n;
|
|||
|
||||
return [
|
||||
'mixins' => [
|
||||
'batch',
|
||||
'details',
|
||||
'empty',
|
||||
'headline',
|
||||
|
@ -90,14 +91,15 @@ return [
|
|||
$files = $files->flip();
|
||||
}
|
||||
|
||||
return $files;
|
||||
},
|
||||
'modelsPaginated' => function () {
|
||||
// apply the default pagination
|
||||
$files = $files->paginate([
|
||||
return $this->models()->paginate([
|
||||
'page' => $this->page,
|
||||
'limit' => $this->limit,
|
||||
'method' => 'none' // the page is manually provided
|
||||
]);
|
||||
|
||||
return $files;
|
||||
},
|
||||
'files' => function () {
|
||||
return $this->models;
|
||||
|
@ -105,15 +107,16 @@ return [
|
|||
'data' => function () {
|
||||
$data = [];
|
||||
|
||||
// the drag text needs to be absolute when the files come from
|
||||
// a different parent model
|
||||
$dragTextAbsolute = $this->model->is($this->parent) === false;
|
||||
|
||||
foreach ($this->models as $file) {
|
||||
$panel = $file->panel();
|
||||
foreach ($this->modelsPaginated() as $file) {
|
||||
$panel = $file->panel();
|
||||
$permissions = $file->permissions();
|
||||
|
||||
$item = [
|
||||
'dragText' => $panel->dragText('auto', $dragTextAbsolute),
|
||||
'dragText' => $panel->dragText(
|
||||
// the drag text needs to be absolute
|
||||
// when the files come from a different parent model
|
||||
absolute: $this->model->is($this->parent) === false
|
||||
),
|
||||
'extension' => $file->extension(),
|
||||
'filename' => $file->filename(),
|
||||
'id' => $file->id(),
|
||||
|
@ -125,6 +128,10 @@ return [
|
|||
'link' => $panel->url(true),
|
||||
'mime' => $file->mime(),
|
||||
'parent' => $file->parent()->panel()->path(),
|
||||
'permissions' => [
|
||||
'delete' => $permissions->can('delete'),
|
||||
'sort' => $permissions->can('sort'),
|
||||
],
|
||||
'template' => $file->template(),
|
||||
'text' => $file->toSafeString($this->text),
|
||||
'url' => $file->url(),
|
||||
|
@ -140,7 +147,7 @@ return [
|
|||
return $data;
|
||||
},
|
||||
'total' => function () {
|
||||
return $this->models->pagination()->total();
|
||||
return $this->models()->count();
|
||||
},
|
||||
'errors' => function () {
|
||||
$errors = [];
|
||||
|
@ -179,14 +186,8 @@ return [
|
|||
}
|
||||
|
||||
// count all uploaded files
|
||||
$max = $this->max ? $this->max - $this->total : null;
|
||||
|
||||
if ($this->max && $this->total === $this->max - 1) {
|
||||
$multiple = false;
|
||||
} else {
|
||||
$multiple = true;
|
||||
}
|
||||
|
||||
$max = $this->max ? $this->max - $this->total : null;
|
||||
$multiple = !$max || $max > 1;
|
||||
$template = $this->template === 'default' ? null : $this->template;
|
||||
|
||||
return [
|
||||
|
@ -220,6 +221,15 @@ return [
|
|||
|
||||
return true;
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'delete',
|
||||
'method' => 'DELETE',
|
||||
'action' => function () {
|
||||
return $this->section()->deleteSelected(
|
||||
ids: $this->requestBody('ids'),
|
||||
);
|
||||
}
|
||||
]
|
||||
];
|
||||
},
|
||||
|
@ -231,6 +241,7 @@ return [
|
|||
'options' => [
|
||||
'accept' => $this->accept,
|
||||
'apiUrl' => $this->parent->apiUrl(true) . '/sections/' . $this->name,
|
||||
'batch' => $this->batch,
|
||||
'columns' => $this->columnsWithTypes(),
|
||||
'empty' => $this->empty,
|
||||
'headline' => $this->headline,
|
||||
|
|
45
kirby/config/sections/mixins/batch.php
Normal file
45
kirby/config/sections/mixins/batch.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
/**
|
||||
* Activates the batch delete option for the section
|
||||
*/
|
||||
'batch' => function (bool $batch = false) {
|
||||
return $batch;
|
||||
},
|
||||
],
|
||||
'methods' => [
|
||||
'deleteSelected' => function (array $ids): bool {
|
||||
if ($ids === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if batch deletion is allowed
|
||||
if ($this->batch() === false) {
|
||||
throw new PermissionException(
|
||||
message: 'The section does not support batch actions'
|
||||
);
|
||||
}
|
||||
|
||||
$min = $this->min();
|
||||
|
||||
// check if the section has enough items after the deletion
|
||||
if ($this->total() - count($ids) < $min) {
|
||||
throw new Exception(
|
||||
message: I18n::template('error.section.' . $this->type() . '.min.' . I18n::form($min), [
|
||||
'min' => $min,
|
||||
'section' => $this->headline()
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
$this->models()->delete($ids);
|
||||
return true;
|
||||
}
|
||||
]
|
||||
];
|
|
@ -19,7 +19,7 @@ return [
|
|||
*/
|
||||
'layout' => function (string $layout = 'list') {
|
||||
$layouts = ['list', 'cardlets', 'cards', 'table'];
|
||||
return in_array($layout, $layouts) ? $layout : 'list';
|
||||
return in_array($layout, $layouts, true) ? $layout : 'list';
|
||||
},
|
||||
/**
|
||||
* Whether the raw content file values should be used for the table column previews. Should not be used unless it eases performance issues in your setup introduced with Kirby 4.2
|
||||
|
|
|
@ -24,7 +24,9 @@ return [
|
|||
$parent = $this->model->query($query);
|
||||
|
||||
if (!$parent) {
|
||||
throw new Exception('The parent for the query "' . $query . '" cannot be found in the section "' . $this->name() . '"');
|
||||
throw new Exception(
|
||||
message: 'The parent for the query "' . $query . '" cannot be found in the section "' . $this->name() . '"'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -33,7 +35,9 @@ return [
|
|||
$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');
|
||||
throw new Exception(
|
||||
message: 'The parent for the section "' . $this->name() . '" has to be a page, site or user object'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ return [
|
|||
|
||||
if (
|
||||
$this->type === 'pages' &&
|
||||
in_array($this->status, ['listed', 'published', 'all']) === false
|
||||
in_array($this->status, ['listed', 'published', 'all'], true) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use Kirby\Toolkit\I18n;
|
|||
|
||||
return [
|
||||
'mixins' => [
|
||||
'batch',
|
||||
'details',
|
||||
'empty',
|
||||
'headline',
|
||||
|
@ -44,7 +45,7 @@ return [
|
|||
$status = 'draft';
|
||||
}
|
||||
|
||||
if (in_array($status, ['all', 'draft', 'published', 'listed', 'unlisted']) === false) {
|
||||
if (in_array($status, ['all', 'draft', 'published', 'listed', 'unlisted'], true) === false) {
|
||||
$status = 'all';
|
||||
}
|
||||
|
||||
|
@ -77,7 +78,9 @@ return [
|
|||
$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.');
|
||||
throw new InvalidArgumentException(
|
||||
message: 'The parent is invalid. You must choose the site or a page as parent.'
|
||||
);
|
||||
}
|
||||
|
||||
return $parent;
|
||||
|
@ -111,7 +114,7 @@ return [
|
|||
// filter by all set templates
|
||||
if (
|
||||
$this->templates &&
|
||||
in_array($intendedTemplate, $this->templates) === false
|
||||
in_array($intendedTemplate, $this->templates, true) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -119,7 +122,7 @@ return [
|
|||
// exclude by all ignored templates
|
||||
if (
|
||||
$this->templatesIgnore &&
|
||||
in_array($intendedTemplate, $this->templatesIgnore) === true
|
||||
in_array($intendedTemplate, $this->templatesIgnore, true) === true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -147,25 +150,26 @@ return [
|
|||
$pages = $pages->flip();
|
||||
}
|
||||
|
||||
return $pages;
|
||||
},
|
||||
'modelsPaginated' => function () {
|
||||
// pagination
|
||||
$pages = $pages->paginate([
|
||||
return $this->models()->paginate([
|
||||
'page' => $this->page,
|
||||
'limit' => $this->limit,
|
||||
'method' => 'none' // the page is manually provided
|
||||
]);
|
||||
|
||||
return $pages;
|
||||
},
|
||||
'pages' => function () {
|
||||
return $this->models;
|
||||
},
|
||||
'total' => function () {
|
||||
return $this->models->pagination()->total();
|
||||
return $this->models()->count();
|
||||
},
|
||||
'data' => function () {
|
||||
$data = [];
|
||||
|
||||
foreach ($this->models as $page) {
|
||||
foreach ($this->modelsPaginated() as $page) {
|
||||
$panel = $page->panel();
|
||||
$permissions = $page->permissions();
|
||||
|
||||
|
@ -180,10 +184,11 @@ return [
|
|||
'link' => $panel->url(true),
|
||||
'parent' => $page->parentId(),
|
||||
'permissions' => [
|
||||
'sort' => $permissions->can('sort'),
|
||||
'delete' => $permissions->can('delete'),
|
||||
'changeSlug' => $permissions->can('changeSlug'),
|
||||
'changeStatus' => $permissions->can('changeStatus'),
|
||||
'changeTitle' => $permissions->can('changeTitle'),
|
||||
'sort' => $permissions->can('sort'),
|
||||
],
|
||||
'status' => $page->status(),
|
||||
'template' => $page->intendedTemplate()->name(),
|
||||
|
@ -313,12 +318,28 @@ return [
|
|||
return $blueprints;
|
||||
},
|
||||
],
|
||||
// @codeCoverageIgnoreStart
|
||||
'api' => function () {
|
||||
return [
|
||||
[
|
||||
'pattern' => 'delete',
|
||||
'method' => 'DELETE',
|
||||
'action' => function () {
|
||||
return $this->section()->deleteSelected(
|
||||
ids: $this->requestBody('ids'),
|
||||
);
|
||||
}
|
||||
]
|
||||
];
|
||||
},
|
||||
// @codeCoverageIgnoreEnd
|
||||
'toArray' => function () {
|
||||
return [
|
||||
'data' => $this->data,
|
||||
'errors' => $this->errors,
|
||||
'options' => [
|
||||
'add' => $this->add,
|
||||
'batch' => $this->batch,
|
||||
'columns' => $this->columnsWithTypes(),
|
||||
'empty' => $this->empty,
|
||||
'headline' => $this->headline,
|
||||
|
|
|
@ -216,14 +216,18 @@ return [
|
|||
// if url is empty, throw exception or link to the error page
|
||||
if ($tag->value === null) {
|
||||
if ($tag->kirby()->option('debug', false) === true) {
|
||||
$error = 'The linked page cannot be found';
|
||||
|
||||
if (empty($tag->text) === false) {
|
||||
throw new NotFoundException('The linked page cannot be found for the link text "' . $tag->text . '"');
|
||||
} else {
|
||||
throw new NotFoundException('The linked page cannot be found');
|
||||
$error .= ' for the link text "' . $tag->text . '"';
|
||||
}
|
||||
} else {
|
||||
$tag->value = Url::to($tag->kirby()->site()->errorPageId());
|
||||
|
||||
throw new NotFoundException(
|
||||
message: $error
|
||||
);
|
||||
}
|
||||
|
||||
$tag->value = Url::to($tag->kirby()->site()->errorPageId());
|
||||
}
|
||||
|
||||
return Html::a($tag->value, $tag->text, [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue