Update to Kirby 4.7.0
This commit is contained in:
parent
02a9ab387c
commit
ba25a9a198
509 changed files with 26604 additions and 14872 deletions
|
@ -3,7 +3,6 @@
|
|||
return [
|
||||
// cms classes
|
||||
'collection' => 'Kirby\Cms\Collection',
|
||||
'field' => 'Kirby\Cms\Field',
|
||||
'file' => 'Kirby\Cms\File',
|
||||
'files' => 'Kirby\Cms\Files',
|
||||
'find' => 'Kirby\Cms\Find',
|
||||
|
@ -24,6 +23,9 @@ return [
|
|||
'users' => 'Kirby\Cms\Users',
|
||||
'visitor' => 'Kirby\Cms\Visitor',
|
||||
|
||||
// content classes
|
||||
'field' => 'Kirby\Content\Field',
|
||||
|
||||
// data handler
|
||||
'data' => 'Kirby\Data\Data',
|
||||
'json' => 'Kirby\Data\Json',
|
||||
|
@ -69,17 +71,25 @@ return [
|
|||
'v' => 'Kirby\Toolkit\V',
|
||||
'xml' => 'Kirby\Toolkit\Xml',
|
||||
|
||||
// TODO: remove in 4.0.0
|
||||
'kirby\cms\asset' => 'Kirby\Filesystem\Asset',
|
||||
'kirby\cms\dir' => 'Kirby\Filesystem\Dir',
|
||||
'kirby\cms\filename' => 'Kirby\Filesystem\Filename',
|
||||
'kirby\cms\filefoundation' => 'Kirby\Filesystem\IsFile',
|
||||
'kirby\cms\form' => 'Kirby\Form\Form',
|
||||
'kirby\cms\kirbytag' => 'Kirby\Text\KirbyTag',
|
||||
'kirby\cms\kirbytags' => 'Kirby\Text\KirbyTags',
|
||||
'kirby\cms\template' => 'Kirby\Template\Template',
|
||||
'kirby\toolkit\dir' => 'Kirby\Filesystem\Dir',
|
||||
'kirby\toolkit\f' => 'Kirby\Filesystem\F',
|
||||
'kirby\toolkit\file' => 'Kirby\Filesystem\File',
|
||||
'kirby\toolkit\mime' => 'Kirby\Filesystem\Mime',
|
||||
// Deprecated aliases:
|
||||
// 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',
|
||||
'kirby\cms\field' => 'Kirby\Content\Field',
|
||||
'kirby\cms\form' => 'Kirby\Form\Form',
|
||||
'kirby\cms\kirbytag' => 'Kirby\Text\KirbyTag',
|
||||
'kirby\cms\kirbytags' => 'Kirby\Text\KirbyTags',
|
||||
'kirby\cms\template' => 'Kirby\Template\Template',
|
||||
'kirby\form\options' => 'Kirby\Option\Options',
|
||||
'kirby\form\optionsapi' => 'Kirby\Option\OptionsApi',
|
||||
'kirby\form\optionsquery' => 'Kirby\Option\OptionsQuery',
|
||||
'kirby\toolkit\dir' => 'Kirby\Filesystem\Dir',
|
||||
'kirby\toolkit\f' => 'Kirby\Filesystem\F',
|
||||
'kirby\toolkit\file' => 'Kirby\Filesystem\File',
|
||||
'kirby\toolkit\mime' => 'Kirby\Filesystem\Mime',
|
||||
'kirby\toolkit\query' => 'Kirby\Query\Query',
|
||||
];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Exception\AuthException;
|
||||
|
||||
return function () {
|
||||
$auth = $this->kirby()->auth();
|
||||
|
@ -11,17 +11,17 @@ return function () {
|
|||
$auth->type($allowImpersonation) === 'session' &&
|
||||
$auth->csrf() === false
|
||||
) {
|
||||
throw new PermissionException('Unauthenticated');
|
||||
throw new AuthException('Unauthenticated');
|
||||
}
|
||||
|
||||
// get user from session or basic auth
|
||||
if ($user = $auth->user(null, $allowImpersonation)) {
|
||||
if ($user->role()->permissions()->for('access', 'panel') === false) {
|
||||
throw new PermissionException(['key' => 'access.panel']);
|
||||
throw new AuthException(['key' => 'access.panel']);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
throw new PermissionException('Unauthenticated');
|
||||
throw new AuthException('Unauthenticated');
|
||||
};
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
/**
|
||||
* Api Collection Definitions
|
||||
*/
|
||||
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Languages;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Roles;
|
||||
use Kirby\Cms\Translations;
|
||||
use Kirby\Cms\Users;
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
|
@ -10,7 +18,7 @@ return [
|
|||
*/
|
||||
'children' => [
|
||||
'model' => 'page',
|
||||
'type' => 'Kirby\Cms\Pages',
|
||||
'type' => Pages::class,
|
||||
'view' => 'compact'
|
||||
],
|
||||
|
||||
|
@ -19,7 +27,7 @@ return [
|
|||
*/
|
||||
'files' => [
|
||||
'model' => 'file',
|
||||
'type' => 'Kirby\Cms\Files'
|
||||
'type' => Files::class,
|
||||
],
|
||||
|
||||
/**
|
||||
|
@ -27,7 +35,7 @@ return [
|
|||
*/
|
||||
'languages' => [
|
||||
'model' => 'language',
|
||||
'type' => 'Kirby\Cms\Languages'
|
||||
'type' => Languages::class,
|
||||
],
|
||||
|
||||
/**
|
||||
|
@ -35,7 +43,7 @@ return [
|
|||
*/
|
||||
'pages' => [
|
||||
'model' => 'page',
|
||||
'type' => 'Kirby\Cms\Pages',
|
||||
'type' => Pages::class,
|
||||
'view' => 'compact'
|
||||
],
|
||||
|
||||
|
@ -44,7 +52,7 @@ return [
|
|||
*/
|
||||
'roles' => [
|
||||
'model' => 'role',
|
||||
'type' => 'Kirby\Cms\Roles',
|
||||
'type' => Roles::class,
|
||||
'view' => 'compact'
|
||||
],
|
||||
|
||||
|
@ -53,7 +61,7 @@ return [
|
|||
*/
|
||||
'translations' => [
|
||||
'model' => 'translation',
|
||||
'type' => 'Kirby\Cms\Translations',
|
||||
'type' => Translations::class,
|
||||
'view' => 'compact'
|
||||
],
|
||||
|
||||
|
@ -63,7 +71,7 @@ return [
|
|||
'users' => [
|
||||
'default' => fn () => $this->users(),
|
||||
'model' => 'user',
|
||||
'type' => 'Kirby\Cms\Users',
|
||||
'type' => Users::class,
|
||||
'view' => 'compact'
|
||||
]
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ return [
|
|||
'FileBlueprint' => include __DIR__ . '/models/FileBlueprint.php',
|
||||
'FileVersion' => include __DIR__ . '/models/FileVersion.php',
|
||||
'Language' => include __DIR__ . '/models/Language.php',
|
||||
'License' => include __DIR__ . '/models/License.php',
|
||||
'Page' => include __DIR__ . '/models/Page.php',
|
||||
'PageBlueprint' => include __DIR__ . '/models/PageBlueprint.php',
|
||||
'Role' => include __DIR__ . '/models/Role.php',
|
||||
|
|
|
@ -59,7 +59,7 @@ return [
|
|||
'url' => fn (File $file) => $file->url(),
|
||||
'uuid' => fn (File $file) => $file->uuid()?->toString()
|
||||
],
|
||||
'type' => 'Kirby\Cms\File',
|
||||
'type' => File::class,
|
||||
'views' => [
|
||||
'default' => [
|
||||
'content',
|
||||
|
|
|
@ -12,7 +12,6 @@ return [
|
|||
'tabs' => fn (FileBlueprint $blueprint) => $blueprint->tabs(),
|
||||
'title' => fn (FileBlueprint $blueprint) => $blueprint->title(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\FileBlueprint',
|
||||
'views' => [
|
||||
],
|
||||
'type' => FileBlueprint::class,
|
||||
'views' => [],
|
||||
];
|
||||
|
|
|
@ -20,7 +20,7 @@ return [
|
|||
'type' => fn (FileVersion $file) => $file->type(),
|
||||
'url' => fn (FileVersion $file) => $file->url(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\FileVersion',
|
||||
'type' => FileVersion::class,
|
||||
'views' => [
|
||||
'default' => [
|
||||
'dimensions',
|
||||
|
|
|
@ -15,7 +15,7 @@ return [
|
|||
'rules' => fn (Language $language) => $language->rules(),
|
||||
'url' => fn (Language $language) => $language->url(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\Language',
|
||||
'type' => Language::class,
|
||||
'views' => [
|
||||
'default' => [
|
||||
'code',
|
||||
|
|
17
kirby/config/api/models/License.php
Normal file
17
kirby/config/api/models/License.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\License;
|
||||
|
||||
/**
|
||||
* Page
|
||||
*/
|
||||
return [
|
||||
'fields' => [
|
||||
'status' => fn (License $license) => $license->status()->value(),
|
||||
'code' => function (License $license) {
|
||||
return $this->kirby()->user()->isAdmin() ? $license->code() : $license->code(true);
|
||||
},
|
||||
'type' => fn (License $license) => $license->type()->label(),
|
||||
],
|
||||
'type' => License::class,
|
||||
];
|
|
@ -40,7 +40,7 @@ return [
|
|||
'url' => fn (Page $page) => $page->url(),
|
||||
'uuid' => fn (Page $page) => $page->uuid()?->toString()
|
||||
],
|
||||
'type' => 'Kirby\Cms\Page',
|
||||
'type' => Page::class,
|
||||
'views' => [
|
||||
'compact' => [
|
||||
'id',
|
||||
|
|
|
@ -15,7 +15,6 @@ return [
|
|||
'tabs' => fn (PageBlueprint $blueprint) => $blueprint->tabs(),
|
||||
'title' => fn (PageBlueprint $blueprint) => $blueprint->title(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\PageBlueprint',
|
||||
'views' => [
|
||||
],
|
||||
'type' => PageBlueprint::class,
|
||||
'views' => [],
|
||||
];
|
||||
|
|
|
@ -12,7 +12,7 @@ return [
|
|||
'permissions' => fn (Role $role) => $role->permissions()->toArray(),
|
||||
'title' => fn (Role $role) => $role->title(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\Role',
|
||||
'type' => Role::class,
|
||||
'views' => [
|
||||
'compact' => [
|
||||
'description',
|
||||
|
|
|
@ -19,7 +19,7 @@ return [
|
|||
'title' => fn (Site $site) => $site->title()->value(),
|
||||
'url' => fn (Site $site) => $site->url(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\Site',
|
||||
'type' => Site::class,
|
||||
'views' => [
|
||||
'compact' => [
|
||||
'title',
|
||||
|
|
|
@ -12,6 +12,6 @@ return [
|
|||
'tabs' => fn (SiteBlueprint $blueprint) => $blueprint->tabs(),
|
||||
'title' => fn (SiteBlueprint $blueprint) => $blueprint->title(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\SiteBlueprint',
|
||||
'type' => SiteBlueprint::class,
|
||||
'views' => [],
|
||||
];
|
||||
|
|
|
@ -49,7 +49,7 @@ return [
|
|||
return null;
|
||||
}
|
||||
],
|
||||
'type' => 'Kirby\Cms\System',
|
||||
'type' => System::class,
|
||||
'views' => [
|
||||
'login' => [
|
||||
'authStatus',
|
||||
|
|
|
@ -13,7 +13,7 @@ return [
|
|||
'id' => fn (Translation $translation) => $translation->id(),
|
||||
'name' => fn (Translation $translation) => $translation->name(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\Translation',
|
||||
'type' => Translation::class,
|
||||
'views' => [
|
||||
'compact' => [
|
||||
'direction',
|
||||
|
|
|
@ -27,7 +27,7 @@ return [
|
|||
'username' => fn (User $user) => $user->username(),
|
||||
'uuid' => fn (User $user) => $user->uuid()?->toString()
|
||||
],
|
||||
'type' => 'Kirby\Cms\User',
|
||||
'type' => User::class,
|
||||
'views' => [
|
||||
'default' => [
|
||||
'avatar',
|
||||
|
|
|
@ -12,7 +12,6 @@ return [
|
|||
'tabs' => fn (UserBlueprint $blueprint) => $blueprint->tabs(),
|
||||
'title' => fn (UserBlueprint $blueprint) => $blueprint->title(),
|
||||
],
|
||||
'type' => 'Kirby\Cms\UserBlueprint',
|
||||
'views' => [
|
||||
],
|
||||
'type' => UserBlueprint::class,
|
||||
'views' => [],
|
||||
];
|
||||
|
|
|
@ -19,7 +19,10 @@ return function ($kirby) {
|
|||
// 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 = array_merge(
|
||||
$routes,
|
||||
include __DIR__ . '/routes/languages.php'
|
||||
);
|
||||
}
|
||||
|
||||
return $routes;
|
||||
|
|
|
@ -1,63 +1,70 @@
|
|||
<?php
|
||||
|
||||
// routing pattern to match all models with files
|
||||
$pattern = '(account|pages/[^/]+|site|users/[^/]+)';
|
||||
$filePattern = '(account/|pages/[^/]+/|site/|users/[^/]+/|)files/(:any)';
|
||||
$parentPattern = '(account|pages/[^/]+|site|users/[^/]+)/files';
|
||||
|
||||
/**
|
||||
* Files Routes
|
||||
*/
|
||||
return [
|
||||
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)/sections/(:any)',
|
||||
'method' => 'GET',
|
||||
'action' => function (string $path, string $filename, string $sectionName) {
|
||||
return $this->file($path, $filename)->blueprint()->section($sectionName)?->toResponse();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)/fields/(:any)/(:all?)',
|
||||
'pattern' => $filePattern . '/fields/(:any)/(:all?)',
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $parent, string $filename, string $fieldName, string $path = null) {
|
||||
'action' => function (string $parent, string $filename, string $fieldName, string|null $path = null) {
|
||||
if ($file = $this->file($parent, $filename)) {
|
||||
return $this->fieldApi($file, $fieldName, $path);
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files',
|
||||
'pattern' => $filePattern . '/sections/(:any)',
|
||||
'method' => 'GET',
|
||||
'action' => function (string $path) {
|
||||
return $this->parent($path)->files()->sorted();
|
||||
'action' => function (string $path, string $filename, string $sectionName) {
|
||||
return $this->file($path, $filename)->blueprint()->section($sectionName)?->toResponse();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files',
|
||||
'pattern' => $filePattern . '/sections/(:any)/(:all?)',
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $parent, string $filename, string $sectionName, string|null $path = null) {
|
||||
if ($file = $this->file($parent, $filename)) {
|
||||
return $this->sectionApi($file, $sectionName, $path);
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $parentPattern,
|
||||
'method' => 'GET',
|
||||
'action' => function (string $path) {
|
||||
return $this->files($path)->sorted();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $parentPattern,
|
||||
'method' => 'POST',
|
||||
'action' => function (string $path) {
|
||||
// move_uploaded_file() not working with unit test
|
||||
// @codeCoverageIgnoreStart
|
||||
return $this->upload(function ($source, $filename) use ($path) {
|
||||
$props = [
|
||||
// move the source file from the temp dir
|
||||
return $this->parent($path)->createFile([
|
||||
'content' => [
|
||||
'sort' => $this->requestBody('sort')
|
||||
],
|
||||
'source' => $source,
|
||||
'template' => $this->requestBody('template'),
|
||||
'filename' => $filename
|
||||
];
|
||||
|
||||
// move the source file from the temp dir
|
||||
return $this->parent($path)->createFile($props, true);
|
||||
], true);
|
||||
});
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/search',
|
||||
'pattern' => $parentPattern . '/search',
|
||||
'method' => 'GET|POST',
|
||||
'action' => function (string $path) {
|
||||
$files = $this->parent($path)->files();
|
||||
$files = $this->files($path);
|
||||
|
||||
if ($this->requestMethod() === 'GET') {
|
||||
return $files->search($this->requestQuery('q'));
|
||||
|
@ -67,24 +74,24 @@ return [
|
|||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/sort',
|
||||
'pattern' => $parentPattern . '/sort',
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $path) {
|
||||
return $this->parent($path)->files()->changeSort(
|
||||
return $this->files($path)->changeSort(
|
||||
$this->requestBody('files'),
|
||||
$this->requestBody('index')
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)',
|
||||
'pattern' => $filePattern,
|
||||
'method' => 'GET',
|
||||
'action' => function (string $path, string $filename) {
|
||||
return $this->file($path, $filename);
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)',
|
||||
'pattern' => $filePattern,
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $path, string $filename) {
|
||||
return $this->file($path, $filename)->update(
|
||||
|
@ -95,7 +102,7 @@ return [
|
|||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)',
|
||||
'pattern' => $filePattern,
|
||||
'method' => 'POST',
|
||||
'action' => function (string $path, string $filename) {
|
||||
// move the source file from the temp dir
|
||||
|
@ -105,28 +112,29 @@ return [
|
|||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)',
|
||||
'pattern' => $filePattern,
|
||||
'method' => 'DELETE',
|
||||
'action' => function (string $path, string $filename) {
|
||||
return $this->file($path, $filename)->delete();
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => $pattern . '/files/(:any)/name',
|
||||
'pattern' => $filePattern . '/name',
|
||||
'method' => 'PATCH',
|
||||
'action' => function (string $path, string $filename) {
|
||||
return $this->file($path, $filename)->changeName($this->requestBody('name'));
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'files/search',
|
||||
'pattern' => $parentPattern . '/search',
|
||||
'method' => 'GET|POST',
|
||||
'action' => function () {
|
||||
$files = $this
|
||||
->site()
|
||||
->index(true)
|
||||
->filter('isReadable', true)
|
||||
->files();
|
||||
->filter('isListable', true)
|
||||
->files()
|
||||
->filter('isListable', true);
|
||||
|
||||
if ($this->requestMethod() === 'GET') {
|
||||
return $files->search($this->requestQuery('q'));
|
||||
|
|
35
kirby/config/api/routes/kql.php
Normal file
35
kirby/config/api/routes/kql.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
return [
|
||||
'routes' => function ($kirby) {
|
||||
return [
|
||||
[
|
||||
'pattern' => 'query',
|
||||
'method' => 'POST|GET',
|
||||
'auth' => $kirby->option('kql.auth') !== false,
|
||||
'action' => function () use ($kirby) {
|
||||
$kql = '\Kirby\Kql\Kql';
|
||||
|
||||
if (class_exists($kql) === false) {
|
||||
return [
|
||||
'code' => 500,
|
||||
'status' => 'error',
|
||||
'message' => 'KQL plugin is not installed',
|
||||
];
|
||||
}
|
||||
|
||||
$input = $kirby->request()->get();
|
||||
$result = $kql::run($input);
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'result' => $result,
|
||||
'status' => 'ok',
|
||||
];
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
// @codeCoverageIgnoreEnd
|
|
@ -5,7 +5,7 @@
|
|||
* Page Routes
|
||||
*/
|
||||
return [
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
[
|
||||
'pattern' => 'pages/(:any)',
|
||||
'method' => 'GET',
|
||||
|
@ -100,6 +100,15 @@ return [
|
|||
return $this->page($id)->changeTitle($this->requestBody('title'));
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'pages/(:any)/fields/(:any)/(:all?)',
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $id, string $fieldName, string|null $path = null) {
|
||||
if ($page = $this->page($id)) {
|
||||
return $this->fieldApi($page, $fieldName, $path);
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'pages/(:any)/sections/(:any)',
|
||||
'method' => 'GET',
|
||||
|
@ -108,12 +117,13 @@ return [
|
|||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'pages/(:any)/fields/(:any)/(:all?)',
|
||||
'pattern' => 'pages/(:any)/sections/(:any)/(:all?)',
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $id, string $fieldName, string $path = null) {
|
||||
'action' => function (string $id, string $sectionName, string|null $path = null) {
|
||||
if ($page = $this->page($id)) {
|
||||
return $this->fieldApi($page, $fieldName, $path);
|
||||
return $this->sectionApi($page, $sectionName, $path);
|
||||
}
|
||||
}
|
||||
],
|
||||
// @codeCoverageIgnoreEnd
|
||||
];
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* Site Routes
|
||||
*/
|
||||
return [
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
[
|
||||
'pattern' => 'site',
|
||||
'action' => function () {
|
||||
|
@ -75,7 +75,7 @@ return [
|
|||
$pages = $this
|
||||
->site()
|
||||
->index(true)
|
||||
->filter('isReadable', true);
|
||||
->filter('isListable', true);
|
||||
|
||||
if ($this->requestMethod() === 'GET') {
|
||||
return $pages->search($this->requestQuery('q'));
|
||||
|
@ -84,6 +84,13 @@ return [
|
|||
return $pages->query($this->requestBody());
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'site/fields/(:any)/(:all?)',
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $fieldName, string|null $path = null) {
|
||||
return $this->fieldApi($this->site(), $fieldName, $path);
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'site/sections/(:any)',
|
||||
'method' => 'GET',
|
||||
|
@ -92,11 +99,11 @@ return [
|
|||
}
|
||||
],
|
||||
[
|
||||
'pattern' => 'site/fields/(:any)/(:all?)',
|
||||
'pattern' => 'site/sections/(:any)/(:all?)',
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $fieldName, string $path = null) {
|
||||
return $this->fieldApi($this->site(), $fieldName, $path);
|
||||
'action' => function (string $sectionName, string|null $path = null) {
|
||||
return $this->sectionApi($this->site(), $sectionName, $path);
|
||||
}
|
||||
]
|
||||
|
||||
],
|
||||
// @codeCoverageIgnoreEnd
|
||||
];
|
||||
|
|
|
@ -31,6 +31,18 @@ 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',
|
||||
|
|
|
@ -8,6 +8,7 @@ use Kirby\Toolkit\Str;
|
|||
* User Routes
|
||||
*/
|
||||
return [
|
||||
// @codeCoverageIgnoreStart
|
||||
[
|
||||
'pattern' => 'users',
|
||||
'method' => 'GET',
|
||||
|
@ -202,7 +203,19 @@ return [
|
|||
'users/(:any)/roles',
|
||||
],
|
||||
'action' => function (string $id) {
|
||||
return $this->user($id)->roles();
|
||||
$kirby = $this->kirby();
|
||||
$purpose = $kirby->request()->get('purpose');
|
||||
return $this->user($id)->roles($purpose);
|
||||
}
|
||||
],
|
||||
[
|
||||
'pattern' => [
|
||||
'(account)/fields/(:any)/(:all?)',
|
||||
'users/(:any)/fields/(:any)/(:all?)',
|
||||
],
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $id, string $fieldName, string|null $path = null) {
|
||||
return $this->fieldApi($this->user($id), $fieldName, $path);
|
||||
}
|
||||
],
|
||||
[
|
||||
|
@ -219,12 +232,13 @@ return [
|
|||
],
|
||||
[
|
||||
'pattern' => [
|
||||
'(account)/fields/(:any)/(:all?)',
|
||||
'users/(:any)/fields/(:any)/(:all?)',
|
||||
'(account)/sections/(:any)/(:all?)',
|
||||
'users/(:any)/sections/(:any)/(:all?)',
|
||||
],
|
||||
'method' => 'ALL',
|
||||
'action' => function (string $id, string $fieldName, string $path = null) {
|
||||
return $this->fieldApi($this->user($id), $fieldName, $path);
|
||||
'action' => function (string $id, string $sectionName, string|null $path = null) {
|
||||
return $this->sectionApi($this->user($id), $sectionName, $path);
|
||||
}
|
||||
],
|
||||
// @codeCoverageIgnoreEnd
|
||||
];
|
||||
|
|
|
@ -8,6 +8,7 @@ return function () {
|
|||
'label' => I18n::translate('view.account'),
|
||||
'search' => 'users',
|
||||
'dialogs' => require __DIR__ . '/account/dialogs.php',
|
||||
'drawers' => require __DIR__ . '/account/drawers.php',
|
||||
'dropdowns' => require __DIR__ . '/account/dropdowns.php',
|
||||
'views' => require __DIR__ . '/account/views.php'
|
||||
];
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Panel\UserTotpEnableDialog;
|
||||
|
||||
$dialogs = require __DIR__ . '/../users/dialogs.php';
|
||||
|
||||
return [
|
||||
|
@ -46,6 +48,13 @@ return [
|
|||
'submit' => $dialogs['user.delete']['submit'],
|
||||
],
|
||||
|
||||
// account fields dialogs
|
||||
'account.fields' => [
|
||||
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $dialogs['user.fields']['load'],
|
||||
'submit' => $dialogs['user.fields']['submit']
|
||||
],
|
||||
|
||||
// change file name
|
||||
'account.file.changeName' => [
|
||||
'pattern' => '(account)/files/(:any)/changeName',
|
||||
|
@ -60,6 +69,13 @@ return [
|
|||
'submit' => $dialogs['user.file.changeSort']['submit'],
|
||||
],
|
||||
|
||||
// change file template
|
||||
'account.file.changeTemplate' => [
|
||||
'pattern' => '(account)/files/(:any)/changeTemplate',
|
||||
'load' => $dialogs['user.file.changeTemplate']['load'],
|
||||
'submit' => $dialogs['user.file.changeTemplate']['submit'],
|
||||
],
|
||||
|
||||
// delete
|
||||
'account.file.delete' => [
|
||||
'pattern' => '(account)/files/(:any)/delete',
|
||||
|
@ -67,4 +83,24 @@ return [
|
|||
'submit' => $dialogs['user.file.delete']['submit'],
|
||||
],
|
||||
|
||||
// account file fields dialogs
|
||||
'account.file.fields' => [
|
||||
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $dialogs['user.file.fields']['load'],
|
||||
'submit' => $dialogs['user.file.fields']['submit']
|
||||
],
|
||||
|
||||
// account enable TOTP
|
||||
'account.totp.enable' => [
|
||||
'pattern' => '(account)/totp/enable',
|
||||
'load' => fn () => (new UserTotpEnableDialog())->load(),
|
||||
'submit' => fn () => (new UserTotpEnableDialog())->submit()
|
||||
],
|
||||
|
||||
// account disable TOTP
|
||||
'account.totp.disable' => [
|
||||
'pattern' => '(account)/totp/disable',
|
||||
'load' => $dialogs['user.totp.disable']['load'],
|
||||
'submit' => $dialogs['user.totp.disable']['submit']
|
||||
],
|
||||
];
|
||||
|
|
19
kirby/config/areas/account/drawers.php
Normal file
19
kirby/config/areas/account/drawers.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
$drawers = require __DIR__ . '/../users/drawers.php';
|
||||
|
||||
return [
|
||||
// account fields drawers
|
||||
'account.fields' => [
|
||||
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $drawers['user.fields']['load'],
|
||||
'submit' => $drawers['user.fields']['submit']
|
||||
],
|
||||
|
||||
// account file fields drawers
|
||||
'account.file.fields' => [
|
||||
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $drawers['user.file.fields']['load'],
|
||||
'submit' => $drawers['user.file.fields']['submit']
|
||||
],
|
||||
];
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'account' => [
|
||||
|
@ -19,6 +20,13 @@ return [
|
|||
],
|
||||
'account.password' => [
|
||||
'pattern' => 'reset-password',
|
||||
'action' => fn () => ['component' => 'k-reset-password-view']
|
||||
'action' => fn () => [
|
||||
'component' => 'k-reset-password-view',
|
||||
'breadcrumb' => [
|
||||
[
|
||||
'label' => I18n::translate('view.resetPassword')
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
|
61
kirby/config/areas/fields/dialogs.php
Normal file
61
kirby/config/areas/fields/dialogs.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Field;
|
||||
|
||||
return [
|
||||
'model' => [
|
||||
'load' => function (
|
||||
string $modelPath,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::dialog(
|
||||
model: Find::parent($modelPath),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'GET'
|
||||
);
|
||||
},
|
||||
'submit' => function (
|
||||
string $modelPath,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::dialog(
|
||||
model: Find::parent($modelPath),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'POST'
|
||||
);
|
||||
}
|
||||
],
|
||||
'file' => [
|
||||
'load' => function (
|
||||
string $modelPath,
|
||||
string $filename,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::dialog(
|
||||
model: Find::file($modelPath, $filename),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'GET'
|
||||
);
|
||||
},
|
||||
'submit' => function (
|
||||
string $modelPath,
|
||||
string $filename,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::dialog(
|
||||
model: Find::file($modelPath, $filename),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'POST'
|
||||
);
|
||||
}
|
||||
],
|
||||
];
|
61
kirby/config/areas/fields/drawers.php
Normal file
61
kirby/config/areas/fields/drawers.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Field;
|
||||
|
||||
return [
|
||||
'model' => [
|
||||
'load' => function (
|
||||
string $modelPath,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::drawer(
|
||||
model: Find::parent($modelPath),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'GET'
|
||||
);
|
||||
},
|
||||
'submit' => function (
|
||||
string $modelPath,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::drawer(
|
||||
model: Find::parent($modelPath),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'POST'
|
||||
);
|
||||
}
|
||||
],
|
||||
'file' => [
|
||||
'load' => function (
|
||||
string $modelPath,
|
||||
string $filename,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::drawer(
|
||||
model: Find::file($modelPath, $filename),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'GET'
|
||||
);
|
||||
},
|
||||
'submit' => function (
|
||||
string $modelPath,
|
||||
string $filename,
|
||||
string $fieldName,
|
||||
string|null $path = null
|
||||
) {
|
||||
return Field::drawer(
|
||||
model: Find::file($modelPath, $filename),
|
||||
fieldName: $fieldName,
|
||||
path: $path,
|
||||
method: 'POST'
|
||||
);
|
||||
}
|
||||
],
|
||||
];
|
|
@ -26,7 +26,7 @@ return [
|
|||
'type' => 'slug',
|
||||
'required' => true,
|
||||
'icon' => 'title',
|
||||
'allow' => '@._-',
|
||||
'allow' => 'a-z0-9@._-',
|
||||
'after' => '.' . $file->extension(),
|
||||
'preselect' => true
|
||||
]
|
||||
|
@ -40,7 +40,8 @@ return [
|
|||
},
|
||||
'submit' => function (string $path, string $filename) {
|
||||
$file = Find::file($path, $filename);
|
||||
$renamed = $file->changeName($file->kirby()->request()->get('name'));
|
||||
$name = $file->kirby()->request()->get('name');
|
||||
$renamed = $file->changeName($name);
|
||||
$oldUrl = $file->panel()->url(true);
|
||||
$newUrl = $renamed->panel()->url(true);
|
||||
$response = [
|
||||
|
@ -96,6 +97,44 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
'changeTemplate' => [
|
||||
'load' => function (string $path, string $filename) {
|
||||
$file = Find::file($path, $filename);
|
||||
$blueprints = $file->blueprints();
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'fields' => [
|
||||
'warning' => [
|
||||
'type' => 'info',
|
||||
'theme' => 'notice',
|
||||
'text' => I18n::translate('file.changeTemplate.notice')
|
||||
],
|
||||
'template' => Field::template($blueprints, [
|
||||
'required' => true
|
||||
])
|
||||
],
|
||||
'theme' => 'notice',
|
||||
'submitButton' => I18n::translate('change'),
|
||||
'value' => [
|
||||
'template' => $file->template()
|
||||
]
|
||||
]
|
||||
];
|
||||
},
|
||||
'submit' => function (string $path, string $filename) {
|
||||
$file = Find::file($path, $filename);
|
||||
$template = $file->kirby()->request()->get('template');
|
||||
|
||||
$file->changeTemplate($template);
|
||||
|
||||
return [
|
||||
'event' => 'file.changeTemplate',
|
||||
];
|
||||
}
|
||||
],
|
||||
|
||||
'delete' => [
|
||||
'load' => function (string $path, string $filename) {
|
||||
$file = Find::file($path, $filename);
|
||||
|
@ -129,4 +168,5 @@ return [
|
|||
];
|
||||
}
|
||||
],
|
||||
|
||||
];
|
||||
|
|
11
kirby/config/areas/lab.php
Normal file
11
kirby/config/areas/lab.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
return function () {
|
||||
return [
|
||||
'icon' => 'lab',
|
||||
'label' => 'Lab',
|
||||
'menu' => false,
|
||||
'drawers' => require __DIR__ . '/lab/drawers.php',
|
||||
'views' => require __DIR__ . '/lab/views.php'
|
||||
];
|
||||
};
|
30
kirby/config/areas/lab/drawers.php
Normal file
30
kirby/config/areas/lab/drawers.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Panel\Lab\Docs;
|
||||
|
||||
return [
|
||||
'lab.docs' => [
|
||||
'pattern' => 'lab/docs/(:any)',
|
||||
'load' => function (string $component) {
|
||||
if (Docs::installed() === false) {
|
||||
return [
|
||||
'component' => 'k-text-drawer',
|
||||
'props' => [
|
||||
'text' => 'The UI docs are not installed.'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$docs = new Docs($component);
|
||||
|
||||
return [
|
||||
'component' => 'k-lab-docs-drawer',
|
||||
'props' => [
|
||||
'icon' => 'book',
|
||||
'title' => $component,
|
||||
'docs' => $docs->toArray()
|
||||
]
|
||||
];
|
||||
},
|
||||
],
|
||||
];
|
151
kirby/config/areas/lab/views.php
Normal file
151
kirby/config/areas/lab/views.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Panel\Lab\Category;
|
||||
use Kirby\Panel\Lab\Docs;
|
||||
|
||||
return [
|
||||
'lab' => [
|
||||
'pattern' => 'lab',
|
||||
'action' => function () {
|
||||
return [
|
||||
'component' => 'k-lab-index-view',
|
||||
'props' => [
|
||||
'categories' => Category::all(),
|
||||
'info' => Category::installed() ? null : 'The default Lab examples are not installed.',
|
||||
'tab' => 'examples',
|
||||
],
|
||||
];
|
||||
}
|
||||
],
|
||||
'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 [
|
||||
'component' => 'k-lab-index-view',
|
||||
'title' => 'Docs',
|
||||
'breadcrumb' => [
|
||||
[
|
||||
'label' => 'Docs',
|
||||
'link' => 'lab/docs'
|
||||
]
|
||||
],
|
||||
'props' => $props,
|
||||
];
|
||||
}
|
||||
],
|
||||
'lab.doc' => [
|
||||
'pattern' => 'lab/docs/(:any)',
|
||||
'action' => function (string $component) {
|
||||
$crumbs = [
|
||||
[
|
||||
'label' => 'Docs',
|
||||
'link' => 'lab/docs'
|
||||
],
|
||||
[
|
||||
'label' => $component,
|
||||
'link' => 'lab/docs/' . $component
|
||||
]
|
||||
];
|
||||
|
||||
if (Docs::installed() === false) {
|
||||
return [
|
||||
'component' => 'k-lab-index-view',
|
||||
'title' => $component,
|
||||
'breadcrumb' => $crumbs,
|
||||
'props' => [
|
||||
'info' => 'The UI docs are not installed.',
|
||||
'tab' => 'docs',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$docs = new Docs($component);
|
||||
|
||||
return [
|
||||
'component' => 'k-lab-docs-view',
|
||||
'title' => $component,
|
||||
'breadcrumb' => $crumbs,
|
||||
'props' => [
|
||||
'component' => $component,
|
||||
'docs' => $docs->toArray(),
|
||||
'lab' => $docs->lab()
|
||||
]
|
||||
];
|
||||
}
|
||||
],
|
||||
'lab.vue' => [
|
||||
'pattern' => [
|
||||
'lab/(:any)/(:any)/index.vue',
|
||||
'lab/(:any)/(:any)/(:any)/index.vue'
|
||||
],
|
||||
'action' => function (
|
||||
string $category,
|
||||
string $id,
|
||||
string|null $tab = null
|
||||
) {
|
||||
return Category::factory($category)->example($id, $tab)->serve();
|
||||
}
|
||||
],
|
||||
'lab.example' => [
|
||||
'pattern' => 'lab/(:any)/(:any)/(:any?)',
|
||||
'action' => function (
|
||||
string $category,
|
||||
string $id,
|
||||
string|null $tab = null
|
||||
) {
|
||||
$category = Category::factory($category);
|
||||
$example = $category->example($id, $tab);
|
||||
$props = $example->props();
|
||||
$vue = $example->vue();
|
||||
$compiler = App::instance()->option('panel.vue.compiler', true);
|
||||
|
||||
if (Docs::installed() === true && $docs = $props['docs'] ?? null) {
|
||||
$docs = new Docs($docs);
|
||||
}
|
||||
|
||||
$github = $docs?->github();
|
||||
|
||||
if ($source = $props['source'] ?? null) {
|
||||
$github ??= 'https://github.com/getkirby/kirby/tree/main/' . $source;
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-lab-playground-view',
|
||||
'breadcrumb' => [
|
||||
[
|
||||
'label' => $category->name(),
|
||||
],
|
||||
[
|
||||
'label' => $example->title(),
|
||||
'link' => $example->url()
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'compiler' => $compiler,
|
||||
'docs' => $docs?->name(),
|
||||
'examples' => $vue['examples'],
|
||||
'file' => $example->module(),
|
||||
'github' => $github,
|
||||
'props' => $props,
|
||||
'styles' => $vue['style'],
|
||||
'tab' => $example->tab(),
|
||||
'tabs' => array_values($example->tabs()),
|
||||
'template' => $vue['template'],
|
||||
'title' => $example->title(),
|
||||
],
|
||||
];
|
||||
}
|
||||
]
|
||||
];
|
|
@ -4,7 +4,7 @@ use Kirby\Toolkit\I18n;
|
|||
|
||||
return function ($kirby) {
|
||||
return [
|
||||
'icon' => 'globe',
|
||||
'icon' => 'translate',
|
||||
'label' => I18n::translate('view.languages'),
|
||||
'menu' => true,
|
||||
'dialogs' => require __DIR__ . '/languages/dialogs.php',
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Field;
|
||||
use Kirby\Cms\LanguageVariable;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
$languageDialogFields = [
|
||||
'name' => [
|
||||
'counter' => false,
|
||||
'label' => I18n::translate('language.name'),
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
|
@ -19,7 +21,7 @@ $languageDialogFields = [
|
|||
'type' => 'text',
|
||||
'required' => true,
|
||||
'counter' => false,
|
||||
'icon' => 'globe',
|
||||
'icon' => 'translate',
|
||||
'width' => '1/2'
|
||||
],
|
||||
'direction' => [
|
||||
|
@ -34,11 +36,27 @@ $languageDialogFields = [
|
|||
'width' => '1/2'
|
||||
],
|
||||
'locale' => [
|
||||
'label' => I18n::translate('language.locale'),
|
||||
'type' => 'text',
|
||||
'counter' => false,
|
||||
'label' => I18n::translate('language.locale'),
|
||||
'type' => 'text',
|
||||
],
|
||||
];
|
||||
|
||||
$translationDialogFields = [
|
||||
'key' => [
|
||||
'counter' => false,
|
||||
'icon' => null,
|
||||
'label' => I18n::translate('language.variable.key'),
|
||||
'type' => 'text'
|
||||
],
|
||||
'value' => [
|
||||
'buttons' => false,
|
||||
'counter' => false,
|
||||
'label' => I18n::translate('language.variable.value'),
|
||||
'type' => 'textarea'
|
||||
]
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
// create language
|
||||
|
@ -92,8 +110,10 @@ return [
|
|||
},
|
||||
'submit' => function (string $id) {
|
||||
Find::language($id)->delete();
|
||||
|
||||
return [
|
||||
'event' => 'language.delete',
|
||||
'event' => 'language.delete',
|
||||
'redirect' => 'languages'
|
||||
];
|
||||
}
|
||||
],
|
||||
|
@ -152,4 +172,109 @@ return [
|
|||
];
|
||||
}
|
||||
],
|
||||
|
||||
'language.translation.create' => [
|
||||
'pattern' => 'languages/(:any)/translations/create',
|
||||
'load' => function (string $languageCode) use ($translationDialogFields) {
|
||||
// find the language to make sure it exists
|
||||
Find::language($languageCode);
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'fields' => $translationDialogFields,
|
||||
'size' => 'large',
|
||||
],
|
||||
];
|
||||
},
|
||||
'submit' => function (string $languageCode) {
|
||||
$request = App::instance()->request();
|
||||
$language = Find::language($languageCode);
|
||||
|
||||
$key = $request->get('key', '');
|
||||
$value = $request->get('value', '');
|
||||
|
||||
LanguageVariable::create($key, $value);
|
||||
|
||||
if ($language->isDefault() === false) {
|
||||
$language->variable($key)->update($value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
],
|
||||
'language.translation.delete' => [
|
||||
'pattern' => 'languages/(:any)/translations/(:any)/delete',
|
||||
'load' => function (string $languageCode, string $translationKey) {
|
||||
$variable = Find::language($languageCode)->variable($translationKey, true);
|
||||
|
||||
if ($variable->exists() === false) {
|
||||
throw new NotFoundException([
|
||||
'key' => 'language.variable.notFound'
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-remove-dialog',
|
||||
'props' => [
|
||||
'text' => I18n::template('language.variable.delete.confirm', [
|
||||
'key' => Escape::html($variable->key())
|
||||
])
|
||||
],
|
||||
];
|
||||
},
|
||||
'submit' => function (string $languageCode, string $translationKey) {
|
||||
return Find::language($languageCode)->variable($translationKey, true)->delete();
|
||||
}
|
||||
],
|
||||
'language.translation.update' => [
|
||||
'pattern' => 'languages/(:any)/translations/(:any)/update',
|
||||
'load' => function (string $languageCode, string $translationKey) use ($translationDialogFields) {
|
||||
$variable = Find::language($languageCode)->variable($translationKey, true);
|
||||
|
||||
if ($variable->exists() === false) {
|
||||
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;
|
||||
|
||||
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',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'cancelButton' => $isVariableArray === false,
|
||||
'fields' => $fields,
|
||||
'size' => 'large',
|
||||
'submitButton' => $isVariableArray === false,
|
||||
'value' => [
|
||||
'key' => $variable->key(),
|
||||
'value' => $variable->value()
|
||||
]
|
||||
],
|
||||
];
|
||||
},
|
||||
'submit' => function (string $languageCode, string $translationKey) {
|
||||
Find::language($languageCode)->variable($translationKey, true)->update(
|
||||
App::instance()->request()->get('value', '')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
]
|
||||
|
||||
];
|
||||
|
|
|
@ -1,9 +1,110 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'language' => [
|
||||
'pattern' => 'languages/(:any)',
|
||||
'when' => function (): bool {
|
||||
return App::instance()->option('languages.variables', true) !== false;
|
||||
},
|
||||
'action' => function (string $code) {
|
||||
$kirby = App::instance();
|
||||
$language = Find::language($code);
|
||||
$link = '/languages/' . $language->code();
|
||||
$strings = [];
|
||||
$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;
|
||||
|
||||
ksort($foundation);
|
||||
|
||||
foreach ($foundation as $key => $value) {
|
||||
$strings[] = [
|
||||
'key' => $key,
|
||||
'value' => $translations[$key] ?? null,
|
||||
'options' => [
|
||||
[
|
||||
'click' => 'update',
|
||||
'disabled' => $canUpdate === false,
|
||||
'icon' => 'edit',
|
||||
'text' => I18n::translate('edit'),
|
||||
],
|
||||
[
|
||||
'click' => 'delete',
|
||||
'disabled' => $canUpdate === false || $language->isDefault() === false,
|
||||
'icon' => 'trash',
|
||||
'text' => I18n::translate('delete'),
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$next = function () use ($language) {
|
||||
if ($next = $language->next()) {
|
||||
return [
|
||||
'link' => '/languages/' . $next->code(),
|
||||
'title' => $next->name(),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
$prev = function () use ($language) {
|
||||
if ($prev = $language->prev()) {
|
||||
return [
|
||||
'link' => '/languages/' . $prev->code(),
|
||||
'title' => $prev->name(),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
'component' => 'k-language-view',
|
||||
'breadcrumb' => [
|
||||
[
|
||||
'label' => $name = $language->name(),
|
||||
'link' => $link,
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'deletable' => $language->isDeletable(),
|
||||
'code' => Escape::html($language->code()),
|
||||
'default' => $language->isDefault(),
|
||||
'direction' => $language->direction(),
|
||||
'id' => $language->code(),
|
||||
'info' => [
|
||||
[
|
||||
'label' => 'Status',
|
||||
'value' => I18n::translate('language.' . ($language->isDefault() ? 'default' : 'secondary')),
|
||||
],
|
||||
[
|
||||
'label' => I18n::translate('language.code'),
|
||||
'value' => $language->code(),
|
||||
],
|
||||
[
|
||||
'label' => I18n::translate('language.locale'),
|
||||
'value' => $language->locale(LC_ALL)
|
||||
],
|
||||
[
|
||||
'label' => I18n::translate('language.direction'),
|
||||
'value' => I18n::translate('language.direction.' . $language->direction()),
|
||||
],
|
||||
],
|
||||
'name' => $name,
|
||||
'next' => $next,
|
||||
'prev' => $prev,
|
||||
'translations' => $strings,
|
||||
'url' => $language->url(),
|
||||
]
|
||||
];
|
||||
}
|
||||
],
|
||||
'languages' => [
|
||||
'pattern' => 'languages',
|
||||
'action' => function () {
|
||||
|
@ -13,13 +114,15 @@ return [
|
|||
'component' => 'k-languages-view',
|
||||
'props' => [
|
||||
'languages' => $kirby->languages()->values(fn ($language) => [
|
||||
'default' => $language->isDefault(),
|
||||
'id' => $language->code(),
|
||||
'info' => Escape::html($language->code()),
|
||||
'text' => Escape::html($language->name()),
|
||||
])
|
||||
'deletable' => $language->isDeletable(),
|
||||
'default' => $language->isDefault(),
|
||||
'id' => $language->code(),
|
||||
'info' => Escape::html($language->code()),
|
||||
'text' => Escape::html($language->name()),
|
||||
]),
|
||||
'variables' => $kirby->option('languages.variables', true)
|
||||
]
|
||||
];
|
||||
}
|
||||
],
|
||||
]
|
||||
];
|
||||
|
|
11
kirby/config/areas/search.php
Normal file
11
kirby/config/areas/search.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return function () {
|
||||
return [
|
||||
'icon' => 'search',
|
||||
'label' => I18n::translate('search'),
|
||||
'views' => require __DIR__ . '/search/views.php'
|
||||
];
|
||||
};
|
17
kirby/config/areas/search/views.php
Normal file
17
kirby/config/areas/search/views.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
|
||||
return [
|
||||
'search' => [
|
||||
'pattern' => 'search',
|
||||
'action' => function () {
|
||||
return [
|
||||
'component' => 'k-search-view',
|
||||
'props' => [
|
||||
'type' => App::instance()->request()->get('type'),
|
||||
]
|
||||
];
|
||||
}
|
||||
],
|
||||
];
|
|
@ -3,15 +3,19 @@
|
|||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return function ($kirby) {
|
||||
$blueprint = $kirby->site()->blueprint();
|
||||
|
||||
return [
|
||||
'breadcrumbLabel' => function () use ($kirby) {
|
||||
return $kirby->site()->title()->or(I18n::translate('view.site'))->toString();
|
||||
},
|
||||
'icon' => 'home',
|
||||
'label' => $kirby->site()->blueprint()->title() ?? I18n::translate('view.site'),
|
||||
'icon' => $blueprint->icon() ?? 'home',
|
||||
'label' => $blueprint->title() ?? I18n::translate('view.site'),
|
||||
'menu' => true,
|
||||
'dialogs' => require __DIR__ . '/site/dialogs.php',
|
||||
'drawers' => require __DIR__ . '/site/drawers.php',
|
||||
'dropdowns' => require __DIR__ . '/site/dropdowns.php',
|
||||
'requests' => require __DIR__ . '/site/requests.php',
|
||||
'searches' => require __DIR__ . '/site/searches.php',
|
||||
'views' => require __DIR__ . '/site/views.php',
|
||||
];
|
||||
|
|
|
@ -2,14 +2,21 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Cms\PageRules;
|
||||
use Kirby\Cms\Url;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Panel\ChangesDialog;
|
||||
use Kirby\Panel\Field;
|
||||
use Kirby\Panel\PageCreateDialog;
|
||||
use Kirby\Panel\Panel;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Uuids;
|
||||
|
||||
$fields = require __DIR__ . '/../fields/dialogs.php';
|
||||
$files = require __DIR__ . '/../files/dialogs.php';
|
||||
|
||||
return [
|
||||
|
@ -155,10 +162,16 @@ return [
|
|||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'fields' => [
|
||||
'notice' => [
|
||||
'type' => 'info',
|
||||
'theme' => 'notice',
|
||||
'text' => I18n::translate('page.changeTemplate.notice')
|
||||
],
|
||||
'template' => Field::template($blueprints, [
|
||||
'required' => true
|
||||
])
|
||||
],
|
||||
'theme' => 'notice',
|
||||
'submitButton' => I18n::translate('change'),
|
||||
'value' => [
|
||||
'template' => $page->intendedTemplate()->name()
|
||||
|
@ -167,9 +180,10 @@ return [
|
|||
];
|
||||
},
|
||||
'submit' => function (string $id) {
|
||||
$request = App::instance()->request();
|
||||
$page = Find::page($id);
|
||||
$template = App::instance()->request()->get('template');
|
||||
|
||||
Find::page($id)->changeTemplate($request->get('template'));
|
||||
$page->changeTemplate($template);
|
||||
|
||||
return [
|
||||
'event' => 'page.changeTemplate',
|
||||
|
@ -181,12 +195,23 @@ return [
|
|||
'page.changeTitle' => [
|
||||
'pattern' => 'pages/(:any)/changeTitle',
|
||||
'load' => function (string $id) {
|
||||
$request = App::instance()->request();
|
||||
$kirby = App::instance();
|
||||
$request = $kirby->request();
|
||||
|
||||
$page = Find::page($id);
|
||||
$permissions = $page->permissions();
|
||||
$select = $request->get('select', 'title');
|
||||
|
||||
// build the path prefix
|
||||
$path = match ($kirby->multilang()) {
|
||||
true => Str::after($kirby->site()->url(), $kirby->url()) . '/',
|
||||
false => '/'
|
||||
};
|
||||
|
||||
if ($parent = $page->parent()) {
|
||||
$path .= $parent->uri() . '/';
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
|
@ -199,7 +224,7 @@ return [
|
|||
'slug' => Field::slug([
|
||||
'required' => true,
|
||||
'preselect' => $select === 'slug',
|
||||
'path' => $page->parent() ? '/' . $page->parent()->uri() . '/' : '/',
|
||||
'path' => $path,
|
||||
'disabled' => $permissions->can('changeSlug') === false,
|
||||
'wizard' => [
|
||||
'text' => I18n::translate('page.changeSlug.fromTitle'),
|
||||
|
@ -224,17 +249,8 @@ return [
|
|||
$slug = trim($request->get('slug', ''));
|
||||
|
||||
// basic input validation before we move on
|
||||
if (Str::length($title) === 0) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'page.changeTitle.empty'
|
||||
]);
|
||||
}
|
||||
|
||||
if (Str::length($slug) === 0) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'page.slug.invalid'
|
||||
]);
|
||||
}
|
||||
PageRules::validateTitleLength($title);
|
||||
PageRules::validateSlugLength($slug);
|
||||
|
||||
// nothing changed
|
||||
if ($page->title()->value() === $title && $page->slug() === $slug) {
|
||||
|
@ -277,93 +293,30 @@ return [
|
|||
'page.create' => [
|
||||
'pattern' => 'pages/create',
|
||||
'load' => function () {
|
||||
$kirby = App::instance();
|
||||
$request = $kirby->request();
|
||||
$request = App::instance()->request();
|
||||
$dialog = new PageCreateDialog(
|
||||
parentId: $request->get('parent'),
|
||||
sectionId: $request->get('section'),
|
||||
slug: $request->get('slug'),
|
||||
template: $request->get('template'),
|
||||
title: $request->get('title'),
|
||||
viewId: $request->get('view'),
|
||||
);
|
||||
|
||||
// the parent model for the new page
|
||||
$parent = $request->get('parent', 'site');
|
||||
|
||||
// the view on which the add button is located
|
||||
// this is important to find the right section
|
||||
// and provide the correct templates for the new page
|
||||
$view = $request->get('view', $parent);
|
||||
|
||||
// templates will be fetched depending on the
|
||||
// section settings in the blueprint
|
||||
$section = $request->get('section');
|
||||
|
||||
// this is the parent model
|
||||
$model = Find::parent($parent);
|
||||
|
||||
// this is the view model
|
||||
// i.e. site if the add button is on
|
||||
// the dashboard
|
||||
$view = Find::parent($view);
|
||||
|
||||
// available blueprints/templates for the new page
|
||||
// are always loaded depending on the matching section
|
||||
// in the view model blueprint
|
||||
$blueprints = $view->blueprints($section);
|
||||
|
||||
// the pre-selected template
|
||||
$template = $blueprints[0]['name'] ?? $blueprints[0]['value'] ?? null;
|
||||
|
||||
$fields = [
|
||||
'parent' => Field::hidden(),
|
||||
'title' => Field::title([
|
||||
'required' => true,
|
||||
'preselect' => true
|
||||
]),
|
||||
'slug' => Field::slug([
|
||||
'required' => true,
|
||||
'sync' => 'title',
|
||||
'path' => empty($model->id()) === false ? '/' . $model->id() . '/' : '/'
|
||||
]),
|
||||
'template' => Field::hidden()
|
||||
];
|
||||
|
||||
// only show template field if > 1 templates available
|
||||
// or when in debug mode
|
||||
if (count($blueprints) > 1 || $kirby->option('debug') === true) {
|
||||
$fields['template'] = Field::template($blueprints, [
|
||||
'required' => true
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'fields' => $fields,
|
||||
'submitButton' => I18n::translate('page.draft.create'),
|
||||
'value' => [
|
||||
'parent' => $parent,
|
||||
'slug' => '',
|
||||
'template' => $template,
|
||||
'title' => '',
|
||||
]
|
||||
]
|
||||
];
|
||||
return $dialog->load();
|
||||
},
|
||||
'submit' => function () {
|
||||
$request = App::instance()->request();
|
||||
$title = trim($request->get('title', ''));
|
||||
$dialog = new PageCreateDialog(
|
||||
parentId: $request->get('parent'),
|
||||
sectionId: $request->get('section'),
|
||||
slug: $request->get('slug'),
|
||||
template: $request->get('template'),
|
||||
title: $request->get('title'),
|
||||
viewId: $request->get('view'),
|
||||
);
|
||||
|
||||
if (Str::length($title) === 0) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'page.changeTitle.empty'
|
||||
]);
|
||||
}
|
||||
|
||||
$page = Find::parent($request->get('parent', 'site'))->createChild([
|
||||
'content' => ['title' => $title],
|
||||
'slug' => $request->get('slug'),
|
||||
'template' => $request->get('template'),
|
||||
]);
|
||||
|
||||
return [
|
||||
'event' => 'page.create',
|
||||
'redirect' => $page->panel()->url(true)
|
||||
];
|
||||
return $dialog->submit($request->get());
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -479,7 +432,7 @@ return [
|
|||
];
|
||||
}
|
||||
|
||||
$slugAppendix = Str::slug(I18n::translate('page.duplicate.appendix'));
|
||||
$slugAppendix = Url::slug(I18n::translate('page.duplicate.appendix'));
|
||||
$titleAppendix = I18n::translate('page.duplicate.appendix');
|
||||
|
||||
// if the item to be duplicated already exists
|
||||
|
@ -529,6 +482,13 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// page field dialogs
|
||||
'page.fields' => [
|
||||
'pattern' => '(pages/.*?)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit']
|
||||
],
|
||||
|
||||
// change filename
|
||||
'page.file.changeName' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/changeName',
|
||||
|
@ -543,6 +503,13 @@ return [
|
|||
'submit' => $files['changeSort']['submit'],
|
||||
],
|
||||
|
||||
// change template
|
||||
'page.file.changeTemplate' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/changeTemplate',
|
||||
'load' => $files['changeTemplate']['load'],
|
||||
'submit' => $files['changeTemplate']['submit'],
|
||||
],
|
||||
|
||||
// delete
|
||||
'page.file.delete' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/delete',
|
||||
|
@ -550,6 +517,56 @@ return [
|
|||
'submit' => $files['delete']['submit'],
|
||||
],
|
||||
|
||||
// page file field dialogs
|
||||
'page.file.fields' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
],
|
||||
|
||||
// move page
|
||||
'page.move' => [
|
||||
'pattern' => 'pages/(:any)/move',
|
||||
'load' => function (string $id) {
|
||||
$page = Find::page($id);
|
||||
$parent = $page->parentModel();
|
||||
|
||||
if (Uuids::enabled() === false) {
|
||||
$parentId = $parent?->id() ?? '/';
|
||||
} else {
|
||||
$parentId = $parent?->uuid()->toString() ?? 'site://';
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-page-move-dialog',
|
||||
'props' => [
|
||||
'value' => [
|
||||
'move' => $page->panel()->url(true),
|
||||
'parent' => $parentId
|
||||
]
|
||||
]
|
||||
];
|
||||
},
|
||||
'submit' => function (string $id) {
|
||||
$kirby = App::instance();
|
||||
$parentId = $kirby->request()->get('parent');
|
||||
$parent = (empty($parentId) === true || $parentId === '/' || $parentId === 'site://') ? $kirby->site() : Find::page($parentId);
|
||||
$oldPage = Find::page($id);
|
||||
$newPage = $oldPage->move($parent);
|
||||
|
||||
return [
|
||||
'event' => 'page.move',
|
||||
'redirect' => $newPage->panel()->url(true),
|
||||
'dispatch' => [
|
||||
'content/move' => [
|
||||
$oldPage->panel()->url(true),
|
||||
$newPage->panel()->url(true)
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
],
|
||||
|
||||
// change site title
|
||||
'site.changeTitle' => [
|
||||
'pattern' => 'site/changeTitle',
|
||||
|
@ -572,14 +589,21 @@ return [
|
|||
},
|
||||
'submit' => function () {
|
||||
$kirby = App::instance();
|
||||
|
||||
$kirby->site()->changeTitle($kirby->request()->get('title'));
|
||||
|
||||
return [
|
||||
'event' => 'site.changeTitle',
|
||||
];
|
||||
}
|
||||
],
|
||||
|
||||
// site field dialogs
|
||||
'site.fields' => [
|
||||
'pattern' => '(site)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit'],
|
||||
],
|
||||
|
||||
// change filename
|
||||
'site.file.changeName' => [
|
||||
'pattern' => '(site)/files/(:any)/changeName',
|
||||
|
@ -594,6 +618,13 @@ return [
|
|||
'submit' => $files['changeSort']['submit'],
|
||||
],
|
||||
|
||||
// change template
|
||||
'site.file.changeTemplate' => [
|
||||
'pattern' => '(site)/files/(:any)/changeTemplate',
|
||||
'load' => $files['changeTemplate']['load'],
|
||||
'submit' => $files['changeTemplate']['submit'],
|
||||
],
|
||||
|
||||
// delete
|
||||
'site.file.delete' => [
|
||||
'pattern' => '(site)/files/(:any)/delete',
|
||||
|
@ -601,4 +632,24 @@ return [
|
|||
'submit' => $files['delete']['submit'],
|
||||
],
|
||||
|
||||
// site file field dialogs
|
||||
'site.file.fields' => [
|
||||
'pattern' => '(site)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
],
|
||||
|
||||
// content changes
|
||||
'changes' => [
|
||||
'pattern' => 'changes',
|
||||
'load' => function () {
|
||||
$dialog = new ChangesDialog();
|
||||
return $dialog->load();
|
||||
},
|
||||
'submit' => function () {
|
||||
$dialog = new ChangesDialog();
|
||||
$ids = App::instance()->request()->get('ids');
|
||||
return $dialog->submit($ids);
|
||||
}
|
||||
],
|
||||
];
|
||||
|
|
33
kirby/config/areas/site/drawers.php
Normal file
33
kirby/config/areas/site/drawers.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
$fields = require __DIR__ . '/../fields/drawers.php';
|
||||
|
||||
return [
|
||||
// page field drawers
|
||||
'page.fields' => [
|
||||
'pattern' => '(pages/.*?)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit']
|
||||
],
|
||||
|
||||
// page file field drawers
|
||||
'page.file.fields' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
],
|
||||
|
||||
// site field drawers
|
||||
'site.fields' => [
|
||||
'pattern' => '(site)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit'],
|
||||
],
|
||||
|
||||
// site file field drawers
|
||||
'site.file.fields' => [
|
||||
'pattern' => '(site)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
],
|
||||
];
|
|
@ -1,14 +1,9 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Panel\Dropdown;
|
||||
|
||||
$files = require __DIR__ . '/../files/dropdowns.php';
|
||||
|
||||
return [
|
||||
'changes' => [
|
||||
'pattern' => 'changes',
|
||||
'options' => fn () => Dropdown::changes()
|
||||
],
|
||||
'page' => [
|
||||
'pattern' => 'pages/(:any)',
|
||||
'options' => function (string $path) {
|
||||
|
|
90
kirby/config/areas/site/requests.php
Normal file
90
kirby/config/areas/site/requests.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
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;
|
||||
}
|
||||
],
|
||||
'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
|
||||
];
|
||||
}
|
||||
]
|
||||
// @codeCoverageIgnoreEnd
|
||||
];
|
|
@ -8,50 +8,49 @@ return [
|
|||
'pages' => [
|
||||
'label' => I18n::translate('pages'),
|
||||
'icon' => 'page',
|
||||
'query' => function (string $query = null) {
|
||||
$pages = App::instance()->site()
|
||||
'query' => function (string|null $query, int $limit, int $page) {
|
||||
$kirby = App::instance();
|
||||
$pages = $kirby->site()
|
||||
->index(true)
|
||||
->search($query)
|
||||
->filter('isReadable', true)
|
||||
->limit(10);
|
||||
->filter('isListable', true)
|
||||
->paginate($limit, $page);
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($pages as $page) {
|
||||
$results[] = [
|
||||
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())
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
'info' => Escape::html($page->id()),
|
||||
'uuid' => $page->uuid()?->toString(),
|
||||
]),
|
||||
'pagination' => $pages->pagination()->toArray()
|
||||
];
|
||||
}
|
||||
],
|
||||
'files' => [
|
||||
'label' => I18n::translate('files'),
|
||||
'icon' => 'image',
|
||||
'query' => function (string $query = null) {
|
||||
$files = App::instance()->site()
|
||||
'query' => function (string|null $query, int $limit, int $page) {
|
||||
$kirby = App::instance();
|
||||
$files = $kirby->site()
|
||||
->index(true)
|
||||
->filter('isReadable', true)
|
||||
->filter('isListable', true)
|
||||
->files()
|
||||
->filter('isListable', true)
|
||||
->search($query)
|
||||
->limit(10);
|
||||
->paginate($limit, $page);
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$results[] = [
|
||||
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())
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
'info' => Escape::html($file->id()),
|
||||
'uuid' => $file->uuid()->toString(),
|
||||
]),
|
||||
'pagination' => $files->pagination()->toArray()
|
||||
];
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Panel\Field;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
|
@ -8,64 +9,85 @@ return [
|
|||
// license key
|
||||
'license' => [
|
||||
'load' => function () {
|
||||
$license = App::instance()->system()->license();
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
// the system is registered but the license
|
||||
// key is only visible for admins
|
||||
if ($license === true) {
|
||||
$license = 'Kirby 3';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$kirby = App::instance();
|
||||
$license = $kirby->system()->license();
|
||||
$obfuscated = $kirby->user()->isAdmin() === false;
|
||||
$status = $license->status();
|
||||
$renewable = $status->renewable();
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'component' => 'k-license-dialog',
|
||||
'props' => [
|
||||
'size' => 'medium',
|
||||
'fields' => [
|
||||
'license' => [
|
||||
'type' => 'info',
|
||||
'label' => I18n::translate('license'),
|
||||
'text' => $license ? $license : I18n::translate('license.unregistered.label'),
|
||||
'theme' => $license ? 'code' : 'negative',
|
||||
'help' => $license ?
|
||||
// @codeCoverageIgnoreStart
|
||||
'<a href="https://hub.getkirby.com">' . I18n::translate('license.manage') . ' →</a>' :
|
||||
// @codeCoverageIgnoreEnd
|
||||
'<a href="https://getkirby.com/buy">' . I18n::translate('license.buy') . ' →</a>'
|
||||
]
|
||||
'license' => [
|
||||
'code' => $license->code($obfuscated),
|
||||
'icon' => $status->icon(),
|
||||
'info' => $status->info($license->renewal('Y-m-d', 'date')),
|
||||
'theme' => $status->theme(),
|
||||
'type' => $license->label(),
|
||||
],
|
||||
'submitButton' => false,
|
||||
'cancelButton' => false,
|
||||
'cancelButton' => $renewable,
|
||||
'submitButton' => $renewable ? [
|
||||
'icon' => 'refresh',
|
||||
'text' => I18n::translate('renew'),
|
||||
'theme' => 'love',
|
||||
] : false,
|
||||
]
|
||||
];
|
||||
},
|
||||
'submit' => function () {
|
||||
// @codeCoverageIgnoreStart
|
||||
$response = App::instance()->system()->license()->upgrade();
|
||||
|
||||
// the upgrade is still needed
|
||||
if ($response['status'] === 'upgrade') {
|
||||
return [
|
||||
'redirect' => $response['url']
|
||||
];
|
||||
}
|
||||
|
||||
// the upgrade has already been completed
|
||||
if ($response['status'] === 'complete') {
|
||||
return [
|
||||
'event' => 'system.renew',
|
||||
'message' => I18n::translate('license.success')
|
||||
];
|
||||
}
|
||||
|
||||
throw new LogicException('The upgrade failed');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
],
|
||||
// license registration
|
||||
'registration' => [
|
||||
'load' => function () {
|
||||
$system = App::instance()->system();
|
||||
$local = $system->isLocal();
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'fields' => [
|
||||
'domain' => [
|
||||
'label' => I18n::translate('license.activate.label'),
|
||||
'type' => 'info',
|
||||
'theme' => $system->isLocal() ? 'notice' : 'info',
|
||||
'text' => I18n::template('license.register.' . ($system->isLocal() ? 'local' : 'domain'), ['host' => $system->indexUrl()])
|
||||
'theme' => $local ? 'warning' : 'info',
|
||||
'text' => I18n::template('license.activate.' . ($local ? 'local' : 'domain'), ['host' => $system->indexUrl()])
|
||||
],
|
||||
'license' => [
|
||||
'label' => I18n::translate('license.register.label'),
|
||||
'label' => I18n::translate('license.code.label'),
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'counter' => false,
|
||||
'placeholder' => 'K3-',
|
||||
'help' => I18n::translate('license.register.help')
|
||||
'placeholder' => 'K-',
|
||||
'help' => I18n::translate('license.code.help') . ' ' . '<a href="https://getkirby.com/buy" target="_blank">' . I18n::translate('license.buy') . ' →</a>'
|
||||
],
|
||||
'email' => Field::email(['required' => true])
|
||||
],
|
||||
'submitButton' => I18n::translate('license.register'),
|
||||
'submitButton' => [
|
||||
'icon' => 'key',
|
||||
'text' => I18n::translate('activate'),
|
||||
'theme' => 'love',
|
||||
],
|
||||
'value' => [
|
||||
'license' => null,
|
||||
'email' => null
|
||||
|
@ -83,7 +105,7 @@ return [
|
|||
|
||||
return [
|
||||
'event' => 'system.register',
|
||||
'message' => I18n::translate('license.register.success')
|
||||
'message' => I18n::translate('license.success')
|
||||
];
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
|
|
@ -11,31 +11,34 @@ return [
|
|||
$system = $kirby->system();
|
||||
$updateStatus = $system->updateStatus();
|
||||
$license = $system->license();
|
||||
$debugMode = $kirby->option('debug', false) === true;
|
||||
$isLocal = $system->isLocal();
|
||||
|
||||
$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' => $license->status()->label(),
|
||||
'value' => $license->label(),
|
||||
'theme' => $license->status()->theme(),
|
||||
'icon' => $license->status()->icon(),
|
||||
'dialog' => $license->status()->dialog()
|
||||
],
|
||||
[
|
||||
'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()
|
||||
'link' => $updateStatus?->url() ??
|
||||
'https://github.com/getkirby/kirby/releases/tag/' . $kirby->version(),
|
||||
'theme' => $updateStatus?->theme(),
|
||||
'icon' => $updateStatus?->icon() ?? 'info'
|
||||
],
|
||||
[
|
||||
'label' => 'PHP',
|
||||
'value' => phpversion()
|
||||
'value' => phpversion(),
|
||||
'icon' => 'code'
|
||||
],
|
||||
[
|
||||
'label' => I18n::translate('server'),
|
||||
'value' => $system->serverSoftware() ?? '?'
|
||||
'value' => $system->serverSoftwareShort() ?? '?',
|
||||
'icon' => 'server'
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -44,10 +47,14 @@ return [
|
|||
$plugins = $system->plugins()->values(function ($plugin) use (&$exceptions) {
|
||||
$authors = $plugin->authorsNames();
|
||||
$updateStatus = $plugin->updateStatus();
|
||||
$version = $updateStatus?->toArray() ?? $plugin->version() ?? '–';
|
||||
$version = $updateStatus?->toArray();
|
||||
$version ??= $plugin->version() ?? '–';
|
||||
|
||||
if ($updateStatus !== null) {
|
||||
$exceptions = array_merge($exceptions, $updateStatus->exceptionMessages());
|
||||
$exceptions = [
|
||||
...$exceptions,
|
||||
...$updateStatus->exceptionMessages()
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -63,15 +70,29 @@ return [
|
|||
|
||||
$security = $updateStatus?->messages() ?? [];
|
||||
|
||||
if ($kirby->option('debug', false) === true) {
|
||||
if ($isLocal === true) {
|
||||
$security[] = [
|
||||
'id' => 'debug',
|
||||
'text' => I18n::translate('system.issues.debug'),
|
||||
'link' => 'https://getkirby.com/security/debug'
|
||||
'id' => 'local',
|
||||
'icon' => 'info',
|
||||
'theme' => 'info',
|
||||
'text' => I18n::translate('system.issues.local')
|
||||
];
|
||||
}
|
||||
|
||||
if ($kirby->environment()->https() !== true) {
|
||||
if ($debugMode === true) {
|
||||
$security[] = [
|
||||
'id' => 'debug',
|
||||
'icon' => $isLocal ? 'info' : 'alert',
|
||||
'theme' => $isLocal ? 'info' : 'negative',
|
||||
'text' => I18n::translate('system.issues.debug'),
|
||||
'link' => 'https://getkirby.com/security/debug'
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
$isLocal === false &&
|
||||
$kirby->environment()->https() !== true
|
||||
) {
|
||||
$security[] = [
|
||||
'id' => 'https',
|
||||
'text' => I18n::translate('system.issues.https'),
|
||||
|
@ -79,19 +100,34 @@ return [
|
|||
];
|
||||
}
|
||||
|
||||
if ($kirby->option('panel.vue.compiler', null) === null) {
|
||||
$security[] = [
|
||||
'id' => 'vue-compiler',
|
||||
'link' => 'https://getkirby.com/security/vue-compiler',
|
||||
'text' => I18n::translate('system.issues.vue.compiler'),
|
||||
'theme' => 'notice'
|
||||
];
|
||||
}
|
||||
|
||||
// sensitive URLs
|
||||
if ($isLocal === false) {
|
||||
$sensitive = [
|
||||
'content' => $system->exposedFileUrl('content'),
|
||||
'git' => $system->exposedFileUrl('git'),
|
||||
'kirby' => $system->exposedFileUrl('kirby'),
|
||||
'site' => $system->exposedFileUrl('site')
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'component' => 'k-system-view',
|
||||
'props' => [
|
||||
'environment' => $environment,
|
||||
'exceptions' => $kirby->option('debug') === true ? $exceptions : [],
|
||||
'exceptions' => $debugMode ? $exceptions : [],
|
||||
'info' => $system->info(),
|
||||
'plugins' => $plugins,
|
||||
'security' => $security,
|
||||
'urls' => [
|
||||
'content' => $system->exposedFileUrl('content'),
|
||||
'git' => $system->exposedFileUrl('git'),
|
||||
'kirby' => $system->exposedFileUrl('kirby'),
|
||||
'site' => $system->exposedFileUrl('site')
|
||||
]
|
||||
'urls' => $sensitive ?? null
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ return function ($kirby) {
|
|||
'search' => 'users',
|
||||
'menu' => true,
|
||||
'dialogs' => require __DIR__ . '/users/dialogs.php',
|
||||
'drawers' => require __DIR__ . '/users/drawers.php',
|
||||
'dropdowns' => require __DIR__ . '/users/dropdowns.php',
|
||||
'searches' => require __DIR__ . '/users/searches.php',
|
||||
'views' => require __DIR__ . '/users/views.php'
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Cms\UserRules;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Panel\Field;
|
||||
use Kirby\Panel\Panel;
|
||||
use Kirby\Panel\UserTotpDisableDialog;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
$fields = require __DIR__ . '/../fields/dialogs.php';
|
||||
$files = require __DIR__ . '/../files/dialogs.php';
|
||||
|
||||
return [
|
||||
|
@ -18,6 +21,19 @@ return [
|
|||
'pattern' => 'users/create',
|
||||
'load' => function () {
|
||||
$kirby = App::instance();
|
||||
$roles = $kirby->roles()->canBeCreated();
|
||||
|
||||
// get default value for role
|
||||
if ($role = $kirby->request()->get('role')) {
|
||||
$role = $roles->find($role)?->id();
|
||||
}
|
||||
|
||||
// get role field definition, incl. available role options
|
||||
$roles = Field::role(
|
||||
roles: $roles,
|
||||
props: ['required' => true]
|
||||
);
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
|
@ -27,13 +43,13 @@ return [
|
|||
'link' => false,
|
||||
'required' => true
|
||||
]),
|
||||
'password' => Field::password(),
|
||||
'password' => Field::password([
|
||||
'autocomplete' => 'new-password'
|
||||
]),
|
||||
'translation' => Field::translation([
|
||||
'required' => true
|
||||
]),
|
||||
'role' => Field::role([
|
||||
'required' => true
|
||||
])
|
||||
'role' => $roles
|
||||
],
|
||||
'submitButton' => I18n::translate('create'),
|
||||
'value' => [
|
||||
|
@ -41,7 +57,7 @@ return [
|
|||
'email' => '',
|
||||
'password' => '',
|
||||
'translation' => $kirby->panelLanguage(),
|
||||
'role' => $kirby->user()->role()->name()
|
||||
'role' => $role ?? $roles['options'][0]['value'] ?? null
|
||||
]
|
||||
]
|
||||
];
|
||||
|
@ -167,17 +183,23 @@ return [
|
|||
'user.changePassword' => [
|
||||
'pattern' => 'users/(:any)/changePassword',
|
||||
'load' => function (string $id) {
|
||||
$user = Find::user($id);
|
||||
Find::user($id);
|
||||
|
||||
return [
|
||||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'fields' => [
|
||||
'fields' => [
|
||||
'currentPassword' => Field::password([
|
||||
'label' => I18n::translate('user.changePassword.current'),
|
||||
'autocomplete' => 'current-password'
|
||||
]),
|
||||
'password' => Field::password([
|
||||
'label' => I18n::translate('user.changePassword.new'),
|
||||
'label' => I18n::translate('user.changePassword.new'),
|
||||
'autocomplete' => 'new-password'
|
||||
]),
|
||||
'passwordConfirmation' => Field::password([
|
||||
'label' => I18n::translate('user.changePassword.new.confirm'),
|
||||
'label' => I18n::translate('user.changePassword.new.confirm'),
|
||||
'autocomplete' => 'new-password'
|
||||
])
|
||||
],
|
||||
'submitButton' => I18n::translate('change'),
|
||||
|
@ -185,13 +207,26 @@ return [
|
|||
];
|
||||
},
|
||||
'submit' => function (string $id) {
|
||||
$request = App::instance()->request();
|
||||
$kirby = App::instance();
|
||||
$request = $kirby->request();
|
||||
|
||||
$user = Find::user($id);
|
||||
$currentPassword = $request->get('currentPassword');
|
||||
$password = $request->get('password');
|
||||
$passwordConfirmation = $request->get('passwordConfirmation');
|
||||
|
||||
// validate the password
|
||||
// validate the current password of the acting user
|
||||
try {
|
||||
$kirby->user()->validatePassword($currentPassword);
|
||||
} catch (Exception) {
|
||||
// catching and re-throwing exception to avoid automatic
|
||||
// sign-out of current user from the Panel
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'user.password.wrong'
|
||||
]);
|
||||
}
|
||||
|
||||
// validate the new password
|
||||
UserRules::validPassword($user, $password ?? '');
|
||||
|
||||
// compare passwords
|
||||
|
@ -220,10 +255,13 @@ return [
|
|||
'component' => 'k-form-dialog',
|
||||
'props' => [
|
||||
'fields' => [
|
||||
'role' => Field::role([
|
||||
'label' => I18n::translate('user.changeRole.select'),
|
||||
'required' => true,
|
||||
])
|
||||
'role' => Field::role(
|
||||
roles: $user->roles(),
|
||||
props: [
|
||||
'label' => I18n::translate('user.changeRole.select'),
|
||||
'required' => true,
|
||||
]
|
||||
)
|
||||
],
|
||||
'submitButton' => I18n::translate('user.changeRole'),
|
||||
'value' => [
|
||||
|
@ -287,6 +325,13 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// user field dialogs
|
||||
'user.fields' => [
|
||||
'pattern' => '(users/.*?)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit']
|
||||
],
|
||||
|
||||
// change file name
|
||||
'user.file.changeName' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/changeName',
|
||||
|
@ -301,11 +346,31 @@ return [
|
|||
'submit' => $files['changeSort']['submit'],
|
||||
],
|
||||
|
||||
// change file template
|
||||
'user.file.changeTemplate' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/changeTemplate',
|
||||
'load' => $files['changeTemplate']['load'],
|
||||
'submit' => $files['changeTemplate']['submit'],
|
||||
],
|
||||
|
||||
// delete file
|
||||
'user.file.delete' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/delete',
|
||||
'load' => $files['delete']['load'],
|
||||
'submit' => $files['delete']['submit'],
|
||||
]
|
||||
],
|
||||
|
||||
// user file fields dialogs
|
||||
'user.file.fields' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit']
|
||||
],
|
||||
|
||||
// user disable TOTP
|
||||
'user.totp.disable' => [
|
||||
'pattern' => 'users/(:any)/totp/disable',
|
||||
'load' => fn (string $id) => (new UserTotpDisableDialog($id))->load(),
|
||||
'submit' => fn (string $id) => (new UserTotpDisableDialog($id))->submit()
|
||||
],
|
||||
];
|
||||
|
|
18
kirby/config/areas/users/drawers.php
Normal file
18
kirby/config/areas/users/drawers.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
$fields = require __DIR__ . '/../fields/drawers.php';
|
||||
|
||||
return [
|
||||
// user field drawers
|
||||
'user.fields' => [
|
||||
'pattern' => '(users/.*?)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit']
|
||||
],
|
||||
// user file fields drawers
|
||||
'user.file.fields' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit']
|
||||
],
|
||||
];
|
|
@ -8,20 +8,22 @@ return [
|
|||
'users' => [
|
||||
'label' => I18n::translate('users'),
|
||||
'icon' => 'users',
|
||||
'query' => function (string $query = null) {
|
||||
$users = App::instance()->users()->search($query)->limit(10);
|
||||
$results = [];
|
||||
'query' => function (string|null $query, int $limit, int $page) {
|
||||
$kirby = App::instance();
|
||||
$users = $kirby->users()
|
||||
->search($query)
|
||||
->paginate($limit, $page);
|
||||
|
||||
foreach ($users as $user) {
|
||||
$results[] = [
|
||||
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())
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
'info' => Escape::html($user->role()->title()),
|
||||
'uuid' => $user->uuid()->toString(),
|
||||
]),
|
||||
'pagination' => $users->pagination()->toArray()
|
||||
];
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
@ -18,7 +18,8 @@ return [
|
|||
return [
|
||||
'component' => 'k-users-view',
|
||||
'props' => [
|
||||
'role' => function () use ($kirby, $roles, $role) {
|
||||
'canCreate' => $kirby->roles()->canBeCreated()->count() > 0,
|
||||
'role' => function () use ($roles, $role) {
|
||||
if ($role) {
|
||||
return $roles[$role] ?? null;
|
||||
}
|
||||
|
@ -31,6 +32,10 @@ return [
|
|||
$users = $users->role($role);
|
||||
}
|
||||
|
||||
// sort users alphabetically
|
||||
$users = $users->sortBy('username', 'asc');
|
||||
|
||||
// paginate
|
||||
$users = $users->paginate([
|
||||
'limit' => 20,
|
||||
'page' => $kirby->request()->get('page')
|
||||
|
|
|
@ -8,7 +8,7 @@ fields:
|
|||
query: model.images
|
||||
multiple: true
|
||||
layout: cards
|
||||
size: tiny
|
||||
size: small
|
||||
empty: field.blocks.gallery.images.empty
|
||||
uploads:
|
||||
template: blocks/image
|
||||
|
|
|
@ -5,20 +5,31 @@ preview: heading
|
|||
fields:
|
||||
level:
|
||||
label: field.blocks.heading.level
|
||||
type: select
|
||||
type: toggles
|
||||
empty: false
|
||||
default: "h2"
|
||||
width: 1/6
|
||||
labels: false
|
||||
options:
|
||||
- h1
|
||||
- h2
|
||||
- h3
|
||||
- h4
|
||||
- h5
|
||||
- h6
|
||||
- value: h1
|
||||
icon: h1
|
||||
text: H1
|
||||
- value: h2
|
||||
icon: h2
|
||||
text: H2
|
||||
- value: h3
|
||||
icon: h3
|
||||
text: H3
|
||||
- value: h4
|
||||
icon: h4
|
||||
text: H4
|
||||
- value: h5
|
||||
icon: h5
|
||||
text: H5
|
||||
- value: h6
|
||||
icon: h6
|
||||
text: H6
|
||||
text:
|
||||
label: field.blocks.heading.text
|
||||
type: writer
|
||||
inline: true
|
||||
width: 5/6
|
||||
placeholder: field.blocks.heading.placeholder
|
||||
|
|
|
@ -7,9 +7,10 @@ fields:
|
|||
type: radio
|
||||
columns: 2
|
||||
default: "kirby"
|
||||
required: true
|
||||
options:
|
||||
kirby: Kirby
|
||||
web: Web
|
||||
kirby: "{{ t('field.blocks.image.location.internal') }}"
|
||||
web: "{{ t('field.blocks.image.location.external') }}"
|
||||
image:
|
||||
label: field.blocks.image.name
|
||||
type: files
|
||||
|
|
|
@ -2,12 +2,31 @@
|
|||
use Kirby\Cms\Html;
|
||||
|
||||
/** @var \Kirby\Cms\Block $block */
|
||||
$caption = $block->caption();
|
||||
|
||||
if (
|
||||
$block->location() == 'kirby' &&
|
||||
$video = $block->video()->toFile()
|
||||
) {
|
||||
$url = $video->url();
|
||||
$attrs = array_filter([
|
||||
'autoplay' => $block->autoplay()->toBool(),
|
||||
'controls' => $block->controls()->toBool(),
|
||||
'loop' => $block->loop()->toBool(),
|
||||
'muted' => $block->muted()->toBool() || $block->autoplay()->toBool(),
|
||||
'playsinline' => $block->autoplay()->toBool(),
|
||||
'poster' => $block->poster()->toFile()?->url(),
|
||||
'preload' => $block->preload()->value(),
|
||||
]);
|
||||
} else {
|
||||
$url = $block->url();
|
||||
}
|
||||
?>
|
||||
<?php if ($video = Html::video($block->url())): ?>
|
||||
<?php if ($video = Html::video($url, [], $attrs ?? [])): ?>
|
||||
<figure>
|
||||
<?= $video ?>
|
||||
<?php if ($block->caption()->isNotEmpty()): ?>
|
||||
<figcaption><?= $block->caption() ?></figcaption>
|
||||
<?php if ($caption->isNotEmpty()): ?>
|
||||
<figcaption><?= $caption ?></figcaption>
|
||||
<?php endif ?>
|
||||
</figure>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -2,11 +2,77 @@ name: field.blocks.video.name
|
|||
icon: video
|
||||
preview: video
|
||||
fields:
|
||||
location:
|
||||
label: field.blocks.video.location
|
||||
type: radio
|
||||
columns: 2
|
||||
default: "web"
|
||||
options:
|
||||
kirby: "{{ t('field.blocks.image.location.internal') }}"
|
||||
web: "{{ t('field.blocks.image.location.external') }}"
|
||||
url:
|
||||
label: field.blocks.video.url.label
|
||||
type: url
|
||||
placeholder: field.blocks.video.url.placeholder
|
||||
when:
|
||||
location: web
|
||||
video:
|
||||
label: field.blocks.video.name
|
||||
type: files
|
||||
query: model.videos
|
||||
multiple: false
|
||||
# you might want to add a template for videos here
|
||||
when:
|
||||
location: kirby
|
||||
poster:
|
||||
label: field.blocks.video.poster
|
||||
type: files
|
||||
query: model.images
|
||||
multiple: false
|
||||
image:
|
||||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
when:
|
||||
location: kirby
|
||||
caption:
|
||||
label: field.blocks.video.caption
|
||||
type: writer
|
||||
inline: true
|
||||
autoplay:
|
||||
label: field.blocks.video.autoplay
|
||||
type: toggle
|
||||
width: 1/3
|
||||
when:
|
||||
location: kirby
|
||||
muted:
|
||||
label: field.blocks.video.muted
|
||||
type: toggle
|
||||
width: 1/3
|
||||
default: true
|
||||
when:
|
||||
location: kirby
|
||||
loop:
|
||||
label: field.blocks.video.loop
|
||||
type: toggle
|
||||
width: 1/3
|
||||
when:
|
||||
location: kirby
|
||||
controls:
|
||||
label: field.blocks.video.controls
|
||||
type: toggle
|
||||
width: 1/3
|
||||
default: true
|
||||
when:
|
||||
location: kirby
|
||||
preload:
|
||||
label: field.blocks.video.preload
|
||||
type: select
|
||||
width: 2/3
|
||||
default: auto
|
||||
options:
|
||||
- auto
|
||||
- metadata
|
||||
- none
|
||||
when:
|
||||
location: kirby
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
name: Code
|
||||
icon: code
|
||||
fields:
|
||||
code:
|
||||
label: Code
|
||||
type: textarea
|
||||
buttons: false
|
||||
font: monospace
|
||||
language:
|
||||
label: Language
|
||||
type: select
|
||||
default: text
|
||||
options:
|
||||
bash: Bash
|
||||
basic: BASIC
|
||||
c: C
|
||||
clojure: Clojure
|
||||
cpp: C++
|
||||
csharp: C#
|
||||
css: CSS
|
||||
diff: Diff
|
||||
elixir: Elixir
|
||||
elm: Elm
|
||||
erlang: Erlang
|
||||
go: Go
|
||||
graphql: GraphQL
|
||||
haskell: Haskell
|
||||
html: HTML
|
||||
java: Java
|
||||
js: JavaScript
|
||||
json: JSON
|
||||
latext: LaTeX
|
||||
less: Less
|
||||
lisp: Lisp
|
||||
lua: Lua
|
||||
makefile: Makefile
|
||||
markdown: Markdown
|
||||
markup: Markup
|
||||
objectivec: Objective-C
|
||||
pascal: Pascal
|
||||
perl: Perl
|
||||
php: PHP
|
||||
text: Plain Text
|
||||
python: Python
|
||||
r: R
|
||||
ruby: Ruby
|
||||
rust: Rust
|
||||
sass: Sass
|
||||
scss: SCSS
|
||||
shell: Shell
|
||||
sql: SQL
|
||||
swift: Swift
|
||||
typescript: TypeScript
|
||||
vbnet: VB.net
|
||||
xml: XML
|
||||
yaml: YAML
|
|
@ -1,20 +0,0 @@
|
|||
icon: title
|
||||
fields:
|
||||
text:
|
||||
type: text
|
||||
level:
|
||||
type: select
|
||||
width: 1/2
|
||||
empty: false
|
||||
default: "2"
|
||||
options:
|
||||
- value: "1"
|
||||
text: Heading 1
|
||||
- value: "2"
|
||||
text: Heading 2
|
||||
- value: "3"
|
||||
text: Heading 3
|
||||
id:
|
||||
type: text
|
||||
label: ID
|
||||
width: 1/2
|
|
@ -1,16 +0,0 @@
|
|||
name: Image
|
||||
icon: image
|
||||
fields:
|
||||
image:
|
||||
type: files
|
||||
multiple: false
|
||||
alt:
|
||||
type: text
|
||||
icon: title
|
||||
caption:
|
||||
type: writer
|
||||
inline: true
|
||||
icon: text
|
||||
link:
|
||||
type: text
|
||||
icon: url
|
|
@ -1,12 +0,0 @@
|
|||
name: Quote
|
||||
icon: quote
|
||||
fields:
|
||||
text:
|
||||
label: Quote Text
|
||||
type: writer
|
||||
inline: true
|
||||
citation:
|
||||
label: Citation
|
||||
type: writer
|
||||
inline: true
|
||||
placeholder: by …
|
|
@ -1,25 +0,0 @@
|
|||
name: Table
|
||||
icon: menu
|
||||
fields:
|
||||
rows:
|
||||
label: Menu
|
||||
type: structure
|
||||
columns:
|
||||
dish: true
|
||||
description: true
|
||||
price:
|
||||
before: €
|
||||
width: 1/4
|
||||
align: right
|
||||
fields:
|
||||
dish:
|
||||
label: Dish
|
||||
type: text
|
||||
description:
|
||||
label: Description
|
||||
type: text
|
||||
price:
|
||||
label: Price
|
||||
type: number
|
||||
before: €
|
||||
step: 0.01
|
|
@ -1,5 +0,0 @@
|
|||
name: Text
|
||||
icon: text
|
||||
fields:
|
||||
text:
|
||||
type: writer
|
|
@ -1,8 +0,0 @@
|
|||
name: Video
|
||||
icon: video
|
||||
label: "{{ url }}"
|
||||
fields:
|
||||
url:
|
||||
type: url
|
||||
caption:
|
||||
type: writer
|
|
@ -1,2 +0,0 @@
|
|||
name: File
|
||||
title: file
|
|
@ -1,3 +0,0 @@
|
|||
name: Page
|
||||
title: Page
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
name: Site
|
||||
title: Site
|
||||
sections:
|
||||
pages:
|
||||
headline: Pages
|
||||
type: pages
|
||||
|
|
@ -8,24 +8,26 @@ use Kirby\Cms\Page;
|
|||
use Kirby\Cms\User;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Email\PHPMailer as Emailer;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Filesystem\Filename;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Http\Url;
|
||||
use Kirby\Image\Darkroom;
|
||||
use Kirby\Session\SessionStore;
|
||||
use Kirby\Template\Snippet;
|
||||
use Kirby\Template\Template;
|
||||
use Kirby\Text\Markdown;
|
||||
use Kirby\Text\SmartyPants;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Uuid;
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* Used by the `css()` helper
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string $url Relative or absolute URL
|
||||
* @param string|array $options An array of attributes for the link tag or a media attribute string
|
||||
*/
|
||||
|
@ -33,35 +35,39 @@ return [
|
|||
|
||||
/**
|
||||
* Add your own email provider
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param array $props
|
||||
* @param bool $debug
|
||||
*/
|
||||
'email' => function (App $kirby, array $props = [], bool $debug = false) {
|
||||
'email' => function (
|
||||
App $kirby,
|
||||
array $props = [],
|
||||
bool $debug = false
|
||||
) {
|
||||
return new Emailer($props, $debug);
|
||||
},
|
||||
|
||||
/**
|
||||
* Modify URLs for file objects
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param \Kirby\Cms\File $file The original file object
|
||||
* @return string
|
||||
*/
|
||||
'file::url' => function (App $kirby, File $file): string {
|
||||
'file::url' => function (
|
||||
App $kirby,
|
||||
File $file
|
||||
): string {
|
||||
return $file->mediaUrl();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adapt file characteristics
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @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, array $options = []) {
|
||||
'file::version' => function (
|
||||
App $kirby,
|
||||
$file,
|
||||
array $options = []
|
||||
) {
|
||||
// if file is not resizable, return
|
||||
if ($file->isResizable() === false) {
|
||||
return $file;
|
||||
|
@ -100,7 +106,6 @@ return [
|
|||
/**
|
||||
* Used by the `js()` helper
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string $url Relative or absolute URL
|
||||
* @param string|array $options An array of attributes for the link tag or a media attribute string
|
||||
*/
|
||||
|
@ -109,14 +114,12 @@ return [
|
|||
/**
|
||||
* Add your own Markdown parser
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string $text Text to parse
|
||||
* @param array $options Markdown options
|
||||
* @return string
|
||||
*/
|
||||
'markdown' => function (
|
||||
App $kirby,
|
||||
string $text = null,
|
||||
string|null $text = null,
|
||||
array $options = []
|
||||
): string {
|
||||
static $markdown;
|
||||
|
@ -140,9 +143,9 @@ return [
|
|||
'search' => function (
|
||||
App $kirby,
|
||||
Collection $collection,
|
||||
string|null $query = '',
|
||||
array|string $params = []
|
||||
): Collection|bool {
|
||||
string|null $query = null,
|
||||
string|array $params = []
|
||||
): Collection {
|
||||
if (is_string($params) === true) {
|
||||
$params = ['fields' => Str::split($params, '|')];
|
||||
}
|
||||
|
@ -154,8 +157,9 @@ return [
|
|||
'words' => false,
|
||||
];
|
||||
|
||||
$options = array_merge($defaults, $params);
|
||||
$query = trim($query ?? '');
|
||||
$collection = clone $collection;
|
||||
$options = array_merge($defaults, $params);
|
||||
$query = trim($query ?? '');
|
||||
|
||||
// empty or too short search query
|
||||
if (Str::length($query) < $options['minlength']) {
|
||||
|
@ -227,7 +231,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
|
||||
|
@ -235,7 +239,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;
|
||||
|
@ -259,15 +263,24 @@ return [
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add your own session store
|
||||
*/
|
||||
'session::store' => function (App $kirby): string|SessionStore {
|
||||
return $kirby->root('sessions');
|
||||
},
|
||||
|
||||
/**
|
||||
* Add your own SmartyPants parser
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string $text Text to parse
|
||||
* @param array $options SmartyPants options
|
||||
* @return string
|
||||
*/
|
||||
'smartypants' => function (App $kirby, string $text = null, array $options = []): string {
|
||||
'smartypants' => function (
|
||||
App $kirby,
|
||||
string|null $text = null,
|
||||
array $options = []
|
||||
): string {
|
||||
static $smartypants;
|
||||
static $config;
|
||||
|
||||
|
@ -284,43 +297,55 @@ return [
|
|||
/**
|
||||
* Add your own snippet loader
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string|array $name Snippet name
|
||||
* @param array $data Data array for the snippet
|
||||
*/
|
||||
'snippet' => function (App $kirby, string|array|null $name, array $data = [], bool $slots = false): Snippet|string {
|
||||
'snippet' => function (
|
||||
App $kirby,
|
||||
string|array|null $name,
|
||||
array $data = [],
|
||||
bool $slots = false
|
||||
): Snippet|string {
|
||||
return Snippet::factory($name, $data, $slots);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add your own template engine
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string $name Template name
|
||||
* @param string $type Extension type
|
||||
* @param string $defaultType Default extension type
|
||||
* @return \Kirby\Template\Template
|
||||
*/
|
||||
'template' => function (App $kirby, string $name, string $type = 'html', string $defaultType = 'html') {
|
||||
'template' => function (
|
||||
App $kirby,
|
||||
string $name,
|
||||
string $type = 'html',
|
||||
string $defaultType = 'html'
|
||||
) {
|
||||
return new Template($name, $type, $defaultType);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add your own thumb generator
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @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, string $src, string $dst, array $options): string {
|
||||
'thumb' => function (
|
||||
App $kirby,
|
||||
string $src,
|
||||
string $dst,
|
||||
array $options
|
||||
): string {
|
||||
$darkroom = Darkroom::factory(
|
||||
$kirby->option('thumbs.driver', 'gd'),
|
||||
$kirby->option('thumbs', [])
|
||||
);
|
||||
$options = $darkroom->preprocess($src, $options);
|
||||
$root = (new Filename($src, $dst, $options))->toString();
|
||||
$options = $darkroom->preprocess($src, $options);
|
||||
$root = (new Filename($src, $dst, $options))->toString();
|
||||
|
||||
F::copy($src, $root, true);
|
||||
$darkroom->process($root, $options);
|
||||
|
@ -331,12 +356,15 @@ return [
|
|||
/**
|
||||
* Modify all URLs
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby Kirby instance
|
||||
* @param string|null $path URL path
|
||||
* @param array|string|null $options Array of options for the Uri class
|
||||
* @return string
|
||||
* @throws \Kirby\Exception\NotFoundException If an invalid UUID was passed
|
||||
*/
|
||||
'url' => function (App $kirby, string $path = null, $options = null): string {
|
||||
'url' => function (
|
||||
App $kirby,
|
||||
string|null $path = null,
|
||||
$options = null
|
||||
): string {
|
||||
$language = null;
|
||||
|
||||
// get language from simple string option
|
||||
|
@ -378,6 +406,23 @@ return [
|
|||
return $path;
|
||||
}
|
||||
|
||||
// support UUIDs
|
||||
if (
|
||||
$path !== null &&
|
||||
(
|
||||
Uuid::is($path, 'page') === true ||
|
||||
Uuid::is($path, 'file') === true
|
||||
)
|
||||
) {
|
||||
$model = Uuid::for($path)->model();
|
||||
|
||||
if ($model === null) {
|
||||
throw new NotFoundException('The model could not be found for "' . $path . '" uuid');
|
||||
}
|
||||
|
||||
$path = $model->url();
|
||||
}
|
||||
|
||||
$url = Url::makeAbsolute($path, $kirby->url());
|
||||
|
||||
if ($options === null) {
|
||||
|
|
|
@ -29,13 +29,13 @@ return [
|
|||
/**
|
||||
* Maximum number of checked boxes
|
||||
*/
|
||||
'max' => function (int $max = null) {
|
||||
'max' => function (int|null $max = null) {
|
||||
return $max;
|
||||
},
|
||||
/**
|
||||
* Minimum number of checked boxes
|
||||
*/
|
||||
'min' => function (int $min = null) {
|
||||
'min' => function (int|null $min = null) {
|
||||
return $min;
|
||||
},
|
||||
'value' => function ($value = null) {
|
||||
|
|
145
kirby/config/fields/color.php
Normal file
145
kirby/config/fields/color.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Field\FieldOptions;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
/**
|
||||
* Unset inherited props
|
||||
*/
|
||||
'after' => null,
|
||||
'before' => null,
|
||||
|
||||
/**
|
||||
* Whether to allow alpha transparency in the color
|
||||
*/
|
||||
'alpha' => function (bool $alpha = false) {
|
||||
return $alpha;
|
||||
},
|
||||
/**
|
||||
* 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)');
|
||||
}
|
||||
|
||||
return $format;
|
||||
},
|
||||
/**
|
||||
* Change mode to disable the color picker (`input`) or to only
|
||||
* 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)');
|
||||
}
|
||||
|
||||
return $mode;
|
||||
},
|
||||
/**
|
||||
* List of colors that will be shown as buttons
|
||||
* to directly select them
|
||||
*/
|
||||
'options' => function (array $options = []): array {
|
||||
return $options;
|
||||
}
|
||||
],
|
||||
'computed' => [
|
||||
'default' => function (): string {
|
||||
return Str::lower($this->default);
|
||||
},
|
||||
'options' => function (): array {
|
||||
// resolve options to support manual arrays
|
||||
// alongside api and query options
|
||||
$props = FieldOptions::polyfill($this->props);
|
||||
$options = FieldOptions::factory([
|
||||
'text' => '{{ item.value }}',
|
||||
'value' => '{{ item.key }}',
|
||||
...$props['options']
|
||||
]);
|
||||
|
||||
$options = $options->render($this->model());
|
||||
|
||||
if (empty($options) === true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = match (true) {
|
||||
// simple array of values
|
||||
// or value=text (from Options class)
|
||||
is_numeric($options[0]['value']) ||
|
||||
$options[0]['value'] === $options[0]['text']
|
||||
=> 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'])
|
||||
]),
|
||||
|
||||
default
|
||||
=> A::map($options, fn ($option) => [
|
||||
'value' => $option['value'],
|
||||
'text' => $option['text']
|
||||
]),
|
||||
};
|
||||
|
||||
return $options;
|
||||
}
|
||||
],
|
||||
'methods' => [
|
||||
'isColor' => function (string $value): bool {
|
||||
return
|
||||
$this->isHex($value) ||
|
||||
$this->isRgb($value) ||
|
||||
$this->isHsl($value);
|
||||
},
|
||||
'isHex' => function (string $value): bool {
|
||||
return preg_match('/^#([\da-f]{3,4}){1,2}$/i', $value) === 1;
|
||||
},
|
||||
'isHsl' => function (string $value): bool {
|
||||
return preg_match('/^hsla?\(\s*(\d{1,3}\.?\d*)(deg|rad|grad|turn)?(?:,|\s)+(\d{1,3})%(?:,|\s)+(\d{1,3})%(?:,|\s|\/)*(\d*(?:\.\d+)?)(%?)\s*\)?$/i', $value) === 1;
|
||||
},
|
||||
'isRgb' => function (string $value): bool {
|
||||
return preg_match('/^rgba?\(\s*(\d{1,3})(%?)(?:,|\s)+(\d{1,3})(%?)(?:,|\s)+(\d{1,3})(%?)(?:,|\s|\/)*(\d*(?:\.\d+)?)(%?)\s*\)?$/i', $value) === 1;
|
||||
},
|
||||
],
|
||||
'validations' => [
|
||||
'color' => function ($value) {
|
||||
if (empty($value) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->format === 'hex' && $this->isHex($value) === false) {
|
||||
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']
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->format === 'hsl' && $this->isHsl($value) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.color',
|
||||
'data' => ['format' => 'hsl']
|
||||
]);
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
|
@ -24,7 +24,7 @@ return [
|
|||
/**
|
||||
* Default date when a new page/file/user gets created
|
||||
*/
|
||||
'default' => function (string $default = null): string {
|
||||
'default' => function (string|null $default = null): string {
|
||||
return $this->toDatetime($default) ?? '';
|
||||
},
|
||||
|
||||
|
@ -46,13 +46,13 @@ return [
|
|||
/**
|
||||
* Latest date, which can be selected/saved (Y-m-d)
|
||||
*/
|
||||
'max' => function (string $max = null): string|null {
|
||||
'max' => function (string|null $max = null): string|null {
|
||||
return Date::optional($max);
|
||||
},
|
||||
/**
|
||||
* Earliest date, which can be selected/saved (Y-m-d)
|
||||
*/
|
||||
'min' => function (string $min = null): string|null {
|
||||
'min' => function (string|null $min = null): string|null {
|
||||
return Date::optional($min);
|
||||
},
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Toolkit\A;
|
||||
|
||||
|
@ -36,7 +37,10 @@ return [
|
|||
'parentModel' => function () {
|
||||
if (
|
||||
is_string($this->parent) === true &&
|
||||
$model = $this->model()->query($this->parent, 'Kirby\Cms\Model')
|
||||
$model = $this->model()->query(
|
||||
$this->parent,
|
||||
ModelWithContent::class
|
||||
)
|
||||
) {
|
||||
return $model;
|
||||
}
|
||||
|
|
|
@ -14,13 +14,6 @@ return [
|
|||
'icon' => null,
|
||||
'placeholder' => null,
|
||||
'required' => null,
|
||||
'translate' => null,
|
||||
|
||||
/**
|
||||
* If `false`, the prepended number will be hidden
|
||||
*/
|
||||
'numbered' => function (bool $numbered = true) {
|
||||
return $numbered;
|
||||
}
|
||||
'translate' => null
|
||||
]
|
||||
];
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<?php
|
||||
|
||||
return [];
|
||||
return [
|
||||
'hidden' => true
|
||||
];
|
||||
|
|
|
@ -12,7 +12,6 @@ return [
|
|||
'before' => null,
|
||||
'default' => null,
|
||||
'disabled' => null,
|
||||
'icon' => null,
|
||||
'placeholder' => null,
|
||||
'required' => null,
|
||||
'translate' => null,
|
||||
|
@ -27,7 +26,7 @@ return [
|
|||
/**
|
||||
* Change the design of the info box
|
||||
*/
|
||||
'theme' => function (string $theme = null) {
|
||||
'theme' => function (string|null $theme = null) {
|
||||
return $theme;
|
||||
}
|
||||
],
|
||||
|
|
172
kirby/config/fields/link.php
Normal file
172
kirby/config/fields/link.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Http\Url;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
'after' => null,
|
||||
'before' => null,
|
||||
'icon' => null,
|
||||
'placeholder' => null,
|
||||
|
||||
/**
|
||||
* @values 'anchor', 'url, 'page, 'file', 'email', 'tel', 'custom'
|
||||
*/
|
||||
'options' => function (array|null $options = null): array {
|
||||
// default options
|
||||
if ($options === null) {
|
||||
return [
|
||||
'url',
|
||||
'page',
|
||||
'file',
|
||||
'email',
|
||||
'tel',
|
||||
'anchor'
|
||||
];
|
||||
}
|
||||
|
||||
// validate options
|
||||
$available = array_keys($this->availableTypes());
|
||||
|
||||
if ($unavailable = array_diff($options, $available)) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'field.link.options',
|
||||
'data' => ['options' => implode(', ', $unavailable)]
|
||||
]);
|
||||
}
|
||||
|
||||
return $options;
|
||||
},
|
||||
'value' => function (string|null $value = null) {
|
||||
return $value ?? '';
|
||||
}
|
||||
],
|
||||
'methods' => [
|
||||
'activeTypes' => function () {
|
||||
return array_filter(
|
||||
$this->availableTypes(),
|
||||
fn (string $type) => in_array($type, $this->props['options']),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
},
|
||||
'availableTypes' => function () {
|
||||
return [
|
||||
'anchor' => [
|
||||
'detect' => function (string $value): bool {
|
||||
return Str::startsWith($value, '#') === true;
|
||||
},
|
||||
'link' => function (string $value): string {
|
||||
return $value;
|
||||
},
|
||||
'validate' => function (string $value): bool {
|
||||
return Str::startsWith($value, '#') === true;
|
||||
},
|
||||
],
|
||||
'email' => [
|
||||
'detect' => function (string $value): bool {
|
||||
return Str::startsWith($value, 'mailto:') === true;
|
||||
},
|
||||
'link' => function (string $value): string {
|
||||
return str_replace('mailto:', '', $value);
|
||||
},
|
||||
'validate' => function (string $value): bool {
|
||||
return V::email($value);
|
||||
},
|
||||
],
|
||||
'file' => [
|
||||
'detect' => function (string $value): bool {
|
||||
return Str::startsWith($value, 'file://') === true;
|
||||
},
|
||||
'link' => function (string $value): string {
|
||||
return $value;
|
||||
},
|
||||
'validate' => function (string $value): bool {
|
||||
return V::uuid($value, 'file');
|
||||
},
|
||||
],
|
||||
'page' => [
|
||||
'detect' => function (string $value): bool {
|
||||
return Str::startsWith($value, 'page://') === true;
|
||||
},
|
||||
'link' => function (string $value): string {
|
||||
return $value;
|
||||
},
|
||||
'validate' => function (string $value): bool {
|
||||
return V::uuid($value, 'page');
|
||||
},
|
||||
],
|
||||
'tel' => [
|
||||
'detect' => function (string $value): bool {
|
||||
return Str::startsWith($value, 'tel:') === true;
|
||||
},
|
||||
'link' => function (string $value): string {
|
||||
return str_replace('tel:', '', $value);
|
||||
},
|
||||
'validate' => function (string $value): bool {
|
||||
return V::tel($value);
|
||||
},
|
||||
],
|
||||
'url' => [
|
||||
'detect' => function (string $value): bool {
|
||||
return Str::startsWith($value, 'http://') === true || Str::startsWith($value, 'https://') === true;
|
||||
},
|
||||
'link' => function (string $value): string {
|
||||
return $value;
|
||||
},
|
||||
'validate' => function (string $value): bool {
|
||||
return V::url($value);
|
||||
},
|
||||
],
|
||||
|
||||
// needs to come last
|
||||
'custom' => [
|
||||
'detect' => function (string $value): bool {
|
||||
return true;
|
||||
},
|
||||
'link' => function (string $value): string {
|
||||
return $value;
|
||||
},
|
||||
'validate' => function (): bool {
|
||||
return true;
|
||||
},
|
||||
]
|
||||
];
|
||||
},
|
||||
],
|
||||
'validations' => [
|
||||
'value' => function (string|null $value) {
|
||||
if (empty($value) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$detected = false;
|
||||
|
||||
foreach ($this->activeTypes() as $type => $options) {
|
||||
if ($options['detect']($value) !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$link = $options['link']($value);
|
||||
$detected = true;
|
||||
|
||||
if ($options['validate']($link) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.' . $type
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// none of the configured types has been detected
|
||||
if ($detected === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.linkType'
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
]
|
||||
];
|
|
@ -7,6 +7,12 @@ return [
|
|||
*/
|
||||
'marks' => function ($marks = true) {
|
||||
return $marks;
|
||||
},
|
||||
/**
|
||||
* Sets the allowed nodes. Available nodes: `bulletList`, `orderedList`
|
||||
*/
|
||||
'nodes' => function ($nodes = null) {
|
||||
return $nodes;
|
||||
}
|
||||
],
|
||||
'computed' => [
|
||||
|
|
|
@ -7,7 +7,7 @@ return [
|
|||
/**
|
||||
* Defines a custom format that is used when the field is saved
|
||||
*/
|
||||
'format' => function (string $format = null) {
|
||||
'format' => function (string|null $format = null) {
|
||||
return $format;
|
||||
}
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@ return [
|
|||
},
|
||||
|
||||
/**
|
||||
* Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge`
|
||||
* Layout size for cards: `tiny`, `small`, `medium`, `large`, `huge`, `full`
|
||||
*/
|
||||
'size' => function (string $size = 'auto') {
|
||||
return $size;
|
||||
|
|
|
@ -36,7 +36,7 @@ return [
|
|||
},
|
||||
'sanitizeOption' => function ($value) {
|
||||
$options = array_column($this->options(), 'value');
|
||||
return in_array($value, $options, true) === true ? $value : null;
|
||||
return in_array($value, $options) === true ? $value : null;
|
||||
},
|
||||
'sanitizeOptions' => function ($values) {
|
||||
$options = array_column($this->options(), 'value');
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Uuids;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
|
@ -22,7 +23,7 @@ return [
|
|||
/**
|
||||
* Info text for each item
|
||||
*/
|
||||
'info' => function (string $info = null) {
|
||||
'info' => function (string|null $info = null) {
|
||||
return $info;
|
||||
},
|
||||
|
||||
|
@ -36,14 +37,14 @@ return [
|
|||
/**
|
||||
* The minimum number of required selected
|
||||
*/
|
||||
'min' => function (int $min = null) {
|
||||
'min' => function (int|null $min = null) {
|
||||
return $min;
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum number of allowed selected
|
||||
*/
|
||||
'max' => function (int $max = null) {
|
||||
'max' => function (int|null $max = null) {
|
||||
return $max;
|
||||
},
|
||||
|
||||
|
@ -57,7 +58,7 @@ return [
|
|||
/**
|
||||
* Query for the items to be included in the picker
|
||||
*/
|
||||
'query' => function (string $query = null) {
|
||||
'query' => function (string|null $query = null) {
|
||||
return $query;
|
||||
},
|
||||
|
||||
|
@ -75,13 +76,17 @@ return [
|
|||
* @param string $store 'uuid'|'id'
|
||||
*/
|
||||
'store' => function (string $store = 'uuid') {
|
||||
return Str::lower($store);
|
||||
// fall back to ID, if UUIDs globally disabled
|
||||
return match (Uuids::enabled()) {
|
||||
false => 'id',
|
||||
default => Str::lower($store)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Main text for each item
|
||||
*/
|
||||
'text' => function (string $text = null) {
|
||||
'text' => function (string|null $text = null) {
|
||||
return $text;
|
||||
},
|
||||
],
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use Kirby\Cms\Api;
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
|
@ -22,18 +23,27 @@ return [
|
|||
$uploads = [];
|
||||
}
|
||||
|
||||
$template = $uploads['template'] ?? null;
|
||||
$uploads['accept'] = '*';
|
||||
|
||||
if ($preview = $this->image) {
|
||||
$uploads['preview'] = $preview;
|
||||
}
|
||||
|
||||
if ($template = $uploads['template'] ?? null) {
|
||||
// get parent object for upload target
|
||||
$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');
|
||||
}
|
||||
|
||||
if ($template) {
|
||||
$file = new File([
|
||||
'filename' => 'tmp',
|
||||
'parent' => $this->model(),
|
||||
'parent' => $parent,
|
||||
'template' => $template
|
||||
]);
|
||||
|
||||
$uploads['accept'] = $file->blueprint()->acceptMime();
|
||||
} else {
|
||||
$uploads['accept'] = '*';
|
||||
$uploads['accept'] = $file->blueprint()->acceptAttribute();
|
||||
}
|
||||
|
||||
return $uploads;
|
||||
|
@ -45,15 +55,7 @@ return [
|
|||
throw new Exception('Uploads are disabled for this field');
|
||||
}
|
||||
|
||||
if ($parentQuery = ($params['parent'] ?? null)) {
|
||||
$parent = $this->model()->query($parentQuery);
|
||||
} else {
|
||||
$parent = $this->model();
|
||||
}
|
||||
|
||||
if ($parent instanceof File) {
|
||||
$parent = $parent->parent();
|
||||
}
|
||||
$parent = $this->uploadParent($params['parent'] ?? null);
|
||||
|
||||
return $api->upload(function ($source, $filename) use ($parent, $params, $map) {
|
||||
$props = [
|
||||
|
@ -71,6 +73,19 @@ return [
|
|||
|
||||
return $map($file, $parent);
|
||||
});
|
||||
},
|
||||
'uploadParent' => function (string|null $parentQuery = null) {
|
||||
$parent = $this->model();
|
||||
|
||||
if ($parentQuery) {
|
||||
$parent = $parent->query($parentQuery);
|
||||
}
|
||||
|
||||
if ($parent instanceof File) {
|
||||
$parent = $parent->parent();
|
||||
}
|
||||
|
||||
return $parent;
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
@ -1,35 +1,23 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
return [
|
||||
'extends' => 'tags',
|
||||
'props' => [
|
||||
/**
|
||||
* Unset inherited props
|
||||
* If set to `all`, any type of input is accepted. If set to `options` only the predefined options are accepted as input.
|
||||
*/
|
||||
'accept' => null,
|
||||
'accept' => function ($value = 'options') {
|
||||
return V::in($value, ['all', 'options']) ? $value : 'all';
|
||||
},
|
||||
/**
|
||||
* Custom icon to replace the arrow down.
|
||||
*/
|
||||
'icon' => function (string $icon = null) {
|
||||
'icon' => function (string $icon = 'checklist') {
|
||||
return $icon;
|
||||
},
|
||||
/**
|
||||
* Enable/disable the search in the dropdown
|
||||
* Also limit displayed items (display: 20)
|
||||
* and set minimum number of characters to search (min: 3)
|
||||
*/
|
||||
'search' => function ($search = true) {
|
||||
return $search;
|
||||
},
|
||||
/**
|
||||
* If `true`, selected entries will be sorted
|
||||
* according to their position in the dropdown
|
||||
*/
|
||||
'sort' => function (bool $sort = false) {
|
||||
return $sort;
|
||||
},
|
||||
],
|
||||
'methods' => [
|
||||
'toValues' => function ($value) {
|
||||
|
|
|
@ -13,13 +13,13 @@ return [
|
|||
/**
|
||||
* The lowest allowed number
|
||||
*/
|
||||
'min' => function (float $min = null) {
|
||||
'min' => function (float|null $min = null) {
|
||||
return $min;
|
||||
},
|
||||
/**
|
||||
* The highest allowed number
|
||||
*/
|
||||
'max' => function (float $max = null) {
|
||||
'max' => function (float|null $max = null) {
|
||||
return $max;
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -47,7 +47,7 @@ return [
|
|||
},
|
||||
'fields' => function () {
|
||||
if (empty($this->fields) === true) {
|
||||
throw new Exception('Please provide some fields for the object');
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->form()->fields()->toArray();
|
||||
|
|
|
@ -31,7 +31,7 @@ return [
|
|||
/**
|
||||
* Optional query to select a specific set of pages
|
||||
*/
|
||||
'query' => function (string $query = null) {
|
||||
'query' => function (string|null $query = null) {
|
||||
return $query;
|
||||
},
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
'extends' => 'number',
|
||||
'props' => [
|
||||
|
@ -18,6 +20,13 @@ return [
|
|||
* Enables/disables the tooltip and set the before and after values
|
||||
*/
|
||||
'tooltip' => function ($tooltip = true) {
|
||||
if (is_array($tooltip) === true) {
|
||||
$after = $tooltip['after'] ?? null;
|
||||
$before = $tooltip['before'] ?? null;
|
||||
$tooltip['after'] = I18n::translate($after, $after);
|
||||
$tooltip['before'] = I18n::translate($before, $before);
|
||||
}
|
||||
|
||||
return $tooltip;
|
||||
},
|
||||
]
|
||||
|
|
|
@ -13,7 +13,7 @@ return [
|
|||
/**
|
||||
* Custom icon to replace the arrow down.
|
||||
*/
|
||||
'icon' => function (string $icon = null) {
|
||||
'icon' => function (string|null $icon = null) {
|
||||
return $icon;
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,7 @@ return [
|
|||
/**
|
||||
* Set prefix for the help text
|
||||
*/
|
||||
'path' => function (string $path = null) {
|
||||
'path' => function (string|null $path = null) {
|
||||
return $path;
|
||||
},
|
||||
|
||||
|
@ -36,7 +36,7 @@ return [
|
|||
* Name of another field that should be used to
|
||||
* automatically update this field's value
|
||||
*/
|
||||
'sync' => function (string $sync = null) {
|
||||
'sync' => function (string|null $sync = null) {
|
||||
return $sync;
|
||||
},
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
return [
|
||||
'mixins' => ['min'],
|
||||
|
@ -42,51 +45,51 @@ return [
|
|||
/**
|
||||
* Set the default rows for the structure
|
||||
*/
|
||||
'default' => function (array $default = null) {
|
||||
'default' => function (array|null $default = null) {
|
||||
return $default;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fields setup for the structure form. Works just like fields in regular forms.
|
||||
*/
|
||||
'fields' => function (array $fields) {
|
||||
'fields' => function (array $fields = []) {
|
||||
return $fields;
|
||||
},
|
||||
/**
|
||||
* The number of entries that will be displayed on a single page. Afterwards pagination kicks in.
|
||||
*/
|
||||
'limit' => function (int $limit = null) {
|
||||
'limit' => function (int|null $limit = null) {
|
||||
return $limit;
|
||||
},
|
||||
/**
|
||||
* Maximum allowed entries in the structure. Afterwards the "Add" button will be switched off.
|
||||
*/
|
||||
'max' => function (int $max = null) {
|
||||
'max' => function (int|null $max = null) {
|
||||
return $max;
|
||||
},
|
||||
/**
|
||||
* Minimum required entries in the structure
|
||||
*/
|
||||
'min' => function (int $min = null) {
|
||||
'min' => function (int|null $min = null) {
|
||||
return $min;
|
||||
},
|
||||
/**
|
||||
* Toggles adding to the top or bottom of the list
|
||||
*/
|
||||
'prepend' => function (bool $prepend = null) {
|
||||
'prepend' => function (bool|null $prepend = null) {
|
||||
return $prepend;
|
||||
},
|
||||
/**
|
||||
* Toggles drag & drop sorting
|
||||
*/
|
||||
'sortable' => function (bool $sortable = null) {
|
||||
'sortable' => function (bool|null $sortable = null) {
|
||||
return $sortable;
|
||||
},
|
||||
/**
|
||||
* Sorts the entries by the given field and order (i.e. `title desc`)
|
||||
* Drag & drop is disabled in this case
|
||||
*/
|
||||
'sortBy' => function (string $sort = null) {
|
||||
'sortBy' => function (string|null $sort = null) {
|
||||
return $sort;
|
||||
}
|
||||
],
|
||||
|
@ -99,57 +102,54 @@ return [
|
|||
},
|
||||
'fields' => function () {
|
||||
if (empty($this->fields) === true) {
|
||||
throw new Exception('Please provide some fields for the structure');
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->form()->fields()->toArray();
|
||||
},
|
||||
'columns' => function () {
|
||||
$columns = [];
|
||||
$mobile = 0;
|
||||
$columns = [];
|
||||
$blueprint = $this->columns;
|
||||
|
||||
if (empty($this->columns) === true) {
|
||||
foreach ($this->fields as $field) {
|
||||
// Skip hidden and unsaveable fields
|
||||
// They should never be included as column
|
||||
if ($field['type'] === 'hidden' || $field['saveable'] === false) {
|
||||
continue;
|
||||
}
|
||||
// if no custom columns have been defined,
|
||||
// gather all fields as columns
|
||||
if (empty($blueprint) === true) {
|
||||
// skip hidden fields
|
||||
$fields = array_filter(
|
||||
$this->fields,
|
||||
fn ($field) =>
|
||||
$field['type'] !== 'hidden' && $field['hidden'] !== true
|
||||
);
|
||||
$fields = array_column($fields, 'name');
|
||||
$blueprint = array_fill_keys($fields, true);
|
||||
}
|
||||
|
||||
$columns[$field['name']] = [
|
||||
'type' => $field['type'],
|
||||
'label' => $field['label'] ?? $field['name']
|
||||
];
|
||||
foreach ($blueprint as $name => $column) {
|
||||
$field = $this->fields[$name] ?? null;
|
||||
|
||||
// Skip empty and unsaveable fields
|
||||
// They should never be included as column
|
||||
if (
|
||||
empty($field) === true ||
|
||||
$field['saveable'] === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
foreach ($this->columns as $columnName => $columnProps) {
|
||||
if (is_array($columnProps) === false) {
|
||||
$columnProps = [];
|
||||
}
|
||||
|
||||
$field = $this->fields[$columnName] ?? null;
|
||||
|
||||
if (
|
||||
empty($field) === true ||
|
||||
$field['saveable'] === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($columnProps['mobile'] ?? false) === true) {
|
||||
$mobile++;
|
||||
}
|
||||
|
||||
$columns[$columnName] = array_merge([
|
||||
'type' => $field['type'],
|
||||
'label' => $field['label'] ?? $field['name']
|
||||
], $columnProps);
|
||||
if (is_array($column) === false) {
|
||||
$column = [];
|
||||
}
|
||||
|
||||
$column['type'] ??= $field['type'];
|
||||
$column['label'] ??= $field['label'] ?? $name;
|
||||
$column['label'] = I18n::translate($column['label'], $column['label']);
|
||||
|
||||
$columns[$name] = $column;
|
||||
}
|
||||
|
||||
// make the first column visible on mobile
|
||||
// if no other mobile columns are defined
|
||||
if ($mobile === 0) {
|
||||
if (in_array(true, array_column($columns, 'mobile')) === false) {
|
||||
$columns[array_key_first($columns)]['mobile'] = true;
|
||||
}
|
||||
|
||||
|
@ -173,34 +173,53 @@ return [
|
|||
},
|
||||
'form' => function (array $values = []) {
|
||||
return new Form([
|
||||
'fields' => $this->attrs['fields'],
|
||||
'fields' => $this->attrs['fields'] ?? [],
|
||||
'values' => $values,
|
||||
'model' => $this->model
|
||||
]);
|
||||
},
|
||||
],
|
||||
'api' => function () {
|
||||
return [
|
||||
[
|
||||
'pattern' => 'validate',
|
||||
'method' => 'ALL',
|
||||
'action' => function () {
|
||||
return array_values($this->field()->form($this->requestBody())->errors());
|
||||
}
|
||||
]
|
||||
];
|
||||
},
|
||||
'save' => function ($value) {
|
||||
$data = [];
|
||||
|
||||
foreach ($value as $row) {
|
||||
$data[] = $this->form($row)->content();
|
||||
$row = $this->form($row)->content();
|
||||
|
||||
// remove frontend helper id
|
||||
unset($row['_id']);
|
||||
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
return $data;
|
||||
},
|
||||
'validations' => [
|
||||
'min',
|
||||
'max'
|
||||
'max',
|
||||
'structure' => function ($value) {
|
||||
if (empty($value) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$values = A::wrap($value);
|
||||
|
||||
foreach ($values as $index => $value) {
|
||||
$form = $this->form($value);
|
||||
|
||||
foreach ($form->fields() as $field) {
|
||||
$errors = $field->errors();
|
||||
|
||||
if (empty($errors) === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'structure.validation',
|
||||
'data' => [
|
||||
'field' => $field->label() ?? Str::ucfirst($field->name()),
|
||||
'index' => $index + 1
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
@ -37,21 +37,36 @@ return [
|
|||
/**
|
||||
* Minimum number of required entries/tags
|
||||
*/
|
||||
'min' => function (int $min = null) {
|
||||
'min' => function (int|null $min = null) {
|
||||
return $min;
|
||||
},
|
||||
/**
|
||||
* Maximum number of allowed entries/tags
|
||||
*/
|
||||
'max' => function (int $max = null) {
|
||||
'max' => function (int|null $max = null) {
|
||||
return $max;
|
||||
},
|
||||
/**
|
||||
* Enable/disable the search in the dropdown
|
||||
* Also limit displayed items (display: 20)
|
||||
* and set minimum number of characters to search (min: 3)
|
||||
*/
|
||||
'search' => function (bool|array $search = true) {
|
||||
return $search;
|
||||
},
|
||||
/**
|
||||
* Custom tags separator, which will be used to store tags in the content file
|
||||
*/
|
||||
'separator' => function (string $separator = ',') {
|
||||
return $separator;
|
||||
},
|
||||
/**
|
||||
* If `true`, selected entries will be sorted
|
||||
* according to their position in the dropdown
|
||||
*/
|
||||
'sort' => function (bool $sort = false) {
|
||||
return $sort;
|
||||
},
|
||||
],
|
||||
'computed' => [
|
||||
'default' => function (): array {
|
||||
|
@ -78,7 +93,7 @@ return [
|
|||
return $value;
|
||||
}
|
||||
],
|
||||
'save' => function (array $value = null): string {
|
||||
'save' => function (array|null $value = null): string {
|
||||
return A::join(
|
||||
$value,
|
||||
$this->separator() . ' '
|
||||
|
|
|
@ -27,24 +27,31 @@ return [
|
|||
return $counter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the font family (sans or monospace)
|
||||
*/
|
||||
'font' => function (string|null $font = null) {
|
||||
return $font === 'monospace' ? 'monospace' : 'sans-serif';
|
||||
},
|
||||
|
||||
/**
|
||||
* Maximum number of allowed characters
|
||||
*/
|
||||
'maxlength' => function (int $maxlength = null) {
|
||||
'maxlength' => function (int|null $maxlength = null) {
|
||||
return $maxlength;
|
||||
},
|
||||
|
||||
/**
|
||||
* Minimum number of required characters
|
||||
*/
|
||||
'minlength' => function (int $minlength = null) {
|
||||
'minlength' => function (int|null $minlength = null) {
|
||||
return $minlength;
|
||||
},
|
||||
|
||||
/**
|
||||
* A regular expression, which will be used to validate the input
|
||||
*/
|
||||
'pattern' => function (string $pattern = null) {
|
||||
'pattern' => function (string|null $pattern = null) {
|
||||
return $pattern;
|
||||
},
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ return [
|
|||
/**
|
||||
* Sets the default text when a new page/file/user is created
|
||||
*/
|
||||
'default' => function (string $default = null) {
|
||||
'default' => function (string|null $default = null) {
|
||||
return trim($default ?? '');
|
||||
},
|
||||
|
||||
|
@ -48,28 +48,28 @@ return [
|
|||
/**
|
||||
* Sets the font family (sans or monospace)
|
||||
*/
|
||||
'font' => function (string $font = null) {
|
||||
'font' => function (string|null $font = null) {
|
||||
return $font === 'monospace' ? 'monospace' : 'sans-serif';
|
||||
},
|
||||
|
||||
/**
|
||||
* Maximum number of allowed characters
|
||||
*/
|
||||
'maxlength' => function (int $maxlength = null) {
|
||||
'maxlength' => function (int|null $maxlength = null) {
|
||||
return $maxlength;
|
||||
},
|
||||
|
||||
/**
|
||||
* Minimum number of required characters
|
||||
*/
|
||||
'minlength' => function (int $minlength = null) {
|
||||
'minlength' => function (int|null $minlength = null) {
|
||||
return $minlength;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the size of the textarea. Available sizes: `small`, `medium`, `large`, `huge`
|
||||
*/
|
||||
'size' => function (string $size = null) {
|
||||
'size' => function (string|null $size = null) {
|
||||
return $size;
|
||||
},
|
||||
|
||||
|
@ -80,7 +80,7 @@ return [
|
|||
return $spellcheck;
|
||||
},
|
||||
|
||||
'value' => function (string $value = null) {
|
||||
'value' => function (string|null $value = null) {
|
||||
return trim($value ?? '');
|
||||
}
|
||||
],
|
||||
|
|
|
@ -36,13 +36,13 @@ return [
|
|||
/**
|
||||
* Latest time, which can be selected/saved (H:i or H:i:s)
|
||||
*/
|
||||
'max' => function (string $max = null): string|null {
|
||||
'max' => function (string|null $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|null {
|
||||
'min' => function (string|null $min = null): string|null {
|
||||
return Date::optional($min);
|
||||
},
|
||||
|
||||
|
|
|
@ -24,18 +24,8 @@ return [
|
|||
/**
|
||||
* Default selected user(s) when a new page/file/user is created
|
||||
*/
|
||||
'default' => function ($default = null) {
|
||||
if ($default === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($default === null && $user = $this->kirby()->user()) {
|
||||
return [
|
||||
$this->userResponse($user)
|
||||
];
|
||||
}
|
||||
|
||||
return $this->toUsers($default);
|
||||
'default' => function (string|array|bool|null $default = null) {
|
||||
return $default;
|
||||
},
|
||||
|
||||
'value' => function ($value = null) {
|
||||
|
@ -43,10 +33,22 @@ return [
|
|||
},
|
||||
],
|
||||
'computed' => [
|
||||
/**
|
||||
* Unset inherited computed
|
||||
*/
|
||||
'default' => null
|
||||
'default' => function (): array {
|
||||
if ($this->default === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (
|
||||
$this->default === true &&
|
||||
$user = $this->kirby()->user()
|
||||
) {
|
||||
return [
|
||||
$this->userResponse($user)
|
||||
];
|
||||
}
|
||||
|
||||
return $this->toUsers($this->default);
|
||||
}
|
||||
],
|
||||
'methods' => [
|
||||
'userResponse' => function ($user) {
|
||||
|
@ -57,7 +59,7 @@ return [
|
|||
'text' => $this->text,
|
||||
]);
|
||||
},
|
||||
'toUsers' => function ($value = null) {
|
||||
'toUsers' => function ($value = null): array {
|
||||
$users = [];
|
||||
$kirby = App::instance();
|
||||
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Sane\Sane;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
return [
|
||||
'props' => [
|
||||
/**
|
||||
* Enables/disables the character counter in the top right corner
|
||||
*/
|
||||
'counter' => function (bool $counter = true) {
|
||||
return $counter;
|
||||
},
|
||||
/**
|
||||
* Available heading levels
|
||||
*/
|
||||
'headings' => function (array|null $headings = null) {
|
||||
return array_intersect($headings ?? range(1, 6), range(1, 6));
|
||||
},
|
||||
/**
|
||||
* Enables inline mode, which will not wrap new lines in paragraphs and creates hard breaks instead.
|
||||
*
|
||||
|
@ -13,24 +27,74 @@ return [
|
|||
return $inline;
|
||||
},
|
||||
/**
|
||||
* Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`, `email`. Activate them all by passing `true`. Deactivate them all by passing `false`
|
||||
* Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`, `email`. Activate/deactivate them all by passing `true`/`false`. Default marks are `bold`, `italic`, `underline`, `strike`, `link`, `email`
|
||||
* @param array|bool $marks
|
||||
*/
|
||||
'marks' => function ($marks = true) {
|
||||
'marks' => function ($marks = null) {
|
||||
return $marks;
|
||||
},
|
||||
/**
|
||||
* Sets the allowed nodes. Available nodes: `paragraph`, `heading`, `bulletList`, `orderedList`. Activate/deactivate them all by passing `true`/`false`. Default nodes are `paragraph`, `heading`, `bulletList`, `orderedList`.
|
||||
* Maximum number of allowed characters
|
||||
*/
|
||||
'maxlength' => function (int|null $maxlength = null) {
|
||||
return $maxlength;
|
||||
},
|
||||
|
||||
/**
|
||||
* Minimum number of required characters
|
||||
*/
|
||||
'minlength' => function (int|null $minlength = null) {
|
||||
return $minlength;
|
||||
},
|
||||
/**
|
||||
* Sets the allowed nodes. Available nodes: `paragraph`, `heading`, `bulletList`, `orderedList`, `quote`. Activate/deactivate them all by passing `true`/`false`. Default nodes are `paragraph`, `heading`, `bulletList`, `orderedList`.
|
||||
* @param array|bool|null $nodes
|
||||
*/
|
||||
'nodes' => function ($nodes = null) {
|
||||
return $nodes;
|
||||
},
|
||||
/**
|
||||
* Toolbar options, incl. `marks` (to narrow down which marks should have toolbar buttons), `nodes` (to narrow down which nodes should have toolbar dropdown entries) and `inline` to set the position of the toolbar (false = sticking on top of the field)
|
||||
*/
|
||||
'toolbar' => function ($toolbar = null) {
|
||||
return $toolbar;
|
||||
}
|
||||
],
|
||||
'computed' => [
|
||||
'value' => function () {
|
||||
$value = trim($this->value ?? '');
|
||||
return Sane::sanitize($value, 'html');
|
||||
$value = Sane::sanitize($value, 'html');
|
||||
|
||||
// convert non-breaking spaces to HTML entity
|
||||
// as that's how ProseMirror handles it internally;
|
||||
// will allow comparing saved and current content
|
||||
$value = str_replace(' ', ' ', $value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
],
|
||||
'validations' => [
|
||||
'minlength' => function ($value) {
|
||||
if (
|
||||
$this->minlength &&
|
||||
V::minLength(strip_tags($value), $this->minlength) === false
|
||||
) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.minlength',
|
||||
'data' => ['min' => $this->minlength]
|
||||
]);
|
||||
}
|
||||
},
|
||||
'maxlength' => function ($value) {
|
||||
if (
|
||||
$this->maxlength &&
|
||||
V::maxLength(strip_tags($value), $this->maxlength) === false
|
||||
) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'validation.maxlength',
|
||||
'data' => ['max' => $this->maxlength]
|
||||
]);
|
||||
}
|
||||
},
|
||||
]
|
||||
];
|
||||
|
|
|
@ -4,14 +4,18 @@ use Kirby\Cms\App;
|
|||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Helpers;
|
||||
use Kirby\Cms\Html;
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Plugin;
|
||||
use Kirby\Cms\PluginAssets;
|
||||
use Kirby\Cms\Response;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\Url;
|
||||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Router;
|
||||
use Kirby\Image\QrCode;
|
||||
use Kirby\Template\Slot;
|
||||
use Kirby\Template\Snippet;
|
||||
use Kirby\Toolkit\Date;
|
||||
|
@ -87,7 +91,7 @@ if (Helpers::hasOverride('css') === false) { // @codeCoverageIgnore
|
|||
* @param string|array|null $options Pass an array of attributes for the link tag or a media attribute string
|
||||
*/
|
||||
function css(
|
||||
string|array $url,
|
||||
string|array|Plugin|PluginAssets $url,
|
||||
string|array|null $options = null
|
||||
): string|null {
|
||||
return Html::css($url, $options);
|
||||
|
@ -107,7 +111,7 @@ if (Helpers::hasOverride('deprecated') === false) { // @codeCoverageIgnore
|
|||
}
|
||||
}
|
||||
|
||||
if (Helpers::hasOverride('dump') === false) { // @codeCoverageIgnore
|
||||
if (Helpers::hasOverride('dump') === false && function_exists('dump') === false) { // @codeCoverageIgnore
|
||||
/**
|
||||
* Simple object and variable dumper
|
||||
* to help with debugging.
|
||||
|
@ -258,7 +262,7 @@ if (Helpers::hasOverride('js') === false) { // @codeCoverageIgnore
|
|||
* Creates a script tag to load a javascript file
|
||||
*/
|
||||
function js(
|
||||
string|array $url,
|
||||
string|array|Plugin|PluginAssets $url,
|
||||
string|array|bool|null $options = null
|
||||
): string|null {
|
||||
return Html::js($url, $options);
|
||||
|
@ -432,6 +436,20 @@ if (Helpers::hasOverride('params') === false) { // @codeCoverageIgnore
|
|||
}
|
||||
}
|
||||
|
||||
if (Helpers::hasOverride('qr') === false) { // @codeCoverageIgnore
|
||||
/**
|
||||
* Creates a QR code object
|
||||
*/
|
||||
function qr(string|ModelWithContent $data): QrCode
|
||||
{
|
||||
if ($data instanceof ModelWithContent) {
|
||||
$data = $data->url();
|
||||
}
|
||||
|
||||
return new QrCode($data);
|
||||
}
|
||||
}
|
||||
|
||||
if (Helpers::hasOverride('r') === false) { // @codeCoverageIgnore
|
||||
/**
|
||||
* Smart version of return with an if condition as first argument
|
||||
|
@ -586,25 +604,6 @@ if (Helpers::hasOverride('tt') === false) { // @codeCoverageIgnore
|
|||
}
|
||||
}
|
||||
|
||||
if (Helpers::hasOverride('twitter') === false) { // @codeCoverageIgnore
|
||||
/**
|
||||
* Builds a Twitter link
|
||||
*/
|
||||
function twitter(
|
||||
string $username,
|
||||
string|null $text = null,
|
||||
string|null $title = null,
|
||||
string|null $class = null
|
||||
): string {
|
||||
return App::instance()->kirbytag([
|
||||
'twitter' => $username,
|
||||
'text' => $text,
|
||||
'title' => $title,
|
||||
'class' => $class
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (Helpers::hasOverride('u') === false) { // @codeCoverageIgnore
|
||||
/**
|
||||
* Shortcut for url()
|
||||
|
@ -645,8 +644,11 @@ if (Helpers::hasOverride('video') === false) { // @codeCoverageIgnore
|
|||
* videos. The embed Urls are automatically detected from
|
||||
* the given Url.
|
||||
*/
|
||||
function video(string $url, array $options = [], array $attr = []): string|null
|
||||
{
|
||||
function video(
|
||||
string $url,
|
||||
array $options = [],
|
||||
array $attr = []
|
||||
): string|null {
|
||||
return Html::video($url, $options, $attr);
|
||||
}
|
||||
}
|
||||
|
@ -655,8 +657,11 @@ if (Helpers::hasOverride('vimeo') === false) { // @codeCoverageIgnore
|
|||
/**
|
||||
* Embeds a Vimeo video by URL in an iframe
|
||||
*/
|
||||
function vimeo(string $url, array $options = [], array $attr = []): string|null
|
||||
{
|
||||
function vimeo(
|
||||
string $url,
|
||||
array $options = [],
|
||||
array $attr = []
|
||||
): string|null {
|
||||
return Html::vimeo($url, $options, $attr);
|
||||
}
|
||||
}
|
||||
|
@ -677,8 +682,11 @@ if (Helpers::hasOverride('youtube') === false) { // @codeCoverageIgnore
|
|||
/**
|
||||
* Embeds a Youtube video by URL in an iframe
|
||||
*/
|
||||
function youtube(string $url, array $options = [], array $attr = []): string|null
|
||||
{
|
||||
function youtube(
|
||||
string $url,
|
||||
array $options = [],
|
||||
array $attr = []
|
||||
): string|null {
|
||||
return Html::youtube($url, $options, $attr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,29 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Blocks;
|
||||
use Kirby\Cms\Content;
|
||||
use Kirby\Cms\Field;
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Html;
|
||||
use Kirby\Cms\Layouts;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Structure;
|
||||
use Kirby\Cms\Url;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Cms\Users;
|
||||
use Kirby\Content\Content;
|
||||
use Kirby\Content\Field;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Image\QrCode;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Dom;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
use Kirby\Toolkit\Xml;
|
||||
use Kirby\Uuid\Uuid;
|
||||
|
||||
/**
|
||||
* Field method setup
|
||||
|
@ -26,9 +36,6 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Converts the field value into a proper boolean and inverts it
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return bool
|
||||
*/
|
||||
'isFalse' => function (Field $field): bool {
|
||||
return $field->toBool() === false;
|
||||
|
@ -36,9 +43,6 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Converts the field value into a proper boolean
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return bool
|
||||
*/
|
||||
'isTrue' => function (Field $field): bool {
|
||||
return $field->toBool() === true;
|
||||
|
@ -47,22 +51,21 @@ return function (App $app) {
|
|||
/**
|
||||
* Validates the field content with the given validator and parameters
|
||||
*
|
||||
* @param string $validator
|
||||
* @param mixed ...$arguments A list of optional validator arguments
|
||||
* @return bool
|
||||
*/
|
||||
'isValid' => function (Field $field, string $validator, ...$arguments): bool {
|
||||
'isValid' => function (
|
||||
Field $field,
|
||||
string $validator,
|
||||
...$arguments
|
||||
): bool {
|
||||
return V::$validator($field->value, ...$arguments);
|
||||
},
|
||||
|
||||
// converters
|
||||
/**
|
||||
* Converts a yaml or json field to a Blocks object
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Blocks
|
||||
*/
|
||||
'toBlocks' => function (Field $field) {
|
||||
'toBlocks' => function (Field $field): Blocks {
|
||||
try {
|
||||
$blocks = Blocks::parse($field->value());
|
||||
$blocks = Blocks::factory($blocks, [
|
||||
|
@ -84,11 +87,9 @@ return function (App $app) {
|
|||
/**
|
||||
* Converts the field value into a proper boolean
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param bool $default Default value if the field is empty
|
||||
* @return bool
|
||||
*/
|
||||
'toBool' => function (Field $field, $default = false): bool {
|
||||
'toBool' => function (Field $field, bool $default = false): bool {
|
||||
$value = $field->isEmpty() ? $default : $field->value;
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
},
|
||||
|
@ -96,11 +97,9 @@ return function (App $app) {
|
|||
/**
|
||||
* Parses the field value with the given method
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param string $method [',', 'yaml', 'json']
|
||||
* @return array
|
||||
*/
|
||||
'toData' => function (Field $field, string $method = ',') {
|
||||
'toData' => function (Field $field, string $method = ','): array {
|
||||
return match ($method) {
|
||||
'yaml', 'json' => Data::decode($field->value, $method),
|
||||
default => $field->split($method)
|
||||
|
@ -110,12 +109,14 @@ return function (App $app) {
|
|||
/**
|
||||
* Converts the field value to a timestamp or a formatted date
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param string|\IntlDateFormatter|null $format PHP date formatting string
|
||||
* @param string|null $fallback Fallback string for `strtotime` (since 3.2)
|
||||
* @return string|int
|
||||
* @param string|null $fallback Fallback string for `strtotime`
|
||||
*/
|
||||
'toDate' => function (Field $field, $format = null, string $fallback = null) use ($app) {
|
||||
'toDate' => function (
|
||||
Field $field,
|
||||
string|IntlDateFormatter|null $format = null,
|
||||
string|null $fallback = null
|
||||
) use ($app): string|int|null {
|
||||
if (empty($field->value) === true && $fallback === null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -126,33 +127,28 @@ return function (App $app) {
|
|||
$time = strtotime($fallback);
|
||||
}
|
||||
|
||||
$handler = $app->option('date.handler', 'date');
|
||||
return Str::date($time, $format, $handler);
|
||||
return Str::date($time, $format);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a file object from a filename in the field
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\File|null
|
||||
*/
|
||||
'toFile' => function (Field $field) {
|
||||
'toFile' => function (Field $field): File|null {
|
||||
return $field->toFiles()->first();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a file collection from a yaml list of filenames in the field
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param string $separator
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
'toFiles' => function (Field $field, string $separator = 'yaml') {
|
||||
'toFiles' => function (
|
||||
Field $field,
|
||||
string $separator = 'yaml'
|
||||
): Files {
|
||||
$parent = $field->parent();
|
||||
$files = new Files([]);
|
||||
|
||||
foreach ($field->toData($separator) as $id) {
|
||||
if ($file = $parent->kirby()->file($id, $parent)) {
|
||||
if (is_string($id) === true && $file = $parent->kirby()->file($id, $parent)) {
|
||||
$files->add($file);
|
||||
}
|
||||
}
|
||||
|
@ -163,11 +159,9 @@ return function (App $app) {
|
|||
/**
|
||||
* Converts the field value into a proper float
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param float $default Default value if the field is empty
|
||||
* @return float
|
||||
*/
|
||||
'toFloat' => function (Field $field, float $default = 0) {
|
||||
'toFloat' => function (Field $field, float $default = 0): float {
|
||||
$value = $field->isEmpty() ? $default : $field->value;
|
||||
return (float)$value;
|
||||
},
|
||||
|
@ -175,23 +169,17 @@ return function (App $app) {
|
|||
/**
|
||||
* Converts the field value into a proper integer
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param int $default Default value if the field is empty
|
||||
* @return int
|
||||
*/
|
||||
'toInt' => function (Field $field, int $default = 0) {
|
||||
'toInt' => function (Field $field, int $default = 0): int {
|
||||
$value = $field->isEmpty() ? $default : $field->value;
|
||||
return (int)$value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse layouts and turn them into
|
||||
* Layout objects
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Layouts
|
||||
* Parse layouts and turn them into Layout objects
|
||||
*/
|
||||
'toLayouts' => function (Field $field) {
|
||||
'toLayouts' => function (Field $field): Layouts {
|
||||
return Layouts::factory(Layouts::parse($field->value()), [
|
||||
'parent' => $field->parent(),
|
||||
'field' => $field,
|
||||
|
@ -201,12 +189,14 @@ return function (App $app) {
|
|||
/**
|
||||
* Wraps a link tag around the field value. The field value is used as the link text
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param mixed $attr1 Can be an optional Url. If no Url is set, the Url of the Page, File or Site will be used. Can also be an array of link attributes
|
||||
* @param mixed $attr2 If `$attr1` is used to set the Url, you can use `$attr2` to pass an array of additional attributes.
|
||||
* @return string
|
||||
*/
|
||||
'toLink' => function (Field $field, $attr1 = null, $attr2 = null) {
|
||||
'toLink' => function (
|
||||
Field $field,
|
||||
string|array|null $attr1 = null,
|
||||
array|null $attr2 = null
|
||||
): string {
|
||||
if (is_string($attr1) === true) {
|
||||
$href = $attr1;
|
||||
$attr = $attr2;
|
||||
|
@ -225,49 +215,55 @@ return function (App $app) {
|
|||
/**
|
||||
* Parse yaml data and convert it to a
|
||||
* content object
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Content
|
||||
*/
|
||||
'toObject' => function (Field $field) {
|
||||
'toObject' => function (Field $field): Content {
|
||||
return new Content($field->yaml(), $field->parent(), true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a page object from a page id in the field
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
'toPage' => function (Field $field) {
|
||||
'toPage' => function (Field $field): Page|null {
|
||||
return $field->toPages()->first();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a pages collection from a yaml list of page ids in the field
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param string $separator Can be any other separator to split the field value by
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
'toPages' => function (Field $field, string $separator = 'yaml') use ($app) {
|
||||
return $app->site()->find(false, false, ...$field->toData($separator));
|
||||
'toPages' => function (
|
||||
Field $field,
|
||||
string $separator = 'yaml'
|
||||
) use ($app): Pages {
|
||||
return $app->site()->find(
|
||||
false,
|
||||
false,
|
||||
...$field->toData($separator)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Turns the field value into an QR code object
|
||||
*/
|
||||
'toQrCode' => function (Field $field): QrCode|null {
|
||||
return $field->isNotEmpty() ? new QrCode($field->value) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a yaml field to a Structure object
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Structure
|
||||
*/
|
||||
'toStructure' => function (Field $field) {
|
||||
'toStructure' => function (Field $field): Structure {
|
||||
try {
|
||||
return new Structure(Data::decode($field->value, 'yaml'), $field->parent());
|
||||
return Structure::factory(
|
||||
Data::decode($field->value, 'yaml'),
|
||||
['parent' => $field->parent(), 'field' => $field]
|
||||
);
|
||||
} catch (Exception) {
|
||||
$message = 'Invalid structure data for "' . $field->key() . '" field';
|
||||
|
||||
if ($parent = $field->parent()) {
|
||||
$message .= ' on parent "' . $parent->title() . '"';
|
||||
$message .= ' on parent "' . $parent->id() . '"';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($message);
|
||||
|
@ -276,9 +272,6 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Converts the field value to a Unix timestamp
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return int|false
|
||||
*/
|
||||
'toTimestamp' => function (Field $field): int|false {
|
||||
return strtotime($field->value ?? '');
|
||||
|
@ -286,33 +279,35 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Turns the field value into an absolute Url
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return string
|
||||
*/
|
||||
'toUrl' => function (Field $field): string {
|
||||
return Url::to($field->value);
|
||||
'toUrl' => function (Field $field): string|null {
|
||||
try {
|
||||
return $field->isNotEmpty() ? Url::to($field->value) : null;
|
||||
} catch (NotFoundException) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a user email address to a user object
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\User|null
|
||||
*/
|
||||
'toUser' => function (Field $field) {
|
||||
'toUser' => function (Field $field): User|null {
|
||||
return $field->toUsers()->first();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a users collection from a yaml list of user email addresses in the field
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param string $separator
|
||||
* @return \Kirby\Cms\Users
|
||||
* Returns a users collection from a yaml list
|
||||
* of user email addresses in the field
|
||||
*/
|
||||
'toUsers' => function (Field $field, string $separator = 'yaml') use ($app) {
|
||||
return $app->users()->find(false, false, ...$field->toData($separator));
|
||||
'toUsers' => function (
|
||||
Field $field,
|
||||
string $separator = 'yaml'
|
||||
) use ($app): Users {
|
||||
return $app->users()->find(
|
||||
false,
|
||||
false,
|
||||
...$field->toData($separator)
|
||||
);
|
||||
},
|
||||
|
||||
// inspectors
|
||||
|
@ -320,14 +315,14 @@ return function (App $app) {
|
|||
/**
|
||||
* Returns the length of the field content
|
||||
*/
|
||||
'length' => function (Field $field) {
|
||||
'length' => function (Field $field): int {
|
||||
return Str::length($field->value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the number of words in the text
|
||||
*/
|
||||
'words' => function (Field $field) {
|
||||
'words' => function (Field $field): int {
|
||||
return str_word_count(strip_tags($field->value ?? ''));
|
||||
},
|
||||
|
||||
|
@ -336,11 +331,8 @@ return function (App $app) {
|
|||
/**
|
||||
* Applies the callback function to the field
|
||||
* @since 3.4.0
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param Closure $callback
|
||||
*/
|
||||
'callback' => function (Field $field, Closure $callback) {
|
||||
'callback' => function (Field $field, Closure $callback): mixed {
|
||||
return $callback($field);
|
||||
},
|
||||
|
||||
|
@ -348,10 +340,9 @@ return function (App $app) {
|
|||
* Escapes the field value to be safely used in HTML
|
||||
* templates without the risk of XSS attacks
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param string $context Location of output (`html`, `attr`, `js`, `css`, `url` or `xml`)
|
||||
*/
|
||||
'escape' => function (Field $field, string $context = 'html') {
|
||||
'escape' => function (Field $field, string $context = 'html'): Field {
|
||||
$field->value = Str::esc($field->value ?? '', $context);
|
||||
return $field;
|
||||
},
|
||||
|
@ -359,25 +350,26 @@ return function (App $app) {
|
|||
/**
|
||||
* Creates an excerpt of the field value without html
|
||||
* or any other formatting.
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param int $cahrs
|
||||
* @param bool $strip
|
||||
* @param string $rep
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'excerpt' => function (Field $field, int $chars = 0, bool $strip = true, string $rep = ' …') {
|
||||
$field->value = Str::excerpt($field->kirbytext()->value(), $chars, $strip, $rep);
|
||||
'excerpt' => function (
|
||||
Field $field,
|
||||
int $chars = 0,
|
||||
bool $strip = true,
|
||||
string $rep = ' …'
|
||||
): Field {
|
||||
$field->value = Str::excerpt(
|
||||
$field->kirbytext()->value(),
|
||||
$chars,
|
||||
$strip,
|
||||
$rep
|
||||
);
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts the field content to valid HTML
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'html' => function (Field $field) {
|
||||
'html' => function (Field $field): Field {
|
||||
$field->value = Html::encode($field->value);
|
||||
return $field;
|
||||
},
|
||||
|
@ -387,11 +379,8 @@ return function (App $app) {
|
|||
* it can be safely placed inside of other inline elements
|
||||
* without the risk of breaking the HTML structure.
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'inline' => function (Field $field) {
|
||||
'inline' => function (Field $field): Field {
|
||||
// List of valid inline elements taken from: https://developer.mozilla.org/de/docs/Web/HTML/Inline_elemente
|
||||
// Obsolete elements, script tags, image maps and form elements have
|
||||
// been excluded for safety reasons and as they are most likely not
|
||||
|
@ -402,12 +391,11 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Converts the field content from Markdown/Kirbytext to valid HTML
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param array $options
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'kirbytext' => function (Field $field, array $options = []) use ($app) {
|
||||
'kirbytext' => function (
|
||||
Field $field,
|
||||
array $options = []
|
||||
) use ($app): Field {
|
||||
$field->value = $app->kirbytext($field->value, A::merge($options, [
|
||||
'parent' => $field->parent(),
|
||||
'field' => $field
|
||||
|
@ -420,12 +408,11 @@ return function (App $app) {
|
|||
* Converts the field content from inline Markdown/Kirbytext
|
||||
* to valid HTML
|
||||
* @since 3.1.0
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param array $options
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'kirbytextinline' => function (Field $field, array $options = []) use ($app) {
|
||||
'kirbytextinline' => function (
|
||||
Field $field,
|
||||
array $options = []
|
||||
) use ($app): Field {
|
||||
$field->value = $app->kirbytext($field->value, A::merge($options, [
|
||||
'parent' => $field->parent(),
|
||||
'field' => $field,
|
||||
|
@ -439,11 +426,8 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Parses all KirbyTags without also parsing Markdown
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'kirbytags' => function (Field $field) use ($app) {
|
||||
'kirbytags' => function (Field $field) use ($app): Field {
|
||||
$field->value = $app->kirbytags($field->value, [
|
||||
'parent' => $field->parent(),
|
||||
'field' => $field
|
||||
|
@ -454,23 +438,19 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Converts the field content to lowercase
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'lower' => function (Field $field) {
|
||||
'lower' => function (Field $field): Field {
|
||||
$field->value = Str::lower($field->value);
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts markdown to valid HTML
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param array $options
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'markdown' => function (Field $field, array $options = []) use ($app) {
|
||||
'markdown' => function (
|
||||
Field $field,
|
||||
array $options = []
|
||||
) use ($app): Field {
|
||||
$field->value = $app->markdown($field->value, $options);
|
||||
return $field;
|
||||
},
|
||||
|
@ -478,23 +458,54 @@ return function (App $app) {
|
|||
/**
|
||||
* Converts all line breaks in the field content to `<br>` tags.
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'nl2br' => function (Field $field) {
|
||||
'nl2br' => function (Field $field): Field {
|
||||
$field->value = nl2br($field->value ?? '', false);
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Uses the field value as Kirby query
|
||||
* Parses the field value as DOM and replaces
|
||||
* any permalinks in href/src attributes with
|
||||
* the regular url
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param string|null $expect
|
||||
* @return mixed
|
||||
* This method is still experimental! You can use
|
||||
* it to solve potential problems with permalinks
|
||||
* already, but it might change in the future.
|
||||
*/
|
||||
'query' => function (Field $field, string $expect = null) use ($app) {
|
||||
'permalinksToUrls' => function (Field $field): Field {
|
||||
if ($field->isNotEmpty() === true) {
|
||||
$dom = new Dom($field->value);
|
||||
$attributes = ['href', 'src'];
|
||||
$elements = $dom->query('//*[' . implode(' | ', A::map($attributes, fn ($attribute) => '@' . $attribute)) . ']');
|
||||
|
||||
foreach ($elements as $element) {
|
||||
foreach ($attributes as $attribute) {
|
||||
if ($element->hasAttribute($attribute) && $uuid = $element->getAttribute($attribute)) {
|
||||
try {
|
||||
if ($url = Uuid::for($uuid)?->model()?->url()) {
|
||||
$element->setAttribute($attribute, $url);
|
||||
}
|
||||
} catch (InvalidArgumentException) {
|
||||
// ignore anything else than permalinks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$field->value = $dom->toString();
|
||||
}
|
||||
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Uses the field value as Kirby query
|
||||
*/
|
||||
'query' => function (
|
||||
Field $field,
|
||||
string|null $expect = null
|
||||
) use ($app): mixed {
|
||||
if ($parent = $field->parent()) {
|
||||
return $parent->query($field->value, $expect);
|
||||
}
|
||||
|
@ -509,13 +520,13 @@ return function (App $app) {
|
|||
/**
|
||||
* It parses any queries found in the field value.
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param array $data
|
||||
* @param string|null $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* (`null` to keep the original token)
|
||||
* @return \Kirby\Cms\Field
|
||||
* @param string|null $fallback Fallback for tokens in the template that cannot be replaced (`null` to keep the original token)
|
||||
*/
|
||||
'replace' => function (Field $field, array $data = [], string|null $fallback = '') use ($app) {
|
||||
'replace' => function (
|
||||
Field $field,
|
||||
array $data = [],
|
||||
string|null $fallback = ''
|
||||
) use ($app): Field {
|
||||
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);
|
||||
|
@ -534,55 +545,45 @@ return function (App $app) {
|
|||
* Cuts the string after the given length and
|
||||
* adds "…" if it is longer
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @param int $length The number of characters in the string
|
||||
* @param string $appendix An optional replacement for the missing rest
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'short' => function (Field $field, int $length, string $appendix = '…') {
|
||||
'short' => function (
|
||||
Field $field,
|
||||
int $length,
|
||||
string $appendix = '…'
|
||||
): Field {
|
||||
$field->value = Str::short($field->value, $length, $appendix);
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts the field content to a slug
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'slug' => function (Field $field) {
|
||||
'slug' => function (Field $field): Field {
|
||||
$field->value = Str::slug($field->value);
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies SmartyPants to the field
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'smartypants' => function (Field $field) use ($app) {
|
||||
'smartypants' => function (Field $field) use ($app): Field {
|
||||
$field->value = $app->smartypants($field->value);
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Splits the field content into an array
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return array
|
||||
*/
|
||||
'split' => function (Field $field, $separator = ',') {
|
||||
'split' => function (Field $field, $separator = ','): array {
|
||||
return Str::split((string)$field->value, $separator);
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts the field content to uppercase
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'upper' => function (Field $field) {
|
||||
'upper' => function (Field $field): Field {
|
||||
$field->value = Str::upper($field->value);
|
||||
return $field;
|
||||
},
|
||||
|
@ -590,22 +591,16 @@ return function (App $app) {
|
|||
/**
|
||||
* Avoids typographical widows in strings by replacing
|
||||
* the last space with ` `
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'widont' => function (Field $field) {
|
||||
'widont' => function (Field $field): Field {
|
||||
$field->value = Str::widont($field->value);
|
||||
return $field;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts the field content to valid XML
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
'xml' => function (Field $field) {
|
||||
'xml' => function (Field $field): Field {
|
||||
$field->value = Xml::encode($field->value);
|
||||
return $field;
|
||||
},
|
||||
|
@ -614,9 +609,6 @@ return function (App $app) {
|
|||
|
||||
/**
|
||||
* Parses yaml in the field content and returns an array
|
||||
*
|
||||
* @param \Kirby\Cms\Field $field
|
||||
* @return array
|
||||
*/
|
||||
'yaml' => function (Field $field): array {
|
||||
return $field->toData('yaml');
|
||||
|
|
|
@ -44,7 +44,7 @@ return function ($props) {
|
|||
}
|
||||
|
||||
if (empty($sidebar) === true) {
|
||||
$props['fields'] = $props['fields'] ?? [];
|
||||
$props['fields'] ??= [];
|
||||
|
||||
unset(
|
||||
$props['files'],
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue