325 lines
7.6 KiB
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;
|
|
}
|