From 29f27a71a2d391b9789f395c7818f25ed5d1ebe4 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Thu, 23 Mar 2023 14:36:43 +0100 Subject: [PATCH] Add Shadow Locale to Settings Tab (#238) * Add Shadow Locale to Settings Tab * Apply suggestions from code review Co-authored-by: Prokyonn --------- Co-authored-by: Prokyonn --- .../DataMapper/ShadowDataMapper.php | 43 +++++++ .../ContentMerger/Merger/ShadowMerger.php | 42 +++++++ .../Normalizer/ShadowNormalizer.php | 45 +++++++ Content/Domain/Model/ShadowInterface.php | 39 ++++++ Content/Domain/Model/ShadowTrait.php | 68 +++++++++++ .../Doctrine/MetadataLoader.php | 8 +- .../Sulu/Admin/ContentViewBuilderFactory.php | 12 ++ Resources/config/data-mapper.xml | 4 + .../config/forms/content_settings_shadow.xml | 40 +++++++ Resources/config/merger.xml | 4 + Resources/config/normalizer.xml | 4 + Resources/translations/admin.de.json | 4 + Resources/translations/admin.en.json | 4 + .../Entity/ExampleDimensionContent.php | 5 +- .../Integration/responses/example_get.json | 6 + .../responses/example_get_ghost_locale.json | 4 + .../Integration/responses/example_post.json | 6 + .../responses/example_post_publish.json | 6 + .../example_post_trigger_copy_locale.json | 7 ++ .../example_post_trigger_unpublish.json | 6 + .../Integration/responses/example_put.json | 7 ++ .../DataMapper/ShadowDataMapperTest.php | 111 ++++++++++++++++++ .../ContentMerger/Merger/ShadowMergerTest.php | 92 +++++++++++++++ .../Normalizer/ShadowNormalizerTest.php | 93 +++++++++++++++ .../Content/Domain/Model/ShadowTraitTest.php | 50 ++++++++ .../Doctrine/MetadataLoaderTest.php | 14 +++ .../Admin/ContentViewBuilderFactoryTest.php | 10 ++ UPGRADE.md | 9 ++ phpstan.neon | 1 + 29 files changed, 742 insertions(+), 2 deletions(-) create mode 100644 Content/Application/ContentDataMapper/DataMapper/ShadowDataMapper.php create mode 100644 Content/Application/ContentMerger/Merger/ShadowMerger.php create mode 100644 Content/Application/ContentNormalizer/Normalizer/ShadowNormalizer.php create mode 100644 Content/Domain/Model/ShadowInterface.php create mode 100644 Content/Domain/Model/ShadowTrait.php create mode 100644 Resources/config/forms/content_settings_shadow.xml create mode 100644 Tests/Unit/Content/Application/ContentDataMapper/DataMapper/ShadowDataMapperTest.php create mode 100644 Tests/Unit/Content/Application/ContentMerger/Merger/ShadowMergerTest.php create mode 100644 Tests/Unit/Content/Application/ContentNormalizer/Normalizer/ShadowNormalizerTest.php create mode 100644 Tests/Unit/Content/Domain/Model/ShadowTraitTest.php diff --git a/Content/Application/ContentDataMapper/DataMapper/ShadowDataMapper.php b/Content/Application/ContentDataMapper/DataMapper/ShadowDataMapper.php new file mode 100644 index 00000000..ec703703 --- /dev/null +++ b/Content/Application/ContentDataMapper/DataMapper/ShadowDataMapper.php @@ -0,0 +1,43 @@ +setShadowLocale( + $shadowOn + ? $shadowLocale + : null + ); + } + } +} diff --git a/Content/Application/ContentMerger/Merger/ShadowMerger.php b/Content/Application/ContentMerger/Merger/ShadowMerger.php new file mode 100644 index 00000000..4fd84169 --- /dev/null +++ b/Content/Application/ContentMerger/Merger/ShadowMerger.php @@ -0,0 +1,42 @@ +getShadowLocale()) { + $targetObject->setShadowLocale($shadowLocale); + } + + foreach ($sourceObject->getShadowLocales() ?: [] as $locale => $shadowLocale) { + $targetObject->addShadowLocale($locale, $shadowLocale); + } + } +} diff --git a/Content/Application/ContentNormalizer/Normalizer/ShadowNormalizer.php b/Content/Application/ContentNormalizer/Normalizer/ShadowNormalizer.php new file mode 100644 index 00000000..5e052315 --- /dev/null +++ b/Content/Application/ContentNormalizer/Normalizer/ShadowNormalizer.php @@ -0,0 +1,45 @@ +getShadowLocale(); + $normalizedData['shadowLocales'] = $normalizedData['shadowLocales'] ?? []; + $normalizedData['contentLocales'] = $object->getAvailableLocales() ?? []; // TODO should be changed in Sulu Core (PageSettingsShadowLocaleSelect.js) + + return $normalizedData; + } + + public function getIgnoredAttributes(object $object): array + { + if (!$object instanceof ShadowInterface) { + return []; + } + + return []; + } +} diff --git a/Content/Domain/Model/ShadowInterface.php b/Content/Domain/Model/ShadowInterface.php new file mode 100644 index 00000000..49413997 --- /dev/null +++ b/Content/Domain/Model/ShadowInterface.php @@ -0,0 +1,39 @@ +|null + */ + public function getShadowLocales(): ?array; +} diff --git a/Content/Domain/Model/ShadowTrait.php b/Content/Domain/Model/ShadowTrait.php new file mode 100644 index 00000000..fb0b436f --- /dev/null +++ b/Content/Domain/Model/ShadowTrait.php @@ -0,0 +1,68 @@ +shadowLocale = $shadowLocale; + } + + public function getShadowLocale(): ?string + { + return $this->shadowLocale; + } + + /** + * @internal should only be set by content bundle services not from outside + */ + public function addShadowLocale(string $locale, string $shadowLocale): void + { + if (null === $this->shadowLocales) { + $this->shadowLocales = []; + } + + $this->shadowLocales[$locale] = $shadowLocale; + } + + /** + * @internal should only be set by content bundle services not from outside + */ + public function removeShadowLocale(string $locale): void + { + unset($this->shadowLocales[$locale]); + } + + /** + * @return array + */ + public function getShadowLocales(): ?array + { + return $this->shadowLocales; + } +} diff --git a/Content/Infrastructure/Doctrine/MetadataLoader.php b/Content/Infrastructure/Doctrine/MetadataLoader.php index 8244a7e4..477da89f 100644 --- a/Content/Infrastructure/Doctrine/MetadataLoader.php +++ b/Content/Infrastructure/Doctrine/MetadataLoader.php @@ -25,6 +25,7 @@ use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ExcerptInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\SeoInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\ShadowInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\TemplateInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\WebspaceInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\WorkflowInterface; @@ -53,7 +54,7 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event): void $this->addField($metadata, 'stage', 'string', ['length' => 16, 'nullable' => false]); $this->addField($metadata, 'locale', 'string', ['length' => 7, 'nullable' => true]); $this->addField($metadata, 'ghostLocale', 'string', ['length' => 7, 'nullable' => true]); - $this->addField($metadata, 'availableLocales', 'json', ['nullable' => true]); + $this->addField($metadata, 'availableLocales', 'json', ['nullable' => true, 'options' => ['jsonb' => true]]); $this->addIndex($metadata, 'idx_dimension', ['stage', 'locale']); $this->addIndex($metadata, 'idx_locale', ['locale']); $this->addIndex($metadata, 'idx_stage', ['stage']); @@ -110,6 +111,11 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event): void $this->addField($metadata, 'mainWebspace', 'string', ['nullable' => true]); } + if ($reflection->implementsInterface(ShadowInterface::class)) { + $this->addField($metadata, 'shadowLocale', 'string', ['length' => 7, 'nullable' => true]); + $this->addField($metadata, 'shadowLocales', 'json', ['nullable' => true, 'options' => ['jsonb' => true]]); + } + if ($reflection->implementsInterface(AuthorInterface::class)) { $this->addField($metadata, 'authored', 'datetime_immutable', ['nullable' => true]); $this->addManyToOne($event, $metadata, 'author', ContactInterface::class, true); diff --git a/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactory.php b/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactory.php index 5be50ab5..6f79fc54 100644 --- a/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactory.php +++ b/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactory.php @@ -22,12 +22,16 @@ use Sulu\Bundle\ContentBundle\Content\Application\ContentMetadataInspector\ContentMetadataInspectorInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ExcerptInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\SeoInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\ShadowInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\TemplateInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\WorkflowInterface; use Sulu\Bundle\PreviewBundle\Preview\Object\PreviewObjectProviderRegistryInterface; use Sulu\Component\Security\Authorization\PermissionTypes; use Sulu\Component\Security\Authorization\SecurityCheckerInterface; +/** + * @final + */ class ContentViewBuilderFactory implements ContentViewBuilderFactoryInterface { /** @@ -217,6 +221,14 @@ public function createViews( ); } + if (\is_subclass_of($dimensionContentClass, ShadowInterface::class)) { + foreach ($views as $view) { + if ($view instanceof FormViewBuilderInterface || $view instanceof PreviewFormViewBuilderInterface) { + $view->setTabCondition('shadowOn != true'); + } + } + } + $views[] = $this->createSettingsFormView( $editParentView, $previewEnabled, diff --git a/Resources/config/data-mapper.xml b/Resources/config/data-mapper.xml index 138e2a6e..d4a7cfd5 100644 --- a/Resources/config/data-mapper.xml +++ b/Resources/config/data-mapper.xml @@ -38,6 +38,10 @@ + + + + diff --git a/Resources/config/forms/content_settings_shadow.xml b/Resources/config/forms/content_settings_shadow.xml new file mode 100644 index 00000000..a9547f4e --- /dev/null +++ b/Resources/config/forms/content_settings_shadow.xml @@ -0,0 +1,40 @@ + +
+ content_settings_shadow + + + + +
+ + sulu_content.shadow_page + + + + + + sulu_content.enable_shadow_page_info_text + + + + + + + sulu_content.enable_shadow_page + + + + + + + + sulu_content.shadow_locale + + + +
+
+ diff --git a/Resources/config/merger.xml b/Resources/config/merger.xml index c5778c46..00cfb003 100644 --- a/Resources/config/merger.xml +++ b/Resources/config/merger.xml @@ -31,5 +31,9 @@ + + + + diff --git a/Resources/config/normalizer.xml b/Resources/config/normalizer.xml index 28a5c5a6..35ba9e83 100644 --- a/Resources/config/normalizer.xml +++ b/Resources/config/normalizer.xml @@ -27,5 +27,9 @@ + + + + diff --git a/Resources/translations/admin.de.json b/Resources/translations/admin.de.json index 15532002..6bc4b705 100644 --- a/Resources/translations/admin.de.json +++ b/Resources/translations/admin.de.json @@ -5,6 +5,10 @@ "sulu_content.published": "Veröffentlicht am", "sulu_content.author": "Autor", "sulu_content.authored_date": "Verfasst am", + "sulu_content.shadow_page": "Shadow Seite", + "sulu_content.enable_shadow_page": "Shadow Seite aktivieren", + "sulu_content.enable_shadow_page_info_text": "Diese Funktion ist deaktiviert, wenn die aktuelle Sprache bereits als Shadow verwendet wird.", + "sulu_content.shadow_locale": "Shadow Sprache", "sulu_content.webspace": "Webspace", "sulu_content.main_webspace": "Hauptwebspace", "sulu_content.additional_webspaces": "Weitere Webspaces" diff --git a/Resources/translations/admin.en.json b/Resources/translations/admin.en.json index 74ed95e1..d7db66b2 100644 --- a/Resources/translations/admin.en.json +++ b/Resources/translations/admin.en.json @@ -5,6 +5,10 @@ "sulu_content.published": "Published on", "sulu_content.author": "Author", "sulu_content.authored_date": "Authored Date", + "sulu_content.shadow_page": "Shadow page", + "sulu_content.enable_shadow_page": "Enable Shadow page", + "sulu_content.enable_shadow_page_info_text": "This function is disabled if the current locale is already used as shadow locale.", + "sulu_content.shadow_locale": "Shadow locale", "sulu_content.webspace": "Webspace", "sulu_content.main_webspace": "Main Webspace", "sulu_content.additional_webspaces": "Weitere Webspaces" diff --git a/Tests/Application/ExampleTestBundle/Entity/ExampleDimensionContent.php b/Tests/Application/ExampleTestBundle/Entity/ExampleDimensionContent.php index 060408d8..b2438341 100644 --- a/Tests/Application/ExampleTestBundle/Entity/ExampleDimensionContent.php +++ b/Tests/Application/ExampleTestBundle/Entity/ExampleDimensionContent.php @@ -24,6 +24,8 @@ use Sulu\Bundle\ContentBundle\Content\Domain\Model\RoutableTrait; use Sulu\Bundle\ContentBundle\Content\Domain\Model\SeoInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\SeoTrait; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\ShadowInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\ShadowTrait; use Sulu\Bundle\ContentBundle\Content\Domain\Model\TemplateInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\TemplateTrait; use Sulu\Bundle\ContentBundle\Content\Domain\Model\WebspaceInterface; @@ -34,13 +36,14 @@ /** * @implements DimensionContentInterface */ -class ExampleDimensionContent implements DimensionContentInterface, ExcerptInterface, SeoInterface, TemplateInterface, RoutableInterface, WorkflowInterface, AuthorInterface, WebspaceInterface +class ExampleDimensionContent implements DimensionContentInterface, ExcerptInterface, SeoInterface, TemplateInterface, RoutableInterface, WorkflowInterface, AuthorInterface, WebspaceInterface, ShadowInterface { use AuthorTrait; use DimensionContentTrait; use ExcerptTrait; use RoutableTrait; use SeoTrait; + use ShadowTrait; use TemplateTrait { setTemplateData as parentSetTemplateData; } diff --git a/Tests/Functional/Integration/responses/example_get.json b/Tests/Functional/Integration/responses/example_get.json index 1652a886..00aa7a07 100644 --- a/Tests/Functional/Integration/responses/example_get.json +++ b/Tests/Functional/Integration/responses/example_get.json @@ -4,6 +4,9 @@ "availableLocales": [ "en" ], + "contentLocales": [ + "en" + ], "excerptCategories": [], "excerptDescription": "Excerpt Description", "excerptIcon": null, @@ -28,6 +31,9 @@ "seoNoFollow": true, "seoNoIndex": true, "seoTitle": "Seo Title", + "shadowLocale": null, + "shadowLocales": [], + "shadowOn": false, "stage": "draft", "template": "example-2", "title": "Test Example", diff --git a/Tests/Functional/Integration/responses/example_get_ghost_locale.json b/Tests/Functional/Integration/responses/example_get_ghost_locale.json index d86ea55a..99727b2a 100644 --- a/Tests/Functional/Integration/responses/example_get_ghost_locale.json +++ b/Tests/Functional/Integration/responses/example_get_ghost_locale.json @@ -2,6 +2,7 @@ "author": null, "authored": null, "availableLocales": ["en"], + "contentLocales": ["en"], "excerptCategories": [], "excerptDescription": null, "excerptIcon": null, @@ -22,6 +23,9 @@ "seoNoFollow": false, "seoNoIndex": false, "seoTitle": null, + "shadowLocale": null, + "shadowLocales": [], + "shadowOn": false, "stage": "draft", "template": null, "title": null, diff --git a/Tests/Functional/Integration/responses/example_post.json b/Tests/Functional/Integration/responses/example_post.json index d68da06a..30d31e1b 100644 --- a/Tests/Functional/Integration/responses/example_post.json +++ b/Tests/Functional/Integration/responses/example_post.json @@ -4,6 +4,9 @@ "availableLocales": [ "en" ], + "contentLocales": [ + "en" + ], "excerptCategories": [], "excerptDescription": "Excerpt Description", "excerptIcon": null, @@ -27,6 +30,9 @@ "seoNoFollow": true, "seoNoIndex": true, "seoTitle": "Seo Title", + "shadowLocale": null, + "shadowLocales": [], + "shadowOn": false, "stage": "draft", "template": "example-2", "title": "Test Example", diff --git a/Tests/Functional/Integration/responses/example_post_publish.json b/Tests/Functional/Integration/responses/example_post_publish.json index 868de249..1fcfbf81 100644 --- a/Tests/Functional/Integration/responses/example_post_publish.json +++ b/Tests/Functional/Integration/responses/example_post_publish.json @@ -4,6 +4,9 @@ "availableLocales": [ "en" ], + "contentLocales": [ + "en" + ], "excerptCategories": [], "excerptDescription": "Excerpt Description", "excerptIcon": null, @@ -27,6 +30,9 @@ "seoNoFollow": true, "seoNoIndex": true, "seoTitle": "Seo Title", + "shadowLocale": null, + "shadowLocales": [], + "shadowOn": false, "stage": "draft", "template": "example-2", "title": "Test Example", diff --git a/Tests/Functional/Integration/responses/example_post_trigger_copy_locale.json b/Tests/Functional/Integration/responses/example_post_trigger_copy_locale.json index 8ba1161e..88c9a53a 100644 --- a/Tests/Functional/Integration/responses/example_post_trigger_copy_locale.json +++ b/Tests/Functional/Integration/responses/example_post_trigger_copy_locale.json @@ -5,6 +5,10 @@ "en", "de" ], + "contentLocales": [ + "en", + "de" + ], "excerptCategories": [], "excerptDescription": "Excerpt Description", "excerptIcon": null, @@ -29,6 +33,9 @@ "seoNoFollow": true, "seoNoIndex": true, "seoTitle": "Seo Title", + "shadowLocale": null, + "shadowLocales": [], + "shadowOn": false, "stage": "draft", "template": "example-2", "title": "Test Example", diff --git a/Tests/Functional/Integration/responses/example_post_trigger_unpublish.json b/Tests/Functional/Integration/responses/example_post_trigger_unpublish.json index 10c26a31..bfa1a3cd 100644 --- a/Tests/Functional/Integration/responses/example_post_trigger_unpublish.json +++ b/Tests/Functional/Integration/responses/example_post_trigger_unpublish.json @@ -4,6 +4,9 @@ "availableLocales": [ "en" ], + "contentLocales": [ + "en" + ], "excerptCategories": [], "excerptDescription": "Excerpt Description", "excerptIcon": null, @@ -27,6 +30,9 @@ "seoNoFollow": true, "seoNoIndex": true, "seoTitle": "Seo Title", + "shadowLocale": null, + "shadowLocales": [], + "shadowOn": false, "stage": "draft", "template": "example-2", "title": "Test Example", diff --git a/Tests/Functional/Integration/responses/example_put.json b/Tests/Functional/Integration/responses/example_put.json index 70db621d..31644c04 100644 --- a/Tests/Functional/Integration/responses/example_put.json +++ b/Tests/Functional/Integration/responses/example_put.json @@ -6,6 +6,10 @@ "en", "de" ], + "contentLocales": [ + "en", + "de" + ], "blocks": null, "description": null, "excerptCategories": [], @@ -31,6 +35,9 @@ "seoNoFollow": false, "seoNoIndex": false, "seoTitle": "Seo Title 2", + "shadowLocale": null, + "shadowLocales": [], + "shadowOn": false, "stage": "draft", "template": "default", "title": "Test Example 2", diff --git a/Tests/Unit/Content/Application/ContentDataMapper/DataMapper/ShadowDataMapperTest.php b/Tests/Unit/Content/Application/ContentDataMapper/DataMapper/ShadowDataMapperTest.php new file mode 100644 index 00000000..a82b46f6 --- /dev/null +++ b/Tests/Unit/Content/Application/ContentDataMapper/DataMapper/ShadowDataMapperTest.php @@ -0,0 +1,111 @@ + true, + 'shadowLocale' => 'en', + ]; + + $unlocalizedDimensionContent = $this->prophesize(DimensionContentInterface::class); + $localizedDimensionContent = $this->prophesize(DimensionContentInterface::class); + + $shadowMapper = $this->createShadowDataMapperInstance(); + $shadowMapper->map($unlocalizedDimensionContent->reveal(), $localizedDimensionContent->reveal(), $data); + + $this->assertTrue(true); // nothing called in this case + } + + public function testMapShadowNoData(): void + { + $data = []; + + $example = new Example(); + $unlocalizedDimensionContent = new ExampleDimensionContent($example); + $localizedDimensionContent = new ExampleDimensionContent($example); + + $shadowMapper = $this->createShadowDataMapperInstance(); + $shadowMapper->map($unlocalizedDimensionContent, $localizedDimensionContent, $data); + + $this->assertNull($localizedDimensionContent->getShadowLocale()); + } + + public function testMapShadowNoDataExistData(): void + { + $data = []; + + $example = new Example(); + $unlocalizedDimensionContent = new ExampleDimensionContent($example); + $localizedDimensionContent = new ExampleDimensionContent($example); + $localizedDimensionContent->setShadowLocale('en'); + + $shadowMapper = $this->createShadowDataMapperInstance(); + $shadowMapper->map($unlocalizedDimensionContent, $localizedDimensionContent, $data); + + $this->assertSame('en', $localizedDimensionContent->getShadowLocale()); + } + + public function testMapData(): void + { + $data = [ + 'shadowOn' => true, + 'shadowLocale' => 'en', + ]; + + $example = new Example(); + $unlocalizedDimensionContent = new ExampleDimensionContent($example); + $localizedDimensionContent = new ExampleDimensionContent($example); + + $shadowMapper = $this->createShadowDataMapperInstance(); + $shadowMapper->map($unlocalizedDimensionContent, $localizedDimensionContent, $data); + + $this->assertSame('en', $localizedDimensionContent->getShadowLocale()); + } + + public function testMapDataNull(): void + { + $data = [ + 'shadowOn' => null, + 'shadowLocale' => null, + ]; + + $example = new Example(); + $unlocalizedDimensionContent = new ExampleDimensionContent($example); + $localizedDimensionContent = new ExampleDimensionContent($example); + $localizedDimensionContent->setShadowLocale(null); + + $shadowMapper = $this->createShadowDataMapperInstance(); + $shadowMapper->map($unlocalizedDimensionContent, $localizedDimensionContent, $data); + + $this->assertNull($localizedDimensionContent->getShadowLocale()); + } +} diff --git a/Tests/Unit/Content/Application/ContentMerger/Merger/ShadowMergerTest.php b/Tests/Unit/Content/Application/ContentMerger/Merger/ShadowMergerTest.php new file mode 100644 index 00000000..b35008ee --- /dev/null +++ b/Tests/Unit/Content/Application/ContentMerger/Merger/ShadowMergerTest.php @@ -0,0 +1,92 @@ +getShadowMergerInstance(); + + $source = $this->prophesize(DimensionContentInterface::class); + + $target = $this->prophesize(DimensionContentInterface::class); + $target->willImplement(ShadowInterface::class); + $target->setShadowLocale(Argument::any())->shouldNotBeCalled(); + + $merger->merge($target->reveal(), $source->reveal()); + } + + public function testMergeTargetNotImplementShadowInterface(): void + { + $merger = $this->getShadowMergerInstance(); + + $source = $this->prophesize(DimensionContentInterface::class); + $source->willImplement(ShadowInterface::class); + $source->getShadowLocale(Argument::any())->shouldNotBeCalled(); + + $target = $this->prophesize(DimensionContentInterface::class); + + $merger->merge($target->reveal(), $source->reveal()); + } + + public function testMergeSet(): void + { + $merger = $this->getShadowMergerInstance(); + + $source = $this->prophesize(DimensionContentInterface::class); + $source->willImplement(ShadowInterface::class); + $source->getShadowLocale()->willReturn('en')->shouldBeCalled(); + $source->getShadowLocales()->willReturn(['de' => 'en'])->shouldBeCalled(); + + $target = $this->prophesize(DimensionContentInterface::class); + $target->willImplement(ShadowInterface::class); + $target->setShadowLocale('en')->shouldBeCalled(); + $target->addShadowLocale('de', 'en')->shouldBeCalled(); + + $merger->merge($target->reveal(), $source->reveal()); + } + + public function testMergeNotSet(): void + { + $merger = $this->getShadowMergerInstance(); + + $source = $this->prophesize(DimensionContentInterface::class); + $source->willImplement(ShadowInterface::class); + $source->getShadowLocale()->willReturn(null)->shouldBeCalled(); + $source->getShadowLocales()->willReturn(null)->shouldBeCalled(); + + $target = $this->prophesize(DimensionContentInterface::class); + $target->willImplement(ShadowInterface::class); + $target->setShadowLocale(Argument::any())->shouldNotBeCalled(); + $target->addShadowLocale(Argument::any())->shouldNotBeCalled(); + + $merger->merge($target->reveal(), $source->reveal()); + } +} diff --git a/Tests/Unit/Content/Application/ContentNormalizer/Normalizer/ShadowNormalizerTest.php b/Tests/Unit/Content/Application/ContentNormalizer/Normalizer/ShadowNormalizerTest.php new file mode 100644 index 00000000..9567018b --- /dev/null +++ b/Tests/Unit/Content/Application/ContentNormalizer/Normalizer/ShadowNormalizerTest.php @@ -0,0 +1,93 @@ +createShadowNormalizerInstance(); + $object = $this->prophesize(\stdClass::class); + + $this->assertSame( + [], + $normalizer->getIgnoredAttributes($object->reveal()) + ); + } + + public function testIgnoredAttributes(): void + { + $normalizer = $this->createShadowNormalizerInstance(); + $object = $this->prophesize(ShadowInterface::class); + + $this->assertSame( + [], + $normalizer->getIgnoredAttributes($object->reveal()) + ); + } + + public function testEnhanceNotImplementShadowInterface(): void + { + $normalizer = $this->createShadowNormalizerInstance(); + $object = $this->prophesize(\stdClass::class); + + $data = []; + + $this->assertSame( + $data, + $normalizer->enhance($object->reveal(), $data) + ); + } + + public function testEnhance(): void + { + $normalizer = $this->createShadowNormalizerInstance(); + $object = $this->prophesize(DimensionContentInterface::class); + $object->willImplement(ShadowInterface::class); + $object->getAvailableLocales()->willReturn(['en', 'de']); + $object->getShadowLocale()->willReturn('en'); + + $data = [ + 'availableLocales' => ['en', 'de'], + 'shadowLocale' => 'en', + 'shadowLocales' => ['de' => 'en'], + ]; + + $expectedResult = [ + 'availableLocales' => ['en', 'de'], + 'shadowLocale' => 'en', + 'shadowLocales' => ['de' => 'en'], + 'shadowOn' => true, + 'contentLocales' => ['en', 'de'], + ]; + + $this->assertSame( + $expectedResult, + $normalizer->enhance($object->reveal(), $data) + ); + } +} diff --git a/Tests/Unit/Content/Domain/Model/ShadowTraitTest.php b/Tests/Unit/Content/Domain/Model/ShadowTraitTest.php new file mode 100644 index 00000000..b0417a29 --- /dev/null +++ b/Tests/Unit/Content/Domain/Model/ShadowTraitTest.php @@ -0,0 +1,50 @@ +getShadowInstance(); + $this->assertNull($model->getShadowLocale()); + $model->setShadowLocale('en'); + $this->assertSame('en', $model->getShadowLocale()); + } + + public function testAddRemoveShadowLocales(): void + { + $model = $this->getShadowInstance(); + $Shadowed = new \DateTimeImmutable('2020-05-08T00:00:00+00:00'); + $this->assertNull($model->getShadowLocales()); + $model->addShadowLocale('de', 'en'); + $this->assertSame(['de' => 'en'], $model->getShadowLocales()); + $model->removeShadowLocale('de'); + $this->assertSame([], $model->getShadowLocales()); + } +} diff --git a/Tests/Unit/Content/Infrastructure/Doctrine/MetadataLoaderTest.php b/Tests/Unit/Content/Infrastructure/Doctrine/MetadataLoaderTest.php index bfb02fc1..7eef7285 100644 --- a/Tests/Unit/Content/Infrastructure/Doctrine/MetadataLoaderTest.php +++ b/Tests/Unit/Content/Infrastructure/Doctrine/MetadataLoaderTest.php @@ -26,6 +26,7 @@ use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ExcerptInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\SeoInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\ShadowInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\TemplateInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\WebspaceInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\WorkflowInterface; @@ -71,6 +72,7 @@ public function testInvalidMetadata(array $interfaces, array $fields, array $man $reflectionClass->implementsInterface(WorkflowInterface::class)->willReturn(\in_array(WorkflowInterface::class, $interfaces, true)); $reflectionClass->implementsInterface(WebspaceInterface::class)->willReturn(\in_array(WebspaceInterface::class, $interfaces, true)); $reflectionClass->implementsInterface(AuthorInterface::class)->willReturn(\in_array(AuthorInterface::class, $interfaces, true)); + $reflectionClass->implementsInterface(ShadowInterface::class)->willReturn(\in_array(ShadowInterface::class, $interfaces, true)); foreach ($interfaces as $interface) { $reflectionClass->implementsInterface($interface)->willReturn(true); @@ -262,5 +264,17 @@ public function dataProvider(): \Generator [], [], ]; + + yield [ + [ + ShadowInterface::class, + ], + [ + 'shadowLocale' => false, + 'shadowLocales' => false, + ], + [], + [], + ]; } } diff --git a/Tests/Unit/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactoryTest.php b/Tests/Unit/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactoryTest.php index 38456251..0141cc45 100644 --- a/Tests/Unit/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactoryTest.php +++ b/Tests/Unit/Content/Infrastructure/Sulu/Admin/ContentViewBuilderFactoryTest.php @@ -140,18 +140,27 @@ public function testCreateViews(): void $this->assertInstanceOf(FormViewBuilderInterface::class, $views[0]); $this->assertSame('add_parent_key.content', $views[0]->getName()); $this->assertSame(Example::TEMPLATE_TYPE, $views[0]->getView()->getOption('formKey')); + $this->assertSame('shadowOn != true', $views[0]->getView()->getOption('tabCondition')); $this->assertInstanceOf(FormViewBuilderInterface::class, $views[1]); $this->assertSame('edit_parent_key.content', $views[1]->getName()); $this->assertSame(Example::TEMPLATE_TYPE, $views[1]->getView()->getOption('formKey')); + $this->assertSame('shadowOn != true', $views[1]->getView()->getOption('tabCondition')); $this->assertInstanceOf(FormViewBuilderInterface::class, $views[2]); $this->assertSame('edit_parent_key.seo', $views[2]->getName()); $this->assertSame('content_seo', $views[2]->getView()->getOption('formKey')); + $this->assertSame('shadowOn != true', $views[2]->getView()->getOption('tabCondition')); $this->assertInstanceOf(FormViewBuilderInterface::class, $views[3]); $this->assertSame('edit_parent_key.excerpt', $views[3]->getName()); $this->assertSame('content_excerpt', $views[3]->getView()->getOption('formKey')); + $this->assertSame('shadowOn != true', $views[3]->getView()->getOption('tabCondition')); + + $this->assertInstanceOf(FormViewBuilderInterface::class, $views[4]); + $this->assertSame('edit_parent_key.settings', $views[4]->getName()); + $this->assertSame('content_settings', $views[4]->getView()->getOption('formKey')); + $this->assertNull($views[4]->getView()->getOption('tabCondition')); } public function testCreateViewsWithPreview(): void @@ -187,6 +196,7 @@ public function testCreateViewsWithPreview(): void $this->assertInstanceOf(PreviewFormViewBuilderInterface::class, $views[0]); $this->assertInstanceOf(PreviewFormViewBuilderInterface::class, $views[1]); $this->assertInstanceOf(PreviewFormViewBuilderInterface::class, $views[2]); + $this->assertInstanceOf(PreviewFormViewBuilderInterface::class, $views[3]); } /** diff --git a/UPGRADE.md b/UPGRADE.md index ae75c503..1bf0b47c 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -2,6 +2,15 @@ ## 0.7.0 +### Add shadowLocale and shadowLocales fields + +To support shadow feature of sulu shadowLocale and shadowLocales fields need to be added to +the dimension content tables. + +```sql +ALTER TABLE test_example_dimension_contents ADD shadowLocale VARCHAR(7) DEFAULT NULL, ADD shadowLocales JSON DEFAULT NULL; -- replace `test_example_dimension_contents` with your table +``` + ### Add ghostLocale and availableLocales field To support multi localization feature of sulu a ghostLocale need to be added to diff --git a/phpstan.neon b/phpstan.neon index 8422236b..137738bb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -16,6 +16,7 @@ parameters: - %currentWorkingDirectory%/vendor/* - %currentWorkingDirectory%/Tests/Application/Kernel.php - %currentWorkingDirectory%/Tests/Application/var/* + - %currentWorkingDirectory%/Tests/Application/assets/* - %currentWorkingDirectory%/Tests/Unit/Mocks/* symfony: containerXmlPath: %currentWorkingDirectory%/Tests/Application/var/cache/admin/dev/Sulu_Bundle_ContentBundle_Tests_Application_KernelDevDebugContainer.xml