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,39 @@
<?php
namespace Kirby\Uuid;
use Kirby\Cms\Blocks;
use Kirby\Content\Field;
/**
* UUID for \Kirby\Cms\Block
*
* Not yet supported
* @todo Finish for uuid-block-structure-support
* @codeCoverageIgnore
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class BlockUuid extends FieldUuid
{
protected const TYPE = 'block';
protected const FIELD = 'blocks';
/**
* @var \Kirby\Cms\Block|null
*/
public Identifiable|null $model = null;
/**
* Converts content field to a Blocks collection
* @unstable
*/
public static function fieldToCollection(Field $field): Blocks
{
return $field->toBlocks();
}
}

View file

@ -0,0 +1,131 @@
<?php
namespace Kirby\Uuid;
use Generator;
use Kirby\Cms\Collection;
use Kirby\Content\Field;
use Kirby\Toolkit\A;
/**
* Base for UUIDs for models from content fields,
* such as blocks and structure entries
*
* Not yet supported
* @todo Finish for uuid-block-structure-support
* @codeCoverageIgnore
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
abstract class FieldUuid extends Uuid
{
protected const FIELD = 'field';
/**
* Converts a content field to a related
* models collection (e.g. Blocks or Structure)
* @unstable
*/
abstract public static function fieldToCollection(Field $field): Collection;
/**
* Looks up UUID in cache and resolves
* to identifiable model object
*/
protected function findByCache(): Identifiable|null
{
// get mixed Uri from cache
if ($key = $this->key()) {
if ($value = Uuids::cache()->get($key)) {
// value is an array containing
// the UUID for the parent, the field name
// and the specific ID
$parent = Uuid::for($value['parent'])->model();
if ($field = $parent?->content()->get($value['field'])) {
return static::fieldToCollection($field)->get($value['id']);
}
}
}
return null;
}
/**
* Looks up UUID in local and global index
* and returns the identifiable model object
*/
protected function findByIndex(): Identifiable|null
{
foreach ($this->indexes() as $collection) {
if ($found = $collection->get($this->id())) {
return $found;
}
}
return null;
}
/*
* Returns the ID for the specific entry/row of the field
* (we can rely in this case that the Uri was filled on initiation)
* @todo needs to be ensured for structure field once refactoring
*/
public function id(): string
{
return $this->uri->host();
}
/**
* Generator function that returns collections for all fields globally
* (in any page's, file's, user's or site's content file)
*
* @return \Generator|\Kirby\Cms\Collection[]
*/
public static function index(): Generator
{
$generate = static function (Generator $models): Generator {
foreach ($models as $model) {
$fields = $model->blueprint()->fields();
foreach ($fields as $name => $field) {
if (A::get($field, 'type') === static::FIELD) {
yield static::fieldToCollection($model->$name());
}
}
}
};
yield from $generate(SiteUuid::index());
yield from $generate(PageUuid::index());
yield from $generate(FileUuid::index());
yield from $generate(UserUuid::index());
}
/**
* Returns value to be stored in cache,
* constisting of three parts:
* - parent UUID including scheme
* - field name
* - UUID id string for model
*/
public function value(): array
{
$model = $this->model();
$parent = Uuid::for($model->parent());
// populate parent to cache itself as we'll need it
// as well when resolving model later on
$parent->populate();
return [
'parent' => $parent->toString(),
'field' => $model->field()->key(),
'id' => $model->id()
];
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Kirby\Uuid;
use Generator;
use Kirby\Cms\App;
use Kirby\Cms\File;
/**
* UUID for \Kirby\Cms\File
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class FileUuid extends ModelUuid
{
protected const TYPE = 'file';
/**
* @var \Kirby\Cms\File|null
*/
public Identifiable|null $model = null;
/**
* Looks up UUID in cache and resolves to file object;
* special for `FileUuid` as the value stored in cache is
* a hybrid URI from the parent's UUID and filename; needs
* to resolve parent UUID and then get file by filename
*/
protected function findByCache(): File|null
{
// get mixed Uri from cache
if ($key = $this->key()) {
if ($value = Uuids::cache()->get($key)) {
// value is an array containing
// the UUID for the parent and the filename
$parent = Uuid::for($value['parent'])->model();
return $parent?->file($value['filename']);
}
}
return null;
}
/**
* Generator for all files in the site
* (of all pages, users and site)
*
* @return \Generator|\Kirby\Cms\File[]
*/
public static function index(): Generator
{
foreach (SiteUuid::index() as $site) {
yield from $site->files();
}
foreach (PageUuid::index() as $page) {
yield from $page->files();
}
foreach (UserUuid::index() as $user) {
yield from $user->files();
}
}
/**
* Returns value to be stored in cache
*/
public function value(): array
{
$model = $this->model();
$parent = Uuid::for($model->parent());
// populate parent to cache itself as we'll need it
// as well when resolving model later on
$parent->populate();
return [
'parent' => $parent->toString(),
'filename' => $model->filename()
];
}
/**
* Returns permalink url
*/
public function toPermalink(): 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();
}
/**
* @deprecated 5.1.0 Use `::toPermalink()` instead
*/
public function url(): string
{
return $this->toPermalink();
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Kirby\Uuid;
/**
* Adds UUID lookup to collections
*
* @package Kirby Cms
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
trait HasUuids
{
/**
* Find a single element by global UUID
* @since 3.8.0
*/
protected function findByUuid(
string $uuid,
string|array|null $scheme = null
): Identifiable|null {
// handle UUID shortcuts with a leading @
if ($scheme !== null && str_starts_with($uuid, '@') === true) {
$uuid = $scheme . '://' . substr($uuid, 1);
}
if (Uuid::is($uuid, $scheme) === true) {
// look up model by UUID while prioritizing
// $this collection when searching
return Uuid::for($uuid, $this)->model();
}
return null;
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Kirby\Uuid;
/**
* Qualifies an object (e.g. page, file) to
* be identifiable via UUID. Mostly useful for
* type-hinting inside the Uuid classes.
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
interface Identifiable
{
public function id();
public function uuid(): Uuid|null;
}

View file

@ -0,0 +1,97 @@
<?php
namespace Kirby\Uuid;
/**
* Base for UUIDs for models where id string
* is stored in the content, such as pages and files
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
abstract class ModelUuid extends Uuid
{
/**
* @var \Kirby\Cms\ModelWithContent|null
*/
public Identifiable|null $model = null;
/**
* Looks up UUID in local and global index
* and returns the identifiable model object
*/
protected function findByIndex(): Identifiable|null
{
foreach ($this->indexes() as $model) {
if (static::retrieveId($model) === $this->id()) {
return $model;
}
}
return null;
}
/**
* Returns the UUID's id string; if not set yet,
* creates a new unique ID and writes it to content file
*/
public function id(): string
{
if ($id = $this->uri->host()) {
return $id;
}
// generate a new ID (to be saved in the content file)
$id = static::generate();
// store the new UUID
$this->storeId($id);
// update the Uri object
$this->uri->host($id);
return $id;
}
/**
* Retrieves the ID string (UUID without scheme) for the model
* from the content file, if it is already stored there
*
* @param \Kirby\Cms\ModelWithContent $model
*/
public static function retrieveId(Identifiable $model): string|null
{
return $model->content('default')->get('uuid')->value();
}
/**
* Stores the UUID for the model and makes sure
* to update the content file and content object cache
*/
protected function storeId(string $id): void
{
// get the content array from the page
$data = $this->model->content('default')->toArray();
// check for an empty content array
// and read content from file again,
// just to be sure we don't lose content
if (empty($data) === true) {
usleep(1000);
$data = $this->model->version()->read('default');
}
// add the UUID to the content array
if (empty($data['uuid']) === true) {
$data['uuid'] = $id;
}
// overwrite the content in the file;
// use the most basic write method to avoid object cloning
$this->model->version()->save($data, 'default');
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Kirby\Uuid;
use Generator;
use Kirby\Cms\App;
use Kirby\Cms\Page;
/**
* UUID for \Kirby\Cms\Page
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class PageUuid extends ModelUuid
{
protected const TYPE = 'page';
/**
* @var \Kirby\Cms\Page|null
*/
public Identifiable|null $model = null;
/**
* Looks up UUID in cache and resolves
* to page object
*/
protected function findByCache(): Page|null
{
if ($key = $this->key()) {
if ($value = Uuids::cache()->get($key)) {
return App::instance()->page($value);
}
}
return null;
}
/**
* Generator for all pages and drafts in the site
*
* @return \Generator|\Kirby\Cms\Page[]
*/
public static function index(Page|null $entry = null): Generator
{
$entry ??= App::instance()->site();
foreach ($entry->childrenAndDrafts() as $page) {
yield $page;
yield from static::index($page);
}
}
/**
* Returns permalink url
*/
public function toPermalink(): 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->url();
}
return $url . '/@/' . static::TYPE . '/' . $this->id();
}
/**
* @deprecated 5.1.0 Use `::toPermalink()` instead
*/
public function url(): string
{
return $this->toPermalink();
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Kirby\Uuid;
use Generator;
use Kirby\Cms\App;
use Kirby\Cms\Site;
/**
* UUID for \Kirby\Cms\Site
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class SiteUuid extends Uuid
{
protected const TYPE = 'site';
/**
* @var \Kirby\Cms\Site|null
*/
public Identifiable|null $model = null;
/*
* Returns empty string since
* site doesn't really need an ID
*/
public function id(): string
{
return '';
}
/**
* Generator for the one and only site object
*
* @return \Generator|\Kirby\Cms\Site[]
*/
public static function index(): Generator
{
yield App::instance()->site();
}
/**
* Returns the site object
*/
public function model(bool $lazy = false): Site
{
return $this->model ??= App::instance()->site();
}
/**
* Pretends to fill cache - we don't need it in cache
*/
public function populate(bool $force = false): bool
{
return true;
}
/**
* Returns empty string since
* site doesn't really need an ID
*/
public static function retrieveId(Identifiable $model): string
{
return '';
}
/**
* Returns the full UUID string including scheme
*/
public function toString(): string
{
return 'site://';
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Kirby\Uuid;
use Kirby\Cms\Structure;
use Kirby\Content\Field;
/**
* UUID for \Kirby\Cms\StructureObject
*
* Not yet supported
* @todo Finish for uuid-block-structure-support
* @codeCoverageIgnore
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class StructureUuid extends FieldUuid
{
protected const TYPE = 'struct';
protected const FIELD = 'structure';
/**
* @var \Kirby\Cms\StructureObject|null
*/
public Identifiable|null $model = null;
/**
* Converts content field to a Structure collection
* @unstable
*/
public static function fieldToCollection(Field $field): Structure
{
return $field->toStructure();
}
}

View file

@ -0,0 +1,94 @@
<?php
namespace Kirby\Uuid;
use Kirby\Http\Uri as BaseUri;
use Kirby\Toolkit\Str;
/**
* Uri protocol for UUIDs
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class Uri extends BaseUri
{
/**
* Supported schemes
*/
public static array $schemes = [
'site',
'page',
'file',
'user',
// TODO: acitivate for uuid-block-structure-support
// 'block',
// 'struct'
];
public function __construct(array|string $props = [], array $inject = [])
{
// treat `site://` differently:
// there is no host for site type, rest is always the path
if (
is_string($props) === true &&
Str::startsWith($props, 'site://') === true
) {
return parent::__construct([
'scheme' => 'site',
'host' => '',
'path' => Str::after($props, 'site://')
]);
}
return parent::__construct($props, $inject);
}
/**
* Custom base method to ensure that
* scheme is always included
*/
public function base(): string|null
{
return $this->scheme . '://' . $this->host;
}
/**
* Returns the ID part of the UUID string
* (and sets it when new one passed)
*/
public function host(string|null $host = null): string|null
{
if ($host !== null) {
return $this->host = $host;
}
return $this->host;
}
/**
* Return the full UUID string
*/
public function toString(): string
{
$url = parent::toString();
// correction for protocols without host,
// e.g. mainly `site://`
$url = Str::replace($url, ':///', '://');
return $url;
}
/**
* Returns the scheme as model type
*/
public function type(): string
{
return $this->scheme;
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Kirby\Uuid;
use Generator;
use Kirby\Cms\App;
use Kirby\Cms\User;
/**
* UUID for \Kirby\Cms\User
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class UserUuid extends Uuid
{
protected const TYPE = 'user';
/**
* @var \Kirby\Cms\User|null
*/
public Identifiable|null $model = null;
/*
* Returns the user ID
* (we can rely in this case that the Uri was filled
* with the model ID on initiation)
*/
public function id(): string
{
return $this->uri->host();
}
/**
* Generator for all users
*
* @return \Generator|\Kirby\Cms\User[]
*/
public static function index(): Generator
{
yield from App::instance()->users();
}
/**
* Returns the user object
*/
public function model(bool $lazy = false): User|null
{
return $this->model ??= App::instance()->user($this->id());
}
/**
* Pretends to fill cache - we don't need it in cache
*/
public function populate(bool $force = false): bool
{
return true;
}
}

View file

@ -0,0 +1,456 @@
<?php
namespace Kirby\Uuid;
use Closure;
use Generator;
use Kirby\Cms\App;
use Kirby\Cms\Collection;
use Kirby\Cms\File;
use Kirby\Cms\Page;
use Kirby\Cms\Site;
use Kirby\Cms\User;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use Stringable;
/**
* The `Uuid` classes provide an interface to connect
* identifiable models (page, file, site, user, blocks,
* structure entries) with a dedicated UUID string.
* It also provides methods to cache these connections
* for faster lookup.
*
* ```php
* // get UUID string
* $model->uuid()->toString();
*
* // get model from an UUID string
* Uuid::for('page://HhX1YtRR2ImG6h4')->model();
*
* // cache actions
* $model->uuid()->populate();
* $model->uuid()->clear();
* ```
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
abstract class Uuid implements Stringable
{
protected const TYPE = 'uuid';
/**
* Customizable callback function for generating new ID strings instead
* of `Str::random()`. Receives length of string as parameter.
*/
public static Closure|null $generator = null;
/**
* Collection that is likely to contain the model and
* that will be checked first to speed up the lookup
*/
public Collection|null $context;
public Identifiable|null $model;
public Uri $uri;
public function __construct(
string|null $uuid = null,
Identifiable|null $model = null,
Collection|null $context = null
) {
// throw exception when globally disabled
if (Uuids::enabled() === false) {
throw new LogicException(
message: 'UUIDs have been disabled via the `content.uuid` config option.'
);
}
$this->context = $context;
$this->model = $model;
if ($model) {
$this->uri = new Uri([
'scheme' => static::TYPE,
'host' => static::retrieveId($model)
]);
// in the rare case that both model and ID string
// got passed, make sure they match
if ($uuid && $uuid !== $this->uri->toString()) {
throw new LogicException(
message: 'UUID: can\'t create new instance from both model and UUID string that do not match'
);
}
} elseif ($uuid) {
$this->uri = new Uri($uuid);
}
}
/**
* Removes the current UUID from cache,
* recursively including all children if needed
*/
public function clear(bool $recursive = false): bool
{
// For all models with children: if $recursive,
// also clear UUIDs from cache for all children
if ($recursive === true && $model = $this->model()) {
if (method_exists($model, 'children') === true) {
foreach ($model->children() as $child) {
$child->uuid()->clear(true);
}
}
}
if ($key = $this->key()) {
return Uuids::cache()->remove($key);
}
return true;
}
/**
* Generator function for the local context
* collection, which takes priority when looking
* up the UUID/model from index
* @internal
*/
final public function context(): Generator
{
yield from $this->context ?? [];
}
/**
* Looks up UUID in cache and resolves
* to identifiable model object;
* implemented on child classes
*
* @codeCoverageIgnore
*/
protected function findByCache(): Identifiable|null
{
throw new LogicException(
message: 'UUID class needs to implement the ::findByCache() method'
);
}
/**
* Looks up UUID in local and global index
* and returns the identifiable model object;
* implemented on child classes
*
* @codeCoverageIgnore
*/
protected function findByIndex(): Identifiable|null
{
throw new LogicException(
message: 'UUID class needs to implement the ::findByIndex() method'
);
}
/**
* Shorthand to create instance
* by passing either UUID or model
*/
final public static function for(
string|Identifiable $seed,
Collection|null $context = null
): static|null {
// if globally disabled, return null
if (Uuids::enabled() === false) {
return null;
}
// for UUID string
if (is_string($seed) === true) {
if ($uri = Str::before($seed, '://')) {
return match ($uri) {
'page' => new PageUuid(uuid: $seed, context: $context),
'file' => new FileUuid(uuid: $seed, context: $context),
'site' => new SiteUuid(uuid: $seed, context: $context),
'user' => new UserUuid(uuid: $seed, context: $context),
// TODO: activate for uuid-block-structure-support
// 'block' => new BlockUuid(uuid: $seed, context: $context),
// 'struct' => new StructureUuid(uuid: $seed, context: $context),
default => throw new InvalidArgumentException(
message: 'Invalid UUID URI: ' . $seed
)
};
}
// permalinks
if ($url = Str::after($seed, '/@/')) {
$parts = explode('/', $url);
return static::for(
$parts[0] . '://' . $parts[1],
$context
);
}
throw new InvalidArgumentException(
message: 'Invalid UUID string: ' . $seed
);
}
// for model object
return match (true) {
$seed instanceof Page
=> new PageUuid(model: $seed, context: $context),
$seed instanceof File
=> new FileUuid(model: $seed, context: $context),
$seed instanceof Site
=> new SiteUuid(model: $seed, context: $context),
$seed instanceof User
=> new UserUuid(model: $seed, context: $context),
// TODO: activate for uuid-block-structure-support
// $seed instanceof Block
// => new BlockUuid(model: $seed, context: $context),
// $seed instanceof StructureObject
// => new StructureUuid(model: $seed, context: $context),
default => throw new InvalidArgumentException(
message: 'UUID not supported for: ' . $seed::class
)
};
}
/**
* Generates a new ID string
*/
final public static function generate(int $length = 16): string
{
if (static::$generator !== null) {
return (static::$generator)($length);
}
$option = App::instance()->option('content.uuid');
if (is_array($option) === true) {
$option = $option['format'] ?? null;
}
if ($option === 'uuid-v4') {
return Str::uuid();
}
return Str::lower(Str::random($length, 'alphaNum'));
}
/**
* Returns the UUID's id string (UUID without scheme);
* in child classes, this method must ensure that the
* model has an ID (or generate a new one if the model
* does not have one yet)
*/
abstract public function id(): string;
/**
* Generator function that creates an index of
* all identifiable model objects globally;
* implemented in child classes
*/
public static function index(): Generator
{
yield from [];
}
/**
* Merges local and global index generators
* into one iterator
* @internal
*
* @return \Generator|\Kirby\Uuid\Identifiable[]
*/
final public function indexes(): Generator
{
yield from $this->context();
yield from static::index();
}
/**
* Checks if a string resembles an UUID URI,
* optionally of the given type (scheme)
*/
final public static function is(
string $string,
string|array|null $type = null
): bool {
// always return false when UUIDs have been disabled
if (Uuids::enabled() === false) {
return false;
}
// use all available schemes by default
$type ??= Uri::$schemes;
$type = implode('|', A::wrap($type));
$pattern = sprintf('!^(%s)://(.*)!', $type);
if (preg_match($pattern, $string, $matches) !== 1) {
return false;
}
if ($matches[1] === 'site') {
return strlen($matches[2]) === 0;
}
return strlen($matches[2]) > 0;
}
/**
* Checks if the UUID has already been cached
*/
public function isCached(): bool
{
if ($key = $this->key()) {
return Uuids::cache()->exists($key);
}
return false;
}
/**
* Returns key for cache entry
*/
public function key(bool $generate = false): string|null
{
// the generation happens in the child class
// that overrides the `id()` method
$id = $generate === true ? $this->id() : $this->uri->host();
if ($id !== null) {
// for better performance when using a file-based cache,
// turn first two characters of the id into a directory
$id =
static::TYPE . '/' .
Str::substr($id, 0, 2) . '/' .
Str::substr($id, 2);
}
return $id;
}
/**
* Tries to find the identifiable model in cache
* or index and returns the object
*
* @param bool $lazy If `true`, only lookup from cache
*/
public function model(bool $lazy = false): Identifiable|null
{
if ($this->model !== null) {
return $this->model;
}
if ($this->model = $this->findByCache()) {
return $this->model;
}
if ($lazy === false) {
if (App::instance()->option('content.uuid.index') === false) {
throw new NotFoundException(
message: 'Model for UUID ' . $this->uri->toString() . ' could not be found without searching in the site index'
);
}
if ($this->model = $this->findByIndex()) {
// lazily fill cache by writing to cache
// whenever looked up from index to speed
// up future lookups of the same UUID
// also force to update value again if it is already cached
$this->populate($this->isCached());
return $this->model;
}
}
return null;
}
/**
* Feeds the UUID into the cache
*/
public function populate(bool $force = false): bool
{
if ($force === false && $this->isCached() === true) {
return true;
}
return Uuids::cache()->set($this->key(true), $this->value());
}
/**
* Retrieves the existing ID string (UUID without
* scheme) for the model;
* can be overridden in child classes depending
* on how the model stores the UUID
*/
public static function retrieveId(Identifiable $model): string|null
{
return $model->id();
}
/**
* Returns the full UUID string including scheme
*/
public function toString(): string
{
// make sure the id is cached
// that it can be found again
// (will also ensure ID is generated if non-existent yet)
$this->populate();
return $this->uri->toString();
}
/**
* Returns the URL of the model, including the query and fragment
* @since 5.1.0
*/
public function toUrl(): string|null
{
$model = $this->model();
if ($model === null) {
return null;
}
if (method_exists($model, 'url') === false) {
return null;
}
$url = $model->url();
$url .= $this->uri->query->toString(true);
if ($this->uri->hasFragment() === true) {
$url .= '#' . $this->uri->fragment();
}
return $url;
}
/**
* Returns value to be stored in cache
*/
public function value(): string|array
{
return $this->model()->id();
}
/**
* @see self::render()
*/
public function __toString(): string
{
return $this->toString();
}
}

View file

@ -0,0 +1,126 @@
<?php
namespace Kirby\Uuid;
use Closure;
use Kirby\Cache\Cache;
use Kirby\Cms\App;
use Kirby\Exception\LogicException;
/**
* Helper methods that deal with the entirety of UUIDs in the system
* @since 3.8.0
*
* @package Kirby Uuid
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class Uuids
{
/**
* Returns the instance for the lookup cache
*/
public static function cache(): Cache
{
return App::instance()->cache('uuid');
}
/**
* Runs the callback for each identifiable model of type
*
* @param string $type which models to include (`all`|`page`|`file`|`block`|`struct`)
*/
public static function each(Closure $callback, string $type = 'all'): void
{
if ($type === 'all' || $type === 'page' || $type === 'file') {
foreach (PageUuid::index() as $page) {
if ($type === 'all' || $type === 'page') {
$callback($page);
}
if ($type === 'all' || $type === 'file') {
foreach ($page->files() as $file) {
$callback($file);
}
}
}
}
if ($type === 'all' || $type === 'file') {
foreach (SiteUuid::index() as $site) {
foreach ($site->files() as $file) {
$callback($file);
}
}
foreach (UserUuid::index() as $user) {
foreach ($user->files() as $file) {
$callback($file);
}
}
}
// TODO: activate for uuid-block-structure-support
// if ($type === 'all' || $type === 'block') {
// foreach (BlockUuid::index() as $blocks) {
// foreach ($blocks as $block) {
// $callback($block);
// }
// }
// }
// if ($type === 'all' || $type === 'struct') {
// foreach (StructureUuid::index() as $structure) {
// foreach ($structure as $entry) {
// $callback($entry);
// }
// }
// }
}
public static function enabled(): bool
{
return App::instance()->option('content.uuid') !== false;
}
/**
* Generates UUID for all identifiable models of type
*
* @param string $type which models to include (`all`|`page`|`file`|`block`|`struct`)
*/
public static function generate(string $type = 'all'): void
{
if (static::enabled() === false) {
throw new LogicException(
message: 'UUIDs have been disabled via the `content.uuid` config option.'
);
}
static::each(
fn (Identifiable $model) => Uuid::for($model)->id(),
$type
);
}
/**
* Populates cache with UUIDs for all identifiable models
* that need to be cached (not site and users)
*
* @param string $type which models to include (`all`|`page`|`file`|`block`|`struct`)
*/
public static function populate(string $type = 'all'): void
{
if (static::enabled() === false) {
throw new LogicException(
message: 'UUIDs have been disabled via the `content.uuid` config option.'
);
}
static::each(
fn (Identifiable $model) => Uuid::for($model)->populate(),
$type
);
}
}