designtopack/public/kirby/src/Content/Storage.php

325 lines
7.6 KiB
PHP

<?php
namespace Kirby\Content;
use Generator;
use Kirby\Cms\Language;
use Kirby\Cms\Languages;
use Kirby\Cms\ModelWithContent;
use Kirby\Toolkit\A;
/**
* Abstract for content storage handlers;
* note that it is so far not viable to build custom
* handlers because the CMS core relies on the filesystem
* and cannot fully benefit from this abstraction yet
*
* @package Kirby Content
* @author Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 4.0.0
* @unstable
*/
abstract class Storage
{
public function __construct(protected ModelWithContent $model)
{
}
/**
* Returns generator for all existing version-language combinations
*
* @return Generator<\Kirby\Content\VersionId, \Kirby\Cms\Language>
*/
public function all(): Generator
{
foreach (Languages::ensure() as $language) {
foreach ($this->model->versions() as $version) {
if ($this->exists($version->id(), $language) === true) {
yield $version->id() => $language;
}
}
}
}
/**
* Copies content from one version-language combination to another
*/
public function copy(
VersionId $fromVersionId,
Language $fromLanguage,
VersionId|null $toVersionId = null,
Language|null $toLanguage = null,
Storage|null $toStorage = null
): void {
// fallbacks to allow keeping the method call lean
$toVersionId ??= $fromVersionId;
$toLanguage ??= $fromLanguage;
$toStorage ??= $this;
// don't copy content to the same version-language-storage combination
if ($this->isSameStorageLocation(
fromVersionId: $fromVersionId,
fromLanguage: $fromLanguage,
toVersionId: $toVersionId,
toLanguage: $toLanguage,
toStorage: $toStorage
)) {
return;
}
// read the existing fields
$content = $this->read($fromVersionId, $fromLanguage);
// create the new version
$toStorage->create($toVersionId, $toLanguage, $content);
}
/**
* Copies all content to another storage
*/
public function copyAll(Storage $to): void
{
foreach ($this->all() as $versionId => $language) {
$this->copy($versionId, $language, toStorage: $to);
}
}
/**
* Creates a new version
*
* @param array<string, string> $fields Content fields
*/
public function create(VersionId $versionId, Language $language, array $fields): void
{
$this->write($versionId, $language, $fields);
}
/**
* Deletes an existing version in an idempotent way if it was already deleted
*/
abstract public function delete(VersionId $versionId, Language $language): void;
/**
* Deletes all versions when deleting a language
* @unstable
* @todo Move to `Language` class
*/
public function deleteLanguage(Language $language): void
{
foreach ($this->model->versions() as $version) {
$this->delete($version->id(), $language);
}
}
/**
* Checks if a version exists
*/
abstract public function exists(VersionId $versionId, Language $language): bool;
/**
* Creates a new storage instance with all the versions
* from the given storage instance.
*/
public static function from(self $fromStorage): static
{
$toStorage = new static(
model: $fromStorage->model()
);
// copy all versions from the given storage instance
// and add them to the new storage instance.
$fromStorage->copyAll($toStorage);
return $toStorage;
}
/**
* Compare two version-language-storage combinations
*/
public function isSameStorageLocation(
VersionId $fromVersionId,
Language $fromLanguage,
VersionId|null $toVersionId = null,
Language|null $toLanguage = null,
Storage|null $toStorage = null
) {
// fallbacks to allow keeping the method call lean
$toVersionId ??= $fromVersionId;
$toLanguage ??= $fromLanguage;
$toStorage ??= $this;
if (
$fromVersionId->is($toVersionId) &&
$fromLanguage->is($toLanguage) &&
$this === $toStorage
) {
return true;
}
return false;
}
/**
* Returns the related model
*/
public function model(): ModelWithContent
{
return $this->model;
}
/**
* Returns the modification timestamp of a version if it exists
*/
abstract public function modified(VersionId $versionId, Language $language): int|null;
/**
* Moves content from one version-language combination to another
*/
public function move(
VersionId $fromVersionId,
Language $fromLanguage,
VersionId|null $toVersionId = null,
Language|null $toLanguage = null,
Storage|null $toStorage = null
): void {
// fallbacks to allow keeping the method call lean
$toVersionId ??= $fromVersionId;
$toLanguage ??= $fromLanguage;
$toStorage ??= $this;
// don't move content to the same version-language-storage combination
if ($this->isSameStorageLocation(
fromVersionId: $fromVersionId,
fromLanguage: $fromLanguage,
toVersionId: $toVersionId,
toLanguage: $toLanguage,
toStorage: $toStorage
)) {
return;
}
// copy content to new version
$this->copy(
$fromVersionId,
$fromLanguage,
$toVersionId,
$toLanguage,
$toStorage
);
// clean up the old version
$this->delete($fromVersionId, $fromLanguage);
}
/**
* Moves all content to another storage
*/
public function moveAll(Storage $to): void
{
foreach ($this->all() as $versionId => $language) {
$this->move($versionId, $language, toStorage: $to);
}
}
/**
* Adapts all versions when converting languages
* @unstable
* @todo Move to `Language` class
*/
public function moveLanguage(
Language $fromLanguage,
Language $toLanguage
): void {
foreach ($this->model->versions() as $version) {
if ($this->exists($version->id(), $fromLanguage) === true) {
$this->move(
$version->id(),
$fromLanguage,
toLanguage: $toLanguage
);
}
}
}
/**
* Returns the stored content fields
*
* @return array<string, string>
*/
abstract public function read(VersionId $versionId, Language $language): array;
/**
* Searches and replaces one or multiple strings
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function replaceStrings(
VersionId $versionId,
Language $language,
array $map
): void {
$fields = $this->read($versionId, $language);
$fields = A::map(
$fields,
function ($value) use ($map) {
// skip fields with null values
if ($value === null) {
return null;
}
return str_replace(
array_keys($map),
array_values($map),
$value
);
}
);
$this->update($versionId, $language, $fields);
}
/**
* Updates the modification timestamp of an existing version
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
abstract public function touch(VersionId $versionId, Language $language): void;
/**
* Touches all versions of a language
* @unstable
* @todo Move to `Language` class
*/
public function touchLanguage(Language $language): void
{
foreach ($this->model->versions() as $version) {
if ($this->exists($version->id(), $language) === true) {
$this->touch($version->id(), $language);
}
}
}
/**
* Updates the content fields of an existing version
*
* @param array<string, string> $fields Content fields
*
* @throws \Kirby\Exception\Exception If the file cannot be written
*/
public function update(VersionId $versionId, Language $language, array $fields): void
{
$this->write($versionId, $language, $fields);
}
/**
* Writes the content fields of an existing version
*
* @param array<string, string> $fields Content fields
*
* @throws \Kirby\Exception\Exception If the content cannot be written
*/
abstract protected function write(VersionId $versionId, Language $language, array $fields): void;
}