diff --git a/applications/common/backend/src/main/java/pl/ds/kyanite/common/components/utils/LinkUtil.java b/applications/common/backend/src/main/java/pl/ds/kyanite/common/components/utils/LinkUtil.java index 6e0bec14a..5dce33b02 100644 --- a/applications/common/backend/src/main/java/pl/ds/kyanite/common/components/utils/LinkUtil.java +++ b/applications/common/backend/src/main/java/pl/ds/kyanite/common/components/utils/LinkUtil.java @@ -26,9 +26,11 @@ public class LinkUtil { - private static final String ANCHOR_LINK_PREFIX = "#"; public static final String PUBLISHED = "/published"; public static final String CONTENT = "/content"; + private static final String ANCHOR_LINK_PREFIX = "#"; + private static final String KYANITE_WEB_RESOURCE_ROOT_PATH = "/libs/kyanite/webroot/"; + private LinkUtil() { // no instance @@ -66,7 +68,7 @@ public static boolean isAnchorLink(String link) { } public static boolean isInternal(String link, ResourceResolver resourceResolver) { - return Objects.nonNull(getResource(link, resourceResolver)); + return Objects.nonNull(getResource(link, resourceResolver)) && !isWebResource(link); } private static boolean isAsset(String link, ResourceResolver resourceResolver) { @@ -105,4 +107,8 @@ private static String getPrimaryType(Resource resource) { } return resource.getValueMap().get("jcr:primaryType", String.class); } -} \ No newline at end of file + + private static boolean isWebResource(final String link) { + return StringUtils.startsWith(link, KYANITE_WEB_RESOURCE_ROOT_PATH); + } +} diff --git a/applications/common/backend/src/main/resources/libs/kyanite/common/components/layouts/bentobox/2in1row/template/.content.json b/applications/common/backend/src/main/resources/libs/kyanite/common/components/layouts/bentobox/2in1row/template/.content.json index 9c5a1c4a2..7124be369 100644 --- a/applications/common/backend/src/main/resources/libs/kyanite/common/components/layouts/bentobox/2in1row/template/.content.json +++ b/applications/common/backend/src/main/resources/libs/kyanite/common/components/layouts/bentobox/2in1row/template/.content.json @@ -24,7 +24,7 @@ "jcr:primaryType": "nt:unstructured", "imageSrcType": "asset", "height": "720", - "assetReference": "/content/kyanite/assets/images/personal/Placeholder.svg", + "assetReference": "/libs/kyanite/webroot/images/placeholder.svg", "hasShadow": "false", "hasVideoOptions": "false", "alt": "", @@ -50,7 +50,7 @@ "jcr:primaryType": "nt:unstructured", "imageSrcType": "asset", "height": "720", - "assetReference": "/content/kyanite/assets/images/personal/Placeholder.svg", + "assetReference": "/libs/kyanite/webroot/images/placeholder.svg", "hasVideoOptions": "false", "alt": "", "type": "none", diff --git a/applications/common/backend/src/test/java/pl/ds/kyanite/common/components/utils/LinkUtilTest.java b/applications/common/backend/src/test/java/pl/ds/kyanite/common/components/utils/LinkUtilTest.java new file mode 100644 index 000000000..2e81d14f8 --- /dev/null +++ b/applications/common/backend/src/test/java/pl/ds/kyanite/common/components/utils/LinkUtilTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 Dynamic Solutions + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package pl.ds.kyanite.common.components.utils; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.function.Function; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit5.SlingContext; +import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import pl.ds.websight.assets.core.api.Asset; +import pl.ds.websight.assets.core.api.Rendition; + +@ExtendWith(SlingContextExtension.class) +class LinkUtilTest { + + private final SlingContext context = new SlingContext(ResourceResolverType.RESOURCERESOLVER_MOCK); + + private final Asset assetMock = Mockito.mock(Asset.class); + private final Rendition renditionMock = Mockito.mock(Rendition.class); + + @BeforeEach + public void init() { + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + context.load() + .json(requireNonNull(contextClassLoader.getResourceAsStream("links/pages.json")), + "/content/test/pages"); + context.load() + .json(requireNonNull(contextClassLoader.getResourceAsStream("links/assets.json")), + "/content/test/assets"); + context.load() + .json(requireNonNull(contextClassLoader.getResourceAsStream("links/web-resources.json")), + "/libs/kyanite/webroot"); + + context.registerAdapter(Resource.class, Asset.class, + (Function) (Resource adaptable) -> { + if ("/content/test/assets/PortfolioForum.png".equals(adaptable.getPath())) { + when(assetMock.getOriginalRendition()).thenReturn(renditionMock); + when(renditionMock.getPath()).thenReturn( + "/content/test/assets/PortfolioForum.png/jcr:content/renditions/original.png"); + + return assetMock; + } else { + return null; + } + }); + } + + @Test + public void shouldProduceCorrectLinkWhenPageExists() { + assertLink("/content/test/pages/links/page", + "/content/test/pages/links/page.html"); + } + + @Test + public void shouldProduceCorrectLinkForPublishedPagesWhenPageExists() { + assertLink("/published/test/pages/links/page", + "/published/test/pages/links/page.html"); + } + + @Test + public void shouldNotAddHtmlExtensionWhenPageDoesNotExist() { + assertLink("/content/test/pages/links/non-existent-page", + "/content/test/pages/links/non-existent-page"); + } + + @Test + public void shouldProduceCorrectLinkForAnchors() { + assertLink("#i-am-an-anchor", "#i-am-an-anchor"); + } + + @Test + public void shouldProduceCorrectLinkToOriginalRenditionWhenAssetExists() { + assertLink("/content/test/assets/PortfolioForum.png", + "/content/test/assets/PortfolioForum.png/jcr:content/renditions/original.png"); + + verify(assetMock, times(2)).getOriginalRendition(); + verify(renditionMock).getPath(); + } + + @Test + public void shouldProduceOriginalLinkWhenAssetDoesNotExist() { + assertLink("/content/test/assets/no-such-asset.png", + "/content/test/assets/no-such-asset.png"); + + verifyNoInteractions(assetMock); + verifyNoInteractions(renditionMock); + } + + @Test + public void shouldNotRewriteLinksForWebResources() { + assertLink("/libs/test/webroot/image.png", + "/libs/test/webroot/image.png"); + } + + private void assertLink(final String path, final String expectedLink) { + final String actualLink = LinkUtil.handleLink(path, context.resourceResolver()); + + assertThat(actualLink).isNotNull(); + assertThat(actualLink).isEqualTo(expectedLink); + } +} diff --git a/applications/common/backend/src/test/resources/links/assets.json b/applications/common/backend/src/test/resources/links/assets.json new file mode 100644 index 000000000..ba95ca225 --- /dev/null +++ b/applications/common/backend/src/test/resources/links/assets.json @@ -0,0 +1,20 @@ +{ + "jcr:primaryType": "ws:Assets", + "PortfolioForum.png": { + "jcr:primaryType": "ws:Asset", + "jcr:content": { + "jcr:primaryType": "ws:AssetContent", + "renditions": { + "jcr:primaryType": "nt:folder", + "original.png": { + "jcr:primaryType": "nt:file", + "jcr:content": { + "jcr:primaryType": "oak:Resource", + "jcr:mimeType": "image/png", + ":jcr:data": 177642 + } + } + } + } + } +} \ No newline at end of file diff --git a/applications/common/backend/src/test/resources/links/pages.json b/applications/common/backend/src/test/resources/links/pages.json new file mode 100644 index 000000000..2f585b31c --- /dev/null +++ b/applications/common/backend/src/test/resources/links/pages.json @@ -0,0 +1,20 @@ +{ + "jcr:primaryType": "ws:Pages", + "ws:template": "kyanite/common/templates/pagesspace", + "links": { + "jcr:primaryType": "ws:Page", + "jcr:content": { + "jcr:primaryType": "ws:PageContent", + "ws:template": "/libs/kyanite/common/templates/structurepage", + "sling:resourceType": "kyanite/common/components/page" + }, + "page": { + "jcr:primaryType": "ws:Page", + "jcr:content": { + "jcr:primaryType": "ws:PageContent", + "ws:template": "/libs/kyanite/common/templates/homepage", + "sling:resourceType": "kyanite/common/components/page" + } + } + } +} diff --git a/applications/common/backend/src/test/resources/links/web-resources.json b/applications/common/backend/src/test/resources/links/web-resources.json new file mode 100644 index 000000000..3fe0e10df --- /dev/null +++ b/applications/common/backend/src/test/resources/links/web-resources.json @@ -0,0 +1,4 @@ +{ + "image.png": { + } +} \ No newline at end of file diff --git a/applications/common/frontend/src/resources/images/placeholder.svg b/applications/common/frontend/src/resources/images/placeholder.svg new file mode 100644 index 000000000..b4dbd602c --- /dev/null +++ b/applications/common/frontend/src/resources/images/placeholder.svg @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/.content.xml b/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/.content.xml deleted file mode 100644 index 5d2deb283..000000000 --- a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/.content.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/LICENSE.md b/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/LICENSE.md deleted file mode 100644 index c94ca27a4..000000000 --- a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -Image is from https://en.wikipedia.org/wiki/File:Placeholder_view_vector.svg. This file is made available under the [Creative Commons](https://en.wikipedia.org/wiki/en:Creative_Commons) [CC0 1.0 Universal Public Domain Dedication](https://creativecommons.org/publicdomain/zero/1.0/deed.en). \ No newline at end of file diff --git a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/_jcr_content/renditions/original.svg b/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/_jcr_content/renditions/original.svg deleted file mode 100644 index d2e692d61..000000000 --- a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/_jcr_content/renditions/original.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/_jcr_content/renditions/original.svg.dir/.content.xml b/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/_jcr_content/renditions/original.svg.dir/.content.xml deleted file mode 100644 index 9c1b16798..000000000 --- a/content/src/main/content/jcr_root/content/kyanite/assets/images/personal/Placeholder.svg/_jcr_content/renditions/original.svg.dir/.content.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - -