Initial commit

This commit is contained in:
isUnknown 2024-07-10 16:10:33 +02:00
commit 08a8a71c55
631 changed files with 139902 additions and 0 deletions

View file

@ -0,0 +1,248 @@
<?php
namespace Kirby\Content;
use Kirby\Cms\Blueprint;
use Kirby\Cms\ModelWithContent;
use Kirby\Form\Form;
/**
* The Content class handles all fields
* for content from pages, the site and users
*
* @package Kirby Content
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class Content
{
/**
* The raw data array
*/
protected array $data = [];
/**
* Cached field objects
* Once a field is being fetched
* it is added to this array for
* later reuse
*/
protected array $fields = [];
/**
* A potential parent object.
* Not necessarily needed. Especially
* for testing, but field methods might
* need it.
*/
protected ModelWithContent|null $parent;
/**
* Magic getter for content fields
*/
public function __call(string $name, array $arguments = []): Field
{
return $this->get($name);
}
/**
* Creates a new Content object
*
* @param bool $normalize Set to `false` if the input field keys are already lowercase
*/
public function __construct(
array $data = [],
ModelWithContent $parent = null,
bool $normalize = true
) {
if ($normalize === true) {
$data = array_change_key_case($data, CASE_LOWER);
}
$this->data = $data;
$this->parent = $parent;
}
/**
* Same as `self::data()` to improve
* `var_dump` output
* @codeCoverageIgnore
*
* @see self::data()
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Converts the content to a new blueprint
*/
public function convertTo(string $to): array
{
// prepare data
$data = [];
$content = $this;
// blueprints
$old = $this->parent->blueprint();
$subfolder = dirname($old->name());
$new = Blueprint::factory(
$subfolder . '/' . $to,
$subfolder . '/default',
$this->parent
);
// forms
$oldForm = new Form([
'fields' => $old->fields(),
'model' => $this->parent
]);
$newForm = new Form([
'fields' => $new->fields(),
'model' => $this->parent
]);
// fields
$oldFields = $oldForm->fields();
$newFields = $newForm->fields();
// go through all fields of new template
foreach ($newFields as $newField) {
$name = $newField->name();
$oldField = $oldFields->get($name);
// field name and type matches with old template
if ($oldField?->type() === $newField->type()) {
$data[$name] = $content->get($name)->value();
} else {
$data[$name] = $newField->default();
}
}
// preserve existing fields
return array_merge($this->data, $data);
}
/**
* Returns the raw data array
*/
public function data(): array
{
return $this->data;
}
/**
* Returns all registered field objects
*/
public function fields(): array
{
foreach ($this->data as $key => $value) {
$this->get($key);
}
return $this->fields;
}
/**
* Returns either a single field object
* or all registered fields
*/
public function get(string $key = null): Field|array
{
if ($key === null) {
return $this->fields();
}
$key = strtolower($key);
return $this->fields[$key] ??= new Field(
$this->parent,
$key,
$this->data()[$key] ?? null
);
}
/**
* Checks if a content field is set
*/
public function has(string $key): bool
{
return isset($this->data[strtolower($key)]) === true;
}
/**
* Returns all field keys
*/
public function keys(): array
{
return array_keys($this->data());
}
/**
* Returns a clone of the content object
* without the fields, specified by the
* passed key(s)
*/
public function not(string ...$keys): static
{
$copy = clone $this;
$copy->fields = [];
foreach ($keys as $key) {
unset($copy->data[strtolower($key)]);
}
return $copy;
}
/**
* Returns the parent
* Site, Page, File or User object
*/
public function parent(): ModelWithContent|null
{
return $this->parent;
}
/**
* Set the parent model
*
* @return $this
*/
public function setParent(ModelWithContent $parent): static
{
$this->parent = $parent;
return $this;
}
/**
* Returns the raw data array
*
* @see self::data()
*/
public function toArray(): array
{
return $this->data();
}
/**
* Updates the content and returns
* a cloned object
*
* @return $this
*/
public function update(
array $content = null,
bool $overwrite = false
): static {
$content = array_change_key_case((array)$content, CASE_LOWER);
$this->data = $overwrite === true ? $content : array_merge($this->data, $content);
// clear cache of Field objects
$this->fields = [];
return $this;
}
}

View file

@ -0,0 +1,314 @@
<?php
namespace Kirby\Content;
use Generator;
use Kirby\Cms\ModelWithContent;
use Kirby\Cms\Page;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
/**
* Wrapper for the ContentStorageHandler to
* bundle some business logic that should not
* be included in the handlers themselves but
* also not in the general code calling the storage
* methods
*
* @internal
* @since 4.0.0
*
* @package Kirby Content
* @author Lukas Bestle <lukas@getkirby.com>
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class ContentStorage
{
protected ContentStorageHandler $handler;
public function __construct(
protected ModelWithContent $model,
string $handler = PlainTextContentStorageHandler::class
) {
$this->handler = new $handler($model);
}
/**
* Magic caller for handler methods
*/
public function __call(string $name, array $args): mixed
{
return $this->handler->$name(...$args);
}
/**
* Returns generator for all existing versions-languages combinations
*
* @return Generator<string|string>
* @todo 4.0.0 consider more descpritive name
*/
public function all(): Generator
{
foreach ($this->model->kirby()->languages()->codes() as $lang) {
foreach ($this->dynamicVersions() as $version) {
if ($this->exists($version, $lang) === true) {
yield $version => $lang;
}
}
}
}
/**
* Returns the absolute path to the content file
* @internal eventually should only exists in PlainTextContentStorage,
* when not relying anymore on language helper
*
* @param string $lang Code `'default'` in a single-lang installation
*
* @throws \Kirby\Exception\LogicException If the model type doesn't have a known content filename
*/
public function contentFile(
string $version,
string $lang,
bool $force = false
): string {
$lang = $this->language($lang, $force);
return $this->handler->contentFile($version, $lang);
}
/**
* Adapts all versions when converting languages
* @internal
*/
public function convertLanguage(string $from, string $to): void
{
$from = $this->language($from, true);
$to = $this->language($to, true);
foreach ($this->dynamicVersions() as $version) {
$this->handler->move($version, $from, $version, $to);
}
}
/**
* Creates a new version
*
* @param string|null $lang Code `'default'` in a single-lang installation
* @param array<string, string> $fields Content fields
*/
public function create(
string $versionType,
string|null $lang,
array $fields
): void {
$lang = $this->language($lang);
$this->handler->create($versionType, $lang, $fields);
}
/**
* Returns the default version identifier for the model
* @internal
*/
public function defaultVersion(): string
{
if (
$this->model instanceof Page === true &&
$this->model->isDraft() === true
) {
return 'changes';
}
return 'published';
}
/**
* Deletes an existing version in an idempotent way if it was already deleted
*
* @param string $lang Code `'default'` in a single-lang installation
*/
public function delete(
string $version,
string|null $lang = null,
bool $force = false
): void {
$lang = $this->language($lang, $force);
$this->handler->delete($version, $lang);
}
/**
* Deletes all versions when deleting a language
* @internal
*/
public function deleteLanguage(string|null $lang): void
{
$lang = $this->language($lang, true);
foreach ($this->dynamicVersions() as $version) {
$this->handler->delete($version, $lang);
}
}
/**
* Returns all versions availalbe for the model that can be updated
* @internal
*/
public function dynamicVersions(): array
{
$versions = ['changes'];
if (
$this->model instanceof Page === false ||
$this->model->isDraft() === false
) {
$versions[] = 'published';
}
return $versions;
}
/**
* Checks if a version exists
*
* @param string|null $lang Code `'default'` in a single-lang installation;
* checks for "any language" if not provided
*/
public function exists(
string $version,
string|null $lang
): bool {
if ($lang !== null) {
$lang = $this->language($lang);
}
return $this->handler->exists($version, $lang);
}
/**
* Returns the modification timestamp of a version
* if it exists
*
* @param string $lang Code `'default'` in a single-lang installation
*/
public function modified(
string $version,
string|null $lang = null
): int|null {
$lang = $this->language($lang);
return $this->handler->modified($version, $lang);
}
/**
* Returns the stored content fields
*
* @param string $lang Code `'default'` in a single-lang installation
* @return array<string, string>
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function read(
string $version,
string|null $lang = null
): array {
$lang = $this->language($lang);
$this->ensureExistingVersion($version, $lang);
return $this->handler->read($version, $lang);
}
/**
* Updates the modification timestamp of an existing version
*
* @param string $lang Code `'default'` in a single-lang installation
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function touch(
string $version,
string|null $lang = null
): void {
$lang = $this->language($lang);
$this->ensureExistingVersion($version, $lang);
$this->handler->touch($version, $lang);
}
/**
* Touches all versions of a language
* @internal
*/
public function touchLanguage(string|null $lang): void
{
$lang = $this->language($lang, true);
foreach ($this->dynamicVersions() as $version) {
if ($this->exists($version, $lang) === true) {
$this->handler->touch($version, $lang);
}
}
}
/**
* Updates the content fields of an existing version
*
* @param string $lang Code `'default'` in a single-lang installation
* @param array<string, string> $fields Content fields
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function update(
string $version,
string|null $lang = null,
array $fields = []
): void {
$lang = $this->language($lang);
$this->ensureExistingVersion($version, $lang);
$this->handler->update($version, $lang, $fields);
}
/**
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
protected function ensureExistingVersion(
string $version,
string $lang
): void {
if ($this->exists($version, $lang) !== true) {
throw new NotFoundException('Version "' . $version . ' (' . $lang . ')" does not already exist');
}
}
/**
* Converts a "user-facing" language code to a "raw" language code to be
* used for storage
*
* @param bool $force If set to `true`, the language code is not validated
* @return string Language code
*/
protected function language(
string|null $languageCode = null,
bool $force = false
): string {
// in force mode, use the provided language code even in single-lang for
// compatibility with the previous behavior in `$model->contentFile()`
if ($force === true) {
return $languageCode ?? 'default';
}
// in multi-lang, …
if ($this->model->kirby()->multilang() === true) {
// look up the actual language object if possible
$language = $this->model->kirby()->language($languageCode);
// validate the language code
if ($language === null) {
throw new InvalidArgumentException('Invalid language: ' . $languageCode);
}
return $language->code();
}
// otherwise use hardcoded "default" code for single lang
return 'default';
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Kirby\Content;
use Kirby\Cms\ModelWithContent;
/**
* Interface 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
* @internal
* @since 4.0.0
*
* @package Kirby Content
* @author Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
interface ContentStorageHandler
{
public function __construct(ModelWithContent $model);
/**
* Creates a new version
*
* @param string $lang Code `'default'` in a single-lang installation
* @param array<string, string> $fields Content fields
*/
public function create(string $versionType, string $lang, array $fields): void;
/**
* Deletes an existing version in an idempotent way if it was already deleted
*
* @param string $lang Code `'default'` in a single-lang installation
*/
public function delete(string $version, string $lang): void;
/**
* Checks if a version exists
*
* @param string|null $lang Code `'default'` in a single-lang installation;
* checks for "any language" if not provided
*/
public function exists(string $version, string|null $lang): bool;
/**
* Returns the modification timestamp of a version if it exists
*
* @param string $lang Code `'default'` in a single-lang installation
*/
public function modified(string $version, string $lang): int|null;
/**
* Moves content from one version-language combination to another
*
* @param string $fromLang Code `'default'` in a single-lang installation
* @param string $toLang Code `'default'` in a single-lang installation
*/
public function move(
string $fromVersion,
string $fromLang,
string $toVersion,
string $toLang
): void;
/**
* Returns the stored content fields
*
* @param string $lang Code `'default'` in a single-lang installation
* @return array<string, string>
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function read(string $version, string $lang): array;
/**
* Updates the modification timestamp of an existing version
*
* @param string $lang Code `'default'` in a single-lang installation
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function touch(string $version, string $lang): void;
/**
* Updates the content fields of an existing version
*
* @param string $lang Code `'default'` in a single-lang installation
* @param array<string, string> $fields Content fields
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function update(string $version, string $lang, array $fields): void;
}

View file

@ -0,0 +1,173 @@
<?php
namespace Kirby\Content;
use Kirby\Cms\ModelWithContent;
/**
* Each page, file or site can have multiple
* translated versions of their content,
* represented by this class
*
* @package Kirby Content
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class ContentTranslation
{
protected string $code;
protected array|null $content;
protected string $contentFile;
protected ModelWithContent $parent;
protected string|null $slug;
/**
* Creates a new translation object
*/
public function __construct(array $props)
{
$this->code = $props['code'];
$this->parent = $props['parent'];
$this->slug = $props['slug'] ?? null;
if ($content = $props['content'] ?? null) {
$this->content = array_change_key_case($content);
} else {
$this->content = null;
}
}
/**
* Improve `var_dump` output
* @codeCoverageIgnore
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Returns the language code of the
* translation
*/
public function code(): string
{
return $this->code;
}
/**
* Returns the translation content
* as plain array
*/
public function content(): array
{
$parent = $this->parent();
$content = $this->content ??= $parent->readContent($this->code());
// merge with the default content
if (
$this->isDefault() === false &&
$defaultLanguage = $parent->kirby()->defaultLanguage()
) {
$content = array_merge(
$parent->translation($defaultLanguage->code())?->content() ?? [],
$content
);
}
return $content;
}
/**
* Absolute path to the translation content file
*/
public function contentFile(): string
{
// temporary compatibility change (TODO: take this from the parent `ModelVersion` object)
$identifier = $this->parent::CLASS_ALIAS === 'page' && $this->parent->isDraft() === true ?
'changes' :
'published';
return $this->contentFile = $this->parent->storage()->contentFile(
$identifier,
$this->code,
true
);
}
/**
* Checks if the translation file exists
*/
public function exists(): bool
{
return
empty($this->content) === false ||
file_exists($this->contentFile()) === true;
}
/**
* Returns the translation code as id
*/
public function id(): string
{
return $this->code();
}
/**
* Checks if the this is the default translation
* of the model
*/
public function isDefault(): bool
{
return $this->code() === $this->parent->kirby()->defaultLanguage()?->code();
}
/**
* Returns the parent page, file or site object
*/
public function parent(): ModelWithContent
{
return $this->parent;
}
/**
* Returns the custom translation slug
*/
public function slug(): string|null
{
return $this->slug ??= ($this->content()['slug'] ?? null);
}
/**
* Merge the old and new data
*
* @return $this
*/
public function update(array $data = null, bool $overwrite = false): static
{
$data = array_change_key_case((array)$data);
$this->content = match ($overwrite) {
true => $data,
default => array_merge($this->content(), $data)
};
return $this;
}
/**
* Converts the most important translation
* props to an array
*/
public function toArray(): array
{
return [
'code' => $this->code(),
'content' => $this->content(),
'exists' => $this->exists(),
'slug' => $this->slug(),
];
}
}

View file

@ -0,0 +1,220 @@
<?php
namespace Kirby\Content;
use Closure;
use Kirby\Cms\ModelWithContent;
/**
* Every field in a Kirby content text file
* is being converted into such a Field object.
*
* Field methods can be registered for those Field
* objects, which can then be used to transform or
* convert the field value. This enables our
* daisy-chaining API for templates and other components
*
* ```php
* // Page field example with lowercase conversion
* $page->myField()->lower();
* ```
*
* @package Kirby Content
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class Field
{
/**
* Field method aliases
*/
public static array $aliases = [];
/**
* The field name
*/
protected string $key;
/**
* Registered field methods
*/
public static array $methods = [];
/**
* The parent object if available.
* This will be the page, site, user or file
* to which the content belongs
*/
protected ModelWithContent|null $parent;
/**
* The value of the field
*/
public mixed $value;
/**
* Creates a new field object
*/
public function __construct(
ModelWithContent|null $parent,
string $key,
mixed $value
) {
$this->key = $key;
$this->value = $value;
$this->parent = $parent;
}
/**
* Magic caller for field methods
*/
public function __call(string $method, array $arguments = []): mixed
{
$method = strtolower($method);
if (isset(static::$methods[$method]) === true) {
return (static::$methods[$method])(clone $this, ...$arguments);
}
if (isset(static::$aliases[$method]) === true) {
$method = strtolower(static::$aliases[$method]);
if (isset(static::$methods[$method]) === true) {
return (static::$methods[$method])(clone $this, ...$arguments);
}
}
return $this;
}
/**
* Simplifies the var_dump result
* @codeCoverageIgnore
*
* @see Field::toArray
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Makes it possible to simply echo
* or stringify the entire object
*
* @see Field::toString
*/
public function __toString(): string
{
return $this->toString();
}
/**
* Checks if the field exists in the content data array
*/
public function exists(): bool
{
return $this->parent->content()->has($this->key);
}
/**
* Checks if the field content is empty
*/
public function isEmpty(): bool
{
return
empty($this->value) === true &&
in_array($this->value, [0, '0', false], true) === false;
}
/**
* Checks if the field content is not empty
*/
public function isNotEmpty(): bool
{
return $this->isEmpty() === false;
}
/**
* Returns the name of the field
*/
public function key(): string
{
return $this->key;
}
/**
* @see Field::parent()
*/
public function model(): ModelWithContent|null
{
return $this->parent;
}
/**
* Provides a fallback if the field value is empty
*
* @return $this|static
*/
public function or(mixed $fallback = null): static
{
if ($this->isNotEmpty()) {
return $this;
}
if ($fallback instanceof self) {
return $fallback;
}
$field = clone $this;
$field->value = $fallback;
return $field;
}
/**
* Returns the parent object of the field
*/
public function parent(): ModelWithContent|null
{
return $this->parent;
}
/**
* Converts the Field object to an array
*/
public function toArray(): array
{
return [$this->key => $this->value];
}
/**
* Returns the field value as string
*/
public function toString(): string
{
return (string)$this->value;
}
/**
* Returns the field content. If a new value is passed,
* the modified field will be returned. Otherwise it
* will return the field value.
*/
public function value(string|Closure $value = null): mixed
{
if ($value === null) {
return $this->value;
}
if ($value instanceof Closure) {
$value = $value->call($this, $this->value);
}
$clone = clone $this;
$clone->value = (string)$value;
return $clone;
}
}

View file

@ -0,0 +1,253 @@
<?php
namespace Kirby\Content;
use Kirby\Cms\ModelWithContent;
use Kirby\Data\Data;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Filesystem\Dir;
use Kirby\Filesystem\F;
/**
* Content storage handler using plain text files
* stored in the content folder
* @internal
* @since 4.0.0
*
* @package Kirby Content
* @author Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class PlainTextContentStorageHandler implements ContentStorageHandler
{
public function __construct(protected ModelWithContent $model)
{
}
/**
* Creates a new version
*
* @param string $lang Code `'default'` in a single-lang installation
* @param array<string, string> $fields Content fields
*/
public function create(string $versionType, string $lang, array $fields): void
{
$success = Data::write($this->contentFile($versionType, $lang), $fields);
// @codeCoverageIgnoreStart
if ($success !== true) {
throw new Exception('Could not write new content file');
}
// @codeCoverageIgnoreEnd
}
/**
* Deletes an existing version in an idempotent way if it was already deleted
*
* @param string $lang Code `'default'` in a single-lang installation
*/
public function delete(string $version, string $lang): void
{
$contentFile = $this->contentFile($version, $lang);
$success = F::unlink($contentFile);
// @codeCoverageIgnoreStart
if ($success !== true) {
throw new Exception('Could not delete content file');
}
// @codeCoverageIgnoreEnd
// clean up empty directories
$contentDir = dirname($contentFile);
if (
Dir::exists($contentDir) === true &&
Dir::isEmpty($contentDir) === true
) {
$success = rmdir($contentDir);
// @codeCoverageIgnoreStart
if ($success !== true) {
throw new Exception('Could not delete empty content directory');
}
// @codeCoverageIgnoreEnd
}
}
/**
* Checks if a version exists
*
* @param string|null $lang Code `'default'` in a single-lang installation;
* checks for "any language" if not provided
*/
public function exists(string $version, string|null $lang): bool
{
if ($lang === null) {
foreach ($this->contentFiles($version) as $file) {
if (is_file($file) === true) {
return true;
}
}
return false;
}
return is_file($this->contentFile($version, $lang)) === true;
}
/**
* Returns the modification timestamp of a version
* if it exists
*
* @param string $lang Code `'default'` in a single-lang installation
*/
public function modified(string $version, string $lang): int|null
{
$modified = F::modified($this->contentFile($version, $lang));
if (is_int($modified) === true) {
return $modified;
}
return null;
}
/**
* Returns the stored content fields
*
* @param string $lang Code `'default'` in a single-lang installation
* @return array<string, string>
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function read(string $version, string $lang): array
{
return Data::read($this->contentFile($version, $lang));
}
/**
* Updates the modification timestamp of an existing version
*
* @param string $lang Code `'default'` in a single-lang installation
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function touch(string $version, string $lang): void
{
$success = touch($this->contentFile($version, $lang));
// @codeCoverageIgnoreStart
if ($success !== true) {
throw new Exception('Could not touch existing content file');
}
// @codeCoverageIgnoreEnd
}
/**
* Updates the content fields of an existing version
*
* @param string $lang Code `'default'` in a single-lang installation
* @param array<string, string> $fields Content fields
*
* @throws \Kirby\Exception\NotFoundException If the version does not exist
*/
public function update(string $version, string $lang, array $fields): void
{
$success = Data::write($this->contentFile($version, $lang), $fields);
// @codeCoverageIgnoreStart
if ($success !== true) {
throw new Exception('Could not write existing content file');
}
// @codeCoverageIgnoreEnd
}
/**
* Returns the absolute path to the content file
* @internal To be made `protected` when the CMS core no longer relies on it
*
* @param string $lang Code `'default'` in a single-lang installation
*
* @throws \Kirby\Exception\LogicException If the model type doesn't have a known content filename
*/
public function contentFile(string $version, string $lang): string
{
if (in_array($version, ['published', 'changes']) !== true) {
throw new InvalidArgumentException('Invalid version identifier "' . $version . '"');
}
$extension = $this->model->kirby()->contentExtension();
$directory = $this->model->root();
$directory = match ($this->model::CLASS_ALIAS) {
'file' => dirname($this->model->root()),
default => $this->model->root()
};
$filename = match ($this->model::CLASS_ALIAS) {
'file' => $this->model->filename(),
'page' => $this->model->intendedTemplate()->name(),
'site',
'user' => $this->model::CLASS_ALIAS,
// @codeCoverageIgnoreStart
default => throw new LogicException('Cannot determine content filename for model type "' . $this->model::CLASS_ALIAS . '"')
// @codeCoverageIgnoreEnd
};
if ($this->model::CLASS_ALIAS === 'page' && $this->model->isDraft() === true) {
// changes versions don't need anything extra
// (drafts already have the `_drafts` prefix in their root),
// but a published version is not possible
if ($version === 'published') {
throw new LogicException('Drafts cannot have a published content file');
}
} elseif ($version === 'changes') {
// other model type or published page that has a changes subfolder
$directory .= '/_changes';
}
if ($lang !== 'default') {
return $directory . '/' . $filename . '.' . $lang . '.' . $extension;
}
return $directory . '/' . $filename . '.' . $extension;
}
/**
* Returns an array with content files of all languages
* @internal To be made `protected` when the CMS core no longer relies on it
*/
public function contentFiles(string $version): array
{
if ($this->model->kirby()->multilang() === true) {
return $this->model->kirby()->languages()->values(
fn ($lang) => $this->contentFile($version, $lang)
);
}
return [
$this->contentFile($version, 'default')
];
}
/**
* Moves content from one version-language combination to another
*
* @param string $fromLang Code `'default'` in a single-lang installation
* @param string $toLang Code `'default'` in a single-lang installation
*/
public function move(
string $fromVersion,
string $fromLang,
string $toVersion,
string $toLang
): void {
F::move(
$this->contentFile($fromVersion, $fromLang),
$this->contentFile($toVersion, $toLang)
);
}
}