Upgrade to 4.5.0
This commit is contained in:
@@ -338,13 +338,12 @@ class File extends ModelWithContent
|
||||
return false;
|
||||
}
|
||||
|
||||
static $accessible = [];
|
||||
static $accessible = [];
|
||||
$role = $this->kirby()->user()?->role()->id() ?? '__none__';
|
||||
$template = $this->template() ?? '__none__';
|
||||
$accessible[$role] ??= [];
|
||||
|
||||
if ($template = $this->template()) {
|
||||
return $accessible[$template] ??= $this->permissions()->can('access');
|
||||
}
|
||||
|
||||
return $accessible['__none__'] ??= $this->permissions()->can('access');
|
||||
return $accessible[$role][$template] ??= $this->permissions()->can('access');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,13 +362,12 @@ class File extends ModelWithContent
|
||||
return false;
|
||||
}
|
||||
|
||||
static $listable = [];
|
||||
static $listable = [];
|
||||
$role = $this->kirby()->user()?->role()->id() ?? '__none__';
|
||||
$template = $this->template() ?? '__none__';
|
||||
$listable[$role] ??= [];
|
||||
|
||||
if ($template = $this->template()) {
|
||||
return $listable[$template] ??= $this->permissions()->can('list');
|
||||
}
|
||||
|
||||
return $listable['__none__'] ??= $this->permissions()->can('list');
|
||||
return $listable[$role][$template] ??= $this->permissions()->can('list');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,13 +377,12 @@ class File extends ModelWithContent
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
static $readable = [];
|
||||
static $readable = [];
|
||||
$role = $this->kirby()->user()?->role()->id() ?? '__none__';
|
||||
$template = $this->template() ?? '__none__';
|
||||
$readable[$role] ??= [];
|
||||
|
||||
if ($template = $this->template()) {
|
||||
return $readable[$template] ??= $this->permissions()->can('read');
|
||||
}
|
||||
|
||||
return $readable['__none__'] ??= $this->permissions()->can('read');
|
||||
return $readable[$role][$template] ??= $this->permissions()->can('read');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,12 +630,6 @@ class File extends ModelWithContent
|
||||
case 'page':
|
||||
$preview = $parent->blueprint()->preview();
|
||||
|
||||
// user has no permission to preview page,
|
||||
// also return null for file preview
|
||||
if ($preview === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// the page has a custom preview setting,
|
||||
// thus the file is only accessible through
|
||||
// the direct media URL
|
||||
|
@@ -139,10 +139,9 @@ trait FileActions
|
||||
|
||||
$file = $file->update(['template' => $template]);
|
||||
|
||||
// rename and/or resize the file if configured by new blueprint
|
||||
// resize the file if configured by new blueprint
|
||||
$create = $file->blueprint()->create();
|
||||
$file = $file->manipulate($create);
|
||||
$file = $file->changeExtension($file, $create['format'] ?? null);
|
||||
$file = $file->manipulate($create);
|
||||
|
||||
return $file;
|
||||
});
|
||||
@@ -185,6 +184,7 @@ trait FileActions
|
||||
|
||||
/**
|
||||
* Copy the file to the given page
|
||||
* @internal
|
||||
*/
|
||||
public function copy(Page $page): static
|
||||
{
|
||||
@@ -285,7 +285,6 @@ trait FileActions
|
||||
|
||||
// resize the file on upload if configured
|
||||
$file = $file->manipulate($create);
|
||||
$file = $file->changeExtension($file, $create['format'] ?? null);
|
||||
|
||||
// store the content if necessary
|
||||
// (always create files in the default language)
|
||||
@@ -338,7 +337,14 @@ trait FileActions
|
||||
// generate image file and overwrite it in place
|
||||
$this->kirby()->thumb($this->root(), $this->root(), $options);
|
||||
|
||||
return $this->clone([]);
|
||||
$file = $this->clone();
|
||||
|
||||
// change the file extension if format option configured
|
||||
if ($format = $options['format'] ?? null) {
|
||||
$file = $file->changeExtension($file, $format);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,7 +393,6 @@ trait FileActions
|
||||
// apply the resizing/crop options from the blueprint
|
||||
$create = $file->blueprint()->create();
|
||||
$file = $file->manipulate($create);
|
||||
$file = $file->changeExtension($file, $create['format'] ?? null);
|
||||
|
||||
// return a fresh clone
|
||||
return $file->clone();
|
||||
|
@@ -56,7 +56,7 @@ class LanguageVariable
|
||||
throw new DuplicateException('The variable is part of the core translation and cannot be overwritten');
|
||||
}
|
||||
|
||||
$translations[$key] = trim($value ?? '');
|
||||
$translations[$key] = $value ?? '';
|
||||
|
||||
$language->update(['translations' => $translations]);
|
||||
|
||||
@@ -102,10 +102,10 @@ class LanguageVariable
|
||||
/**
|
||||
* Sets a new value for the language variable
|
||||
*/
|
||||
public function update(string $value): static
|
||||
public function update(string|null $value = null): static
|
||||
{
|
||||
$translations = $this->language->translations();
|
||||
$translations[$this->key] = $value;
|
||||
$translations = $this->language->translations();
|
||||
$translations[$this->key] = $value ?? '';
|
||||
|
||||
$language = $this->language->update(['translations' => $translations]);
|
||||
|
||||
|
@@ -42,8 +42,9 @@ class License
|
||||
protected string|null $date = null,
|
||||
protected string|null $signature = null,
|
||||
) {
|
||||
// normalize the email address
|
||||
$this->email = $this->email === null ? null : $this->normalizeEmail($this->email);
|
||||
// normalize arguments
|
||||
$this->code = $this->code !== null ? trim($this->code) : null;
|
||||
$this->email = $this->email !== null ? $this->normalizeEmail($this->email) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -523,11 +523,12 @@ class Page extends ModelWithContent
|
||||
return false;
|
||||
}
|
||||
|
||||
static $accessible = [];
|
||||
static $accessible = [];
|
||||
$role = $this->kirby()->user()?->role()->id() ?? '__none__';
|
||||
$template = $this->intendedTemplate()->name();
|
||||
$accessible[$role] ??= [];
|
||||
|
||||
$template = $this->intendedTemplate()->name();
|
||||
|
||||
return $accessible[$template] ??= $this->permissions()->can('access');
|
||||
return $accessible[$role][$template] ??= $this->permissions()->can('access');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -689,11 +690,12 @@ class Page extends ModelWithContent
|
||||
return false;
|
||||
}
|
||||
|
||||
static $listable = [];
|
||||
static $listable = [];
|
||||
$role = $this->kirby()->user()?->role()->id() ?? '__none__';
|
||||
$template = $this->intendedTemplate()->name();
|
||||
$listable[$role] ??= [];
|
||||
|
||||
$template = $this->intendedTemplate()->name();
|
||||
|
||||
return $listable[$template] ??= $this->permissions()->can('list');
|
||||
return $listable[$role][$template] ??= $this->permissions()->can('list');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -745,11 +747,12 @@ class Page extends ModelWithContent
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
static $readable = [];
|
||||
static $readable = [];
|
||||
$role = $this->kirby()->user()?->role()->id() ?? '__none__';
|
||||
$template = $this->intendedTemplate()->name();
|
||||
$readable[$role] ??= [];
|
||||
|
||||
$template = $this->intendedTemplate()->name();
|
||||
|
||||
return $readable[$template] ??= $this->permissions()->can('read');
|
||||
return $readable[$role][$template] ??= $this->permissions()->can('read');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -153,7 +153,7 @@ trait PageActions
|
||||
string|null $languageCode = null
|
||||
): static {
|
||||
// always sanitize the slug
|
||||
$slug = Str::slug($slug);
|
||||
$slug = Url::slug($slug);
|
||||
|
||||
// in multi-language installations the slug for the non-default
|
||||
// languages is stored in the text file. The changeSlugForLanguage
|
||||
@@ -431,6 +431,8 @@ trait PageActions
|
||||
* Copies the page to a new parent
|
||||
*
|
||||
* @throws \Kirby\Exception\DuplicateException If the page already exists
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function copy(array $options = []): static
|
||||
{
|
||||
@@ -443,7 +445,7 @@ trait PageActions
|
||||
$files = $options['files'] ?? false;
|
||||
|
||||
// clean up the slug
|
||||
$slug = Str::slug($slug);
|
||||
$slug = Url::slug($slug);
|
||||
|
||||
if ($parentModel->findPageOrDraft($slug)) {
|
||||
throw new DuplicateException([
|
||||
@@ -495,7 +497,7 @@ trait PageActions
|
||||
public static function create(array $props): Page
|
||||
{
|
||||
// clean up the slug
|
||||
$props['slug'] = Str::slug($props['slug'] ?? $props['content']['title'] ?? null);
|
||||
$props['slug'] = Url::slug($props['slug'] ?? $props['content']['title'] ?? null);
|
||||
$props['template'] = $props['model'] = strtolower($props['template'] ?? 'default');
|
||||
$props['isDraft'] ??= $props['draft'] ?? true;
|
||||
|
||||
@@ -685,7 +687,7 @@ trait PageActions
|
||||
public function duplicate(string|null $slug = null, array $options = []): static
|
||||
{
|
||||
// create the slug for the duplicate
|
||||
$slug = Str::slug($slug ?? $this->slug() . '-' . Str::slug(I18n::translate('page.duplicate.appendix')));
|
||||
$slug = Url::slug($slug ?? $this->slug() . '-' . Url::slug(I18n::translate('page.duplicate.appendix')));
|
||||
|
||||
$arguments = [
|
||||
'originalPage' => $this,
|
||||
@@ -762,6 +764,7 @@ trait PageActions
|
||||
/**
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException If the folder cannot be moved
|
||||
* @internal
|
||||
*/
|
||||
public function publish(): static
|
||||
{
|
||||
@@ -913,6 +916,7 @@ trait PageActions
|
||||
/**
|
||||
* Convert a page from listed or
|
||||
* unlisted to draft.
|
||||
* @internal
|
||||
*
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException If the folder cannot be moved
|
||||
|
@@ -365,27 +365,40 @@ class PageRules
|
||||
$allowed = [];
|
||||
|
||||
// collect all allowed subpage templates
|
||||
foreach ($parent->blueprint()->sections() as $section) {
|
||||
// only take pages sections into consideration
|
||||
if ($section->type() !== 'pages') {
|
||||
continue;
|
||||
}
|
||||
// from all pages sections in the blueprint
|
||||
// (only consider page sections that list pages
|
||||
// of the targeted new parent page)
|
||||
$sections = array_filter(
|
||||
$parent->blueprint()->sections(),
|
||||
fn ($section) =>
|
||||
$section->type() === 'pages' &&
|
||||
$section->parent()->is($parent)
|
||||
);
|
||||
|
||||
// only consider page sections that list pages
|
||||
// of the targeted new parent page
|
||||
if ($section->parent() !== $parent) {
|
||||
continue;
|
||||
}
|
||||
// check if the parent has at least one pages section
|
||||
if ($sections === []) {
|
||||
throw new LogicException([
|
||||
'key' => 'page.move.noSections',
|
||||
'data' => [
|
||||
'parent' => $parent->id() ?? '/',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// go through all allowed blueprints and
|
||||
// add the name to the allow list
|
||||
foreach ($section->blueprints() as $blueprint) {
|
||||
$allowed[] = $blueprint['name'];
|
||||
// go through all allowed templates and
|
||||
// add the name to the allowlist
|
||||
foreach ($sections as $section) {
|
||||
foreach ($section->templates() as $template) {
|
||||
$allowed[] = $template;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the template of this page is allowed as subpage type
|
||||
if (in_array($page->intendedTemplate()->name(), $allowed) === false) {
|
||||
// for the potential new parent
|
||||
if (
|
||||
$allowed !== [] &&
|
||||
in_array($page->intendedTemplate()->name(), $allowed) === false
|
||||
) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.move.template',
|
||||
'data' => [
|
||||
|
@@ -25,15 +25,19 @@ class Roles extends Collection
|
||||
|
||||
/**
|
||||
* Returns a filtered list of all
|
||||
* roles that can be created by the
|
||||
* roles that can be changed by the
|
||||
* current user
|
||||
*
|
||||
* Use with `$kirby->roles()`. For retrieving
|
||||
* which roles are available for a specific user,
|
||||
* use `$user->roles()` without additional filters.
|
||||
*
|
||||
* @return $this|static
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function canBeChanged(): static
|
||||
{
|
||||
if (App::instance()->user()) {
|
||||
if (App::instance()->user()?->isAdmin() !== true) {
|
||||
return $this->filter(function ($role) {
|
||||
$newUser = new User([
|
||||
'email' => 'test@getkirby.com',
|
||||
@@ -50,14 +54,16 @@ class Roles extends Collection
|
||||
/**
|
||||
* Returns a filtered list of all
|
||||
* roles that can be created by the
|
||||
* current user
|
||||
* current user.
|
||||
*
|
||||
* Use with `$kirby->roles()`.
|
||||
*
|
||||
* @return $this|static
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function canBeCreated(): static
|
||||
{
|
||||
if (App::instance()->user()) {
|
||||
if (App::instance()->user()?->isAdmin() !== true) {
|
||||
return $this->filter(function ($role) {
|
||||
$newUser = new User([
|
||||
'email' => 'test@getkirby.com',
|
||||
|
@@ -26,7 +26,7 @@ class UpdateStatus
|
||||
/**
|
||||
* Host to request the update data from
|
||||
*/
|
||||
public static string $host = 'https://assets.getkirby.com';
|
||||
public static string $host = 'https://getkirby.com';
|
||||
|
||||
/**
|
||||
* Marker that stores whether a previous remote
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Http\Url as BaseUrl;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The `Url` class extends the
|
||||
@@ -31,6 +32,26 @@ class Url extends BaseUrl
|
||||
return App::instance()->url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to a safe version to be used in a URL,
|
||||
* obeying the `slugs.maxlength` option
|
||||
*
|
||||
* @param string $string The unsafe string
|
||||
* @param string $separator To be used instead of space and
|
||||
* other non-word characters.
|
||||
* @param string $allowed List of all allowed characters (regex)
|
||||
* @param int $maxlength The maximum length of the slug
|
||||
* @return string The safe string
|
||||
*/
|
||||
public static function slug(
|
||||
string $string = null,
|
||||
string $separator = null,
|
||||
string $allowed = null,
|
||||
): string {
|
||||
$maxlength = App::instance()->option('slugs.maxlength', 255);
|
||||
return Str::slug($string, $separator, $allowed, $maxlength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an absolute Url to a template asset if it exists.
|
||||
* This is used in the `css()` and `js()` helpers
|
||||
|
@@ -574,33 +574,24 @@ class User extends ModelWithContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available roles
|
||||
* for this user, that can be selected
|
||||
* by the authenticated user
|
||||
* Returns all available roles for this user,
|
||||
* that the authenticated user can change to.
|
||||
*
|
||||
* For all roles the current user can create
|
||||
* use `$kirby->roles()->canBeCreated()`.
|
||||
*/
|
||||
public function roles(): Roles
|
||||
{
|
||||
$kirby = $this->kirby();
|
||||
$roles = $kirby->roles();
|
||||
|
||||
// a collection with just the one role of the user
|
||||
$myRole = $roles->filter('id', $this->role()->id());
|
||||
|
||||
// if there's an authenticated user …
|
||||
// admin users can select pretty much any role
|
||||
if ($kirby->user()?->isAdmin() === true) {
|
||||
// except if the user is the last admin
|
||||
if ($this->isLastAdmin() === true) {
|
||||
// in which case they have to stay admin
|
||||
return $myRole;
|
||||
}
|
||||
|
||||
// return all roles for mighty admins
|
||||
return $roles;
|
||||
// if the authenticated user doesn't have the permission to change
|
||||
// the role of this user, only the current role is available
|
||||
if ($this->permissions()->can('changeRole') === false) {
|
||||
return $roles->filter('id', $this->role()->id());
|
||||
}
|
||||
|
||||
// any other user can only keep their role
|
||||
return $myRole;
|
||||
return $roles->canBeCreated();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -666,7 +657,7 @@ class User extends ModelWithContent
|
||||
*/
|
||||
protected function siblingsCollection(): Users
|
||||
{
|
||||
return $this->kirby()->users();
|
||||
return $this->kirby()->users()->sortBy('username', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -26,7 +26,20 @@ class UserPermissions extends ModelPermissions
|
||||
|
||||
protected function canChangeRole(): bool
|
||||
{
|
||||
return $this->model->roles()->count() > 1;
|
||||
// protect admin from role changes by non-admin
|
||||
if (
|
||||
$this->model->isAdmin() === true &&
|
||||
$this->user->isAdmin() !== true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent demoting the last admin
|
||||
if ($this->model->isLastAdmin() === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function canCreate(): bool
|
||||
|
@@ -101,17 +101,6 @@ class UserRules
|
||||
*/
|
||||
public static function changeRole(User $user, string $role): bool
|
||||
{
|
||||
// protect admin from role changes by non-admin
|
||||
if (
|
||||
$user->kirby()->user()->isAdmin() === false &&
|
||||
$user->isAdmin() === true
|
||||
) {
|
||||
throw new PermissionException([
|
||||
'key' => 'user.changeRole.permission',
|
||||
'data' => ['name' => $user->username()]
|
||||
]);
|
||||
}
|
||||
|
||||
// prevent non-admins making a user to admin
|
||||
if (
|
||||
$user->kirby()->user()->isAdmin() === false &&
|
||||
@@ -122,8 +111,7 @@ class UserRules
|
||||
]);
|
||||
}
|
||||
|
||||
static::validRole($user, $role);
|
||||
|
||||
// prevent demoting the last admin
|
||||
if ($role !== 'admin' && $user->isLastAdmin() === true) {
|
||||
throw new LogicException([
|
||||
'key' => 'user.changeRole.lastAdmin',
|
||||
@@ -131,6 +119,7 @@ class UserRules
|
||||
]);
|
||||
}
|
||||
|
||||
// check permissions
|
||||
if ($user->permissions()->changeRole() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'user.changeRole.permission',
|
||||
@@ -138,6 +127,13 @@ class UserRules
|
||||
]);
|
||||
}
|
||||
|
||||
// prevent changing to role that is not available for user
|
||||
if ($user->roles()->find($role) instanceof Role === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'user.role.invalid',
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -199,22 +195,27 @@ class UserRules
|
||||
return true;
|
||||
}
|
||||
|
||||
// only admins are allowed to add admins
|
||||
$role = $props['role'] ?? null;
|
||||
// allow to create the first user
|
||||
if ($user->kirby()->users()->count() === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($role === 'admin' && $currentUser?->isAdmin() === false) {
|
||||
// check user permissions
|
||||
if ($user->permissions()->create() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'user.create.permission'
|
||||
]);
|
||||
}
|
||||
|
||||
// check user permissions (if not on install)
|
||||
$role = $props['role'] ?? null;
|
||||
|
||||
// prevent creating a role that is not available for user
|
||||
if (
|
||||
$user->kirby()->users()->count() > 0 &&
|
||||
$user->permissions()->create() !== true
|
||||
in_array($role, [null, 'default', 'nobody'], true) === false &&
|
||||
$user->kirby()->roles()->canBeCreated()->find($role) instanceof Role === false
|
||||
) {
|
||||
throw new PermissionException([
|
||||
'key' => 'user.create.permission'
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'user.role.invalid',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -370,6 +371,7 @@ class UserRules
|
||||
* Validates a user role
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the user role does not exist
|
||||
* @deprecated 4.5.0
|
||||
*/
|
||||
public static function validRole(User $user, string $role): bool
|
||||
{
|
||||
|
@@ -124,9 +124,17 @@ class Field
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
$value = $this->value;
|
||||
|
||||
if (is_string($value) === true) {
|
||||
$value = trim($value);
|
||||
}
|
||||
|
||||
return
|
||||
empty($this->value) === true &&
|
||||
in_array($this->value, [0, '0', false], true) === false;
|
||||
$value === null ||
|
||||
$value === '' ||
|
||||
$value === [] ||
|
||||
$value === '[]';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -28,6 +28,7 @@ class Image extends File
|
||||
protected Dimensions|null $dimensions = null;
|
||||
|
||||
public static array $resizableTypes = [
|
||||
'avif',
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'gif',
|
||||
|
@@ -28,7 +28,9 @@ class OptionsApi extends OptionsProvider
|
||||
public string $url,
|
||||
public string|null $query = null,
|
||||
public string|null $text = null,
|
||||
public string|null $value = null
|
||||
public string|null $value = null,
|
||||
public string|null $icon = null,
|
||||
public string|null $info = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -46,10 +48,12 @@ class OptionsApi extends OptionsProvider
|
||||
}
|
||||
|
||||
return new static(
|
||||
url: $props['url'],
|
||||
url : $props['url'],
|
||||
query: $props['query'] ?? $props['fetch'] ?? null,
|
||||
text: $props['text'] ?? null,
|
||||
value: $props['value'] ?? null
|
||||
text : $props['text'] ?? null,
|
||||
value: $props['value'] ?? null,
|
||||
icon : $props['icon'] ?? null,
|
||||
info : $props['info'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,7 +142,10 @@ class OptionsApi extends OptionsProvider
|
||||
'value' => $model->toString($this->value, ['item' => $item]),
|
||||
// text is only a raw string when using {< >}
|
||||
// or when the safe mode is explicitly disabled (select field)
|
||||
'text' => $model->$safeMethod($this->text, ['item' => $item])
|
||||
'text' => $model->$safeMethod($this->text, ['item' => $item]),
|
||||
// additional data
|
||||
'icon' => $this->icon !== null ? $model->toString($this->icon, ['item' => $item]) : null,
|
||||
'info' => $this->info !== null ? $model->$safeMethod($this->info, ['item' => $item]) : null
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -30,7 +30,9 @@ class OptionsQuery extends OptionsProvider
|
||||
public function __construct(
|
||||
public string $query,
|
||||
public string|null $text = null,
|
||||
public string|null $value = null
|
||||
public string|null $value = null,
|
||||
public string|null $icon = null,
|
||||
public string|null $info = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -56,8 +58,10 @@ class OptionsQuery extends OptionsProvider
|
||||
|
||||
return new static(
|
||||
query: $props['query'] ?? $props['fetch'],
|
||||
text: $props['text'] ?? null,
|
||||
value: $props['value'] ?? null
|
||||
text : $props['text'] ?? null,
|
||||
value: $props['value'] ?? null,
|
||||
icon : $props['icon'] ?? null,
|
||||
info : $props['info'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,7 +182,11 @@ class OptionsQuery extends OptionsProvider
|
||||
$safeMethod = $safeMode === true ? 'toSafeString' : 'toString';
|
||||
$text = $model->$safeMethod($this->text ?? $text, $data);
|
||||
|
||||
return compact('text', 'value');
|
||||
// additional data
|
||||
$icon = $this->icon !== null ? $model->toString($this->icon, $data) : null;
|
||||
$info = $this->info !== null ? $model->$safeMethod($this->info, $data) : null;
|
||||
|
||||
return compact('text', 'value', 'icon', 'info');
|
||||
});
|
||||
|
||||
return $this->options = Options::factory($options);
|
||||
|
@@ -6,6 +6,7 @@ use Kirby\Cms\App;
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Roles;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Http\Router;
|
||||
use Kirby\Toolkit\I18n;
|
||||
@@ -191,29 +192,33 @@ class Field
|
||||
/**
|
||||
* User role radio buttons
|
||||
*/
|
||||
public static function role(array $props = []): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$isAdmin = $kirby->user()?->isAdmin() ?? false;
|
||||
$roles = [];
|
||||
public static function role(
|
||||
array $props = [],
|
||||
Roles|null $roles = null
|
||||
): array {
|
||||
$kirby = App::instance();
|
||||
|
||||
foreach ($kirby->roles() as $role) {
|
||||
// exclude the admin role, if the user
|
||||
// is not allowed to change role to admin
|
||||
if ($role->name() === 'admin' && $isAdmin === false) {
|
||||
continue;
|
||||
}
|
||||
// if no $roles where provided, fall back to all roles
|
||||
$roles ??= $kirby->roles();
|
||||
|
||||
$roles[] = [
|
||||
'text' => $role->title(),
|
||||
'info' => $role->description() ?? I18n::translate('role.description.placeholder'),
|
||||
'value' => $role->name()
|
||||
];
|
||||
}
|
||||
// exclude the admin role, if the user
|
||||
// is not allowed to change role to admin
|
||||
$roles = $roles->filter(
|
||||
fn ($role) =>
|
||||
$role->name() !== 'admin' ||
|
||||
$kirby->user()?->isAdmin() === true
|
||||
);
|
||||
|
||||
// turn roles into radio field options
|
||||
$roles = $roles->values(fn ($role) => [
|
||||
'text' => $role->title(),
|
||||
'info' => $role->description() ?? I18n::translate('role.description.placeholder'),
|
||||
'value' => $role->name()
|
||||
]);
|
||||
|
||||
return array_merge([
|
||||
'label' => I18n::translate('role'),
|
||||
'type' => count($roles) <= 1 ? 'hidden' : 'radio',
|
||||
'type' => count($roles) < 1 ? 'hidden' : 'radio',
|
||||
'options' => $roles
|
||||
], $props);
|
||||
}
|
||||
|
@@ -54,6 +54,7 @@ class PageCreateDialog
|
||||
'tags',
|
||||
'tel',
|
||||
'text',
|
||||
'toggle',
|
||||
'toggles',
|
||||
'time',
|
||||
'url'
|
||||
@@ -247,8 +248,9 @@ class PageCreateDialog
|
||||
*/
|
||||
public function model(): Page
|
||||
{
|
||||
// TODO: use actual in-memory page in v5
|
||||
return $this->model ??= Page::factory([
|
||||
'slug' => 'new',
|
||||
'slug' => '__new__',
|
||||
'template' => $this->template,
|
||||
'model' => $this->template,
|
||||
'parent' => $this->parent instanceof Page ? $this->parent : null
|
||||
@@ -266,12 +268,7 @@ class PageCreateDialog
|
||||
|
||||
// create temporary page object
|
||||
// to resolve the template strings
|
||||
$page = new Page([
|
||||
'slug' => 'tmp',
|
||||
'template' => $this->template,
|
||||
'parent' => $this->model(),
|
||||
'content' => $input
|
||||
]);
|
||||
$page = $this->model()->clone(['content' => $input]);
|
||||
|
||||
if (is_string($title)) {
|
||||
$input['title'] = $page->toSafeString($title);
|
||||
|
@@ -70,7 +70,7 @@ class User extends Model
|
||||
'dialog' => $url . '/changeRole',
|
||||
'icon' => 'bolt',
|
||||
'text' => I18n::translate('user.changeRole'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeRole', $options, $permissions)
|
||||
'disabled' => $this->isDisabledDropdownOption('changeRole', $options, $permissions) || $this->model->roles()->count() < 2
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
@@ -218,14 +218,19 @@ class User extends Model
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$user = $this->model;
|
||||
$account = $user->isLoggedIn();
|
||||
$user = $this->model;
|
||||
$account = $user->isLoggedIn();
|
||||
$permissions = $this->options();
|
||||
|
||||
return array_merge(
|
||||
parent::props(),
|
||||
$account ? [] : $this->prevNext(),
|
||||
$this->prevNext(),
|
||||
[
|
||||
'blueprint' => $this->model->role()->name(),
|
||||
'blueprint' => $this->model->role()->name(),
|
||||
'canChangeEmail' => $permissions['changeEmail'],
|
||||
'canChangeLanguage' => $permissions['changeLanguage'],
|
||||
'canChangeName' => $permissions['changeName'],
|
||||
'canChangeRole' => $this->model->roles()->count() > 1,
|
||||
'model' => [
|
||||
'account' => $account,
|
||||
'avatar' => $user->avatar()?->url(),
|
||||
|
@@ -275,6 +275,9 @@ class View
|
||||
|
||||
return [
|
||||
'$config' => fn () => [
|
||||
'api' => [
|
||||
'methodOverwrite' => $kirby->option('api.methodOverwrite', true)
|
||||
],
|
||||
'debug' => $kirby->option('debug', false),
|
||||
'kirbytext' => $kirby->option('panel.kirbytext', true),
|
||||
'translation' => $kirby->option('panel.language', 'en'),
|
||||
|
@@ -96,8 +96,6 @@ class Collection extends Iterator implements Countable
|
||||
* Low-level setter for elements
|
||||
*
|
||||
* @param string $key string or array
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $key, $value): void
|
||||
{
|
||||
|
@@ -102,4 +102,12 @@ class Obj extends stdClass
|
||||
{
|
||||
return json_encode($this->toArray(), ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property names as keys
|
||||
*/
|
||||
public function toKeys(): array
|
||||
{
|
||||
return array_keys((array)$this);
|
||||
}
|
||||
}
|
||||
|
@@ -1132,7 +1132,7 @@ class Str
|
||||
string $string = null,
|
||||
string $separator = null,
|
||||
string $allowed = null,
|
||||
int $maxlength = 128
|
||||
int|false $maxlength = 128
|
||||
): string {
|
||||
$separator ??= static::$defaults['slug']['separator'];
|
||||
$allowed ??= static::$defaults['slug']['allowed'];
|
||||
@@ -1165,7 +1165,11 @@ class Str
|
||||
$string = preg_replace('![^a-z0-9]+$!', '', $string);
|
||||
|
||||
// cut the string after the given maxlength
|
||||
return static::short($string, $maxlength, '');
|
||||
if ($maxlength !== false) {
|
||||
$string = static::short($string, $maxlength, '');
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Uuid;
|
||||
|
||||
use Generator;
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\File;
|
||||
|
||||
/**
|
||||
@@ -83,4 +84,18 @@ class FileUuid extends ModelUuid
|
||||
'filename' => $model->filename()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns permalink url
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
// make sure UUID is cached because the permalink
|
||||
// route only looks up UUIDs from cache
|
||||
if ($this->isCached() === false) {
|
||||
$this->populate();
|
||||
}
|
||||
|
||||
return App::instance()->url() . '/@/' . static::TYPE . '/' . $this->id();
|
||||
}
|
||||
}
|
||||
|
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Kirby\Uuid;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
|
||||
/**
|
||||
* Base for UUIDs for models where id string
|
||||
* is stored in the content, such as pages and files
|
||||
@@ -107,18 +105,4 @@ abstract class ModelUuid extends Uuid
|
||||
// use the most basic write method to avoid object cloning
|
||||
$this->model->writeContent($data, 'default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns permalink url
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
// make sure UUID is cached because the permalink
|
||||
// route only looks up UUIDs from cache
|
||||
if ($this->isCached() === false) {
|
||||
$this->populate();
|
||||
}
|
||||
|
||||
return App::instance()->url() . '/@/' . static::TYPE . '/' . $this->id();
|
||||
}
|
||||
}
|
||||
|
@@ -54,4 +54,25 @@ class PageUuid extends ModelUuid
|
||||
yield from static::index($page);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns permalink url
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
// make sure UUID is cached because the permalink
|
||||
// route only looks up UUIDs from cache
|
||||
if ($this->isCached() === false) {
|
||||
$this->populate();
|
||||
}
|
||||
|
||||
$kirby = App::instance();
|
||||
$url = $kirby->url();
|
||||
|
||||
if ($language = $kirby->language('current')) {
|
||||
$url .= '/' . $language->code();
|
||||
}
|
||||
|
||||
return $url . '/@/' . static::TYPE . '/' . $this->id();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user