diff --git a/src/Sulu/Bundle/CategoryBundle/DependencyInjection/SuluCategoryExtension.php b/src/Sulu/Bundle/CategoryBundle/DependencyInjection/SuluCategoryExtension.php
index ba92d22b2ab..1c25abefa8e 100644
--- a/src/Sulu/Bundle/CategoryBundle/DependencyInjection/SuluCategoryExtension.php
+++ b/src/Sulu/Bundle/CategoryBundle/DependencyInjection/SuluCategoryExtension.php
@@ -50,9 +50,9 @@ public function load(array $configs, ContainerBuilder $container)
}
if (
- InstalledVersions::isInstalled('sulu/sulu-content-bundle')
- && \version_compare(InstalledVersions::getVersion('sulu/sulu-content-bundle') ?? '0.0.0', '0.9', '>=')
- && \version_compare(InstalledVersions::getVersion('sulu/sulu-content-bundle') ?? '0.0.0', '0.10', '<')
+ InstalledVersions::isInstalled('sulu/content-bundle')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.9', '>=')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.10', '<')
) {
$loader->load('services_content.xml');
}
diff --git a/src/Sulu/Bundle/CategoryBundle/Infrastructure/Sulu/Content/PropertyResolver/SingleCategorySelectionPropertyResolver.php b/src/Sulu/Bundle/CategoryBundle/Infrastructure/Sulu/Content/PropertyResolver/SingleCategorySelectionPropertyResolver.php
new file mode 100644
index 00000000000..dc8643245c9
--- /dev/null
+++ b/src/Sulu/Bundle/CategoryBundle/Infrastructure/Sulu/Content/PropertyResolver/SingleCategorySelectionPropertyResolver.php
@@ -0,0 +1,47 @@
+
-
+
diff --git a/src/Sulu/Bundle/ContactBundle/DependencyInjection/SuluContactExtension.php b/src/Sulu/Bundle/ContactBundle/DependencyInjection/SuluContactExtension.php
index ac2fb1c022f..30071c17aa1 100644
--- a/src/Sulu/Bundle/ContactBundle/DependencyInjection/SuluContactExtension.php
+++ b/src/Sulu/Bundle/ContactBundle/DependencyInjection/SuluContactExtension.php
@@ -279,9 +279,9 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('command.xml');
if (
- InstalledVersions::isInstalled('sulu/sulu-content-bundle')
- && \version_compare(InstalledVersions::getVersion('sulu/sulu-content-bundle') ?? '0.0.0', '0.9', '>=')
- && \version_compare(InstalledVersions::getVersion('sulu/sulu-content-bundle') ?? '0.0.0', '0.10', '<')
+ InstalledVersions::isInstalled('sulu/content-bundle')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.9', '>=')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.10', '<')
) {
$loader->load('services_content.xml');
}
diff --git a/src/Sulu/Bundle/ContactBundle/Resources/config/services_content.xml b/src/Sulu/Bundle/ContactBundle/Resources/config/services_content.xml
index 4bff28f6397..a3d72d9799a 100644
--- a/src/Sulu/Bundle/ContactBundle/Resources/config/services_content.xml
+++ b/src/Sulu/Bundle/ContactBundle/Resources/config/services_content.xml
@@ -27,13 +27,13 @@
-
+
-
+
diff --git a/src/Sulu/Bundle/MediaBundle/DependencyInjection/SuluMediaExtension.php b/src/Sulu/Bundle/MediaBundle/DependencyInjection/SuluMediaExtension.php
index 2d2e1fc77e7..6a59c12452a 100644
--- a/src/Sulu/Bundle/MediaBundle/DependencyInjection/SuluMediaExtension.php
+++ b/src/Sulu/Bundle/MediaBundle/DependencyInjection/SuluMediaExtension.php
@@ -366,9 +366,9 @@ public function load(array $configs, ContainerBuilder $container)
}
if (
- InstalledVersions::isInstalled('sulu/sulu-content-bundle')
- && \version_compare(InstalledVersions::getVersion('sulu/sulu-content-bundle') ?? '0.0.0', '0.9', '>=')
- && \version_compare(InstalledVersions::getVersion('sulu/sulu-content-bundle') ?? '0.0.0', '0.10', '<')
+ InstalledVersions::isInstalled('sulu/content-bundle')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.9', '>=')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.10', '<')
) {
$loader->load('services_content.xml');
}
diff --git a/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/CollectionSelectionPropertyResolver.php b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/CollectionSelectionPropertyResolver.php
new file mode 100644
index 00000000000..0e53347c263
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/CollectionSelectionPropertyResolver.php
@@ -0,0 +1,50 @@
+ [], ...$params]);
+ }
+
+ /** @var string $resourceLoaderKey */
+ $resourceLoaderKey = $params['resourceLoader'] ?? CollectionResourceLoader::getKey();
+
+ return ContentView::createResolvables(
+ $data,
+ $resourceLoaderKey,
+ ['ids' => $data, ...$params],
+ );
+ }
+
+ public static function getType(): string
+ {
+ return 'collection_selection';
+ }
+}
diff --git a/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/ImageMapPropertyResolver.php b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/ImageMapPropertyResolver.php
new file mode 100644
index 00000000000..49cd053ab2f
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/ImageMapPropertyResolver.php
@@ -0,0 +1,155 @@
+metadataResolver = $metadataResolver;
+ }
+
+ public function resolve(mixed $data, string $locale, array $params = []): ContentView
+ {
+ $hotspots = (\is_array($data) && isset($data['hotspots']) && \is_array($data['hotspots'])) && \array_is_list($data['hotspots'])
+ ? $data['hotspots']
+ : [];
+
+ $hotspots = [] !== $hotspots ? $this->resolveHotspots($hotspots, $locale, $params) : ContentView::create([], []);
+
+ $returnedParams = $params;
+ unset($returnedParams['metadata']); // TODO we may should implement a PropertyResolverAwareMetadataInterface
+
+ if (!\is_array($data)
+ || !isset($data['imageId'])
+ || !\is_numeric($data['imageId'])
+ ) {
+ return ContentView::create([
+ 'image' => null,
+ 'hotspots' => $hotspots->getContent(),
+ ], [
+ 'imageId' => null,
+ 'hotspots' => $hotspots->getView(),
+ ...$returnedParams,
+ ]);
+ }
+
+ /** @var string $resourceLoaderKey */
+ $resourceLoaderKey = $params['resourceLoader'] ?? MediaResourceLoader::getKey();
+ $imageId = (int) $data['imageId'];
+
+ return ContentView::create(
+ [
+ 'image' => new ResolvableResource($imageId, $resourceLoaderKey),
+ 'hotspots' => $hotspots->getContent(),
+ ],
+ [
+ 'imageId' => $imageId,
+ 'hotspots' => $hotspots->getView(),
+ ...$returnedParams,
+ ],
+ );
+ }
+
+ /**
+ * @param non-empty-array> $hotspots
+ * @param array $params
+ */
+ private function resolveHotspots(array $hotspots, string $locale, array $params): ContentView
+ {
+ $metadata = $params['metadata'] ?? null;
+ \assert($metadata instanceof FieldMetadata, 'Metadata must be set to resolve hotspots.');
+ $metadataTypes = $metadata->getTypes();
+ $content = [];
+ $view = [];
+ foreach ($hotspots as $key => $block) {
+ if (!\is_array($block) || !isset($block['type']) || !\is_string($block['type'])) {
+ continue;
+ }
+ if (!isset($block['hotspot']) || !\is_array($block['hotspot'])) {
+ continue;
+ }
+
+ $type = $block['type'];
+ $formMetadata = $metadataTypes[$type] ?? null;
+
+ if (!$formMetadata instanceof FormMetadata) {
+ $errorMessage = \sprintf(
+ 'Metadata type "%s" in "%s" not found, founded types are: "%s"',
+ $type,
+ $metadata->getName(),
+ \implode('", "', \array_keys($metadataTypes)),
+ );
+
+ $this->logger->error($errorMessage);
+
+ if ($this->debug) {
+ throw new \UnexpectedValueException($errorMessage);
+ }
+
+ $type = $metadata->getDefaultType();
+ $formMetadata = $metadataTypes[$type] ?? null;
+ if (!$formMetadata instanceof FormMetadata) {
+ continue;
+ }
+ }
+
+ $content[$key] = [
+ 'type' => $type,
+ 'hotspot' => $block['hotspot'],
+ ];
+
+ $view[$key] = [];
+
+ foreach ($this->metadataResolver->resolveItems($formMetadata->getItems(), $block, $locale) as $field => $resolvedItem) {
+ $content[$key][$field] = $resolvedItem->getContent();
+ $view[$key][$field] = $resolvedItem->getView();
+ }
+ }
+
+ return ContentView::create(\array_values($content), \array_values($view));
+ }
+
+ public static function getType(): string
+ {
+ return 'image_map';
+ }
+}
diff --git a/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/SingleCollectionSelectionPropertyResolver.php b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/SingleCollectionSelectionPropertyResolver.php
new file mode 100644
index 00000000000..20c832ea19c
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/PropertyResolver/SingleCollectionSelectionPropertyResolver.php
@@ -0,0 +1,50 @@
+ null, ...$params]);
+ }
+
+ /** @var string $resourceLoaderKey */
+ $resourceLoaderKey = $params['resourceLoader'] ?? CollectionResourceLoader::getKey();
+
+ return ContentView::createResolvable(
+ (int) $data,
+ $resourceLoaderKey,
+ [
+ 'id' => $data,
+ ...$params,
+ ],
+ );
+ }
+
+ public static function getType(): string
+ {
+ return 'single_collection_selection';
+ }
+}
diff --git a/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/ResourceLoader/CollectionResourceLoader.php b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/ResourceLoader/CollectionResourceLoader.php
new file mode 100644
index 00000000000..62471f3545c
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Infrastructure/Sulu/Content/ResourceLoader/CollectionResourceLoader.php
@@ -0,0 +1,53 @@
+collectionManager->getById($id, $locale); // TODO load all over one query
+ $mappedResult[$collection->getId()] = $collection;
+ } catch (CollectionNotFoundException $e) {
+ // @ignoreException: do not crash page if selected collection is deleted
+ }
+ }
+
+ return $mappedResult;
+ }
+
+ public static function getKey(): string
+ {
+ return self::RESOURCE_LOADER_KEY;
+ }
+}
diff --git a/src/Sulu/Bundle/MediaBundle/Resources/config/services_content.xml b/src/Sulu/Bundle/MediaBundle/Resources/config/services_content.xml
index 940c01ca212..36fb019b9dd 100644
--- a/src/Sulu/Bundle/MediaBundle/Resources/config/services_content.xml
+++ b/src/Sulu/Bundle/MediaBundle/Resources/config/services_content.xml
@@ -4,13 +4,26 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
+
+
+
+
+
+
+
+
+
+ %kernel.debug%
+
-
diff --git a/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/CollectionSelectionPropertyResolverTest.php b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/CollectionSelectionPropertyResolverTest.php
new file mode 100644
index 00000000000..2c5af0f4e04
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/CollectionSelectionPropertyResolverTest.php
@@ -0,0 +1,114 @@
+resolver = new CollectionSelectionPropertyResolver();
+ }
+
+ public function testResolveEmpty(): void
+ {
+ $contentView = $this->resolver->resolve([], 'en');
+
+ $this->assertSame([], $contentView->getContent());
+ $this->assertSame(['ids' => []], $contentView->getView());
+ }
+
+ public function testResolveParams(): void
+ {
+ $contentView = $this->resolver->resolve([], 'en', ['custom' => 'params']);
+
+ $this->assertSame([], $contentView->getContent());
+ $this->assertSame([
+ 'ids' => [],
+ 'custom' => 'params',
+ ], $contentView->getView());
+ }
+
+ #[DataProvider('provideUnresolvableData')]
+ public function testResolveUnresolvableData(mixed $data): void
+ {
+ $contentView = $this->resolver->resolve($data, 'en');
+
+ $this->assertSame([], $contentView->getContent());
+ $this->assertSame(['ids' => []], $contentView->getView());
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideUnresolvableData(): iterable
+ {
+ yield 'null' => [null];
+ yield 'smart_content' => [['source' => '123']];
+ yield 'single_value' => [1];
+ yield 'object' => [(object) [1, 2]];
+ }
+
+ /**
+ * @param array $data
+ */
+ #[DataProvider('provideResolvableData')]
+ public function testResolveResolvableData(array $data): void
+ {
+ $contentView = $this->resolver->resolve($data, 'en');
+
+ $content = $contentView->getContent();
+ $this->assertIsArray($content);
+ foreach ($data as $key => $value) {
+ $resolvable = $content[$key] ?? null;
+ $this->assertInstanceOf(ResolvableResource::class, $resolvable);
+ $this->assertSame($value, $resolvable->getId());
+ $this->assertSame('collection', $resolvable->getResourceLoaderKey());
+ }
+
+ $this->assertSame(['ids' => $data], $contentView->getView());
+ }
+
+ /**
+ * @return iterable,
+ * }>
+ */
+ public static function provideResolvableData(): iterable
+ {
+ yield 'empty' => [[]];
+ yield 'int_list' => [[1, 2]];
+ yield 'string_list' => [['1', '2']];
+ }
+
+ public function testCustomResourceLoader(): void
+ {
+ $contentView = $this->resolver->resolve([1], 'en', ['resourceLoader' => 'custom_collection']);
+
+ $content = $contentView->getContent();
+ $this->assertIsArray($content);
+ $resolvable = $content[0] ?? null;
+ $this->assertInstanceOf(ResolvableResource::class, $resolvable);
+ $this->assertSame(1, $resolvable->getId());
+ $this->assertSame('custom_collection', $resolvable->getResourceLoaderKey());
+ }
+}
diff --git a/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/ImageMapPropertyResolverTest.php b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/ImageMapPropertyResolverTest.php
new file mode 100644
index 00000000000..8f8d3b06429
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/ImageMapPropertyResolverTest.php
@@ -0,0 +1,261 @@
+logger = new BufferingLogger();
+ $this->resolver = new ImageMapPropertyResolver(
+ $this->logger,
+ debug: false,
+ );
+ $metadataResolverProperty = new PropertyResolverProvider([
+ 'default' => new DefaultPropertyResolver(),
+ ]);
+ $metadataResolver = new MetadataResolver($metadataResolverProperty);
+ $this->resolver->setMetadataResolver($metadataResolver);
+ }
+
+ public function testResolveEmpty(): void
+ {
+ $contentView = $this->resolver->resolve(null, 'en');
+
+ $this->assertSame(['image' => null, 'hotspots' => []], $contentView->getContent());
+ $this->assertSame(['imageId' => null, 'hotspots' => []], $contentView->getView());
+ $this->assertCount(0, $this->logger->cleanLogs());
+ }
+
+ public function testResolveParams(): void
+ {
+ $contentView = $this->resolver->resolve(null, 'en', ['custom' => 'params']);
+
+ $this->assertSame(['image' => null, 'hotspots' => []], $contentView->getContent());
+ $this->assertSame([
+ 'imageId' => null,
+ 'hotspots' => [],
+ 'custom' => 'params',
+ ], $contentView->getView());
+ $this->assertCount(0, $this->logger->cleanLogs());
+ }
+
+ #[DataProvider('provideUnresolvableData')]
+ public function testResolveUnresolvableData(mixed $data): void
+ {
+ $contentView = $this->resolver->resolve($data, 'en');
+
+ $this->assertSame(['image' => null, 'hotspots' => []], $contentView->getContent());
+ $this->assertSame(['imageId' => null, 'hotspots' => []], $contentView->getView());
+ $this->assertCount(0, $this->logger->cleanLogs());
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideUnresolvableData(): iterable
+ {
+ yield 'null' => [null];
+ yield 'smart_content' => [['source' => '123']];
+ yield 'single_value' => [1];
+ yield 'object' => [(object) [1, 2]];
+ yield 'int_list_not_in_ids' => [[1, 2]];
+ yield 'ids_null' => [['ids' => null]];
+ yield 'ids_list' => [['ids' => [1, 2]]];
+ yield 'id_list' => [['id' => [1, 2]]];
+ yield 'non_numeric_image_id' => [['imageId' => 'a']];
+ }
+
+ /**
+ * @param array{
+ * imageId?: string|int,
+ * hotspots?: array,
+ * } $data
+ */
+ #[DataProvider('provideResolvableData')]
+ public function testResolveResolvableData(array $data): void
+ {
+ $contentView = $this->resolver->resolve($data, 'en', ['metadata' => $this->createMetadata()]);
+
+ $content = $contentView->getContent();
+ $this->assertIsArray($content);
+ $imageId = $data['imageId'] ?? null;
+ if (null !== $imageId) {
+ $imageId = (int) $imageId;
+ $image = $content['image'] ?? null;
+ $this->assertInstanceOf(ResolvableResource::class, $image);
+ $this->assertSame($imageId, $image->getId());
+ $this->assertSame('media', $image->getResourceLoaderKey());
+ }
+
+ $hotspots = $content['hotspots'] ?? [];
+ $this->assertIsArray($hotspots);
+ $expectedView = [];
+ foreach (($data['hotspots'] ?? []) as $key => $hotspot) {
+ $hotspot = $hotspots[$key] ?? null;
+ $this->assertIsArray($hotspot);
+ $this->assertSame($data['hotspots'][$key], $hotspot);
+ $expectedView[] = ['title' => []];
+ }
+
+ $this->assertSame([
+ 'imageId' => $imageId,
+ 'hotspots' => $expectedView,
+ ], $contentView->getView());
+
+ $this->assertCount(0, $this->logger->cleanLogs());
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideResolvableData(): iterable
+ {
+ yield 'empty' => [[]];
+ yield 'int_id' => [['imageId' => 1]];
+ yield 'int_id_with_hotspots' => [
+ ['imageId' => 1, 'hotspots' => [['type' => 'text', 'hotspot' => ['type' => 'circle'], 'title' => 'Title 1'], ['type' => 'text', 'hotspot' => ['type' => 'circle'], 'title' => 'Title 2']]],
+ ];
+ yield 'string_id' => [['imageId' => '1']];
+ yield 'string_id_with_hotspots' => [['imageId' => '1', 'hotspots' => [['type' => 'text', 'hotspot' => ['type' => 'circle'], 'title' => 'Title 1'], ['type' => 'text', 'hotspot' => ['type' => 'circle'], 'title' => 'Title 2']]]];
+ }
+
+ public function testCustomResourceLoader(): void
+ {
+ $contentView = $this->resolver->resolve(
+ ['imageId' => 1, 'hotspots' => [['type' => 'text', 'title' => 'Title'], ['type' => 'text', 'title' => 'Title']]],
+ 'en',
+ [
+ 'metadata' => $this->createMetadata(),
+ 'resourceLoader' => 'custom_media',
+ ]
+ );
+
+ $content = $contentView->getContent();
+ $this->assertIsArray($content);
+ $image = $content['image'] ?? null;
+ $this->assertInstanceOf(ResolvableResource::class, $image);
+ $this->assertSame(1, $image->getId());
+ $this->assertSame('custom_media', $image->getResourceLoaderKey());
+
+ $this->assertSame([
+ 'imageId' => 1,
+ 'hotspots' => [],
+ 'resourceLoader' => 'custom_media',
+ ], $contentView->getView());
+ $this->assertCount(0, $this->logger->cleanLogs());
+ }
+
+ /**
+ * @param array{
+ * imageId: int,
+ * hotspots?: array,
+ * } $data
+ */
+ #[DataProvider('provideUnresolvableHotspotData')]
+ public function testResolveUnresolvableHotspotData(mixed $data): void
+ {
+ $contentView = $this->resolver->resolve($data, 'en', ['metadata' => $this->createMetadata()]);
+
+ $content = $contentView->getContent();
+ $this->assertIsArray($content);
+ $image = $content['image'] ?? null;
+ $this->assertInstanceOf(ResolvableResource::class, $image);
+ $this->assertSame(1, $image->getId());
+ $this->assertSame('media', $image->getResourceLoaderKey());
+ $hotspots = $content['hotspots'] ?? null;
+ $this->assertIsArray($hotspots);
+
+ $expectedView = [];
+ $expectedCount = \count($data['hotspots'] ?? []);
+ $expectedErrorLogs = 0;
+ foreach ($data['hotspots'] ?? [] as $hotspot) {
+ if (!isset($hotspot['type'])) {
+ --$expectedCount;
+ continue;
+ }
+ if (!isset($hotspot['hotspot'])) {
+ --$expectedCount;
+ continue;
+ }
+ ++$expectedErrorLogs;
+ $expectedView[] = ['title' => []];
+ }
+
+ $this->assertCount($expectedCount, $hotspots);
+
+ $this->assertSame(['imageId' => 1, 'hotspots' => $expectedView], $contentView->getView());
+ $logs = $this->logger->cleanLogs();
+ $this->assertCount($expectedErrorLogs, $logs);
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideUnresolvableHotspotData(): iterable
+ {
+ yield 'hotspot_with_not_exist_type' => [['imageId' => 1, 'hotspots' => [['type' => 'not_exist', 'hotspot' => ['type' => 'circle'], 'title' => 'Title']]]];
+ yield 'hotspot_with_no_type' => [['imageId' => 1, 'hotspots' => [['hotspot' => ['type' => 'circle'], 'title' => 'Title']]]];
+ yield 'hotspot_with_no_hotspot' => [['imageId' => 1, 'hotspots' => [['type' => 'not_exist', 'title' => 'Title']]]];
+ }
+
+ private function createMetadata(): FieldMetadata
+ {
+ $fieldMetadata = new FieldMetadata('image');
+ $fieldMetadata->setType('image_map');
+ $fieldMetadata->setDefaultType('text');
+
+ $textFormMetadata = new FormMetadata();
+ $textFormMetadata->setName('text');
+ $itemMetadata = new FieldMetadata('title');
+ $itemMetadata->setType('text_line');
+ $textFormMetadata->addItem($itemMetadata);
+
+ $fieldMetadata->addType($textFormMetadata);
+
+ return $fieldMetadata;
+ }
+}
diff --git a/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/SingleCollectionSelectionPropertyResolverTest.php b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/SingleCollectionSelectionPropertyResolverTest.php
new file mode 100644
index 00000000000..9b4d8b77f30
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/PropertyResolver/SingleCollectionSelectionPropertyResolverTest.php
@@ -0,0 +1,105 @@
+resolver = new SingleCollectionSelectionPropertyResolver();
+ }
+
+ public function testResolveEmpty(): void
+ {
+ $contentView = $this->resolver->resolve(null, 'en');
+
+ $this->assertNull($contentView->getContent());
+ $this->assertSame(['id' => null], $contentView->getView());
+ }
+
+ public function testResolveParams(): void
+ {
+ $contentView = $this->resolver->resolve(null, 'en', ['custom' => 'params']);
+
+ $this->assertNull($contentView->getContent());
+ $this->assertSame([
+ 'id' => null,
+ 'custom' => 'params',
+ ], $contentView->getView());
+ }
+
+ #[DataProvider('provideUnresolvableData')]
+ public function testResolveUnresolvableData(mixed $data): void
+ {
+ $contentView = $this->resolver->resolve($data, 'en');
+
+ $this->assertNull($contentView->getContent());
+ $this->assertSame(['id' => null], $contentView->getView());
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideUnresolvableData(): iterable
+ {
+ yield 'null' => [null];
+ yield 'smart_content' => [['source' => '123']];
+ yield 'multi_value' => [[1]];
+ yield 'object' => [(object) [1]];
+ }
+
+ #[DataProvider('provideResolvableData')]
+ public function testResolveResolvableData(int|string $data): void
+ {
+ $contentView = $this->resolver->resolve($data, 'en');
+
+ $content = $contentView->getContent();
+ $this->assertInstanceOf(ResolvableResource::class, $content);
+ $this->assertSame((int) $data, $content->getId());
+ $this->assertSame('collection', $content->getResourceLoaderKey());
+
+ $this->assertSame(['id' => $data], $contentView->getView());
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideResolvableData(): iterable
+ {
+ yield 'int' => [1];
+ yield 'string' => ['2'];
+ }
+
+ public function testCustomResourceLoader(): void
+ {
+ $contentView = $this->resolver->resolve(1, 'en', ['resourceLoader' => 'custom_collection']);
+
+ $content = $contentView->getContent();
+
+ $this->assertInstanceOf(ResolvableResource::class, $content);
+ $this->assertSame(1, $content->getId());
+ $this->assertSame('custom_collection', $content->getResourceLoaderKey());
+ }
+}
diff --git a/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/ResourceLoader/CollectionResourceLoaderTest.php b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/ResourceLoader/CollectionResourceLoaderTest.php
new file mode 100644
index 00000000000..d705d047479
--- /dev/null
+++ b/src/Sulu/Bundle/MediaBundle/Tests/Unit/Infrastructure/Sulu/Content/ResourceLoader/CollectionResourceLoaderTest.php
@@ -0,0 +1,72 @@
+
+ */
+ private ObjectProphecy $collectionManager;
+
+ private CollectionResourceLoader $loader;
+
+ public function setUp(): void
+ {
+ $this->collectionManager = $this->prophesize(CollectionManagerInterface::class);
+ $this->loader = new CollectionResourceLoader($this->collectionManager->reveal());
+ }
+
+ public function testGetKey(): void
+ {
+ $this->assertSame('collection', $this->loader::getKey());
+ }
+
+ public function testLoad(): void
+ {
+ $collection1 = $this->createCollection(1);
+ $collection3 = $this->createCollection(3);
+
+ $this->collectionManager->getById(1, 'en')->willReturn($collection1)
+ ->shouldBeCalled();
+
+ $this->collectionManager->getById(3, 'en')->willReturn($collection3)
+ ->shouldBeCalled();
+
+ $result = $this->loader->load([1, 3], 'en', []);
+
+ $this->assertSame([
+ 1 => $collection1,
+ 3 => $collection3,
+ ], $result);
+ }
+
+ private static function createCollection(int $id): ApiCollection
+ {
+ $collection = new Collection();
+ static::setPrivateProperty($collection, 'id', $id);
+
+ return new ApiCollection($collection, 'en');
+ }
+}
diff --git a/src/Sulu/Bundle/TagBundle/DependencyInjection/SuluTagExtension.php b/src/Sulu/Bundle/TagBundle/DependencyInjection/SuluTagExtension.php
index 5d80b0b6f0d..c9cf45494da 100644
--- a/src/Sulu/Bundle/TagBundle/DependencyInjection/SuluTagExtension.php
+++ b/src/Sulu/Bundle/TagBundle/DependencyInjection/SuluTagExtension.php
@@ -11,6 +11,7 @@
namespace Sulu\Bundle\TagBundle\DependencyInjection;
+use Composer\InstalledVersions;
use Sulu\Bundle\PersistenceBundle\DependencyInjection\PersistenceExtensionTrait;
use Sulu\Bundle\TagBundle\Tag\TagInterface;
use Sulu\Bundle\TagBundle\Tag\TagRepositoryInterface;
@@ -93,5 +94,13 @@ public function load(array $configs, ContainerBuilder $container)
if (\array_key_exists('SuluTrashBundle', $bundles)) {
$loader->load('services_trash.xml');
}
+
+ if (
+ InstalledVersions::isInstalled('sulu/content-bundle')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.9', '>=')
+ && \version_compare(InstalledVersions::getVersion('sulu/content-bundle') ?? '0.0.0', '0.10', '<')
+ ) {
+ $loader->load('services_content.xml');
+ }
}
}
diff --git a/src/Sulu/Bundle/TagBundle/Resources/config/services_content.xml b/src/Sulu/Bundle/TagBundle/Resources/config/services_content.xml
new file mode 100644
index 00000000000..f51e37ec2df
--- /dev/null
+++ b/src/Sulu/Bundle/TagBundle/Resources/config/services_content.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+