From 4c7154eb71c28983991afcbee887d26b95708410 Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Thu, 16 May 2024 15:47:21 +0200 Subject: [PATCH 01/10] Require language object in the ContentStorageHandler interface --- src/Content/ContentStorageHandler.php | 33 ++++++++------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/Content/ContentStorageHandler.php b/src/Content/ContentStorageHandler.php index 43498add43..8b4de4d567 100644 --- a/src/Content/ContentStorageHandler.php +++ b/src/Content/ContentStorageHandler.php @@ -2,6 +2,7 @@ namespace Kirby\Content; +use Kirby\Cms\Language; use Kirby\Cms\ModelWithContent; /** @@ -25,71 +26,57 @@ public function __construct(ModelWithContent $model); /** * Creates a new version * - * @param string $lang Code `'default'` in a single-lang installation * @param array $fields Content fields */ - public function create(VersionId $versionId, string $lang, array $fields): void; + public function create(VersionId $versionId, Language $language, 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(VersionId $versionId, string $lang): void; + public function delete(VersionId $versionId, Language $language): void; /** * Checks if a version exists - * - * @param string $lang Code `'default'` in a single-lang installation */ - public function exists(VersionId $versionId, string $lang): bool; + public function exists(VersionId $versionId, Language $language): bool; /** * Returns the modification timestamp of a version if it exists - * - * @param string $lang Code `'default'` in a single-lang installation */ - public function modified(VersionId $versionId, string $lang): int|null; + public function modified(VersionId $versionId, Language $language): 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( VersionId $fromVersionId, - string $fromLang, + Language $fromLanguage, VersionId $toVersionId, - string $toLang + Language $toLanguage ): void; /** * Returns the stored content fields * - * @param string $lang Code `'default'` in a single-lang installation * @return array * * @throws \Kirby\Exception\NotFoundException If the version does not exist */ - public function read(VersionId $versionId, string $lang): array; + public function read(VersionId $versionId, Language $language): 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(VersionId $versionId, string $lang): void; + public function touch(VersionId $versionId, Language $language): void; /** * Updates the content fields of an existing version * - * @param string $lang Code `'default'` in a single-lang installation * @param array $fields Content fields * * @throws \Kirby\Exception\NotFoundException If the version does not exist */ - public function update(VersionId $versionId, string $lang, array $fields): void; + public function update(VersionId $versionId, Language $language, array $fields): void; } From 3b0c3f5b97688d4f6eb7653a84971f6729523cdb Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Fri, 17 May 2024 13:13:29 +0200 Subject: [PATCH 02/10] Add new Language::single() method to provide a single placeholder language object --- src/Cms/Language.php | 13 +++++++++++++ tests/Cms/Languages/LanguageTest.php | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Cms/Language.php b/src/Cms/Language.php index e91026d7d7..16cf007c64 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -446,6 +446,19 @@ protected function siblingsCollection(): Languages return App::instance()->languages(); } + /** + * Create a placeholder language object in a + * single-language installation + */ + public static function single(): static + { + return new static([ + 'code' => 'en', + 'default' => true, + 'locale' => App::instance()->option('locale', 'en_US.utf-8') + ]); + } + /** * Returns the custom slug rules for this language */ diff --git a/tests/Cms/Languages/LanguageTest.php b/tests/Cms/Languages/LanguageTest.php index 75d800c110..db17ed829d 100644 --- a/tests/Cms/Languages/LanguageTest.php +++ b/tests/Cms/Languages/LanguageTest.php @@ -565,6 +565,17 @@ public function testSave() $this->assertSame('test', $data['custom']); } + /** + * @covers ::single + */ + public function testSingle() + { + $language = Language::single(); + + $this->assertSame('en', $language->code()); + $this->assertSame('en', $language->name()); + } + /** * @covers ::toArray * @covers ::__debugInfo From cd3dc5d3cc6137a3b61fed037c11b1436ad047d2 Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Fri, 17 May 2024 13:14:43 +0200 Subject: [PATCH 03/10] Always use a full Language object in the content storage handlers --- src/Content/ContentStorage.php | 44 +- src/Content/ContentTranslation.php | 3 +- .../PlainTextContentStorageHandler.php | 103 ++-- tests/Content/ContentStorageTest.php | 38 +- tests/Content/ContentTranslationTest.php | 11 +- .../PlainTextContentStorageHandlerTest.php | 458 +++++++++++------- 6 files changed, 380 insertions(+), 277 deletions(-) diff --git a/src/Content/ContentStorage.php b/src/Content/ContentStorage.php index b202cde56b..4ac5355479 100644 --- a/src/Content/ContentStorage.php +++ b/src/Content/ContentStorage.php @@ -3,6 +3,7 @@ namespace Kirby\Content; use Generator; +use Kirby\Cms\Language; use Kirby\Cms\ModelWithContent; use Kirby\Cms\Page; use Kirby\Exception\InvalidArgumentException; @@ -73,9 +74,8 @@ public function all(): Generator public function contentFile( VersionId $versionId, string $lang, - bool $force = false ): string { - $lang = $this->language($lang, $force); + $lang = $this->language($lang); return $this->handler->contentFile($versionId, $lang); } @@ -116,9 +116,8 @@ public function create( public function delete( VersionId $versionId, string|null $lang = null, - bool $force = false ): void { - $lang = $this->language($lang, $force); + $lang = $this->language($lang); $this->handler->delete($versionId, $lang); } @@ -253,7 +252,13 @@ protected function ensureExistingVersion( string $lang ): void { if ($this->exists($versionId, $lang) !== true) { - throw new NotFoundException('Version "' . $versionId . ' (' . $lang . ')" does not already exist'); + + $message = match($this->model->kirby()->multilang()) { + true => 'Version "' . $versionId . ' (' . $lang . ')" does not already exist', + false => 'Version "' . $versionId . '" does not already exist', + }; + + throw new NotFoundException($message); } } @@ -262,32 +267,21 @@ protected function ensureExistingVersion( * 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'; + ): Language { + // single language + if ($this->model->kirby()->multilang() === false) { + return Language::single(); } - // 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(); + // look up the actual language object if possible + if ($language = $this->model->kirby()->language($languageCode)) { + return $language; } - // otherwise use hardcoded "default" code for single lang - return 'default'; + // validate the language code + throw new InvalidArgumentException('Invalid language: ' . $languageCode); } } diff --git a/src/Content/ContentTranslation.php b/src/Content/ContentTranslation.php index 49475e17f9..ee3eed2a76 100644 --- a/src/Content/ContentTranslation.php +++ b/src/Content/ContentTranslation.php @@ -87,8 +87,7 @@ public function contentFile(): string { return $this->contentFile = $this->parent->storage()->contentFile( VersionId::default($this->parent), - $this->code, - true + $this->code ); } diff --git a/src/Content/PlainTextContentStorageHandler.php b/src/Content/PlainTextContentStorageHandler.php index 432cc9c030..0edc60b8fa 100644 --- a/src/Content/PlainTextContentStorageHandler.php +++ b/src/Content/PlainTextContentStorageHandler.php @@ -3,6 +3,7 @@ namespace Kirby\Content; use Kirby\Cms\File; +use Kirby\Cms\Language; use Kirby\Cms\ModelWithContent; use Kirby\Cms\Page; use Kirby\Cms\Site; @@ -53,17 +54,15 @@ protected function contentDirectory(VersionId $versionId): string /** * 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 */ - public function contentFile(VersionId $versionId, string $lang): string + public function contentFile(VersionId $versionId, Language $language): string { // get the filename without extension and language code return match (true) { - $this->model instanceof File => $this->contentFileForFile($this->model, $versionId, $lang), - $this->model instanceof Page => $this->contentFileForPage($this->model, $versionId, $lang), - $this->model instanceof Site => $this->contentFileForSite($this->model, $versionId, $lang), - $this->model instanceof User => $this->contentFileForUser($this->model, $versionId, $lang), + $this->model instanceof File => $this->contentFileForFile($this->model, $versionId, $language), + $this->model instanceof Page => $this->contentFileForPage($this->model, $versionId, $language), + $this->model instanceof Site => $this->contentFileForSite($this->model, $versionId, $language), + $this->model instanceof User => $this->contentFileForUser($this->model, $versionId, $language), // @codeCoverageIgnoreStart default => throw new LogicException('Cannot determine content file for model type "' . $this->model::CLASS_ALIAS . '"') // @codeCoverageIgnoreEnd @@ -72,20 +71,16 @@ public function contentFile(VersionId $versionId, string $lang): string /** * Returns the absolute path to the content file of a file model - * - * @param string $lang Code `'default'` in a single-lang installation */ - protected function contentFileForFile(File $model, VersionId $versionId, string $lang): string + protected function contentFileForFile(File $model, VersionId $versionId, Language $language): string { - return $this->contentDirectory($versionId) . '/' . $this->contentFilename($model->filename(), $lang); + return $this->contentDirectory($versionId) . '/' . $this->contentFilename($model->filename(), $language); } /** * Returns the absolute path to the content file of a page model - * - * @param string $lang Code `'default'` in a single-lang installation */ - protected function contentFileForPage(Page $model, VersionId $versionId, string $lang): string + protected function contentFileForPage(Page $model, VersionId $versionId, Language $language): string { $directory = $this->contentDirectory($versionId); @@ -99,42 +94,36 @@ protected function contentFileForPage(Page $model, VersionId $versionId, string $directory = $this->model->root(); } - return $directory . '/' . $this->contentFilename($model->intendedTemplate()->name(), $lang); + return $directory . '/' . $this->contentFilename($model->intendedTemplate()->name(), $language); } /** * Returns the absolute path to the content file of a site model - * - * @param string $lang Code `'default'` in a single-lang installation */ - protected function contentFileForSite(Site $model, VersionId $versionId, string $lang): string + protected function contentFileForSite(Site $model, VersionId $versionId, Language $language): string { - return $this->contentDirectory($versionId) . '/' . $this->contentFilename('site', $lang); + return $this->contentDirectory($versionId) . '/' . $this->contentFilename('site', $language); } /** * Returns the absolute path to the content file of a user model - * - * @param string $lang Code `'default'` in a single-lang installation */ - protected function contentFileForUser(User $model, VersionId $versionId, string $lang): string + protected function contentFileForUser(User $model, VersionId $versionId, Language $language): string { - return $this->contentDirectory($versionId) . '/' . $this->contentFilename('user', $lang); + return $this->contentDirectory($versionId) . '/' . $this->contentFilename('user', $language); } /** * Creates a filename with extension and optional language code * in a multi-language installation - * - * @param string $lang Code `'default'` in a single-lang installation */ - protected function contentFilename(string $name, string $lang): string + public function contentFilename(string $name, Language $language): string { $kirby = $this->model->kirby(); $extension = $kirby->contentExtension(); - if ($lang !== 'default') { - return $name . '.' . $lang . '.' . $extension; + if ($kirby->multilang() === true) { + return $name . '.' . $language->code() . '.' . $extension; } return $name . '.' . $extension; @@ -148,36 +137,33 @@ public function contentFiles(VersionId $versionId): array { if ($this->model->kirby()->multilang() === true) { return $this->model->kirby()->languages()->values( - fn ($lang) => $this->contentFile($versionId, $lang) + fn ($language) => $this->contentFile($versionId, $language) ); } return [ - $this->contentFile($versionId, 'default') + $this->contentFile($versionId, Language::single()) ]; } /** * Creates a new version * - * @param string $lang Code `'default'` in a single-lang installation * @param array $fields Content fields * * @throws \Kirby\Exception\Exception If the file cannot be written */ - public function create(VersionId $versionId, string $lang, array $fields): void + public function create(VersionId $versionId, Language $language, array $fields): void { - $this->write($versionId, $lang, $fields); + $this->write($versionId, $language, $fields); } /** * 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(VersionId $versionId, string $lang): void + public function delete(VersionId $versionId, Language $language): void { - $contentFile = $this->contentFile($versionId, $lang); + $contentFile = $this->contentFile($versionId, $language); $success = F::unlink($contentFile); // @codeCoverageIgnoreStart @@ -204,23 +190,19 @@ public function delete(VersionId $versionId, string $lang): void /** * Checks if a version exists - * - * @param string $lang Code `'default'` in a single-lang installation */ - public function exists(VersionId $versionId, string $lang): bool + public function exists(VersionId $versionId, Language $language): bool { - return is_file($this->contentFile($versionId, $lang)) === true; + return is_file($this->contentFile($versionId, $language)) === true; } /** * Returns the modification timestamp of a version * if it exists - * - * @param string $lang Code `'default'` in a single-lang installation */ - public function modified(VersionId $versionId, string $lang): int|null + public function modified(VersionId $versionId, Language $language): int|null { - $modified = F::modified($this->contentFile($versionId, $lang)); + $modified = F::modified($this->contentFile($versionId, $language)); if (is_int($modified) === true) { return $modified; @@ -231,43 +213,37 @@ public function modified(VersionId $versionId, 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( VersionId $fromVersionId, - string $fromLang, + Language $fromLanguage, VersionId $toVersionId, - string $toLang + Language $toLanguage ): void { F::move( - $this->contentFile($fromVersionId, $fromLang), - $this->contentFile($toVersionId, $toLang) + $this->contentFile($fromVersionId, $fromLanguage), + $this->contentFile($toVersionId, $toLanguage) ); } /** * Returns the stored content fields * - * @param string $lang Code `'default'` in a single-lang installation * @return array */ - public function read(VersionId $versionId, string $lang): array + public function read(VersionId $versionId, Language $language): array { - return Data::read($this->contentFile($versionId, $lang)); + return Data::read($this->contentFile($versionId, $language)); } /** * Updates the modification timestamp of an existing version * - * @param string $lang Code `'default'` in a single-lang installation - * * @throws \Kirby\Exception\Exception If the file cannot be touched */ - public function touch(VersionId $versionId, string $lang): void + public function touch(VersionId $versionId, Language $language): void { - $success = touch($this->contentFile($versionId, $lang)); + $success = touch($this->contentFile($versionId, $language)); // @codeCoverageIgnoreStart if ($success !== true) { @@ -279,14 +255,13 @@ public function touch(VersionId $versionId, string $lang): void /** * Updates the content fields of an existing version * - * @param string $lang Code `'default'` in a single-lang installation * @param array $fields Content fields * * @throws \Kirby\Exception\Exception If the file cannot be written */ - public function update(VersionId $versionId, string $lang, array $fields): void + public function update(VersionId $versionId, Language $language, array $fields): void { - $this->write($versionId, $lang, $fields); + $this->write($versionId, $language, $fields); } /** @@ -297,9 +272,9 @@ public function update(VersionId $versionId, string $lang, array $fields): void * * @throws \Kirby\Exception\Exception If the content cannot be written */ - protected function write(VersionId $versionId, string $lang, array $fields): void + protected function write(VersionId $versionId, Language $language, array $fields): void { - $success = Data::write($this->contentFile($versionId, $lang), $fields); + $success = Data::write($this->contentFile($versionId, $language), $fields); // @codeCoverageIgnoreStart if ($success !== true) { diff --git a/tests/Content/ContentStorageTest.php b/tests/Content/ContentStorageTest.php index 086c0a3ec0..c6a1b9e2af 100644 --- a/tests/Content/ContentStorageTest.php +++ b/tests/Content/ContentStorageTest.php @@ -77,9 +77,9 @@ public function testCreateChangesSingleLang() public function testReadDoesNotExist() { $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('Version "published (default)" does not already exist'); + $this->expectExceptionMessage('Version "published" does not already exist'); - $this->storage->read(VersionId::published(), 'default'); + $this->storage->read(VersionId::published()); } /** @@ -89,9 +89,9 @@ public function testReadDoesNotExist() public function testTouchDoesNotExist() { $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('Version "published (default)" does not already exist'); + $this->expectExceptionMessage('Version "published" does not already exist'); - $this->storage->touch(VersionId::published(), 'default'); + $this->storage->touch(VersionId::published()); } /** @@ -101,7 +101,7 @@ public function testTouchDoesNotExist() public function testUpdateDoesNotExist() { $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('Version "published (default)" does not already exist'); + $this->expectExceptionMessage('Version "published" does not already exist'); $fields = [ 'title' => 'Foo', @@ -113,9 +113,8 @@ public function testUpdateDoesNotExist() /** * @covers ::language - * @dataProvider languageProvider */ - public function testLanguageMultiLang(string|null $languageCode, bool $force, array $expectedCodes) + public function testLanguageMultiLang() { $app = new App([ 'languages' => [ @@ -129,8 +128,17 @@ public function testLanguageMultiLang(string|null $languageCode, bool $force, ar ] ]); - $language = $this->language($this->storage, $languageCode, $force); - $this->assertSame($expectedCodes[0], $language); + $language = $this->language($this->storage, 'en'); + $this->assertSame('en', $language->code()); + + $language = $this->language($this->storage, 'de'); + $this->assertSame('de', $language->code()); + + $language = $this->language($this->storage, 'default'); + $this->assertSame('en', $language->code()); + + $language = $this->language($this->storage); + $this->assertSame('en', $language->code()); } /** @@ -158,12 +166,14 @@ public function testLanguageMultiLangInvalid() /** * @covers ::language - * @dataProvider languageProvider */ - public function testLanguageSingleLang(string|null $languageCode, bool $force, array $expectedCodes) + public function testLanguageSingleLang() { - $language = $this->language($this->storage, $languageCode, $force); - $this->assertSame($expectedCodes[1], $language); + $language = $this->language($this->storage, 'en'); + $this->assertSame('en', $language->code()); + + $language = $this->language($this->storage, 'de'); + $this->assertSame('en', $language->code()); } /** @@ -172,7 +182,7 @@ public function testLanguageSingleLang(string|null $languageCode, bool $force, a public function testLanguageSingleLangInvalid() { $language = $this->language($this->storage, 'fr', false); - $this->assertSame('default', $language); + $this->assertSame('en', $language->code()); } public static function languageProvider(): array diff --git a/tests/Content/ContentTranslationTest.php b/tests/Content/ContentTranslationTest.php index 9419347450..6e0dbc8af3 100644 --- a/tests/Content/ContentTranslationTest.php +++ b/tests/Content/ContentTranslationTest.php @@ -109,9 +109,18 @@ public function testContentMerged() public function testContentFile() { $app = new App([ + 'languages' => [ + [ + 'code' => 'en', + 'default' => true + ], + [ + 'code' => 'de' + ] + ], 'roots' => [ 'content' => '/content', - ] + ], ]); $page = new Page([ diff --git a/tests/Content/PlainTextContentStorageHandlerTest.php b/tests/Content/PlainTextContentStorageHandlerTest.php index a2b52747a9..3fc4aff8c7 100644 --- a/tests/Content/PlainTextContentStorageHandlerTest.php +++ b/tests/Content/PlainTextContentStorageHandlerTest.php @@ -4,6 +4,7 @@ use Kirby\Cms\App; use Kirby\Cms\File; +use Kirby\Cms\Language; use Kirby\Cms\Page; use Kirby\Cms\User; use Kirby\Data\Data; @@ -19,21 +20,66 @@ class PlainTextContentStorageHandlerTest extends TestCase { public const TMP = KIRBY_TMP_DIR . '/Content.PlainTextContentStorage'; + protected $app; protected $model; protected $storage; public function setUp(): void { Dir::make(static::TMP); + } - $this->model = new Page([ - 'kirby' => new App(), - 'root' => static::TMP, - 'slug' => 'a-page', - 'template' => 'article' + public function setUpMultiLanguage(): void + { + $this->app = new App([ + 'languages' => [ + [ + 'code' => 'en', + 'default' => true + ], + [ + 'code' => 'de' + ] + ], + 'roots' => [ + 'index' => static::TMP + ], + 'site' => [ + 'children' => [ + [ + 'slug' => 'a-page', + 'template' => 'article', + ] + ] + ] ]); + $this->model = $this->app->page('a-page'); $this->storage = new PlainTextContentStorageHandler($this->model); + + Dir::make($this->model->root()); + } + + public function setUpSingleLanguage(): void + { + $this->app = new App([ + 'roots' => [ + 'index' => static::TMP + ], + 'site' => [ + 'children' => [ + [ + 'slug' => 'a-page', + 'template' => 'article' + ] + ] + ] + ]); + + $this->model = $this->app->page('a-page'); + $this->storage = new PlainTextContentStorageHandler($this->model); + + Dir::make($this->model->root()); } public function tearDown(): void @@ -47,13 +93,15 @@ public function tearDown(): void */ public function testCreateChangesMultiLang() { + $this->setUpMultiLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - $this->storage->create(VersionId::changes(), 'en', $fields); - $this->assertSame($fields, Data::read(static::TMP . '/_changes/article.en.txt')); + $this->storage->create(VersionId::changes(), $this->app->language('en'), $fields); + $this->assertSame($fields, Data::read($this->model->root() . '/_changes/article.en.txt')); } /** @@ -61,13 +109,15 @@ public function testCreateChangesMultiLang() */ public function testCreateChangesSingleLang() { + $this->setUpSingleLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - $this->storage->create(VersionId::changes(), 'default', $fields); - $this->assertSame($fields, Data::read(static::TMP . '/_changes/article.txt')); + $this->storage->create(VersionId::changes(), Language::single(), $fields); + $this->assertSame($fields, Data::read($this->model->root() . '/_changes/article.txt')); } /** @@ -75,13 +125,15 @@ public function testCreateChangesSingleLang() */ public function testCreatePublishedMultiLang() { + $this->setUpMultiLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - $this->storage->create(VersionId::published(), 'en', $fields); - $this->assertSame($fields, Data::read(static::TMP . '/article.en.txt')); + $this->storage->create(VersionId::published(), $this->app->language('en'), $fields); + $this->assertSame($fields, Data::read($this->model->root() . '/article.en.txt')); } /** @@ -89,13 +141,15 @@ public function testCreatePublishedMultiLang() */ public function testCreatePublishedSingleLang() { + $this->setUpSingleLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - $this->storage->create(VersionId::published(), 'default', $fields); - $this->assertSame($fields, Data::read(static::TMP . '/article.txt')); + $this->storage->create(VersionId::published(), Language::single(), $fields); + $this->assertSame($fields, Data::read($this->model->root() . '/article.txt')); } /** @@ -103,9 +157,11 @@ public function testCreatePublishedSingleLang() */ public function testDeleteNonExisting() { + $this->setUpSingleLanguage(); + // test idempotency - $this->storage->delete(VersionId::published(), 'default'); - $this->assertDirectoryDoesNotExist(static::TMP); + $this->storage->delete(VersionId::published(), Language::single()); + $this->assertDirectoryDoesNotExist($this->model->root()); } /** @@ -113,14 +169,16 @@ public function testDeleteNonExisting() */ public function testDeleteChangesMultiLang() { - Dir::make(static::TMP . '/_changes'); - touch(static::TMP . '/article.en.txt'); - touch(static::TMP . '/_changes/article.en.txt'); + $this->setUpMultiLanguage(); + + Dir::make($this->model->root() . '/_changes'); + touch($this->model->root() . '/article.en.txt'); + touch($this->model->root() . '/_changes/article.en.txt'); - $this->storage->delete(VersionId::changes(), 'en'); - $this->assertFileDoesNotExist(static::TMP . '/_changes/article.en.txt'); - $this->assertDirectoryDoesNotExist(static::TMP . '/_changes'); - $this->assertDirectoryExists(static::TMP); + $this->storage->delete(VersionId::changes(), $this->app->language('en')); + $this->assertFileDoesNotExist($this->model->root() . '/_changes/article.en.txt'); + $this->assertDirectoryDoesNotExist($this->model->root() . '/_changes'); + $this->assertDirectoryExists($this->model->root()); } /** @@ -128,13 +186,15 @@ public function testDeleteChangesMultiLang() */ public function testDeleteChangesSingleLang() { - Dir::make(static::TMP . '/_changes'); - touch(static::TMP . '/article.txt'); - touch(static::TMP . '/_changes/article.txt'); + $this->setUpSingleLanguage(); + + Dir::make($this->model->root() . '/_changes'); + touch($this->model->root() . '/article.txt'); + touch($this->model->root() . '/_changes/article.txt'); - $this->storage->delete(VersionId::changes(), 'default'); - $this->assertFileDoesNotExist(static::TMP . '/_changes/article.txt'); - $this->assertDirectoryDoesNotExist(static::TMP . '/_changes'); + $this->storage->delete(VersionId::changes(), Language::single()); + $this->assertFileDoesNotExist($this->model->root() . '/_changes/article.txt'); + $this->assertDirectoryDoesNotExist($this->model->root() . '/_changes'); } /** @@ -142,13 +202,15 @@ public function testDeleteChangesSingleLang() */ public function testDeletePublishedMultiLang() { - Dir::make(static::TMP . '/_changes'); - touch(static::TMP . '/article.en.txt'); - touch(static::TMP . '/_changes/article.en.txt'); + $this->setUpMultiLanguage(); + + Dir::make($this->model->root() . '/_changes'); + touch($this->model->root() . '/_changes/article.en.txt'); + touch($this->model->root() . '/article.en.txt'); - $this->storage->delete(VersionId::published(), 'en'); - $this->assertFileDoesNotExist(static::TMP . '/article.en.txt'); - $this->assertDirectoryExists(static::TMP); + $this->storage->delete(VersionId::published(), $this->app->language('en')); + $this->assertFileDoesNotExist($this->model->root() . '/article.en.txt'); + $this->assertDirectoryExists($this->model->root()); } /** @@ -156,64 +218,86 @@ public function testDeletePublishedMultiLang() */ public function testDeletePublishedSingleLang() { - Dir::make(static::TMP . '/_changes'); - touch(static::TMP . '/article.txt'); - touch(static::TMP . '/_changes/article.txt'); + $this->setUpSingleLanguage(); - $this->storage->delete(VersionId::published(), 'default'); - $this->assertFileDoesNotExist(static::TMP . '/article.txt'); - $this->assertDirectoryExists(static::TMP); + Dir::make($this->model->root() . '/_changes'); + touch($this->model->root() . '/_changes/article.txt'); + touch($this->model->root() . '/article.txt'); + + $this->storage->delete(VersionId::published(), Language::single()); + $this->assertFileDoesNotExist($this->model->root() . '/article.txt'); + $this->assertDirectoryExists($this->model->root()); } /** * @covers ::exists - * @dataProvider existsProvider */ - public function testExistsNoneExisting(VersionId $id, string $language) + public function testExistsNoneExistingMultiLanguage() { - $this->assertFalse($this->storage->exists($id, $language)); + $this->setUpMultiLanguage(); + + $this->assertFalse($this->storage->exists(VersionId::changes(), $this->app->language('en'))); + $this->assertFalse($this->storage->exists(VersionId::changes(), $this->app->language('de'))); } - public static function existsProvider(): array + /** + * @covers ::exists + */ + public function testExistsNoneExistingSingleLanguage() { - return [ - [VersionId::changes(), 'default', [false, false]], - [VersionId::changes(), 'en', [true, true]], - [VersionId::published(), 'default', [true, true]], - [VersionId::published(), 'en', [false, false]] - ]; + $this->setUpSingleLanguage(); + + $this->assertFalse($this->storage->exists(VersionId::changes(), Language::single())); } /** * @covers ::modified - * @dataProvider modifiedProvider */ - public function testModifiedNoneExisting(VersionId $id, string $language) + public function testModifiedNoneExistingMultiLanguage() { - $this->assertNull($this->storage->modified($id, $language)); + $this->setUpMultiLanguage(); + + $this->assertNull($this->storage->modified(VersionId::changes(), $this->app->language('en'))); + $this->assertNull($this->storage->modified(VersionId::published(), $this->app->language('en'))); } /** * @covers ::modified - * @dataProvider modifiedProvider */ - public function testModifiedSomeExisting(VersionId $id, string $language, int|null $expected) + public function testModifiedNoneExistingSingleLanguage() { - Dir::make(static::TMP . '/_changes'); - touch(static::TMP . '/article.txt', 1234567890); - touch(static::TMP . '/_changes/article.en.txt', 1234567890); + $this->setUpSingleLanguage(); - $this->assertSame($expected, $this->storage->modified($id, $language)); + $this->assertNull($this->storage->modified(VersionId::changes(), Language::single())); + $this->assertNull($this->storage->modified(VersionId::published(), Language::single())); } - public static function modifiedProvider(): array + /** + * @covers ::modified + */ + public function testModifiedSomeExistingMultiLanguage() { - return [ - [VersionId::changes(), 'default', null], - [VersionId::changes(), 'en', 1234567890], - [VersionId::published(), 'default', 1234567890], - [VersionId::published(), 'en', null] - ]; + $this->setUpMultiLanguage(); + + Dir::make($this->model->root() . '/_changes'); + touch($this->model->root() . '/_changes/article.en.txt', $modified = 1234567890); + + $this->assertSame($modified, $this->storage->modified(VersionId::changes(), $this->app->language('en'))); + $this->assertNull($this->storage->modified(VersionId::published(), $this->app->language('en'))); + } + + /** + * @covers ::modified + */ + public function testModifiedSomeExistingSingleLanguage() + { + $this->setUpSingleLanguage(); + + Dir::make(static::TMP . '/content/a-page/_changes'); + touch(static::TMP . '/content/a-page/_changes/article.txt', $modified = 1234567890); + + $this->assertSame($modified, $this->storage->modified(VersionId::changes(), Language::single())); + $this->assertNull($this->storage->modified(VersionId::published(), Language::single())); } /** @@ -221,15 +305,17 @@ public static function modifiedProvider(): array */ public function testReadChangesMultiLang() { + $this->setUpMultiLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - Dir::make(static::TMP . '/_changes'); - Data::write(static::TMP . '/_changes/article.en.txt', $fields); + Dir::make($this->model->root() . '/_changes'); + Data::write($this->model->root() . '/_changes/article.en.txt', $fields); - $this->assertSame($fields, $this->storage->read(VersionId::changes(), 'en')); + $this->assertSame($fields, $this->storage->read(VersionId::changes(), $this->app->language('en'))); } /** @@ -237,15 +323,17 @@ public function testReadChangesMultiLang() */ public function testReadChangesSingleLang() { + $this->setUpSingleLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - Dir::make(static::TMP . '/_changes'); - Data::write(static::TMP . '/_changes/article.txt', $fields); + Dir::make($this->model->root() . '/_changes'); + Data::write($this->model->root() . '/_changes/article.txt', $fields); - $this->assertSame($fields, $this->storage->read(VersionId::changes(), 'default')); + $this->assertSame($fields, $this->storage->read(VersionId::changes(), Language::single())); } /** @@ -253,14 +341,16 @@ public function testReadChangesSingleLang() */ public function testReadPublishedMultiLang() { + $this->setUpMultiLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - Data::write(static::TMP . '/article.en.txt', $fields); + Data::write($this->model->root() . '/article.en.txt', $fields); - $this->assertSame($fields, $this->storage->read(VersionId::published(), 'en')); + $this->assertSame($fields, $this->storage->read(VersionId::published(), $this->app->language('en'))); } /** @@ -268,14 +358,16 @@ public function testReadPublishedMultiLang() */ public function testReadPublishedSingleLang() { + $this->setUpSingleLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' ]; - Data::write(static::TMP . '/article.txt', $fields); + Data::write($this->model->root() . '/article.txt', $fields); - $this->assertSame($fields, $this->storage->read(VersionId::published(), 'default')); + $this->assertSame($fields, $this->storage->read(VersionId::published(), Language::single())); } /** @@ -283,16 +375,20 @@ public function testReadPublishedSingleLang() */ public function testTouchChangesMultiLang() { - Dir::make(static::TMP . '/_changes'); - touch(static::TMP . '/_changes/article.en.txt', 123456); - $this->assertSame(123456, filemtime(static::TMP . '/_changes/article.en.txt')); + $this->setUpMultiLanguage(); + + $root = $this->model->root() . '/_changes'; + + Dir::make($root); + touch($root . '/article.en.txt', 123456); + $this->assertSame(123456, filemtime($root . '/article.en.txt')); $minTime = time(); - $this->storage->touch(VersionId::changes(), 'en'); + $this->storage->touch(VersionId::changes(), $this->app->language('en')); clearstatcache(); - $this->assertGreaterThanOrEqual($minTime, filemtime(static::TMP . '/_changes/article.en.txt')); + $this->assertGreaterThanOrEqual($minTime, filemtime($root . '/article.en.txt')); } /** @@ -300,16 +396,20 @@ public function testTouchChangesMultiLang() */ public function testTouchChangesSingleLang() { - Dir::make(static::TMP . '/_changes'); - touch(static::TMP . '/_changes/article.txt', 123456); - $this->assertSame(123456, filemtime(static::TMP . '/_changes/article.txt')); + $this->setUpSingleLanguage(); + + $root = $this->model->root() . '/_changes'; + + Dir::make($root); + touch($root . '/article.txt', 123456); + $this->assertSame(123456, filemtime($root . '/article.txt')); $minTime = time(); - $this->storage->touch(VersionId::changes(), 'default'); + $this->storage->touch(VersionId::changes(), Language::single()); clearstatcache(); - $this->assertGreaterThanOrEqual($minTime, filemtime(static::TMP . '/_changes/article.txt')); + $this->assertGreaterThanOrEqual($minTime, filemtime($root . '/article.txt')); } /** @@ -317,15 +417,19 @@ public function testTouchChangesSingleLang() */ public function testTouchPublishedMultiLang() { - touch(static::TMP . '/article.en.txt', 123456); - $this->assertSame(123456, filemtime(static::TMP . '/article.en.txt')); + $this->setUpMultiLanguage(); + + $root = $this->model->root() . '/article.en.txt'; + + touch($root, 123456); + $this->assertSame(123456, filemtime($root)); $minTime = time(); - $this->storage->touch(VersionId::published(), 'en'); + $this->storage->touch(VersionId::published(), $this->app->language('en')); clearstatcache(); - $this->assertGreaterThanOrEqual($minTime, filemtime(static::TMP . '/article.en.txt')); + $this->assertGreaterThanOrEqual($minTime, filemtime($root)); } /** @@ -333,15 +437,19 @@ public function testTouchPublishedMultiLang() */ public function testTouchPublishedSingleLang() { - touch(static::TMP . '/article.txt', 123456); - $this->assertSame(123456, filemtime(static::TMP . '/article.txt')); + $this->setUpSingleLanguage(); + + $root = $this->model->root() . '/article.txt'; + + touch($root, 123456); + $this->assertSame(123456, filemtime($root)); $minTime = time(); - $this->storage->touch(VersionId::published(), 'default'); + $this->storage->touch(VersionId::published(), Language::single()); clearstatcache(); - $this->assertGreaterThanOrEqual($minTime, filemtime(static::TMP . '/article.txt')); + $this->assertGreaterThanOrEqual($minTime, filemtime($root)); } /** @@ -349,6 +457,8 @@ public function testTouchPublishedSingleLang() */ public function testUpdateChangesMultiLang() { + $this->setUpMultiLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' @@ -357,7 +467,7 @@ public function testUpdateChangesMultiLang() Dir::make(static::TMP . '/_changes'); Data::write(static::TMP . '/_changes/article.en.txt', $fields); - $this->storage->update(VersionId::changes(), 'en', $fields); + $this->storage->update(VersionId::changes(), $this->app->language('en'), $fields); $this->assertSame($fields, Data::read(static::TMP . '/_changes/article.en.txt')); } @@ -366,6 +476,8 @@ public function testUpdateChangesMultiLang() */ public function testUpdateChangesSingleLang() { + $this->setUpSingleLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' @@ -374,7 +486,7 @@ public function testUpdateChangesSingleLang() Dir::make(static::TMP . '/_changes'); Data::write(static::TMP . '/_changes/article.txt', $fields); - $this->storage->update(VersionId::changes(), 'default', $fields); + $this->storage->update(VersionId::changes(), Language::single(), $fields); $this->assertSame($fields, Data::read(static::TMP . '/_changes/article.txt')); } @@ -383,6 +495,8 @@ public function testUpdateChangesSingleLang() */ public function testUpdatePublishedMultiLang() { + $this->setUpMultiLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' @@ -390,7 +504,7 @@ public function testUpdatePublishedMultiLang() Data::write(static::TMP . '/article.en.txt', $fields); - $this->storage->update(VersionId::published(), 'en', $fields); + $this->storage->update(VersionId::published(), $this->app->language('en'), $fields); $this->assertSame($fields, Data::read(static::TMP . '/article.en.txt')); } @@ -399,6 +513,8 @@ public function testUpdatePublishedMultiLang() */ public function testUpdatePublishedSingleLang() { + $this->setUpSingleLanguage(); + $fields = [ 'title' => 'Foo', 'text' => 'Bar' @@ -406,23 +522,19 @@ public function testUpdatePublishedSingleLang() Data::write(static::TMP . '/article.txt', $fields); - $this->storage->update(VersionId::published(), 'default', $fields); + $this->storage->update(VersionId::published(), Language::single(), $fields); $this->assertSame($fields, Data::read(static::TMP . '/article.txt')); } /** * @covers ::contentFile - * @dataProvider contentFileProvider + * @dataProvider contentFileProviderMultiLang */ - public function testContentFile(string $type, VersionId $id, string $language, string $expected) + public function testContentFileMultiLang(string $type, VersionId $id, string $language, string $expected) { - $app = new App([ - 'roots' => [ - 'index' => static::TMP - ] - ]); + $this->setUpMultiLanguage(); - $site = $app->site(); + $site = $this->app->site(); $model = match ($type) { 'file' => new File([ @@ -430,80 +542,107 @@ public function testContentFile(string $type, VersionId $id, string $language, s 'filename' => 'image.jpg' ]), 'page' => new Page([ - 'kirby' => $app, + 'kirby' => $this->app, 'slug' => 'a-page', 'template' => 'article' ]), 'site' => $site, 'user' => new User([ - 'kirby' => $app, + 'kirby' => $this->app, 'id' => 'abcdefgh' ]) }; $storage = new PlainTextContentStorageHandler($model); - $this->assertSame(static::TMP . '/' . $expected, $storage->contentFile($id, $language)); + $this->assertSame(static::TMP . '/' . $expected, $storage->contentFile($id, $this->app->language($language))); } - public static function contentFileProvider(): array + public static function contentFileProviderMultiLang(): array { return [ - ['file', VersionId::changes(), 'default', 'content/_changes/image.jpg.txt'], ['file', VersionId::changes(), 'en', 'content/_changes/image.jpg.en.txt'], - ['file', VersionId::published(), 'default', 'content/image.jpg.txt'], ['file', VersionId::published(), 'en', 'content/image.jpg.en.txt'], - ['page', VersionId::changes(), 'default', 'content/a-page/_changes/article.txt'], ['page', VersionId::changes(), 'en', 'content/a-page/_changes/article.en.txt'], - ['page', VersionId::published(), 'default', 'content/a-page/article.txt'], ['page', VersionId::published(), 'en', 'content/a-page/article.en.txt'], - ['site', VersionId::changes(), 'default', 'content/_changes/site.txt'], ['site', VersionId::changes(), 'en', 'content/_changes/site.en.txt'], - ['site', VersionId::published(), 'default', 'content/site.txt'], ['site', VersionId::published(), 'en', 'content/site.en.txt'], - ['user', VersionId::changes(), 'default', 'site/accounts/abcdefgh/_changes/user.txt'], ['user', VersionId::changes(), 'en', 'site/accounts/abcdefgh/_changes/user.en.txt'], - ['user', VersionId::published(), 'default', 'site/accounts/abcdefgh/user.txt'], ['user', VersionId::published(), 'en', 'site/accounts/abcdefgh/user.en.txt'], ]; } /** * @covers ::contentFile - * @dataProvider contentFileDraftProvider + * @dataProvider contentFileProviderSingleLang */ - public function testContentFileDraft(string $language, string $expected) + public function testContentFileSingleLang(string $type, VersionId $id, string $expected) { - $app = new App([ - 'roots' => [ - 'index' => static::TMP - ] - ]); + $this->setUpSingleLanguage(); + + $site = $this->app->site(); + + $model = match ($type) { + 'file' => new File([ + 'parent' => $site, + 'filename' => 'image.jpg' + ]), + 'page' => new Page([ + 'kirby' => $this->app, + 'slug' => 'a-page', + 'template' => 'article' + ]), + 'site' => $site, + 'user' => new User([ + 'kirby' => $this->app, + 'id' => 'abcdefgh' + ]) + }; + + $storage = new PlainTextContentStorageHandler($model); + $this->assertSame(static::TMP . '/' . $expected, $storage->contentFile($id, Language::single())); + } + + public static function contentFileProviderSingleLang(): array + { + return [ + ['file', VersionId::changes(), 'content/_changes/image.jpg.txt'], + ['file', VersionId::published(), 'content/image.jpg.txt'], + ['page', VersionId::changes(), 'content/a-page/_changes/article.txt'], + ['page', VersionId::published(), 'content/a-page/article.txt'], + ['site', VersionId::changes(), 'content/_changes/site.txt'], + ['site', VersionId::published(), 'content/site.txt'], + ['user', VersionId::changes(), 'site/accounts/abcdefgh/_changes/user.txt'], + ['user', VersionId::published(), 'site/accounts/abcdefgh/user.txt'], + ]; + } + + /** + * @covers ::contentFile + */ + public function testContentFileDraft() + { + $this->setUpSingleLanguage(); $model = new Page([ - 'kirby' => $app, + 'kirby' => $this->app, 'isDraft' => true, 'slug' => 'a-page', 'template' => 'article' ]); $storage = new PlainTextContentStorageHandler($model); - $this->assertSame(static::TMP . '/' . $expected, $storage->contentFile(VersionId::changes(), $language)); + $this->assertSame(static::TMP . '/content/_drafts/a-page/article.txt', $storage->contentFile(VersionId::changes(), Language::single())); } /** * @covers ::contentFile - * @dataProvider contentFileDraftProvider */ - public function testContentFileDraftPublished(string $language, string $expected) + public function testContentFileDraftPublished() { - $app = new App([ - 'roots' => [ - 'index' => static::TMP - ] - ]); + $this->setUpSingleLanguage(); $model = new Page([ - 'kirby' => $app, + 'kirby' => $this->app, 'root' => static::TMP, 'isDraft' => true, 'slug' => 'a-page', @@ -514,36 +653,19 @@ public function testContentFileDraftPublished(string $language, string $expected $this->expectException(LogicException::class); $this->expectExceptionMessage('Drafts cannot have a published content file'); - $storage->contentFile(VersionId::published(), $language); + $storage->contentFile(VersionId::published(), Language::single()); } - public static function contentFileDraftProvider(): array - { - return [ - ['default', 'content/_drafts/a-page/article.txt'], - ['en', 'content/_drafts/a-page/article.en.txt'], - ]; - } /** * @covers ::contentFiles */ public function testContentFilesChangesMultiLang() { - new App([ - 'languages' => [ - [ - 'code' => 'en', - 'default' => true - ], - [ - 'code' => 'de' - ] - ] - ]); + $this->setUpMultiLanguage(); $this->assertSame([ - static::TMP . '/_changes/article.en.txt', - static::TMP . '/_changes/article.de.txt' + $this->model->root() . '/_changes/article.en.txt', + $this->model->root() . '/_changes/article.de.txt' ], $this->storage->contentFiles(VersionId::changes())); } @@ -552,8 +674,10 @@ public function testContentFilesChangesMultiLang() */ public function testContentFilesChangesSingleLang() { + $this->setUpSingleLanguage(); + $this->assertSame([ - static::TMP . '/_changes/article.txt' + $this->model->root() . '/_changes/article.txt' ], $this->storage->contentFiles(VersionId::changes())); } @@ -562,21 +686,11 @@ public function testContentFilesChangesSingleLang() */ public function testContentFilesPublishedMultiLang() { - new App([ - 'languages' => [ - [ - 'code' => 'en', - 'default' => true - ], - [ - 'code' => 'de' - ] - ] - ]); + $this->setUpMultiLanguage(); $this->assertSame([ - static::TMP . '/article.en.txt', - static::TMP . '/article.de.txt' + $this->model->root() . '/article.en.txt', + $this->model->root() . '/article.de.txt' ], $this->storage->contentFiles(VersionId::published())); } @@ -585,8 +699,10 @@ public function testContentFilesPublishedMultiLang() */ public function testContentFilesPublishedSingleLang() { + $this->setUpSingleLanguage(); + $this->assertSame([ - static::TMP . '/article.txt' + $this->model->root() . '/article.txt' ], $this->storage->contentFiles(VersionId::published())); } } From 8ab215af7dd603df169f12033d82fd5b41e2947a Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Wed, 22 May 2024 14:30:06 +0200 Subject: [PATCH 04/10] Fix CS issue --- src/Content/PlainTextContentStorageHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Content/PlainTextContentStorageHandler.php b/src/Content/PlainTextContentStorageHandler.php index 0edc60b8fa..b1838809d1 100644 --- a/src/Content/PlainTextContentStorageHandler.php +++ b/src/Content/PlainTextContentStorageHandler.php @@ -267,8 +267,8 @@ public function update(VersionId $versionId, Language $language, array $fields): /** * Writes the content fields of an existing version * - * @param string $lang Code `'default'` in a single-lang installation * @param array $fields Content fields + * @param string $lang Code `'default'` in a single-lang installation * * @throws \Kirby\Exception\Exception If the content cannot be written */ From 4371d5b1e78f9008da7e70107cb14408ccaccd2d Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Wed, 5 Jun 2024 15:19:31 +0200 Subject: [PATCH 05/10] Remove unnecessary doc block Co-authored-by: Lukas Bestle --- src/Content/PlainTextContentStorageHandler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Content/PlainTextContentStorageHandler.php b/src/Content/PlainTextContentStorageHandler.php index b1838809d1..e80010caf1 100644 --- a/src/Content/PlainTextContentStorageHandler.php +++ b/src/Content/PlainTextContentStorageHandler.php @@ -268,7 +268,6 @@ public function update(VersionId $versionId, Language $language, array $fields): * Writes the content fields of an existing version * * @param array $fields Content fields - * @param string $lang Code `'default'` in a single-lang installation * * @throws \Kirby\Exception\Exception If the content cannot be written */ From 5a744321fa57ffcd4c2d16fa0a654b67b1f49347 Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Wed, 5 Jun 2024 16:52:32 +0200 Subject: [PATCH 06/10] Add Language::isSingle and use in PlainTextStorageHandler to create correct filenames --- src/Cms/Language.php | 21 ++- src/Content/ContentStorage.php | 11 +- .../PlainTextContentStorageHandler.php | 2 +- .../Cms/Languages/LanguageConversionTest.php | 128 ++++++++++++++++++ tests/Cms/Languages/LanguageTest.php | 21 +++ 5 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 tests/Cms/Languages/LanguageConversionTest.php diff --git a/src/Cms/Language.php b/src/Cms/Language.php index 16cf007c64..6ebbe65928 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -45,6 +45,7 @@ class Language implements Stringable protected string $direction; protected array $locale; protected string $name; + protected bool $single; protected array $slugs; protected array $smartypants; protected array $translations; @@ -64,6 +65,7 @@ public function __construct(array $props) $this->default = ($props['default'] ?? false) === true; $this->direction = ($props['direction'] ?? null) === 'rtl' ? 'rtl' : 'ltr'; $this->name = trim($props['name'] ?? $this->code); + $this->single = $props['single'] ?? false; $this->slugs = $props['slugs'] ?? []; $this->smartypants = $props['smartypants'] ?? []; $this->translations = $props['translations'] ?? []; @@ -177,8 +179,8 @@ public static function create(array $props): static if ($languages->count() === 0) { foreach ($kirby->models() as $model) { $model->storage()->convertLanguage( - 'default', - $language->code() + Language::single(), + $language ); } } @@ -225,7 +227,7 @@ public function delete(): bool foreach ($kirby->models() as $model) { if ($this->isLast() === true) { - $model->storage()->convertLanguage($code, 'default'); + $model->storage()->convertLanguage($this, Language::single()); } else { $model->storage()->deleteLanguage($code); } @@ -273,7 +275,7 @@ public function isDefault(): bool public function isDeletable(): bool { // the default language can only be deleted if it's the last - if ($this->isDefault() === true && $this->isLast() === false) { + if ($this->isSingle() === true && $this->isDefault() === true && $this->isLast() === false) { return false; } @@ -288,6 +290,14 @@ public function isLast(): bool return App::instance()->languages()->count() === 1; } + /** + * Checks if this is the single language object + */ + public function isSingle(): bool + { + return $this->single; + } + /** * The id is required for collections * to work properly. The code is used as id @@ -455,7 +465,8 @@ public static function single(): static return new static([ 'code' => 'en', 'default' => true, - 'locale' => App::instance()->option('locale', 'en_US.utf-8') + 'locale' => App::instance()->option('locale', 'en_US.utf-8'), + 'single' => true ]); } diff --git a/src/Content/ContentStorage.php b/src/Content/ContentStorage.php index 4ac5355479..4dd680e796 100644 --- a/src/Content/ContentStorage.php +++ b/src/Content/ContentStorage.php @@ -83,11 +83,8 @@ public function contentFile( * Adapts all versions when converting languages * @internal */ - public function convertLanguage(string $from, string $to): void + public function convertLanguage(Language $from, Language $to): void { - $from = $this->language($from, true); - $to = $this->language($to, true); - foreach ($this->dynamicVersions() as $versionId) { $this->handler->move($versionId, $from, $versionId, $to); } @@ -127,7 +124,7 @@ public function delete( */ public function deleteLanguage(string|null $lang): void { - $lang = $this->language($lang, true); + $lang = $this->language($lang); foreach ($this->dynamicVersions() as $version) { $this->handler->delete($version, $lang); @@ -217,7 +214,7 @@ public function touch( */ public function touchLanguage(string|null $lang): void { - $lang = $this->language($lang, true); + $lang = $this->language($lang); foreach ($this->dynamicVersions() as $version) { if ($this->exists($version, $lang) === true) { @@ -265,8 +262,6 @@ protected function ensureExistingVersion( /** * 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 */ protected function language( string|null $languageCode = null, diff --git a/src/Content/PlainTextContentStorageHandler.php b/src/Content/PlainTextContentStorageHandler.php index e80010caf1..08087f0ea2 100644 --- a/src/Content/PlainTextContentStorageHandler.php +++ b/src/Content/PlainTextContentStorageHandler.php @@ -122,7 +122,7 @@ public function contentFilename(string $name, Language $language): string $kirby = $this->model->kirby(); $extension = $kirby->contentExtension(); - if ($kirby->multilang() === true) { + if ($language->isSingle() === false) { return $name . '.' . $language->code() . '.' . $extension; } diff --git a/tests/Cms/Languages/LanguageConversionTest.php b/tests/Cms/Languages/LanguageConversionTest.php new file mode 100644 index 0000000000..c708466329 --- /dev/null +++ b/tests/Cms/Languages/LanguageConversionTest.php @@ -0,0 +1,128 @@ + [ + 'index' => static::TMP + ], + ]); + + // create the page model + Data::write($app->root('content') . '/test/test.txt', [ + 'title' => 'Title' + ]); + + $this->assertCount(0, $app->languages()); + $this->assertFalse($app->multilang()); + + // get the page model + $page = $app->page('test'); + + // and check if the content is loadable + $this->assertSame('Title', $page->content()->title()->value()); + + // create a new Language to switch to multi-language mode + Language::create([ + 'code' => 'en', + ]); + + // make sure that Kirby actually switched to multi-language mode + $this->assertCount(1, $app->languages()); + $this->assertTrue($app->multilang()); + + // the content file should now have been moved to .en + $this->assertFileExists($page->root() . '/test.en.txt'); + $this->assertFileDoesNotExist($page->root() . '/test.txt'); + + // the content should still be loadable for the page + $this->assertSame('Title', $page->content()->title()->value()); + $this->assertSame('Title', $page->content('en')->title()->value()); + } + + public function testConvertFromMultiLanguageToSingleLanguage(): void + { + $app = new App([ + 'roots' => [ + 'index' => static::TMP + ], + ]); + + // create two languages + Data::write($app->root('languages') . '/en.php', [ + 'code' => 'en', + 'default' => true + ]); + + Data::write($app->root('languages') . '/de.php', [ + 'code' => 'de', + ]); + + // create some models + Data::write($app->root('content') . '/test/test.en.txt', [ + 'title' => 'English Title' + ]); + + Data::write($app->root('content') . '/test/test.de.txt', [ + 'title' => 'German Title' + ]); + + $this->assertCount(2, $app->languages()); + $this->assertTrue($app->multilang()); + + // get the page model + $page = $app->page('test'); + + $this->assertSame('English Title', $page->content('en')->title()->value()); + $this->assertSame('German Title', $page->content('de')->title()->value()); + + // delete the first language to check file removal. + $app->language('de')->delete(); + + // the .de file should now be gone + $this->assertFileDoesNotExist($page->root() . '/test.de.txt'); + + // the .en file should still be there + $this->assertFileExists($page->root() . '/test.en.txt'); + + // delete the last language to switch to single-language mode + $app->language('en')->delete(); + + // the .en file should now be gone + $this->assertFileDoesNotExist($page->root() . '/test.en.txt'); + + // … and should be replaced by the content file without language code + $this->assertFileExists($page->root() . '/test.txt'); + + // meanwhile, Kirby should have been switched to single language mode + $this->assertCount(0, $app->languages()); + $this->assertFalse($app->multilang()); + + // the page should still load the english content + $this->assertSame('English Title', $page->content()->title()->value()); + } + +} diff --git a/tests/Cms/Languages/LanguageTest.php b/tests/Cms/Languages/LanguageTest.php index db17ed829d..a71d326258 100644 --- a/tests/Cms/Languages/LanguageTest.php +++ b/tests/Cms/Languages/LanguageTest.php @@ -280,6 +280,27 @@ public function testIsDefault() $this->assertFalse($language->isDefault()); } + /** + * @covers ::isSingle + */ + public function testIsSingle() + { + // default + $language = new Language([ + 'code' => 'en' + ]); + + $this->assertFalse($language->isSingle()); + + // true + $language = new Language([ + 'code' => 'en', + 'single' => true + ]); + + $this->assertTrue($language->isSingle()); + } + /** * @covers ::locale */ From 6057cb317d183b6d6d622413205c8b8fe3bce979 Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Wed, 5 Jun 2024 17:04:46 +0200 Subject: [PATCH 07/10] Fix Language::isDeletable --- src/Cms/Language.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Cms/Language.php b/src/Cms/Language.php index 6ebbe65928..301ba44184 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -274,8 +274,13 @@ public function isDefault(): bool */ public function isDeletable(): bool { + // a single-language object cannot be deleted + if ($this->isSingle() === true) { + return false; + } + // the default language can only be deleted if it's the last - if ($this->isSingle() === true && $this->isDefault() === true && $this->isLast() === false) { + if ($this->isDefault() === true && $this->isLast() === false) { return false; } From f7be2d63633b8fb3681cfb28110d150faed9eac0 Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Wed, 5 Jun 2024 19:18:37 +0200 Subject: [PATCH 08/10] keep isSingle internal Co-authored-by: Lukas Bestle --- src/Cms/Language.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cms/Language.php b/src/Cms/Language.php index 301ba44184..2cfa204bd4 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -297,6 +297,7 @@ public function isLast(): bool /** * Checks if this is the single language object + * @internal */ public function isSingle(): bool { From 764832fe59aaed0422cfcc3b321a3df066a4866f Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Wed, 5 Jun 2024 19:20:16 +0200 Subject: [PATCH 09/10] keep contentFilename protected Co-authored-by: Lukas Bestle --- src/Content/PlainTextContentStorageHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Content/PlainTextContentStorageHandler.php b/src/Content/PlainTextContentStorageHandler.php index 08087f0ea2..e01410a50e 100644 --- a/src/Content/PlainTextContentStorageHandler.php +++ b/src/Content/PlainTextContentStorageHandler.php @@ -117,7 +117,7 @@ protected function contentFileForUser(User $model, VersionId $versionId, Languag * Creates a filename with extension and optional language code * in a multi-language installation */ - public function contentFilename(string $name, Language $language): string + protected function contentFilename(string $name, Language $language): string { $kirby = $this->model->kirby(); $extension = $kirby->contentExtension(); From 04cbfdd8f14cb87b5fc5f1217ad9b12b57018575 Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Wed, 5 Jun 2024 20:30:05 +0200 Subject: [PATCH 10/10] Read data in tests from disk --- tests/Cms/Languages/LanguageConversionTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Cms/Languages/LanguageConversionTest.php b/tests/Cms/Languages/LanguageConversionTest.php index c708466329..1fbbea7534 100644 --- a/tests/Cms/Languages/LanguageConversionTest.php +++ b/tests/Cms/Languages/LanguageConversionTest.php @@ -61,6 +61,8 @@ public function testConvertFromSingleLanguageToMultiLanguage(): void // the content should still be loadable for the page $this->assertSame('Title', $page->content()->title()->value()); $this->assertSame('Title', $page->content('en')->title()->value()); + + $this->assertSame('Title', Data::read($page->root() . '/test.en.txt')['title']); } public function testConvertFromMultiLanguageToSingleLanguage(): void @@ -123,6 +125,7 @@ public function testConvertFromMultiLanguageToSingleLanguage(): void // the page should still load the english content $this->assertSame('English Title', $page->content()->title()->value()); + $this->assertSame('English Title', Data::read($page->root() . '/test.txt')['title']); } }