init with kirby, vue and pagedjs interactive

This commit is contained in:
isUnknown 2025-11-24 14:01:48 +01:00
commit dc0ae26464
968 changed files with 211706 additions and 0 deletions

View file

@ -0,0 +1,27 @@
<?php
use Kirby\Exception\AuthException;
return function () {
$auth = $this->kirby()->auth();
$allowImpersonation = $this->kirby()->option('api.allowImpersonation') ?? false;
// csrf token check
if (
$auth->type($allowImpersonation) === 'session' &&
$auth->csrf() === false
) {
throw new AuthException(message: 'Unauthenticated');
}
// get user from session or basic auth
if ($user = $auth->user(null, $allowImpersonation)) {
if ($user->role()->permissions()->for('access', 'panel') === false) {
throw new AuthException(key: 'access.panel');
}
return $user;
}
throw new AuthException(message: 'Unauthenticated');
};

View file

@ -0,0 +1,78 @@
<?php
/**
* 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 [
/**
* Children
*/
'children' => [
'model' => 'page',
'type' => Pages::class,
'view' => 'compact'
],
/**
* Files
*/
'files' => [
'model' => 'file',
'type' => Files::class,
],
/**
* Languages
*/
'languages' => [
'model' => 'language',
'type' => Languages::class,
],
/**
* Pages
*/
'pages' => [
'model' => 'page',
'type' => Pages::class,
'view' => 'compact'
],
/**
* Roles
*/
'roles' => [
'model' => 'role',
'type' => Roles::class,
'view' => 'compact'
],
/**
* Translations
*/
'translations' => [
'model' => 'translation',
'type' => Translations::class,
'view' => 'compact'
],
/**
* Users
*/
'users' => [
'default' => fn () => $this->users(),
'model' => 'user',
'type' => Users::class,
'view' => 'compact'
]
];

View file

@ -0,0 +1,21 @@
<?php
/**
* Api Model Definitions
*/
return [
'File' => include __DIR__ . '/models/File.php',
'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',
'Site' => include __DIR__ . '/models/Site.php',
'SiteBlueprint' => include __DIR__ . '/models/SiteBlueprint.php',
'System' => include __DIR__ . '/models/System.php',
'Translation' => include __DIR__ . '/models/Translation.php',
'User' => include __DIR__ . '/models/User.php',
'UserBlueprint' => include __DIR__ . '/models/UserBlueprint.php',
];

View file

@ -0,0 +1,119 @@
<?php
use Kirby\Cms\File;
use Kirby\Form\Form;
/**
* File
*/
return [
'fields' => [
'blueprint' => fn (File $file) => $file->blueprint(),
'content' => fn (File $file) => Form::for($file)->values(),
'dimensions' => fn (File $file) => $file->dimensions()->toArray(),
'dragText' => fn (File $file) => $file->panel()->dragText(),
'exists' => fn (File $file) => $file->exists(),
'extension' => fn (File $file) => $file->extension(),
'filename' => fn (File $file) => $file->filename(),
'id' => fn (File $file) => $file->id(),
'link' => fn (File $file) => $file->panel()->url(true),
'mime' => fn (File $file) => $file->mime(),
'modified' => fn (File $file) => $file->modified('c'),
'name' => fn (File $file) => $file->name(),
'next' => fn (File $file) => $file->next(),
'nextWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sorted();
$index = $files->indexOf($file);
return $files->nth($index + 1);
},
'niceSize' => fn (File $file) => $file->niceSize(),
'options' => fn (File $file) => $file->panel()->options(),
'panelImage' => fn (File $file) => $file->panel()->image(),
'panelUrl' => fn (File $file) => $file->panel()->url(true),
'prev' => fn (File $file) => $file->prev(),
'prevWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sorted();
$index = $files->indexOf($file);
return $files->nth($index - 1);
},
'parent' => fn (File $file) => $file->parent(),
'parents' => fn (File $file) => $file->parents()->flip(),
'size' => fn (File $file) => $file->size(),
'template' => fn (File $file) => $file->template(),
'thumbs' => function ($file) {
if ($file->isResizable() === false) {
return null;
}
return [
'tiny' => $file->resize(128)->url(),
'small' => $file->resize(256)->url(),
'medium' => $file->resize(512)->url(),
'large' => $file->resize(768)->url(),
'huge' => $file->resize(1024)->url(),
];
},
'type' => fn (File $file) => $file->type(),
'url' => fn (File $file) => $file->url(),
'uuid' => fn (File $file) => $file->uuid()?->toString()
],
'type' => File::class,
'views' => [
'default' => [
'content',
'dimensions',
'exists',
'extension',
'filename',
'id',
'link',
'mime',
'modified',
'name',
'next' => 'compact',
'niceSize',
'parent' => 'compact',
'options',
'prev' => 'compact',
'size',
'template',
'type',
'url',
'uuid'
],
'compact' => [
'filename',
'id',
'link',
'type',
'url',
'uuid'
],
'panel' => [
'blueprint',
'content',
'dimensions',
'extension',
'filename',
'id',
'link',
'mime',
'modified',
'name',
'nextWithTemplate' => 'compact',
'niceSize',
'options',
'panelIcon',
'panelImage',
'parent' => 'compact',
'parents' => ['id', 'slug', 'title'],
'prevWithTemplate' => 'compact',
'template',
'type',
'url',
'uuid'
]
],
];

View file

@ -0,0 +1,17 @@
<?php
use Kirby\Cms\FileBlueprint;
/**
* FileBlueprint
*/
return [
'fields' => [
'name' => fn (FileBlueprint $blueprint) => $blueprint->name(),
'options' => fn (FileBlueprint $blueprint) => $blueprint->options(),
'tabs' => fn (FileBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (FileBlueprint $blueprint) => $blueprint->title(),
],
'type' => FileBlueprint::class,
'views' => [],
];

View file

@ -0,0 +1,59 @@
<?php
use Kirby\Cms\FileVersion;
/**
* FileVersion
*/
return [
'fields' => [
'dimensions' => fn (FileVersion $file) => $file->dimensions()->toArray(),
'exists' => fn (FileVersion $file) => $file->exists(),
'extension' => fn (FileVersion $file) => $file->extension(),
'filename' => fn (FileVersion $file) => $file->filename(),
'id' => fn (FileVersion $file) => $file->id(),
'mime' => fn (FileVersion $file) => $file->mime(),
'modified' => fn (FileVersion $file) => $file->modified('c'),
'name' => fn (FileVersion $file) => $file->name(),
'niceSize' => fn (FileVersion $file) => $file->niceSize(),
'size' => fn (FileVersion $file) => $file->size(),
'type' => fn (FileVersion $file) => $file->type(),
'url' => fn (FileVersion $file) => $file->url(),
],
'type' => FileVersion::class,
'views' => [
'default' => [
'dimensions',
'exists',
'extension',
'filename',
'id',
'mime',
'modified',
'name',
'niceSize',
'size',
'type',
'url'
],
'compact' => [
'filename',
'id',
'type',
'url',
],
'panel' => [
'dimensions',
'extension',
'filename',
'id',
'mime',
'modified',
'name',
'niceSize',
'template',
'type',
'url'
]
],
];

View file

@ -0,0 +1,30 @@
<?php
use Kirby\Cms\Language;
/**
* Language
*/
return [
'fields' => [
'code' => fn (Language $language) => $language->code(),
'default' => fn (Language $language) => $language->isDefault(),
'direction' => fn (Language $language) => $language->direction(),
'locale' => fn (Language $language) => $language->locale(),
'name' => fn (Language $language) => $language->name(),
'rules' => fn (Language $language) => $language->rules(),
'url' => fn (Language $language) => $language->url(),
],
'type' => Language::class,
'views' => [
'default' => [
'code',
'default',
'direction',
'locale',
'name',
'rules',
'url'
]
]
];

View 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,
];

View file

@ -0,0 +1,96 @@
<?php
use Kirby\Cms\Page;
use Kirby\Form\Form;
/**
* Page
*/
return [
'fields' => [
'blueprint' => fn (Page $page) => $page->blueprint(),
'blueprints' => fn (Page $page) => $page->blueprints(),
'children' => fn (Page $page) => $page->children(),
'content' => fn (Page $page) => Form::for($page)->values(),
'drafts' => fn (Page $page) => $page->drafts(),
'errors' => fn (Page $page) => $page->errors(),
'files' => fn (Page $page) => $page->files()->sorted(),
'hasChildren' => fn (Page $page) => $page->hasChildren(),
'hasDrafts' => fn (Page $page) => $page->hasDrafts(),
'hasFiles' => fn (Page $page) => $page->hasFiles(),
'id' => fn (Page $page) => $page->id(),
'isSortable' => fn (Page $page) => $page->isSortable(),
'num' => fn (Page $page) => $page->num(),
'options' => fn (Page $page) => $page->panel()->options(['preview']),
'panelImage' => fn (Page $page) => $page->panel()->image(),
'parent' => fn (Page $page) => $page->parent(),
'parents' => fn (Page $page) => $page->parents()->flip(),
'previewUrl' => fn (Page $page) => $page->previewUrl(),
'siblings' => function (Page $page) {
if ($page->isDraft() === true) {
return $page->parentModel()->children()->not($page);
}
return $page->siblings();
},
'slug' => fn (Page $page) => $page->slug(),
'status' => fn (Page $page) => $page->status(),
'template' => fn (Page $page) => $page->intendedTemplate()->name(),
'title' => fn (Page $page) => $page->title()->value(),
'url' => fn (Page $page) => $page->url(),
'uuid' => fn (Page $page) => $page->uuid()?->toString()
],
'type' => Page::class,
'views' => [
'compact' => [
'id',
'title',
'url',
'num',
'uuid'
],
'default' => [
'content',
'id',
'status',
'num',
'options',
'parent' => 'compact',
'slug',
'template',
'title',
'url',
'uuid'
],
'panel' => [
'id',
'blueprint',
'content',
'status',
'options',
'next' => ['id', 'slug', 'title'],
'parents' => ['id', 'slug', 'title'],
'prev' => ['id', 'slug', 'title'],
'previewUrl',
'slug',
'title',
'url',
'uuid'
],
'selector' => [
'id',
'title',
'parent' => [
'id',
'title'
],
'children' => [
'hasChildren',
'id',
'panelIcon',
'panelImage',
'title',
],
]
],
];

View file

@ -0,0 +1,20 @@
<?php
use Kirby\Cms\PageBlueprint;
/**
* PageBlueprint
*/
return [
'fields' => [
'name' => fn (PageBlueprint $blueprint) => $blueprint->name(),
'num' => fn (PageBlueprint $blueprint) => $blueprint->num(),
'options' => fn (PageBlueprint $blueprint) => $blueprint->options(),
'preview' => fn (PageBlueprint $blueprint) => $blueprint->preview(),
'status' => fn (PageBlueprint $blueprint) => $blueprint->status(),
'tabs' => fn (PageBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (PageBlueprint $blueprint) => $blueprint->title(),
],
'type' => PageBlueprint::class,
'views' => [],
];

View file

@ -0,0 +1,23 @@
<?php
use Kirby\Cms\Role;
/**
* Role
*/
return [
'fields' => [
'description' => fn (Role $role) => $role->description(),
'name' => fn (Role $role) => $role->name(),
'permissions' => fn (Role $role) => $role->permissions()->toArray(),
'title' => fn (Role $role) => $role->title(),
],
'type' => Role::class,
'views' => [
'compact' => [
'description',
'name',
'title'
]
]
];

View file

@ -0,0 +1,52 @@
<?php
use Kirby\Cms\Site;
use Kirby\Form\Form;
/**
* Site
*/
return [
'default' => fn () => $this->site(),
'fields' => [
'blueprint' => fn (Site $site) => $site->blueprint(),
'children' => fn (Site $site) => $site->children(),
'content' => fn (Site $site) => Form::for($site)->values(),
'drafts' => fn (Site $site) => $site->drafts(),
'files' => fn (Site $site) => $site->files()->sorted(),
'options' => fn (Site $site) => $site->permissions()->toArray(),
'previewUrl' => fn (Site $site) => $site->previewUrl(),
'title' => fn (Site $site) => $site->title()->value(),
'url' => fn (Site $site) => $site->url(),
],
'type' => Site::class,
'views' => [
'compact' => [
'title',
'url'
],
'default' => [
'content',
'options',
'title',
'url'
],
'panel' => [
'title',
'blueprint',
'content',
'options',
'previewUrl',
'url'
],
'selector' => [
'title',
'children' => [
'id',
'title',
'panelIcon',
'hasChildren'
],
]
]
];

View file

@ -0,0 +1,17 @@
<?php
use Kirby\Cms\SiteBlueprint;
/**
* SiteBlueprint
*/
return [
'fields' => [
'name' => fn (SiteBlueprint $blueprint) => $blueprint->name(),
'options' => fn (SiteBlueprint $blueprint) => $blueprint->options(),
'tabs' => fn (SiteBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (SiteBlueprint $blueprint) => $blueprint->title(),
],
'type' => SiteBlueprint::class,
'views' => [],
];

View file

@ -0,0 +1,91 @@
<?php
use Kirby\Cms\System;
use Kirby\Toolkit\Str;
/**
* System
*/
return [
'fields' => [
'ascii' => fn () => Str::$ascii,
'authStatus' => fn () => $this->kirby()->auth()->status()->toArray(),
'defaultLanguage' => fn () => $this->kirby()->panelLanguage(),
'isOk' => fn (System $system) => $system->isOk(),
'isInstallable' => fn (System $system) => $system->isInstallable(),
'isInstalled' => fn (System $system) => $system->isInstalled(),
'isLocal' => fn (System $system) => $system->isLocal(),
'multilang' => fn () => $this->kirby()->option('languages', false) !== false,
'languages' => fn () => $this->kirby()->languages(),
'license' => fn (System $system) => $system->license(),
'locales' => function () {
$locales = [];
$translations = $this->kirby()->translations();
foreach ($translations as $translation) {
$locales[$translation->code()] = $translation->locale();
}
return $locales;
},
'loginMethods' => fn (System $system) => array_keys($system->loginMethods()),
'requirements' => fn (System $system) => $system->toArray(),
'site' => fn (System $system) => $system->title(),
'slugs' => fn () => Str::$language,
'title' => fn () => $this->site()->title()->value(),
'translation' => function () {
$code = $this->user()?->language() ??
$this->kirby()->panelLanguage();
return
$this->kirby()->translation($code) ??
$this->kirby()->translation('en');
},
'kirbytext' => fn () => $this->kirby()->option('panel.kirbytext') ?? true,
'user' => fn () => $this->user(),
'version' => function () {
if ($this->user()?->role()->permissions()->for('access', 'system') === true) {
return $this->kirby()->version();
}
return null;
}
],
'type' => System::class,
'views' => [
'login' => [
'authStatus',
'isOk',
'isInstallable',
'isInstalled',
'loginMethods',
'title',
'translation'
],
'troubleshooting' => [
'isOk',
'isInstallable',
'isInstalled',
'title',
'translation',
'requirements'
],
'panel' => [
'ascii',
'defaultLanguage',
'isOk',
'isInstalled',
'isLocal',
'kirbytext',
'languages',
'license',
'locales',
'multilang',
'requirements',
'site',
'slugs',
'title',
'translation',
'user' => 'auth',
'version'
]
],
];

View file

@ -0,0 +1,24 @@
<?php
use Kirby\Cms\Translation;
/**
* Translation
*/
return [
'fields' => [
'author' => fn (Translation $translation) => $translation->author(),
'data' => fn (Translation $translation) => $translation->dataWithFallback(),
'direction' => fn (Translation $translation) => $translation->direction(),
'id' => fn (Translation $translation) => $translation->id(),
'name' => fn (Translation $translation) => $translation->name(),
],
'type' => Translation::class,
'views' => [
'compact' => [
'direction',
'id',
'name'
]
]
];

View file

@ -0,0 +1,81 @@
<?php
use Kirby\Cms\User;
use Kirby\Form\Form;
/**
* User
*/
return [
'default' => fn () => $this->user(),
'fields' => [
'avatar' => fn (User $user) => $user->avatar()?->crop(512),
'blueprint' => fn (User $user) => $user->blueprint(),
'content' => fn (User $user) => Form::for($user)->values(),
'email' => fn (User $user) => $user->email(),
'files' => fn (User $user) => $user->files()->sorted(),
'id' => fn (User $user) => $user->id(),
'language' => fn (User $user) => $user->language(),
'name' => fn (User $user) => $user->name()->value(),
'next' => fn (User $user) => $user->next(),
'options' => fn (User $user) => $user->panel()->options(),
'panelImage' => fn (User $user) => $user->panel()->image(),
'permissions' => fn (User $user) => $user->role()->permissions()->toArray(),
'prev' => fn (User $user) => $user->prev(),
'role' => fn (User $user) => $user->role(),
'roles' => fn (User $user) => $user->roles(),
'username' => fn (User $user) => $user->username(),
'uuid' => fn (User $user) => $user->uuid()?->toString()
],
'type' => User::class,
'views' => [
'default' => [
'avatar',
'content',
'email',
'id',
'language',
'name',
'next' => 'compact',
'options',
'prev' => 'compact',
'role',
'username',
'uuid'
],
'compact' => [
'avatar' => 'compact',
'id',
'email',
'language',
'name',
'role' => 'compact',
'username',
'uuid'
],
'auth' => [
'avatar' => 'compact',
'permissions',
'email',
'id',
'name',
'role',
'language'
],
'panel' => [
'avatar' => 'compact',
'blueprint',
'content',
'email',
'id',
'language',
'name',
'next' => ['id', 'name'],
'options',
'prev' => ['id', 'name'],
'role',
'username',
'uuid'
],
]
];

View file

@ -0,0 +1,17 @@
<?php
use Kirby\Cms\UserBlueprint;
/**
* UserBlueprint
*/
return [
'fields' => [
'name' => fn (UserBlueprint $blueprint) => $blueprint->name(),
'options' => fn (UserBlueprint $blueprint) => $blueprint->options(),
'tabs' => fn (UserBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (UserBlueprint $blueprint) => $blueprint->title(),
],
'type' => UserBlueprint::class,
'views' => [],
];

View file

@ -0,0 +1,29 @@
<?php
/**
* Api Routes Definitions
*/
return function ($kirby) {
$routes = [
...include __DIR__ . '/routes/auth.php',
...include __DIR__ . '/routes/changes.php',
...include __DIR__ . '/routes/pages.php',
...include __DIR__ . '/routes/roles.php',
...include __DIR__ . '/routes/site.php',
...include __DIR__ . '/routes/users.php',
...include __DIR__ . '/routes/files.php',
...include __DIR__ . '/routes/system.php',
...include __DIR__ . '/routes/translations.php'
];
// only add the language routes if the
// multi language setup is activated
if ($kirby->option('languages', false) !== false) {
$routes = [
...$routes,
...include __DIR__ . '/routes/languages.php'
];
}
return $routes;
};

View file

@ -0,0 +1,126 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
/**
* Authentication
*/
return [
[
'pattern' => 'auth',
'method' => 'GET',
'action' => function () {
if ($user = $this->kirby()->auth()->user()) {
return $this->resolve($user)->view('auth');
}
throw new NotFoundException(
message: 'The user cannot be found'
);
}
],
[
'pattern' => 'auth/code',
'method' => 'POST',
'auth' => false,
'action' => function () {
$auth = $this->kirby()->auth();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new InvalidArgumentException(
message: 'Invalid CSRF token'
);
}
$user = $auth->verifyChallenge($this->requestBody('code'));
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
],
[
'pattern' => 'auth/login',
'method' => 'POST',
'auth' => false,
'action' => function () {
$auth = $this->kirby()->auth();
$methods = $this->kirby()->system()->loginMethods();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new InvalidArgumentException(
message: 'Invalid CSRF token'
);
}
$email = $this->requestBody('email');
$long = $this->requestBody('long');
$password = $this->requestBody('password');
if ($password) {
if (isset($methods['password']) !== true) {
throw new InvalidArgumentException(
message: 'Login with password is not enabled'
);
}
if (
isset($methods['password']['2fa']) === true &&
$methods['password']['2fa'] === true
) {
$status = $auth->login2fa($email, $password, $long);
} else {
$user = $auth->login($email, $password, $long);
}
} else {
$mode = match (true) {
isset($methods['code']) => 'login',
isset($methods['password-reset']) => 'password-reset',
default => throw new InvalidArgumentException(
message: 'Login without password is not enabled'
)
};
$status = $auth->createChallenge($email, $long, $mode);
}
if (isset($user)) {
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
return [
'code' => 200,
'status' => 'ok',
'challenge' => $status->challenge()
];
}
],
[
'pattern' => 'auth/logout',
'method' => 'POST',
'auth' => false,
'action' => function () {
$this->kirby()->auth()->logout();
return true;
}
],
[
'pattern' => 'auth/ping',
'method' => 'POST',
'auth' => false,
'action' => function () {
// refresh the session timeout
$this->kirby()->session();
return true;
}
],
];

View file

@ -0,0 +1,37 @@
<?php
use Kirby\Api\Controller\Changes;
use Kirby\Cms\App;
use Kirby\Cms\Find;
return [
[
'pattern' => '(:all)/changes/discard',
'method' => 'POST',
'action' => function (string $path) {
return Changes::discard(
model: Find::parent($path),
);
}
],
[
'pattern' => '(:all)/changes/publish',
'method' => 'POST',
'action' => function (string $path) {
return Changes::publish(
model: Find::parent($path),
input: App::instance()->request()->get()
);
}
],
[
'pattern' => '(:all)/changes/save',
'method' => 'POST',
'action' => function (string $path) {
return Changes::save(
model: Find::parent($path),
input: App::instance()->request()->get()
);
}
],
];

View file

@ -0,0 +1,146 @@
<?php
// routing pattern to match all models with files
$filePattern = '(account/|pages/[^/]+/|site/|users/[^/]+/|)files/(:any)';
$parentPattern = '(account|pages/[^/]+|site|users/[^/]+)/files';
/**
* Files Routes
*/
return [
[
'pattern' => $filePattern . '/fields/(:any)/(:all?)',
'method' => 'ALL',
'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' => $filePattern . '/sections/(:any)',
'method' => 'GET',
'action' => function (string $path, string $filename, string $sectionName) {
return $this->file($path, $filename)->blueprint()->section($sectionName)?->toResponse();
}
],
[
'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) {
// move the source file to the content folder
return $this->parent($path)->createFile([
'content' => [
'sort' => $this->requestBody('sort')
],
'source' => $source,
'template' => $this->requestBody('template'),
'filename' => $filename
], true);
});
// @codeCoverageIgnoreEnd
}
],
[
'pattern' => $parentPattern . '/search',
'method' => 'GET|POST',
'action' => function (string $path) {
$files = $this->files($path);
if ($this->requestMethod() === 'GET') {
return $files->search($this->requestQuery('q'));
}
return $files->query($this->requestBody());
}
],
[
'pattern' => $parentPattern . '/sort',
'method' => 'PATCH',
'action' => function (string $path) {
return $this->files($path)->changeSort(
$this->requestBody('files'),
$this->requestBody('index')
);
}
],
[
'pattern' => $filePattern,
'method' => 'GET',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename);
}
],
[
'pattern' => $filePattern,
'method' => 'PATCH',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->update(
$this->requestBody(),
$this->language(),
true
);
}
],
[
'pattern' => $filePattern,
'method' => 'POST',
'action' => function (string $path, string $filename) {
// move the source file from the temp dir
return $this->upload(
fn ($source) => $this->file($path, $filename)->replace($source, true)
);
}
],
[
'pattern' => $filePattern,
'method' => 'DELETE',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->delete();
}
],
[
'pattern' => $filePattern . '/name',
'method' => 'PATCH',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->changeName($this->requestBody('name'));
}
],
[
'pattern' => $parentPattern . '/search',
'method' => 'GET|POST',
'action' => function () {
$files = $this
->site()
->index(true)
->filter('isListable', true)
->files()
->filter('isListable', true);
if ($this->requestMethod() === 'GET') {
return $files->search($this->requestQuery('q'));
}
return $files->query($this->requestBody());
}
],
];

View 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

View file

@ -0,0 +1,42 @@
<?php
/**
* Roles Routes
*/
return [
[
'pattern' => 'languages',
'method' => 'GET',
'action' => function () {
return $this->kirby()->languages();
}
],
[
'pattern' => 'languages',
'method' => 'POST',
'action' => function () {
return $this->kirby()->languages()->create($this->requestBody());
}
],
[
'pattern' => 'languages/(:any)',
'method' => 'GET',
'action' => function (string $code) {
return $this->kirby()->languages()->find($code);
}
],
[
'pattern' => 'languages/(:any)',
'method' => 'PATCH',
'action' => function (string $code) {
return $this->kirby()->languages()->find($code)?->update($this->requestBody());
}
],
[
'pattern' => 'languages/(:any)',
'method' => 'DELETE',
'action' => function (string $code) {
return $this->kirby()->languages()->find($code)?->delete();
}
]
];

View file

@ -0,0 +1,129 @@
<?php
/**
* Page Routes
*/
return [
// @codeCoverageIgnoreStart
[
'pattern' => 'pages/(:any)',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id);
}
],
[
'pattern' => 'pages/(:any)',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => 'pages/(:any)',
'method' => 'DELETE',
'action' => function (string $id) {
return $this->page($id)->delete($this->requestBody('force', false));
}
],
[
'pattern' => 'pages/(:any)/blueprint',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id)->blueprint();
}
],
[
'pattern' => 'pages/(:any)/blueprints',
'method' => 'GET',
'action' => function (string $id) {
return $this->page($id)->blueprints($this->requestQuery('section'));
}
],
[
'pattern' => 'pages/(:any)/children',
'method' => 'GET',
'action' => function (string $id) {
return $this->pages($id, $this->requestQuery('status'));
}
],
[
'pattern' => 'pages/(:any)/children',
'method' => 'POST',
'action' => function (string $id) {
return $this->page($id)->createChild($this->requestBody());
}
],
[
'pattern' => 'pages/(:any)/children/search',
'method' => 'GET|POST',
'action' => function (string $id) {
return $this->searchPages($id);
}
],
[
'pattern' => 'pages/(:any)/duplicate',
'method' => 'POST',
'action' => function (string $id) {
return $this->page($id)->duplicate($this->requestBody('slug'), [
'children' => $this->requestBody('children'),
'files' => $this->requestBody('files'),
]);
}
],
[
'pattern' => 'pages/(:any)/slug',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeSlug($this->requestBody('slug'));
}
],
[
'pattern' => 'pages/(:any)/status',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeStatus($this->requestBody('status'), $this->requestBody('position'));
}
],
[
'pattern' => 'pages/(:any)/template',
'method' => 'PATCH',
'action' => function (string $id) {
return $this->page($id)->changeTemplate($this->requestBody('template'));
}
],
[
'pattern' => 'pages/(:any)/title',
'method' => 'PATCH',
'action' => function (string $id) {
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',
'action' => function (string $id, string $sectionName) {
return $this->page($id)->blueprint()->section($sectionName)?->toResponse();
}
],
[
'pattern' => 'pages/(:any)/sections/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $id, string $sectionName, string|null $path = null) {
if ($page = $this->page($id)) {
return $this->sectionApi($page, $sectionName, $path);
}
}
],
// @codeCoverageIgnoreEnd
];

View file

@ -0,0 +1,27 @@
<?php
/**
* Roles Routes
*/
return [
[
'pattern' => 'roles',
'method' => 'GET',
'action' => function () {
$kirby = $this->kirby();
return match ($kirby->request()->get('canBe')) {
'changed' => $kirby->roles()->canBeChanged(),
'created' => $kirby->roles()->canBeCreated(),
default => $kirby->roles()
};
}
],
[
'pattern' => 'roles/(:any)',
'method' => 'GET',
'action' => function (string $name) {
return $this->kirby()->roles()->find($name);
}
]
];

View file

@ -0,0 +1,109 @@
<?php
/**
* Site Routes
*/
return [
// @codeCoverageIgnoreStart
[
'pattern' => 'site',
'action' => function () {
return $this->site();
}
],
[
'pattern' => 'site',
'method' => 'PATCH',
'action' => function () {
return $this->site()->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => 'site/children',
'method' => 'GET',
'action' => function () {
return $this->pages(null, $this->requestQuery('status'));
}
],
[
'pattern' => 'site/children',
'method' => 'POST',
'action' => function () {
return $this->site()->createChild($this->requestBody());
}
],
[
'pattern' => 'site/children/search',
'method' => 'GET|POST',
'action' => function () {
return $this->searchPages();
}
],
[
'pattern' => 'site/blueprint',
'method' => 'GET',
'action' => function () {
return $this->site()->blueprint();
}
],
[
'pattern' => 'site/blueprints',
'method' => 'GET',
'action' => function () {
return $this->site()->blueprints($this->requestQuery('section'));
}
],
[
'pattern' => 'site/find',
'method' => 'POST',
'action' => function () {
return $this->site()->find(false, ...$this->requestBody());
}
],
[
'pattern' => 'site/title',
'method' => 'PATCH',
'action' => function () {
return $this->site()->changeTitle($this->requestBody('title'));
}
],
[
'pattern' => 'site/search',
'method' => 'GET|POST',
'action' => function () {
$pages = $this
->site()
->index(true)
->filter('isListable', true);
if ($this->requestMethod() === 'GET') {
return $pages->search($this->requestQuery('q'));
}
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',
'action' => function (string $sectionName) {
return $this->site()->blueprint()->section($sectionName)?->toResponse();
}
],
[
'pattern' => 'site/sections/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $sectionName, string|null $path = null) {
return $this->sectionApi($this->site(), $sectionName, $path);
}
],
// @codeCoverageIgnoreEnd
];

View file

@ -0,0 +1,86 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
/**
* System Routes
*/
return [
[
'pattern' => 'system',
'method' => 'GET',
'auth' => false,
'action' => function () {
$system = $this->kirby()->system();
if ($this->kirby()->user()) {
return $system;
}
$info = match ($system->isOk()) {
true => $this->resolve($system)->view('login')->toArray(),
false => $this->resolve($system)->view('troubleshooting')->toArray()
};
return [
'status' => 'ok',
'data' => $info,
'type' => 'model'
];
}
],
[
'pattern' => 'system/register',
'method' => 'POST',
'action' => function () {
return $this->kirby()->system()->register($this->requestBody('license'), $this->requestBody('email'));
}
],
[
'pattern' => 'system/install',
'method' => 'POST',
'auth' => false,
'action' => function () {
$system = $this->kirby()->system();
$auth = $this->kirby()->auth();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new InvalidArgumentException(
message: 'Invalid CSRF token'
);
}
if ($system->isOk() === false) {
throw new Exception(
message: 'The server is not setup correctly'
);
}
if ($system->isInstallable() === false) {
throw new Exception(
message: 'The Panel cannot be installed'
);
}
if ($system->isInstalled() === true) {
throw new Exception(
message: 'The Panel is already installed'
);
}
// create the first user
$user = $this->users()->create($this->requestBody());
$token = $user->login($this->requestBody('password'));
return [
'status' => 'ok',
'token' => $token,
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
]
];

View file

@ -0,0 +1,24 @@
<?php
/**
* Translations Routes
*/
return [
[
'pattern' => 'translations',
'method' => 'GET',
'auth' => false,
'action' => function () {
return $this->kirby()->translations();
}
],
[
'pattern' => 'translations/(:any)',
'method' => 'GET',
'auth' => false,
'action' => function (string $code) {
return $this->kirby()->translations()->find($code);
}
]
];

View file

@ -0,0 +1,260 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Filesystem\F;
use Kirby\Toolkit\Str;
/**
* User Routes
*/
return [
// @codeCoverageIgnoreStart
[
'pattern' => 'users',
'method' => 'GET',
'action' => function () {
return $this->users()->sort('username', 'asc', 'email', 'asc');
}
],
[
'pattern' => 'users',
'method' => 'POST',
'action' => function () {
return $this->users()->create($this->requestBody());
}
],
[
'pattern' => 'users/search',
'method' => 'GET|POST',
'action' => function () {
if ($this->requestMethod() === 'GET') {
return $this->users()->search($this->requestQuery('q'));
}
return $this->users()->query($this->requestBody());
}
],
[
'pattern' => [
'(account)',
'users/(:any)',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id);
}
],
[
'pattern' => [
'(account)',
'users/(:any)',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->update($this->requestBody(), $this->language(), true);
}
],
[
'pattern' => [
'(account)',
'users/(:any)',
],
'method' => 'DELETE',
'action' => function (string $id) {
return $this->user($id)->delete();
}
],
[
'pattern' => [
'(account)/avatar',
'users/(:any)/avatar',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id)->avatar();
}
],
// @codeCoverageIgnoreStart
[
'pattern' => [
'(account)/avatar',
'users/(:any)/avatar',
],
'method' => 'POST',
'action' => function (string $id) {
return $this->upload(
function ($source, $filename) use ($id) {
$type = F::type($filename);
if ($type !== 'image') {
throw new Exception(
key: 'file.type.invalid',
data: compact('type')
);
}
$mime = F::mime($source);
if (Str::startsWith($mime, 'image/') !== true) {
throw new Exception(
key: 'file.mime.invalid',
data: compact('mime')
);
}
// delete the old avatar
$this->user($id)->avatar()?->delete();
$props = [
'filename' => 'profile.' . F::extension($filename),
'template' => 'avatar',
'source' => $source
];
// move the source file from the temp dir
return $this->user($id)->createFile($props, true);
},
single: true
);
}
],
// @codeCoverageIgnoreEnd
[
'pattern' => [
'(account)/avatar',
'users/(:any)/avatar',
],
'method' => 'DELETE',
'action' => function (string $id) {
return $this->user($id)->avatar()->delete();
}
],
[
'pattern' => [
'(account)/blueprint',
'users/(:any)/blueprint',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id)->blueprint();
}
],
[
'pattern' => [
'(account)/blueprints',
'users/(:any)/blueprints',
],
'method' => 'GET',
'action' => function (string $id) {
return $this->user($id)->blueprints($this->requestQuery('section'));
}
],
[
'pattern' => [
'(account)/email',
'users/(:any)/email',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeEmail($this->requestBody('email'));
}
],
[
'pattern' => [
'(account)/language',
'users/(:any)/language',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeLanguage($this->requestBody('language'));
}
],
[
'pattern' => [
'(account)/name',
'users/(:any)/name',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeName($this->requestBody('name'));
}
],
[
'pattern' => [
'(account)/password',
'users/(:any)/password',
],
'method' => 'PATCH',
'action' => function (string $id) {
$user = $this->user($id);
// validate password of acting user unless they have logged in to reset it;
// always validate password of acting user when changing password of other users
if ($this->session()->get('kirby.resetPassword') !== true || $this->user()->is($user) !== true) {
$this->user()->validatePassword($this->requestBody('currentPassword'));
}
$result = $user->changePassword($this->requestBody('password'));
// if we changed the password of the current user…
if ($user->isLoggedIn() === true) {
// …don't allow additional resets (now the password is known again)
$this->session()->remove('kirby.resetPassword');
}
return $result;
}
],
[
'pattern' => [
'(account)/role',
'users/(:any)/role',
],
'method' => 'PATCH',
'action' => function (string $id) {
return $this->user($id)->changeRole($this->requestBody('role'));
}
],
[
'pattern' => [
'(account)/roles',
'users/(:any)/roles',
],
'action' => function (string $id) {
$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);
}
],
[
'pattern' => [
'(account)/sections/(:any)',
'users/(:any)/sections/(:any)',
],
'method' => 'GET',
'action' => function (string $id, string $sectionName) {
if ($section = $this->user($id)->blueprint()->section($sectionName)) {
return $section->toResponse();
}
}
],
[
'pattern' => [
'(account)/sections/(:any)/(:all?)',
'users/(:any)/sections/(:any)/(:all?)',
],
'method' => 'ALL',
'action' => function (string $id, string $sectionName, string|null $path = null) {
return $this->sectionApi($this->user($id), $sectionName, $path);
}
],
// @codeCoverageIgnoreEnd
];