diff --git a/Content/ContentTypeResolver/LinkResolver.php b/Content/ContentTypeResolver/LinkResolver.php
new file mode 100644
index 0000000..af3f005
--- /dev/null
+++ b/Content/ContentTypeResolver/LinkResolver.php
@@ -0,0 +1,106 @@
+linkProviderPool = $linkProviderPool;
+ }
+
+ public function resolve($data, PropertyInterface $property, string $locale, array $attributes = []): ContentView
+ {
+ $content = $this->getContentData($property);
+ $view = $this->getViewData($property);
+
+ return new ContentView($content, $view);
+ }
+
+ /**
+ * @return array{
+ * provider?: string,
+ * locale?: string,
+ * target?: string,
+ * title?: string
+ * }
+ */
+ private function getViewData(PropertyInterface $property): array
+ {
+ $value = $property->getValue();
+
+ if (!$value) {
+ return [];
+ }
+
+ $result = [
+ 'provider' => $value['provider'],
+ 'locale' => $value['locale'],
+ ];
+
+ if (isset($value['target'])) {
+ $result['target'] = $value['target'];
+ }
+
+ if (isset($value['title'])) {
+ $result['title'] = $value['title'];
+ }
+
+ return $result;
+ }
+
+ private function getContentData(PropertyInterface $property): ?string
+ {
+ $value = $property->getValue();
+
+ if (!$value || !isset($value['provider'])) {
+ return null;
+ }
+
+ if (Link::LINK_TYPE_EXTERNAL === $value['provider']) {
+ return $value['href'];
+ }
+
+ $provider = $this->linkProviderPool->getProvider($value['provider']);
+
+ $linkItems = $provider->preload([$value['href']], $value['locale']);
+
+ if (0 === \count($linkItems)) {
+ return null;
+ }
+
+ $url = reset($linkItems)->getUrl();
+ if (isset($value['anchor'])) {
+ $url = sprintf('%s#%s', $url, $value['anchor']);
+ }
+
+ return $url;
+ }
+}
diff --git a/Resources/config/content-type-resolvers.xml b/Resources/config/content-type-resolvers.xml
index 79dd76b..f321d03 100644
--- a/Resources/config/content-type-resolvers.xml
+++ b/Resources/config/content-type-resolvers.xml
@@ -180,6 +180,16 @@
+
+
+
+
+
+
*/
- protected function getKernelParameters()
+ protected function getKernelParameters(): array
{
$parameters = parent::getKernelParameters();
diff --git a/Tests/Unit/Content/ContentTypeResolver/LinkResolverTest.php b/Tests/Unit/Content/ContentTypeResolver/LinkResolverTest.php
new file mode 100644
index 0000000..c70ff13
--- /dev/null
+++ b/Tests/Unit/Content/ContentTypeResolver/LinkResolverTest.php
@@ -0,0 +1,116 @@
+prophesize(LinkProviderPoolInterface::class);
+ $linkResolver = new LinkResolver($linkProviderPool->reveal());
+
+ self::assertSame('link', $linkResolver::getContentType());
+ }
+
+ public function testResolve(): void
+ {
+ $providerPool = $this->prophesize(LinkProviderPoolInterface::class);
+ $provider = $this->prophesize(LinkProviderInterface::class);
+ $linkResolver = new LinkResolver($providerPool->reveal());
+
+ $property = $this->prophesize(PropertyInterface::class);
+ $property->getValue()
+ ->shouldBeCalled()
+ ->willReturn([
+ 'provider' => 'page',
+ 'target' => '_self',
+ 'anchor' => 'link',
+ 'href' => '76fcf58e-0624-4cf0-85a5-170de9f14252',
+ 'title' => 'Internal Link',
+ 'locale' => 'en',
+ ]);
+
+ $providerPool->getProvider(Argument::type('string'))
+ ->shouldBeCalled()
+ ->willReturn($provider->reveal());
+
+ $linkItem = new LinkItem(
+ '76fcf58e-0624-4cf0-85a5-170de9f14252',
+ 'Internal Link',
+ 'https://example.lo/link',
+ true
+ );
+
+ $provider->preload(['76fcf58e-0624-4cf0-85a5-170de9f14252'], 'en')
+ ->shouldBeCalled()
+ ->willReturn([$linkItem]);
+
+ $result = $linkResolver->resolve([], $property->reveal(), 'en');
+
+ $this->assertSame('https://example.lo/link#link', $result->getContent());
+ $this->assertSame([
+ 'provider' => 'page',
+ 'locale' => 'en',
+ 'target' => '_self',
+ 'title' => 'Internal Link',
+ ], $result->getView());
+ }
+
+ public function testResolveMinimal(): void
+ {
+ $providerPool = $this->prophesize(LinkProviderPoolInterface::class);
+ $provider = $this->prophesize(LinkProviderInterface::class);
+ $linkResolver = new LinkResolver($providerPool->reveal());
+
+ $property = $this->prophesize(PropertyInterface::class);
+ $property->getValue()
+ ->shouldBeCalled()
+ ->willReturn([
+ 'provider' => 'page',
+ 'href' => '76fcf58e-0624-4cf0-85a5-170de9f14252',
+ 'locale' => 'en',
+ ]);
+
+ $providerPool->getProvider(Argument::type('string'))
+ ->shouldBeCalled()
+ ->willReturn($provider->reveal());
+
+ $linkItem = new LinkItem(
+ '76fcf58e-0624-4cf0-85a5-170de9f14252',
+ 'Internal Link',
+ 'https://example.lo/link',
+ true
+ );
+
+ $provider->preload(['76fcf58e-0624-4cf0-85a5-170de9f14252'], 'en')
+ ->shouldBeCalled()
+ ->willReturn([$linkItem]);
+
+ $result = $linkResolver->resolve([], $property->reveal(), 'en');
+
+ $this->assertSame('https://example.lo/link', $result->getContent());
+ $this->assertSame([
+ 'provider' => 'page',
+ 'locale' => 'en',
+ ], $result->getView());
+ }
+}
diff --git a/composer.json b/composer.json
index 8d7b6e9..503cb7a 100644
--- a/composer.json
+++ b/composer.json
@@ -5,7 +5,7 @@
"description": "Bundle that provides controllers and services for using Sulu as headless content management system",
"require": {
"php": "^7.3 || ^8.0",
- "sulu/sulu": "^2.2 || ^2.3@dev",
+ "sulu/sulu": "^2.4",
"symfony/config": "^4.4 || ^5.0",
"symfony/dependency-injection": "^4.4 || ^5.0",
"symfony/framework-bundle": "^4.4 || ^5.0",