Update Composer packages
This commit is contained in:
parent
0320235f6c
commit
a8b68fb61b
378 changed files with 28466 additions and 28852 deletions
39
kirby/src/Uuid/BlockUuid.php
Normal file
39
kirby/src/Uuid/BlockUuid.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Uuid;
|
||||
|
||||
use Kirby\Cms\Blocks;
|
||||
use Kirby\Cms\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;
|
||||
|
||||
/**
|
||||
* Converts content field to a Blocks collection
|
||||
* @internal
|
||||
*/
|
||||
public static function fieldToCollection(Field $field): Blocks
|
||||
{
|
||||
return $field->toBlocks();
|
||||
}
|
||||
}
|
126
kirby/src/Uuid/FieldUuid.php
Normal file
126
kirby/src/Uuid/FieldUuid.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Uuid;
|
||||
|
||||
use Generator;
|
||||
use Kirby\Cms\Collection;
|
||||
use Kirby\Cms\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)
|
||||
* @internal
|
||||
*/
|
||||
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
|
||||
$key = $this->key();
|
||||
$value = Uuids::cache()->get($key);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// value is an array containing
|
||||
// the UUID for the parent, the field name
|
||||
// and the specific ID
|
||||
$parent = Uuid::for($value['parent'])->model();
|
||||
$field = $parent?->content()->get($value['field']);
|
||||
|
||||
if ($field) {
|
||||
$collection = $this->fieldToCollection($field);
|
||||
return $collection->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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = 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()
|
||||
];
|
||||
}
|
||||
}
|
87
kirby/src/Uuid/FileUuid.php
Normal file
87
kirby/src/Uuid/FileUuid.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Uuid;
|
||||
|
||||
use Generator;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
$key = $this->key();
|
||||
$value = Uuids::cache()->get($key);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// value is an array containing
|
||||
// the UUID for the parent and the filename
|
||||
$parent = Uuid::for($value['parent'])->model();
|
||||
return $parent?->file($value['filename']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
];
|
||||
}
|
||||
}
|
32
kirby/src/Uuid/HasUuids.php
Normal file
32
kirby/src/Uuid/HasUuids.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?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|null $scheme = null
|
||||
): Identifiable|null {
|
||||
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;
|
||||
}
|
||||
}
|
21
kirby/src/Uuid/Identifiable.php
Normal file
21
kirby/src/Uuid/Identifiable.php
Normal 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;
|
||||
}
|
137
kirby/src/Uuid/ModelUuid.php
Normal file
137
kirby/src/Uuid/ModelUuid.php
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Uuid;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Collection;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public function __construct(
|
||||
string|null $uuid = null,
|
||||
Identifiable|null $model = null,
|
||||
Collection|null $context = null
|
||||
) {
|
||||
parent::__construct($uuid, $model, $context);
|
||||
|
||||
// ensure that ID gets generated right away if
|
||||
// not yet stored any in content file
|
||||
$this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->readContent('default');
|
||||
}
|
||||
|
||||
// add the UUID to the content array
|
||||
if (empty($data['uuid']) === true) {
|
||||
$data['uuid'] = $id;
|
||||
}
|
||||
|
||||
// overwrite the content in memory for the current request
|
||||
if ($this->model->kirby()->multilang() === true) {
|
||||
// update the default translation instead of the content object
|
||||
// (the default content object is always freshly loaded from the
|
||||
// default translation afterwards, so updating the default
|
||||
// content object would not have any effect)
|
||||
$this->model->translation('default')->update($data);
|
||||
} else {
|
||||
$this->model->content('default')->update($data);
|
||||
}
|
||||
|
||||
// overwrite the content in the file;
|
||||
// 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();
|
||||
}
|
||||
}
|
53
kirby/src/Uuid/PageUuid.php
Normal file
53
kirby/src/Uuid/PageUuid.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* Looks up UUID in cache and resolves
|
||||
* to page object
|
||||
*/
|
||||
protected function findByCache(): Page|null
|
||||
{
|
||||
$key = $this->key();
|
||||
$value = Uuids::cache()->get($key);
|
||||
return App::instance()->page($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
70
kirby/src/Uuid/SiteUuid.php
Normal file
70
kirby/src/Uuid/SiteUuid.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
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://';
|
||||
}
|
||||
}
|
39
kirby/src/Uuid/StructureUuid.php
Normal file
39
kirby/src/Uuid/StructureUuid.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Uuid;
|
||||
|
||||
use Kirby\Cms\Field;
|
||||
use Kirby\Cms\Structure;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Converts content field to a Structure collection
|
||||
* @internal
|
||||
*/
|
||||
public static function fieldToCollection(Field $field): Structure
|
||||
{
|
||||
return $field->toStructure();
|
||||
}
|
||||
}
|
94
kirby/src/Uuid/Uri.php
Normal file
94
kirby/src/Uuid/Uri.php
Normal 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 $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;
|
||||
}
|
||||
}
|
53
kirby/src/Uuid/UserUuid.php
Normal file
53
kirby/src/Uuid/UserUuid.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
377
kirby/src/Uuid/Uuid.php
Normal file
377
kirby/src/Uuid/Uuid.php
Normal file
|
@ -0,0 +1,377 @@
|
|||
<?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\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* ```
|
||||
* // 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
|
||||
*/
|
||||
class Uuid
|
||||
{
|
||||
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('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('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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Uuids::cache()->remove($this->key());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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('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('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) {
|
||||
return match (Str::before($seed, '://')) {
|
||||
'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('Invalid UUID URI: ' . $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('UUID not supported for: ' . get_class($seed))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new ID string
|
||||
*/
|
||||
final public static function generate(int $length = 16): string
|
||||
{
|
||||
if (static::$generator !== null) {
|
||||
return (static::$generator)($length);
|
||||
}
|
||||
|
||||
if (App::instance()->option('content.uuid') === 'uuid-v4') {
|
||||
return Str::uuid();
|
||||
}
|
||||
|
||||
return 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
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return $this->uri->host();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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|null $type = null
|
||||
): bool {
|
||||
// always return false when UUIDs have been disabled
|
||||
if (Uuids::enabled() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type ??= implode('|', Uri::$schemes);
|
||||
$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
|
||||
{
|
||||
return Uuids::cache()->exists($this->key());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key for cache entry
|
||||
*/
|
||||
public function key(): string
|
||||
{
|
||||
$id = $this->id();
|
||||
|
||||
// for better performance when using a file-based cache,
|
||||
// turn first two characters of the id into a directory
|
||||
$id = Str::substr($id, 0, 2) . '/' . Str::substr($id, 2);
|
||||
|
||||
return static::TYPE . '/' . $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 ($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
|
||||
$this->populate();
|
||||
|
||||
return $this->model;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds the UUID into the cache
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function populate(): bool
|
||||
{
|
||||
if ($this->isCached() === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Uuids::cache()->set($this->key(), $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 id is generated if
|
||||
// it doesn't exist yet
|
||||
$this->id();
|
||||
|
||||
// make sure the id is cached
|
||||
// that it can be found again
|
||||
$this->populate();
|
||||
|
||||
return $this->uri->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value to be stored in cache
|
||||
*/
|
||||
public function value(): string|array
|
||||
{
|
||||
return $this->model()->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ::render
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
}
|
122
kirby/src/Uuid/Uuids.php
Normal file
122
kirby/src/Uuid/Uuids.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?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('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('UUIDs have been disabled via the `content.uuid` config option.');
|
||||
}
|
||||
|
||||
static::each(
|
||||
fn (Identifiable $model) => Uuid::for($model)->populate(),
|
||||
$type
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue