fix #72 - update to Kirby 4.5

This commit is contained in:
isUnknown 2025-01-07 16:59:31 +01:00
parent cb1f842fc9
commit 44d08b3e21
273 changed files with 678 additions and 54176 deletions

View file

@ -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

View file

@ -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();

View file

@ -3,7 +3,6 @@
namespace Kirby\Cms;
use Kirby\Filesystem\F;
use Kirby\Toolkit\Str;
class LanguageRoutes
{
@ -30,26 +29,9 @@ class LanguageRoutes
'pattern' => $language->pattern(),
'method' => 'ALL',
'env' => 'site',
'action' => function ($path = null) use ($kirby, $language) {
'action' => function ($path = null) use ($language) {
$result = $language->router()->call($path);
// redirect secondary-language pages that have
// been accessed with non-translated slugs in their path
// to their fully translated URL
if ($path !== null && $result instanceof Page) {
if (Str::endsWith($result->url(), $path) === false) {
$url = $result->url();
$query = $kirby->request()->query()->toString();
// preserve query across redirect
if (empty($query) === false) {
$url .= '?' . $query;
}
return $kirby->response()->redirect($url);
}
}
// explicitly test for null as $result can
// contain falsy values that should still be returned
if ($result !== null) {

View file

@ -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]);

View file

@ -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;
}
/**

View file

@ -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');
}
/**

View file

@ -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

View file

@ -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' => [

View file

@ -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',

View file

@ -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

View file

@ -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

View file

@ -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');
}
/**

View file

@ -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

View file

@ -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
{

View file

@ -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 === '[]';
}
/**

View file

@ -2,7 +2,6 @@
namespace Kirby\Filesystem;
use Kirby\Cms\App;
use Kirby\Cms\Language;
use Kirby\Toolkit\Str;
@ -49,7 +48,8 @@ class Filename
public function __construct(
protected string $filename,
protected string $template,
protected array $attributes = []
protected array $attributes = [],
protected string|null $language = null
) {
$this->name = $this->sanitizeName($filename);
$this->extension = $this->sanitizeExtension(
@ -234,13 +234,12 @@ class Filename
{
// temporarily store language rules
$rules = Str::$language;
$kirby = App::instance(null, true);
// if current user, add rules for their language to `Str` class
if ($user = $kirby?->user()) {
// add rules for a particular language to `Str` class
if ($this->language !== null) {
Str::$language = [
...Str::$language,
...Language::loadRules($user->language())];
...Language::loadRules($this->language)];
}
// sanitize name

View file

@ -28,6 +28,7 @@ class Image extends File
protected Dimensions|null $dimensions = null;
public static array $resizableTypes = [
'avif',
'jpg',
'jpeg',
'gif',

View file

@ -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
];
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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(),

View file

@ -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'),

View file

@ -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
{

View file

@ -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);
}
}

View file

@ -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;
}
/**

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}