diff --git a/bundles/core/pom.xml b/bundles/core/pom.xml
index c40103d3..ab80cde6 100644
--- a/bundles/core/pom.xml
+++ b/bundles/core/pom.xml
@@ -25,13 +25,13 @@
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
../../parent/pom.xml
io.wcm
io.wcm.wcm.core.components
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
jar
WCM Core Components
diff --git a/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2Impl.java b/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2Impl.java
index 4055cd2b..c8f1c379 100644
--- a/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2Impl.java
+++ b/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2Impl.java
@@ -19,53 +19,29 @@
*/
package io.wcm.wcm.core.components.impl.models.v2;
-import static com.day.cq.commons.ImageResource.PN_ALT;
-import static com.day.cq.commons.jcr.JcrConstants.JCR_TITLE;
-
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.stream.Collectors;
-import javax.annotation.PostConstruct;
-
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
-import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
-import org.apache.sling.models.annotations.injectorspecific.Self;
-import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.wcm.core.components.models.Image;
import com.adobe.cq.wcm.core.components.models.ImageArea;
-import com.adobe.cq.wcm.core.components.models.datalayer.ImageData;
-import com.adobe.cq.wcm.core.components.models.datalayer.builder.AssetDataBuilder;
-import com.adobe.cq.wcm.core.components.models.datalayer.builder.DataLayerBuilder;
-import com.day.cq.wcm.api.designer.Style;
-import com.fasterxml.jackson.annotation.JsonIgnore;
import io.wcm.handler.link.Link;
-import io.wcm.handler.link.LinkHandler;
-import io.wcm.handler.media.Asset;
import io.wcm.handler.media.Media;
-import io.wcm.handler.media.MediaHandler;
-import io.wcm.handler.media.Rendition;
-import io.wcm.handler.url.UrlHandler;
-import io.wcm.sling.models.annotations.AemObject;
-import io.wcm.wcm.core.components.impl.models.helpers.AbstractComponentImpl;
import io.wcm.wcm.core.components.impl.models.helpers.ImageAreaV1Impl;
+import io.wcm.wcm.core.components.impl.models.v3.ImageV3Impl;
import io.wcm.wcm.core.components.impl.servlets.ImageWidthProxyServlet;
import io.wcm.wcm.core.components.impl.util.HandlerUnwrapper;
import io.wcm.wcm.core.components.models.mixin.LinkMixin;
-import io.wcm.wcm.core.components.models.mixin.MediaMixin;
/**
* wcm.io-based enhancements for {@link Image}:
@@ -84,222 +60,36 @@
@Exporter(
name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
extensions = ExporterConstants.SLING_MODEL_EXTENSION)
-public class ImageV2Impl extends AbstractComponentImpl implements Image, MediaMixin, LinkMixin {
+public class ImageV2Impl extends ImageV3Impl implements LinkMixin {
/**
* Resource type
*/
public static final String RESOURCE_TYPE = "wcm-io/wcm/core/components/image/v2/image";
- private static final String WIDTH_PLACEHOLDER = "{.width}";
-
- @AemObject
- private Style currentStyle;
- @Self
- private LinkHandler linkHandler;
- @Self
- private MediaHandler mediaHandler;
- @Self
- private UrlHandler urlHandler;
-
- @ValueMapValue(name = PN_ALT, injectionStrategy = InjectionStrategy.OPTIONAL)
- private @Nullable String alt;
- @ValueMapValue(name = JCR_TITLE, injectionStrategy = InjectionStrategy.OPTIONAL)
- private @Nullable String title;
-
- @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
- private @Nullable String imageCrop;
- @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
- private @Nullable String imageRotate;
-
- private Link link;
- private Media media;
- private String uuid;
- private String fileReference;
- private boolean displayPopupTitle;
- private boolean enableLazyLoading;
- private int lazyThreshold;
- private boolean isDecorative;
- private List areas;
-
- private List widths = Collections.emptyList();
- private long noScriptWidth;
- private String srcPattern;
-
- @PostConstruct
- private void activate() {
- ValueMap properties = resource.getValueMap();
- // read basic properties
- displayPopupTitle = properties.get(PN_DISPLAY_POPUP_TITLE, currentStyle.get(PN_DISPLAY_POPUP_TITLE, true));
- enableLazyLoading = !currentStyle.get(PN_DESIGN_LAZY_LOADING_ENABLED, true);
- lazyThreshold = currentStyle.get(PN_DESIGN_LAZY_THRESHOLD, 0);
- isDecorative = properties.get(PN_IS_DECORATIVE, currentStyle.get(PN_IS_DECORATIVE, false));
- boolean altFromAsset = properties.get(PN_ALT_VALUE_FROM_DAM, currentStyle.get(PN_ALT_VALUE_FROM_DAM, true));
-
- // resolve media and properties from DAM asset
- media = HandlerUnwrapper.get(mediaHandler, resource)
+ @Override
+ protected Media buildMedia(boolean altFromAsset, boolean imageFromPageImage) {
+ return HandlerUnwrapper.get(mediaHandler, resource)
// disable dynamic media support as it is not compatible with the "src-pattern" concept
.dynamicMediaDisabled(true)
.decorative(isDecorative)
.forceAltValueFromAsset(altFromAsset)
.build();
- if (media.isValid() && !media.getRendition().isImage()) {
- // no image asset selected (cannot be rendered) - set to invalid
- media = mediaHandler.invalid();
- }
- if (media.isValid()) {
- initPropertiesFromDamAsset(properties);
- widths = buildRenditionWidths(media.getRendition());
- noScriptWidth = getNoScriptWidth();
- srcPattern = buildSrcPattern(media.getUrl());
- areas = ImageAreaV1Impl.convertMap(media.getMap());
- }
-
- // resolve link - decorative images have no link and no alt text by definition
- if (isDecorative) {
- link = linkHandler.invalid();
- }
- else {
- link = HandlerUnwrapper.get(linkHandler, resource).build();
- }
- }
-
- /**
- * Checks if the resolved media is a DAM asset, and initializes properties from it.
- * @param properties Resource properties
- */
- private void initPropertiesFromDamAsset(ValueMap properties) {
- Asset asset = media.getAsset();
- if (asset != null) {
- com.day.cq.dam.api.Asset damAsset = asset.adaptTo(com.day.cq.dam.api.Asset.class);
- if (damAsset != null) {
- boolean titleFromAsset = properties.get(PN_TITLE_VALUE_FROM_DAM, currentStyle.get(PN_TITLE_VALUE_FROM_DAM, true));
- boolean uuidDisabled = currentStyle.get(PN_UUID_DISABLED, false);
-
- fileReference = damAsset.getPath();
- alt = asset.getAltText();
-
- if (!uuidDisabled) {
- uuid = damAsset.getID();
- }
-
- if (titleFromAsset) {
- String assetTitle = asset.getTitle();
- if (StringUtils.isNotEmpty(assetTitle)) {
- title = assetTitle;
- }
- }
- }
- }
- }
-
- @Override
- @NotNull
- public Link getLinkObject() {
- return link;
- }
-
- @Override
- @NotNull
- public Media getMediaObject() {
- return media;
- }
-
- @Override
- public String getSrc() {
- if (noScriptWidth > 0) {
- return StringUtils.replace(srcPattern, WIDTH_PLACEHOLDER, "." + noScriptWidth);
- }
- else {
- return media.getUrl();
- }
- }
-
- @Override
- public String getAlt() {
- return alt;
- }
-
- @Override
- public String getTitle() {
- return title;
- }
-
- @Override
- public String getUuid() {
- return uuid;
- }
-
- /**
- * @deprecated Deprecated in API
- */
- @Override
- @Deprecated
- public String getLink() {
- return link.getUrl();
- }
-
- @Override
- public boolean displayPopupTitle() {
- return displayPopupTitle;
}
@Override
- public String getFileReference() {
- return fileReference;
- }
-
- @Override
- public boolean isLazyEnabled() {
- return enableLazyLoading;
- }
-
- @Override
- public int getLazyThreshold() {
- return lazyThreshold;
- }
-
- @Override
- public String getSrcUriTemplate() {
- return srcPattern;
- }
-
- @Override
- public int @NotNull [] getWidths() {
- return widths.stream()
- .mapToInt(Long::intValue)
- .toArray();
- }
-
- @Override
- public List getAreas() {
- return areas;
- }
-
- @Override
- public boolean isDecorative() {
- return isDecorative;
+ protected List buildAreas() {
+ return ImageAreaV1Impl.convertMap(media.getMap());
}
- /**
- * @deprecated Deprecated in API
- */
- @Override
- @Deprecated
- public String getJson() {
- // not required for image v2
- return null;
- }
-
-
/**
* Validates the configured list of widths, removes those that are bigger than the original rendition,
* and returns them sorted by size.
- * @param rendition Primary rendition
* @return Widths
*/
- private List buildRenditionWidths(Rendition rendition) {
- long maxWidth = rendition.getWidth();
+ @Override
+ protected List buildRenditionWidths() {
+ long maxWidth = media.getRendition().getWidth();
String[] configuredWidths = currentStyle.get(PN_DESIGN_ALLOWED_RENDITION_WIDTHS, new String[0]);
return Arrays.stream(configuredWidths)
.map(NumberUtils::toLong)
@@ -308,23 +98,13 @@ private List buildRenditionWidths(Rendition rendition) {
.collect(Collectors.toList());
}
- /**
- * Picks a "medium" widths from the set of available widths.
- * @return Picked width
- */
- private long getNoScriptWidth() {
- if (widths.isEmpty()) {
- return 0;
- }
- return widths.get((int)Math.round(widths.size() / 2d - 0.5d));
- }
-
/**
* Build image url pattern based in ImageWidthServlet for dynamic rendition selection.
- * @param mediaUrl Media Url
* @return Url pattern
*/
- private String buildSrcPattern(String mediaUrl) {
+ @Override
+ protected String buildSrcPattern() {
+ String mediaUrl = media.getUrl();
String extension = StringUtils.substringAfterLast(mediaUrl, ".");
String fileName = StringUtils.substringAfterLast(mediaUrl, "/");
@@ -349,23 +129,32 @@ private String buildSrcPattern(String mediaUrl) {
"." + ImageWidthProxyServlet.SELECTOR + WIDTH_PLACEHOLDER + ".");
}
- // --- data layer ---
+ @Override
+ @NotNull
+ public Link getLinkObject() {
+ return link.getLinkObject();
+ }
@Override
- @JsonIgnore
- @SuppressWarnings("null")
- public @NotNull ImageData getComponentData() {
- return DataLayerBuilder.extending(super.getComponentData()).asImageComponent()
- .withTitle(this::getTitle)
- .withLinkUrl(this::getLink)
- .withAssetData(() -> Optional.of(media)
- .filter(Media::isValid)
- .map(Media::getAsset)
- .map(asset -> asset.adaptTo(com.day.cq.dam.api.Asset.class))
- .map(DataLayerBuilder::forAsset)
- .map(AssetDataBuilder::build)
- .orElse(null))
- .build();
+ public String getSrc() {
+ long noScriptWidth = getNoScriptWidth();
+ if (noScriptWidth > 0) {
+ return StringUtils.replace(srcPattern, WIDTH_PLACEHOLDER, "." + noScriptWidth);
+ }
+ else {
+ return media.getUrl();
+ }
+ }
+
+ /**
+ * Picks a "medium" widths from the set of available widths.
+ * @return Picked width
+ */
+ private long getNoScriptWidth() {
+ if (widths.isEmpty()) {
+ return 0;
+ }
+ return widths.get((int)Math.round(widths.size() / 2d - 0.5d));
}
}
diff --git a/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/models/v3/ImageV3Impl.java b/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/models/v3/ImageV3Impl.java
new file mode 100644
index 00000000..b9069af5
--- /dev/null
+++ b/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/models/v3/ImageV3Impl.java
@@ -0,0 +1,372 @@
+/*
+ * #%L
+ * wcm.io
+ * %%
+ * Copyright (C) 2023 wcm.io
+ * %%
+ * 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.
+ * #L%
+ */
+package io.wcm.wcm.core.components.impl.models.v3;
+
+import static com.day.cq.commons.ImageResource.PN_ALT;
+import static com.day.cq.commons.jcr.JcrConstants.JCR_TITLE;
+import static io.wcm.handler.media.MediaNameConstants.PROP_CSS_CLASS;
+import static io.wcm.handler.media.MediaNameConstants.URI_TEMPLATE_PLACEHOLDER_WIDTH;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.models.annotations.Exporter;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import com.adobe.cq.export.json.ComponentExporter;
+import com.adobe.cq.export.json.ExporterConstants;
+import com.adobe.cq.wcm.core.components.models.Image;
+import com.adobe.cq.wcm.core.components.models.ImageArea;
+import com.adobe.cq.wcm.core.components.models.datalayer.ImageData;
+import com.adobe.cq.wcm.core.components.models.datalayer.builder.AssetDataBuilder;
+import com.adobe.cq.wcm.core.components.models.datalayer.builder.DataLayerBuilder;
+import com.day.cq.wcm.api.designer.Style;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import io.wcm.handler.link.LinkHandler;
+import io.wcm.handler.media.Asset;
+import io.wcm.handler.media.Media;
+import io.wcm.handler.media.MediaHandler;
+import io.wcm.handler.media.Rendition;
+import io.wcm.handler.media.UriTemplate;
+import io.wcm.handler.media.UriTemplateType;
+import io.wcm.handler.media.format.Ratio;
+import io.wcm.handler.url.UrlHandler;
+import io.wcm.sling.models.annotations.AemObject;
+import io.wcm.wcm.core.components.impl.link.LinkWrapper;
+import io.wcm.wcm.core.components.impl.models.helpers.AbstractComponentImpl;
+import io.wcm.wcm.core.components.impl.models.helpers.ImageAreaV2Impl;
+import io.wcm.wcm.core.components.impl.util.ComponentFeatureImageResolver;
+import io.wcm.wcm.core.components.impl.util.HandlerUnwrapper;
+import io.wcm.wcm.core.components.models.mixin.MediaMixin;
+
+/**
+ * wcm.io-based enhancements for {@link Image}:
+ *
+ * - Build image using Media handler
+ * - Build link using Link handler
+ *
+ */
+@Model(adaptables = SlingHttpServletRequest.class,
+ adapters = { Image.class, ComponentExporter.class },
+ resourceType = ImageV3Impl.RESOURCE_TYPE)
+@Exporter(
+ name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
+ extensions = ExporterConstants.SLING_MODEL_EXTENSION)
+public class ImageV3Impl extends AbstractComponentImpl implements Image, MediaMixin {
+
+ /**
+ * Resource type
+ */
+ static final String RESOURCE_TYPE = "wcm-io/wcm/core/components/image/v3/image";
+
+ /**
+ * Width placeholder for URI template
+ */
+ public static final String WIDTH_PLACEHOLDER = "{.width}";
+
+ @AemObject
+ protected Style currentStyle;
+ @Self
+ protected LinkHandler linkHandler;
+ @Self
+ protected MediaHandler mediaHandler;
+ @Self
+ protected UrlHandler urlHandler;
+
+ @ValueMapValue(name = PN_ALT, injectionStrategy = InjectionStrategy.OPTIONAL)
+ protected @Nullable String alt;
+ @ValueMapValue(name = JCR_TITLE, injectionStrategy = InjectionStrategy.OPTIONAL)
+ protected @Nullable String title;
+
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
+ protected @Nullable String imageCrop;
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
+ protected @Nullable String imageRotate;
+
+ protected LinkWrapper link;
+ protected Media media;
+ protected String uuid;
+ protected String fileReference;
+ protected boolean displayPopupTitle;
+ protected boolean enableLazyLoading;
+ protected int lazyThreshold;
+ protected boolean isDecorative;
+ protected List areas;
+
+ protected List widths = Collections.emptyList();
+ protected String srcPattern;
+
+ @PostConstruct
+ private void activate() {
+ ValueMap properties = resource.getValueMap();
+
+ // read basic properties
+ displayPopupTitle = properties.get(PN_DISPLAY_POPUP_TITLE, currentStyle.get(PN_DISPLAY_POPUP_TITLE, true));
+ enableLazyLoading = !currentStyle.get(PN_DESIGN_LAZY_LOADING_ENABLED, true);
+ lazyThreshold = currentStyle.get(PN_DESIGN_LAZY_THRESHOLD, 0);
+ isDecorative = properties.get(PN_IS_DECORATIVE, currentStyle.get(PN_IS_DECORATIVE, false));
+ boolean altFromAsset = properties.get(PN_ALT_VALUE_FROM_DAM, currentStyle.get(PN_ALT_VALUE_FROM_DAM, true));
+ boolean imageFromPageImage = properties.get(PN_IMAGE_FROM_PAGE_IMAGE, true);
+
+ // resolve link - decorative images have no link and no alt text by definition
+ if (isDecorative) {
+ link = new LinkWrapper(linkHandler.invalid());
+ }
+ else {
+ link = new LinkWrapper(HandlerUnwrapper.get(linkHandler, resource).build());
+ }
+
+ // resolve media and properties from DAM asset
+ media = buildMedia(altFromAsset, imageFromPageImage);
+
+ if (media.isValid() && !media.getRendition().isImage()) {
+ // no image asset selected (cannot be rendered) - set to invalid
+ media = mediaHandler.invalid();
+ }
+ if (media.isValid()) {
+ initPropertiesFromDamAsset(properties);
+ widths = buildRenditionWidths();
+ srcPattern = buildSrcPattern();
+ areas = buildAreas();
+ }
+
+ }
+
+ protected Media buildMedia(boolean altFromAsset, boolean imageFromPageImage) {
+ ComponentFeatureImageResolver imageResolver = new ComponentFeatureImageResolver(resource, getCurrentPage(), currentStyle, mediaHandler)
+ .targetPage(getCurrentPage())
+ .altValueFromDam(altFromAsset)
+ .imageFromPageImage(imageFromPageImage)
+ .mediaHandlerProperty(PROP_CSS_CLASS, "cmp-image__image")
+ .mediaHandlerProperty("itemprop", "contentUrl");
+ String imageTitle = title;
+ if (displayPopupTitle && imageTitle != null) {
+ imageResolver.mediaHandlerProperty("title", imageTitle);
+ }
+ return imageResolver.buildMedia();
+ }
+
+ protected List buildAreas() {
+ return ImageAreaV2Impl.convertMap(media.getMap());
+ }
+
+ /**
+ * Checks if the resolved media is a DAM asset, and initializes properties from it.
+ * @param properties Resource properties
+ */
+ private void initPropertiesFromDamAsset(ValueMap properties) {
+ Asset asset = media.getAsset();
+ if (asset != null) {
+ com.day.cq.dam.api.Asset damAsset = asset.adaptTo(com.day.cq.dam.api.Asset.class);
+ if (damAsset != null) {
+ boolean titleFromAsset = properties.get(PN_TITLE_VALUE_FROM_DAM, currentStyle.get(PN_TITLE_VALUE_FROM_DAM, true));
+ boolean uuidDisabled = currentStyle.get(PN_UUID_DISABLED, false);
+
+ fileReference = damAsset.getPath();
+ alt = asset.getAltText();
+
+ if (!uuidDisabled) {
+ uuid = damAsset.getID();
+ }
+
+ if (titleFromAsset) {
+ String assetTitle = asset.getTitle();
+ if (StringUtils.isNotEmpty(assetTitle)) {
+ title = assetTitle;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Build lists of rendition widths based on the resolved media renditions
+ * (those that share the same ratio as the primary rendition)
+ * @return Widths
+ */
+ protected List buildRenditionWidths() {
+ double primaryRatio = media.getRendition().getRatio();
+ return media.getRenditions().stream()
+ .filter(rendition -> Ratio.matches(rendition.getRatio(), primaryRatio))
+ .map(Rendition::getWidth)
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Build image url pattern via Media Handler URI template.
+ * @return Url pattern
+ */
+ protected String buildSrcPattern() {
+ Rendition rendition = media.getRendition();
+ if (!rendition.isImage() || rendition.isVectorImage()) {
+ return null;
+ }
+ UriTemplate uriTempalte = rendition.getUriTemplate(UriTemplateType.SCALE_WIDTH);
+ return StringUtils.replace(uriTempalte.getUriTemplate(), URI_TEMPLATE_PLACEHOLDER_WIDTH, WIDTH_PLACEHOLDER);
+ }
+
+ @Override
+ @NotNull
+ public Media getMediaObject() {
+ return media;
+ }
+
+ @Override
+ public String getSrc() {
+ return media.getUrl();
+ }
+
+ @Override
+ public String getAlt() {
+ return alt;
+ }
+
+ @Override
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public com.adobe.cq.wcm.core.components.commons.link.Link getImageLink() {
+ return link.orNull();
+ }
+
+ /**
+ * @deprecated Deprecated in API
+ */
+ @Override
+ @Deprecated(forRemoval = true)
+ public String getLink() {
+ return link.getURL();
+ }
+
+ @Override
+ public boolean displayPopupTitle() {
+ return displayPopupTitle;
+ }
+
+ @Override
+ public String getFileReference() {
+ return fileReference;
+ }
+
+ @Override
+ public boolean isLazyEnabled() {
+ return enableLazyLoading;
+ }
+
+ @Override
+ public int getLazyThreshold() {
+ return lazyThreshold;
+ }
+
+ @Override
+ public String getSrcUriTemplate() {
+ return srcPattern;
+ }
+
+ @Override
+ public int @NotNull [] getWidths() {
+ return widths.stream()
+ .mapToInt(Long::intValue)
+ .toArray();
+ }
+
+ @Override
+ public List getAreas() {
+ return areas;
+ }
+
+ @Override
+ public boolean isDecorative() {
+ return isDecorative;
+ }
+
+ @Override
+ public String getWidth() {
+ return null;
+ }
+
+ @Override
+ public String getHeight() {
+ return null;
+ }
+
+ @Override
+ public @Nullable String getSizes() {
+ return null;
+ }
+
+ @Override
+ public String getSrcset() {
+ return null;
+ }
+
+ @Override
+ public String getSmartCropRendition() {
+ return null;
+ }
+
+ @Override
+ public boolean isDmImage() {
+ return false;
+ }
+
+
+ // --- data layer ---
+
+ @Override
+ @JsonIgnore
+ @SuppressWarnings("null")
+ public @NotNull ImageData getComponentData() {
+ return DataLayerBuilder.extending(super.getComponentData()).asImageComponent()
+ .withTitle(this::getTitle)
+ .withLinkUrl(() -> this.link.getLinkObject().getUrl())
+ .withAssetData(() -> Optional.of(media)
+ .filter(Media::isValid)
+ .map(Media::getAsset)
+ .map(asset -> asset.adaptTo(com.day.cq.dam.api.Asset.class))
+ .map(DataLayerBuilder::forAsset)
+ .map(AssetDataBuilder::build)
+ .orElse(null))
+ .build();
+ }
+
+}
diff --git a/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/util/ComponentFeatureImageResolver.java b/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/util/ComponentFeatureImageResolver.java
index 477a170f..9ed91ce4 100644
--- a/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/util/ComponentFeatureImageResolver.java
+++ b/bundles/core/src/main/java/io/wcm/wcm/core/components/impl/util/ComponentFeatureImageResolver.java
@@ -55,9 +55,9 @@ public class ComponentFeatureImageResolver {
private final MediaHandler mediaHandler;
private final Map mediaHandlerProperties = new HashMap<>();
- private final boolean imageFromPageImage;
+ private boolean imageFromPageImage;
private final boolean altValueFromPageImage;
- private final boolean altValueFromDam;
+ private boolean altValueFromDam;
private final boolean isDecorative;
private final String componentAltText;
@@ -105,6 +105,24 @@ public ComponentFeatureImageResolver mediaHandlerProperty(@NotNull String key, @
return this;
}
+ /**
+ * @param value Image from page image
+ * @return self
+ */
+ public ComponentFeatureImageResolver imageFromPageImage(boolean value) {
+ this.imageFromPageImage = value;
+ return this;
+ }
+
+ /**
+ * @param value Alt Value from DAM
+ * @return self
+ */
+ public ComponentFeatureImageResolver altValueFromDam(boolean value) {
+ this.altValueFromDam = value;
+ return this;
+ }
+
/**
* Build media after resolving feature images and alt. texts.
* @return Media
@@ -167,6 +185,7 @@ else if (!(altValueFromPageImage || altValueFromDam)) {
// otherwise rely to default media handler behavior
builder.altText(componentAltText);
}
+ builder.forceAltValueFromAsset(altValueFromDam);
// apply custom media handling properties
mediaHandlerProperties.entrySet().forEach(entry -> builder.property(entry.getKey(), entry.getValue()));
diff --git a/bundles/core/src/main/webapp/app-root/components/image/v2/README.md b/bundles/core/src/main/webapp/app-root/components/image/v2/README.md
index 20e7b61a..fe28556d 100644
--- a/bundles/core/src/main/webapp/app-root/components/image/v2/README.md
+++ b/bundles/core/src/main/webapp/app-root/components/image/v2/README.md
@@ -1,4 +1,4 @@
-Image (v2) - deprecated
+Image (v2)
====
Image component written in HTL that renders an adaptive image.
diff --git a/bundles/core/src/main/webapp/app-root/components/image/v3/README.md b/bundles/core/src/main/webapp/app-root/components/image/v3/README.md
new file mode 100644
index 00000000..dbc64c00
--- /dev/null
+++ b/bundles/core/src/main/webapp/app-root/components/image/v3/README.md
@@ -0,0 +1,25 @@
+Image (v3)
+====
+Image component written in HTL that renders an adaptive image.
+
+Extends the [Image AEM Sites Core Component][extends-component]
+
+## Resource Type
+```
+wcm-io/wcm/core/components/image/v3/image
+```
+
+## Enhanced Features
+
+* Inherits all features from its [super component][extends-component]
+* Uses [wcm.io Media Handler][wcmio-handler-media] for processing the image
+* Allows to select media format(s) and auto-cropping in the content policy
+* Automatically customizes the in-place image editor cropping ratios to the selected/the applications media formats
+* Uses the enhanced Media Handler File Upload dialog widget with path field and media format validation
+* Uses [wcm.io Link Handler][wcmio-handler-link] for processing the image link
+* Uses the Link Handler dialog widget for defining link type and link target
+
+
+[extends-component]: https://github.com/adobe/aem-core-wcm-components/tree/master/content/src/content/jcr_root/apps/core/wcm/components/image/v3/image
+[wcmio-handler-media]: https://wcm.io/handler/media/
+[wcmio-handler-link]: https://wcm.io/handler/link/
diff --git a/bundles/core/src/main/webapp/app-root/components/image/v3/image.json b/bundles/core/src/main/webapp/app-root/components/image/v3/image.json
new file mode 100644
index 00000000..174824b8
--- /dev/null
+++ b/bundles/core/src/main/webapp/app-root/components/image/v3/image.json
@@ -0,0 +1,196 @@
+{
+ "jcr:primaryType": "cq:Component",
+ "jcr:title": "wcm.io Image (v3)",
+ "componentGroup": ".wcmio-core-wcm",
+ "sling:resourceSuperType": "core/wcm/components/image/v3/image",
+
+ /* Fallback mode for Link Handler to support existing content that used a single property name */
+ "wcmio:linkTargetUrlFallbackProperty": "linkURL",
+
+ "cq:editConfig": {
+ "jcr:primaryType": "cq:EditConfig",
+ "cq:inherit": true,
+ /*
+ * Overwrite inplace edit config to:
+ * - Disable cropping on inline toolbar as it does not support predefined ratios
+ * - Remove default ratios as they are fetched from assigned media formats
+ * - Remove image/webp support
+ */
+ "cq:inplaceEditing": {
+ "jcr:primaryType": "cq:InplaceEditingConfig",
+ "editorType": "image",
+ "active": true,
+ "configPath": "inplaceEditingConfig",
+ "inplaceEditingConfig": {
+ "jcr:primaryType": "nt:unstructured",
+ "plugins": {
+ "crop": {
+ "features": "*",
+ "supportedMimeTypes": ["image/jpeg", "image/png", "image/tiff"]
+ },
+ "flip": {
+ "features": "-",
+ "supportedMimeTypes": ["image/jpeg", "image/png", "image/tiff"]
+ },
+ "map": {
+ "features": "*",
+ "supportedMimeTypes": ["image/jpeg", "image/png", "image/tiff", "image/svg+xml"]
+ },
+ "rotate": {
+ "features": "*",
+ "supportedMimeTypes": ["image/jpeg", "image/png", "image/tiff"]
+ },
+ "zoom": {
+ "features": "*",
+ "supportedMimeTypes": ["image/jpeg", "image/png", "image/tiff", "image/svg+xml"]
+ }
+ },
+ "ui": {
+ "inline": {
+ "toolbar": ["rotate#right", "history#undo", "history#redo", "fullscreen#fullscreen", "control#close", "control#finish"],
+ "replacementToolbars": {
+ "crop": ["crop#identifier", "crop#unlaunch", "crop#confirm"]
+ }
+ },
+ "fullscreen": {
+ "toolbar": {
+ "left": ["crop#launchwithratio", "rotate#right", "map#launch", "flip#horizontal", "flip#vertical", "zoom#reset100", "zoom#popupslider"],
+ "right": ["history#undo", "history#redo", "fullscreen#fullscreenexit"]
+ },
+ "replacementToolbars": {
+ "crop": {
+ "left": ["crop#identifier"],
+ "right": ["crop#unlaunch", "crop#confirm"]
+ },
+ "map": {
+ "left": ["map#rectangle", "map#circle", "map#polygon"],
+ "right": ["map#unlaunch", "map#confirm"]
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ "cq:dialog": {
+ "jcr:primaryType": "nt:unstructured",
+ "extraClientlibs": ["io.wcm.wcm.core.components.image.v2.editor"],
+ "content": {
+ "items": {
+ "tabs": {
+ "items": {
+
+ "asset": {
+ "items": {
+ "columns": {
+ "items": {
+ "column": {
+ "items": {
+
+ /* Does not work with media handler */
+ "pageImageThumbnail": {
+ "sling:hideResource": true
+ },
+
+ /* Use wcm.io Media Handler FileUpload */
+ "file": {
+ "sling:resourceType": "wcm-io/handler/media/components/granite/form/fileupload",
+ "allowUpload": "${not empty cqDesign.allowUpload ? cqDesign.allowUpload : true}"
+ },
+
+ /* Supported differently with media handler */
+ "dynamicmediaGroup": {
+ "sling:hideResource": true
+ }
+
+ }
+ }
+ }
+ }
+ }
+ },
+
+ "metadata": {
+ "items": {
+ "columns": {
+ "items": {
+ "column": {
+ "items": {
+ "link": {
+ /* hide link pagefield and replace it with wcm.io Link dialog */
+ "sling:hideResource": true
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ /* wcm.io Link Handler Tab */
+ "link": {
+ "sling:resourceType": "granite/ui/components/coral/foundation/include",
+ "path": "wcm-io/handler/link/components/global/include/linkRefNoTitleTab"
+ }
+
+ }
+ }
+ }
+ }
+ },
+
+ "cq:design_dialog": {
+ "jcr:primaryType": "nt:unstructured",
+ "content": {
+ "items": {
+ "tabs": {
+ "items": {
+
+ "properties": {
+ "items": {
+ "content": {
+ "items": {
+
+ "enableDmFeatures": {
+ /* Dynamic Media gets active automatically with media handler */
+ "sling:hideResource": true
+ },
+
+ /* Supported differently with media handler */
+ "enableAssetDelivery": {
+ "sling:hideResource": true
+ },
+ "resizeWidth": {
+ "sling:hideResource": true
+ },
+ "heading": {
+ "sling:hideResource": true
+ },
+ "responsiveGroup": {
+ "sling:hideResource": true
+ },
+
+ /* Media format definition */
+ "mediaFormatSelection": {
+ "sling:resourceType": "granite/ui/components/coral/foundation/include",
+ "path": "wcm-io/handler/media/components/global/include/mediaFormatSelection",
+ "sling:orderBefore": "enableLazyLoading"
+ },
+
+ "jpegQuality": {
+ /* JPEG quality is configured via media handler configuration */
+ "sling:hideResource": true
+ }
+ }
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bundles/core/src/main/webapp/app-root/components/image/v3/image/image.html b/bundles/core/src/main/webapp/app-root/components/image/v3/image/image.html
new file mode 100644
index 00000000..5fc8d75e
--- /dev/null
+++ b/bundles/core/src/main/webapp/app-root/components/image/v3/image/image.html
@@ -0,0 +1,34 @@
+
+
+
diff --git a/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2ImplTest.java b/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2ImplTest.java
index f38570d8..f28b49e2 100644
--- a/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2ImplTest.java
+++ b/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/v2/ImageV2ImplTest.java
@@ -102,7 +102,7 @@ void setUp() {
@Test
@SuppressWarnings("deprecation")
void testNoImage() {
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE));
Image underTest = AdaptTo.notNull(context.request(), Image.class);
@@ -132,7 +132,7 @@ void testNoImage() {
@Test
void testInvalidAssetReference() {
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, "/content/dam/invalid"));
@@ -145,7 +145,7 @@ void testInvalidAssetReference() {
@Test
@SuppressWarnings("deprecation")
void testWithAssetImage() {
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
JCR_TITLE, "Resource Title",
@@ -174,7 +174,7 @@ void testWithAssetImage() {
@Test
@SuppressWarnings("deprecation")
void testWithUploadedImage() {
- Resource imageResource = context.create().resource(page.getContentResource().getPath() + "/image",
+ Resource imageResource = context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
NN_MEDIA_INLINE_STANDARD + "Name", "file1.png");
context.load().binaryFile("/files/test.png", imageResource.getPath() + "/" + NN_MEDIA_INLINE_STANDARD, ContentType.PNG);
@@ -201,11 +201,11 @@ void testWithUploadedImage() {
}
@Test
- @SuppressWarnings({ "null", "deprecation" })
+ @SuppressWarnings("deprecation")
void testWithImageAndLink() {
enableDataLayer(context, true);
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
PN_LINK_TITLE, ExternalLinkType.ID,
@@ -233,7 +233,7 @@ void testWithImageAndLink() {
@Test
@SuppressWarnings("deprecation")
void testWithImageAndLink_Decorative() {
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
PN_LINK_TITLE, ExternalLinkType.ID,
@@ -257,7 +257,7 @@ void testWithImageAndLink_Decorative_ContentPolicy() {
PN_IS_DECORATIVE, true,
PN_DESIGN_LAZY_THRESHOLD, 10);
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
PN_LINK_TITLE, ExternalLinkType.ID,
@@ -275,7 +275,7 @@ void testWithImageAndLink_Decorative_ContentPolicy() {
@Test
void testWithImage_TitleAltNotFormAsset() {
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
JCR_TITLE, "Resource Title",
@@ -295,7 +295,7 @@ void testWithImage_TitleAltNotFormAsset_ContentPolicy() {
PN_TITLE_VALUE_FROM_DAM, false,
PN_ALT_VALUE_FROM_DAM, false);
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
JCR_TITLE, "Resource Title",
@@ -311,7 +311,7 @@ void testWithImage_TitleAltNotFormAsset_ContentPolicy() {
void testWithNoImageAsset() {
Asset pdfAsset = context.create().asset(DAM_ROOT + "/file1.pdf", "/files/test.pdf", ContentType.PDF);
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, pdfAsset.getPath()));
@@ -322,7 +322,7 @@ void testWithNoImageAsset() {
@Test
void testDisplayPopupTitle() {
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
PN_DISPLAY_POPUP_TITLE, false));
@@ -337,7 +337,7 @@ void testDisplayPopupTitle_ContentPolicy() {
context.contentPolicyMapping(RESOURCE_TYPE,
PN_DISPLAY_POPUP_TITLE, false);
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath()));
@@ -351,7 +351,7 @@ void testLazyEnabled_ContentPolicy() {
context.contentPolicyMapping(RESOURCE_TYPE,
PN_DESIGN_LAZY_LOADING_ENABLED, false); // property is internally named "disabled", value is inverted
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath()));
@@ -366,7 +366,7 @@ void testUUID() {
ModifiableValueMap props = AdaptTo.notNull(resource, ModifiableValueMap.class);
props.put(JCR_UUID, "test-uuid");
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath()));
@@ -384,7 +384,7 @@ void testUUID_Disabled() {
ModifiableValueMap props = AdaptTo.notNull(resource, ModifiableValueMap.class);
props.put(JCR_UUID, "test-uuid");
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath()));
@@ -398,7 +398,7 @@ void testWidths() {
context.contentPolicyMapping(RESOURCE_TYPE,
PN_DESIGN_ALLOWED_RENDITION_WIDTHS, new String[] { "100", "50", "200", "-123", "0", "junk" });
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath()));
@@ -414,7 +414,7 @@ void testWidths() {
@Test
void testAreas() {
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath(),
PN_MAP, ImageAreaTestData.MAP_STRING));
@@ -430,7 +430,7 @@ void testWithImageAutoCropping_ContentPolicy() {
PN_COMPONENT_MEDIA_FORMATS, new String[] { MediaFormats.SQUARE.getName() },
PN_COMPONENT_MEDIA_AUTOCROP, true);
- context.currentResource(context.create().resource(page.getContentResource().getPath() + "/image",
+ context.currentResource(context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath()));
@@ -451,7 +451,7 @@ void testWithImageAutoCropping_ContentPolicy_WrappedResource() {
PN_COMPONENT_MEDIA_FORMATS, new String[] { MediaFormats.SQUARE.getName() },
PN_COMPONENT_MEDIA_AUTOCROP, true);
- Resource resource = context.create().resource(page.getContentResource().getPath() + "/image",
+ Resource resource = context.create().resource(page, "image",
PROPERTY_RESOURCE_TYPE, DELEGATE_RESOURCE_TYPE,
PN_MEDIA_REF_STANDARD, asset.getPath());
diff --git a/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/v3/ImageV3ImplTest.java b/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/v3/ImageV3ImplTest.java
new file mode 100644
index 00000000..57522a9e
--- /dev/null
+++ b/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/v3/ImageV3ImplTest.java
@@ -0,0 +1,548 @@
+/*
+ * #%L
+ * wcm.io
+ * %%
+ * Copyright (C) 2023 wcm.io
+ * %%
+ * 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.
+ * #L%
+ */
+package io.wcm.wcm.core.components.impl.models.v3;
+
+import static com.adobe.cq.wcm.core.components.models.Image.PN_ALT_VALUE_FROM_DAM;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_DESIGN_LAZY_LOADING_ENABLED;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_DESIGN_LAZY_THRESHOLD;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_DISPLAY_POPUP_TITLE;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_IMAGE_FROM_PAGE_IMAGE;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_IS_DECORATIVE;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_MAP;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_TITLE_VALUE_FROM_DAM;
+import static com.adobe.cq.wcm.core.components.models.Image.PN_UUID_DISABLED;
+import static com.adobe.cq.wcm.core.components.models.Page.NN_PAGE_FEATURED_IMAGE;
+import static com.day.cq.commons.ImageResource.PN_ALT;
+import static com.day.cq.commons.jcr.JcrConstants.JCR_TITLE;
+import static com.day.cq.commons.jcr.JcrConstants.JCR_UUID;
+import static com.day.cq.dam.api.DamConstants.DC_DESCRIPTION;
+import static com.day.cq.dam.api.DamConstants.DC_TITLE;
+import static io.wcm.handler.link.LinkNameConstants.PN_LINK_EXTERNAL_REF;
+import static io.wcm.handler.link.LinkNameConstants.PN_LINK_TITLE;
+import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES;
+import static io.wcm.handler.media.MediaNameConstants.NN_MEDIA_INLINE_STANDARD;
+import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_AUTOCROP;
+import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS;
+import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_RESPONSIVE_TYPE;
+import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_REF_STANDARD;
+import static io.wcm.wcm.core.components.impl.models.helpers.DataLayerTestUtils.enableDataLayer;
+import static io.wcm.wcm.core.components.impl.models.v3.ImageV3Impl.RESOURCE_TYPE;
+import static io.wcm.wcm.core.components.testcontext.AppAemContext.CONTENT_ROOT;
+import static io.wcm.wcm.core.components.testcontext.AppAemContext.DAM_ROOT;
+import static io.wcm.wcm.core.components.testcontext.TestUtils.assertInvalidLink;
+import static io.wcm.wcm.core.components.testcontext.TestUtils.assertInvalidMedia;
+import static io.wcm.wcm.core.components.testcontext.TestUtils.assertValidLink;
+import static io.wcm.wcm.core.components.testcontext.TestUtils.assertValidMedia;
+import static io.wcm.wcm.core.components.testcontext.TestUtils.loadComponentDefinition;
+import static org.apache.sling.api.resource.ResourceResolver.PROPERTY_RESOURCE_TYPE;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import com.adobe.cq.wcm.core.components.models.Image;
+import com.adobe.cq.wcm.core.components.models.datalayer.AssetData;
+import com.adobe.cq.wcm.core.components.models.datalayer.ComponentData;
+import com.adobe.cq.wcm.core.components.models.datalayer.ImageData;
+import com.day.cq.dam.api.Asset;
+import com.day.cq.wcm.api.Page;
+
+import io.wcm.handler.link.type.ExternalLinkType;
+import io.wcm.sling.commons.adapter.AdaptTo;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.wcm.commons.contenttype.ContentType;
+import io.wcm.wcm.core.components.testcontext.AppAemContext;
+import io.wcm.wcm.core.components.testcontext.ImageAreaTestData;
+import io.wcm.wcm.core.components.testcontext.MediaFormats;
+import io.wcm.wcm.core.components.testcontext.ResourceTypeForcingResourceWrapper;
+
+@ExtendWith(AemContextExtension.class)
+class ImageV3ImplTest {
+
+ private final AemContext context = AppAemContext.newAemContext();
+
+ private Page page;
+ private Asset asset;
+
+ @BeforeEach
+ void setUp() {
+ loadComponentDefinition(context, RESOURCE_TYPE);
+
+ page = context.create().page(CONTENT_ROOT + "/page1");
+ asset = context.create().asset(DAM_ROOT + "/sample.jpg", 160, 90, ContentType.JPEG,
+ DC_TITLE, "Asset Title",
+ DC_DESCRIPTION, "Asset Description");
+ // create web rendition to test auto-cropping
+ context.create().assetRenditionWebEnabled(asset);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testNoImage() {
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertNull(underTest.getSrc());
+ assertNull(underTest.getTitle());
+ assertNull(underTest.getAlt());
+ assertNull(underTest.getUuid());
+ assertNull(underTest.getLink());
+ assertTrue(underTest.displayPopupTitle());
+ assertNull(underTest.getFileReference());
+ assertNull(underTest.getJson());
+ assertArrayEquals(new int[0], underTest.getWidths());
+ assertNull(underTest.getSrcUriTemplate());
+ assertFalse(underTest.isLazyEnabled());
+ assertEquals(0, underTest.getLazyThreshold());
+ assertNull(underTest.getAreas());
+ assertFalse(underTest.isDecorative());
+ assertNotNull(underTest.getId());
+
+ assertInvalidMedia(underTest);
+ assertInvalidLink(underTest.getImageLink());
+ assertNull(underTest.getData());
+
+ assertNull(underTest.getWidth());
+ assertNull(underTest.getHeight());
+ assertNull(underTest.getSizes());
+ assertNull(underTest.getSrcset());
+ assertNull(underTest.getSmartCropRendition());
+ assertFalse(underTest.isDmImage());
+
+ assertEquals(RESOURCE_TYPE, underTest.getExportedType());
+ }
+
+ @Test
+ void testInvalidAssetReference() {
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_MEDIA_REF_STANDARD, "/content/dam/invalid"));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertInvalidMedia(underTest);
+ assertInvalidLink(underTest.getImageLink());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWithAssetImage() {
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ JCR_TITLE, "Resource Title",
+ PN_ALT, "Resource Alt"));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ String expectedMediaUrl = DAM_ROOT + "/sample.jpg/_jcr_content/renditions/original./sample.jpg";
+
+ assertEquals(expectedMediaUrl, underTest.getSrc());
+ assertEquals("Asset Title", underTest.getTitle());
+ assertEquals("Asset Description", underTest.getAlt());
+ assertEquals("", underTest.getUuid());
+ assertNull(underTest.getLink());
+ assertTrue(underTest.displayPopupTitle());
+ assertEquals(asset.getPath(), underTest.getFileReference());
+ assertArrayEquals(new int[] { 160 }, underTest.getWidths());
+ assertEquals("/content/dam/sample/sample.jpg/_jcr_content/renditions/original.image_file.{.width}.0.file/sample.jpg", underTest.getSrcUriTemplate());
+ assertFalse(underTest.isLazyEnabled());
+ assertNull(underTest.getAreas());
+
+ assertValidMedia(underTest, expectedMediaUrl);
+ assertInvalidLink(underTest.getImageLink());
+ }
+
+ @Test
+ void testWithAssetImage_SVG() {
+ Asset svgAsset = context.create().asset(DAM_ROOT + "/sample.svg", 160, 90, ContentType.SVG);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, svgAsset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ String expectedMediaUrl = DAM_ROOT + "/sample.svg/_jcr_content/renditions/original./sample.svg";
+
+ assertEquals(expectedMediaUrl, underTest.getSrc());
+ assertNull(underTest.getSrcUriTemplate());
+ }
+
+ @Test
+ @SuppressWarnings({ "deprecation", "null" })
+ void testWithAssetImageFromPage() {
+ Page page1 = context.currentPage(context.create().page(page, "page1", null,
+ NN_PAGE_FEATURED_IMAGE, Map.of(
+ PN_MEDIA_REF_STANDARD, asset.getPath())));
+
+ context.currentResource(context.create().resource(page1, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ JCR_TITLE, "Resource Title",
+ PN_ALT, "Resource Alt"));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ String expectedMediaUrl = DAM_ROOT + "/sample.jpg/_jcr_content/renditions/original./sample.jpg";
+
+ assertEquals(expectedMediaUrl, underTest.getSrc());
+ assertEquals("Asset Title", underTest.getTitle());
+ assertEquals("Asset Description", underTest.getAlt());
+ assertEquals("", underTest.getUuid());
+ assertNull(underTest.getLink());
+ assertTrue(underTest.displayPopupTitle());
+ assertEquals(asset.getPath(), underTest.getFileReference());
+ assertArrayEquals(new int[] { 160 }, underTest.getWidths());
+ assertEquals("/content/dam/sample/sample.jpg/_jcr_content/renditions/original.image_file.{.width}.0.file/sample.jpg", underTest.getSrcUriTemplate());
+ assertFalse(underTest.isLazyEnabled());
+ assertNull(underTest.getAreas());
+
+ assertValidMedia(underTest, expectedMediaUrl);
+ assertInvalidLink(underTest.getImageLink());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWithUploadedImage() {
+ Resource imageResource = context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ NN_MEDIA_INLINE_STANDARD + "Name", "file1.png");
+ context.load().binaryFile("/files/test.png", imageResource.getPath() + "/" + NN_MEDIA_INLINE_STANDARD, ContentType.PNG);
+ context.currentResource(imageResource);
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ String expectedMediaUrl = "/content/sample/en/page1/_jcr_content/image/file./file1.png";
+
+ assertEquals(expectedMediaUrl, underTest.getSrc());
+ assertNull(underTest.getTitle());
+ assertNull(underTest.getAlt());
+ assertNull(underTest.getUuid());
+ assertNull(underTest.getLink());
+ assertTrue(underTest.displayPopupTitle());
+ assertNull(underTest.getFileReference());
+ assertArrayEquals(new int[] { 160 }, underTest.getWidths());
+ assertEquals("/content/sample/en/page1/_jcr_content/image/file.image_file.{.width}.0.file/file1.png", underTest.getSrcUriTemplate());
+ assertFalse(underTest.isLazyEnabled());
+ assertNull(underTest.getAreas());
+
+ assertValidMedia(underTest, expectedMediaUrl);
+ assertInvalidLink(underTest.getImageLink());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWithImageAndLink() {
+ enableDataLayer(context, true);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ PN_LINK_TITLE, ExternalLinkType.ID,
+ PN_LINK_EXTERNAL_REF, "http://myhost"));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("Asset Title", underTest.getTitle());
+ assertEquals("Asset Description", underTest.getAlt());
+ assertEquals("http://myhost", underTest.getLink());
+
+ assertValidLink(underTest.getImageLink(), "http://myhost");
+
+ ComponentData data = underTest.getData();
+ assertNotNull(data);
+ assertEquals(RESOURCE_TYPE, data.getType());
+ assertEquals("Asset Title", data.getTitle());
+ assertEquals("http://myhost", data.getLinkUrl());
+
+ AssetData assetData = ((ImageData)data).getAssetData();
+ assertEquals(asset.getPath(), assetData.getUrl());
+ assertEquals(asset.getMimeType(), assetData.getFormat());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWithImageAndLink_Decorative() {
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ PN_LINK_TITLE, ExternalLinkType.ID,
+ PN_LINK_EXTERNAL_REF, "http://myhost",
+ PN_IS_DECORATIVE, true));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("Asset Title", underTest.getTitle());
+ assertEquals("", underTest.getAlt());
+ assertNull(underTest.getLink());
+ assertTrue(underTest.isDecorative());
+
+ assertInvalidLink(underTest.getImageLink());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWithImageAndLink_Decorative_ContentPolicy() {
+ context.contentPolicyMapping(RESOURCE_TYPE,
+ PN_IS_DECORATIVE, true,
+ PN_DESIGN_LAZY_THRESHOLD, 10);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ PN_LINK_TITLE, ExternalLinkType.ID,
+ PN_LINK_EXTERNAL_REF, "http://myhost"));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("Asset Title", underTest.getTitle());
+ assertEquals("", underTest.getAlt());
+ assertNull(underTest.getLink());
+ assertEquals(10, underTest.getLazyThreshold());
+
+ assertInvalidLink(underTest.getImageLink());
+ }
+
+ @Test
+ void testWithImage_TitleAltNotFormAsset() {
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ JCR_TITLE, "Resource Title",
+ PN_ALT, "Resource Alt",
+ PN_TITLE_VALUE_FROM_DAM, false,
+ PN_ALT_VALUE_FROM_DAM, false));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("Resource Title", underTest.getTitle());
+ assertEquals("Resource Alt", underTest.getAlt());
+ }
+
+ @Test
+ void testWithImage_TitleAltNotFormAsset_ContentPolicy() {
+ context.contentPolicyMapping(RESOURCE_TYPE,
+ PN_TITLE_VALUE_FROM_DAM, false,
+ PN_ALT_VALUE_FROM_DAM, false);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ JCR_TITLE, "Resource Title",
+ PN_ALT, "Resource Alt"));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("Resource Title", underTest.getTitle());
+ assertEquals("Resource Alt", underTest.getAlt());
+ }
+
+ @Test
+ void testWithNoImageAsset() {
+ Asset pdfAsset = context.create().asset(DAM_ROOT + "/file1.pdf", "/files/test.pdf", ContentType.PDF);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, pdfAsset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertInvalidMedia(underTest);
+ }
+
+ @Test
+ void testDisplayPopupTitle() {
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ PN_DISPLAY_POPUP_TITLE, false));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertFalse(underTest.displayPopupTitle());
+ }
+
+ @Test
+ void testDisplayPopupTitle_ContentPolicy() {
+ context.contentPolicyMapping(RESOURCE_TYPE,
+ PN_DISPLAY_POPUP_TITLE, false);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertFalse(underTest.displayPopupTitle());
+ }
+
+ @Test
+ void testLazyEnabled_ContentPolicy() {
+ context.contentPolicyMapping(RESOURCE_TYPE,
+ PN_DESIGN_LAZY_LOADING_ENABLED, false); // property is internally named "disabled", value is inverted
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertTrue(underTest.isLazyEnabled());
+ }
+
+ @Test
+ void testUUID() {
+ Resource resource = AdaptTo.notNull(asset, Resource.class);
+ ModifiableValueMap props = AdaptTo.notNull(resource, ModifiableValueMap.class);
+ props.put(JCR_UUID, "test-uuid");
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("test-uuid", underTest.getUuid());
+ }
+
+ @Test
+ void testUUID_Disabled() {
+ context.contentPolicyMapping(RESOURCE_TYPE,
+ PN_UUID_DISABLED, true);
+
+ Resource resource = AdaptTo.notNull(asset, Resource.class);
+ ModifiableValueMap props = AdaptTo.notNull(resource, ModifiableValueMap.class);
+ props.put(JCR_UUID, "test-uuid");
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertNull(underTest.getUuid());
+ }
+
+ @Test
+ void testWidths() {
+ context.contentPolicyMapping(RESOURCE_TYPE,
+ PN_COMPONENT_MEDIA_FORMATS, new String[] { MediaFormats.LANDSCAPE.getName() },
+ PN_COMPONENT_MEDIA_RESPONSIVE_TYPE, "imageSizes",
+ NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES, Map.of("sizes", "100vw", "widths", "100,50"),
+ PN_COMPONENT_MEDIA_AUTOCROP, true);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("/content/dam/sample/sample.jpg/_jcr_content/renditions/original./sample.jpg", underTest.getSrc());
+ assertEquals(asset.getPath(), underTest.getFileReference());
+ assertArrayEquals(new int[] { 50, 100, 160 }, underTest.getWidths());
+ assertEquals("/content/dam/sample/sample.jpg/_jcr_content/renditions/original.image_file.{.width}.0.file/sample.jpg", underTest.getSrcUriTemplate());
+
+ assertValidMedia(underTest, DAM_ROOT + "/sample.jpg/_jcr_content/renditions/original./sample.jpg");
+ }
+
+ @Test
+ void testAreas() {
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath(),
+ PN_MAP, ImageAreaTestData.MAP_STRING));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals(ImageAreaTestData.getExpectedAreasV1(context), underTest.getAreas());
+ }
+
+ @Test
+ void testWithImageAutoCropping_ContentPolicy() {
+ context.contentPolicyMapping(RESOURCE_TYPE,
+ PN_COMPONENT_MEDIA_FORMATS, new String[] { MediaFormats.SQUARE.getName() },
+ PN_COMPONENT_MEDIA_AUTOCROP, true);
+
+ context.currentResource(context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath()));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("Asset Title", underTest.getTitle());
+ assertEquals("/content/dam/sample/sample.jpg/_jcr_content/renditions/original.image_file.90.90.35,0,125,90.file/sample.jpg", underTest.getSrc());
+ }
+
+ @Test
+ void testWithImageAutoCropping_ContentPolicy_WrappedResource() {
+ // prepare dummy component that delegates to the component under test
+ final String DELEGATE_RESOURCE_TYPE = "myapp/components/delegate";
+ context.create().resource("/apps/" + DELEGATE_RESOURCE_TYPE,
+ "sling:resourceSuperType", RESOURCE_TYPE);
+
+ context.contentPolicyMapping(DELEGATE_RESOURCE_TYPE,
+ PN_COMPONENT_MEDIA_FORMATS, new String[] { MediaFormats.SQUARE.getName() },
+ PN_COMPONENT_MEDIA_AUTOCROP, true);
+
+ Resource resource = context.create().resource(page, "image",
+ PROPERTY_RESOURCE_TYPE, DELEGATE_RESOURCE_TYPE,
+ PN_IMAGE_FROM_PAGE_IMAGE, false,
+ PN_MEDIA_REF_STANDARD, asset.getPath());
+
+ // set context resource to wrapped resource
+ context.currentResource(new ResourceTypeForcingResourceWrapper(resource, RESOURCE_TYPE));
+
+ Image underTest = AdaptTo.notNull(context.request(), Image.class);
+
+ assertEquals("Asset Title", underTest.getTitle());
+ assertEquals("/content/dam/sample/sample.jpg/_jcr_content/renditions/original.image_file.90.90.35,0,125,90.file/sample.jpg", underTest.getSrc());
+ }
+
+}
diff --git a/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/wcmio/v1/ResponsiveImageV1ImplTest.java b/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/wcmio/v1/ResponsiveImageV1ImplTest.java
index a9b22e15..b5826159 100644
--- a/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/wcmio/v1/ResponsiveImageV1ImplTest.java
+++ b/bundles/core/src/test/java/io/wcm/wcm/core/components/impl/models/wcmio/v1/ResponsiveImageV1ImplTest.java
@@ -178,7 +178,6 @@ void testWithUploadedImage() {
}
@Test
- @SuppressWarnings("null")
void testWithImageAndLink() {
enableDataLayer(context, true);
diff --git a/changes.xml b/changes.xml
index e53f3a9f..ad39c420 100644
--- a/changes.xml
+++ b/changes.xml
@@ -23,6 +23,12 @@
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
+
+
+ Add Image (v3).
+
+
+
Fix detection of teaser actions in Teaser V2.
diff --git a/examples/bundles/examples-core/pom.xml b/examples/bundles/examples-core/pom.xml
index e08eff1e..9607467a 100644
--- a/examples/bundles/examples-core/pom.xml
+++ b/examples/bundles/examples-core/pom.xml
@@ -25,13 +25,13 @@
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
../../../parent/pom.xml
io.wcm.samples
io.wcm.wcm.core.components.examples-core
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
jar
WCM Core Components Examples Core
@@ -44,7 +44,7 @@
io.wcm
io.wcm.wcm.core.components
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
compile
diff --git a/examples/content-packages/examples-libs/pom.xml b/examples/content-packages/examples-libs/pom.xml
index c6d56fe9..92ae6a31 100644
--- a/examples/content-packages/examples-libs/pom.xml
+++ b/examples/content-packages/examples-libs/pom.xml
@@ -25,13 +25,13 @@
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
../../../parent/pom.xml
io.wcm.samples
io.wcm.wcm.core.components.examples-libs
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
content-package
WCM Core Components Examples wcm.io Libraries
@@ -42,7 +42,7 @@
io.wcm
io.wcm.wcm.core.components
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
compile
diff --git a/examples/content-packages/examples-sample-content/jcr_root/content/wcmio-core-components-examples/library/core-content/image/.content.xml b/examples/content-packages/examples-sample-content/jcr_root/content/wcmio-core-components-examples/library/core-content/image/.content.xml
index fe30ebe6..62342e5a 100644
--- a/examples/content-packages/examples-sample-content/jcr_root/content/wcmio-core-components-examples/library/core-content/image/.content.xml
+++ b/examples/content-packages/examples-sample-content/jcr_root/content/wcmio-core-components-examples/library/core-content/image/.content.xml
@@ -1,5 +1,5 @@
-
+ linkURL="https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/image/v3"/>
@@ -114,10 +115,11 @@
sling:resourceType="core-components-examples/components/demo/component">
@@ -160,10 +162,11 @@
sling:resourceType="core-components-examples/components/demo/component">
@@ -207,10 +210,11 @@
sling:resourceType="core-components-examples/components/demo/component">
@@ -234,6 +238,47 @@
reference="../../component"/>
+
+
+
+
+
+
+
+
+
+
+
+
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
../../../parent/pom.xml
io.wcm.samples
io.wcm.wcm.core.components.examples-sample-content
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
content-package
WCM Core Components Examples Content
diff --git a/examples/content-packages/examples/pom.xml b/examples/content-packages/examples/pom.xml
index ac7ec975..054c5f17 100644
--- a/examples/content-packages/examples/pom.xml
+++ b/examples/content-packages/examples/pom.xml
@@ -25,13 +25,13 @@
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
../../../parent/pom.xml
io.wcm.samples
io.wcm.wcm.core.components.examples
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
content-package
WCM Core Components Examples
@@ -42,7 +42,7 @@
io.wcm.samples
io.wcm.wcm.core.components.examples-core
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
compile
diff --git a/examples/pom.xml b/examples/pom.xml
index 486b4441..1bce240b 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -25,13 +25,13 @@
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
../parent/pom.xml
io.wcm.samples
io.wcm.wcm.core.components.examples.root
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
pom
diff --git a/parent/pom.xml b/parent/pom.xml
index 48ca58b2..1461357c 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -31,7 +31,7 @@
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
pom
WCM Core Components
@@ -54,7 +54,7 @@
http://localhost:4503
- 2023-08-21T09:25:52Z
+ 2023-09-02T12:42:05Z
@@ -118,49 +118,49 @@
io.wcm
io.wcm.sling.commons
- 1.4.0
+ 1.6.4
io.wcm
io.wcm.handler.commons
- 1.4.0
+ 1.5.0
io.wcm
io.wcm.handler.url
- 1.5.4
+ 1.10.2
io.wcm
io.wcm.handler.link
- 1.7.4
+ 1.10.2
io.wcm
io.wcm.handler.media
- 1.13.6
+ 1.15.6
io.wcm
io.wcm.handler.richtext
- 1.5.0
+ 1.6.4
io.wcm
io.wcm.wcm.commons
- 1.9.0
+ 1.10.0
io.wcm
io.wcm.wcm.ui.granite
- 1.8.0
+ 1.9.14
io.wcm
io.wcm.testing.aem-mock.junit5
- 5.2.2
+ 5.3.0
org.apache.sling
@@ -170,7 +170,7 @@
org.apache.sling
org.apache.sling.testing.caconfig-mock-plugin
- 1.5.0
+ 1.5.2
io.wcm
@@ -196,7 +196,7 @@
org.skyscreamer
jsonassert
- 1.5.0
+ 1.5.1
diff --git a/pom.xml b/pom.xml
index 8bf91704..0ceb612a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,13 +25,13 @@
io.wcm
io.wcm.wcm.core.components.parent
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
parent/pom.xml
io.wcm
io.wcm.wcm.core.components.root
- 1.13.3-2.22.6-SNAPSHOT
+ 1.14.0-2.22.6-SNAPSHOT
pom
${site.url}/${site.url.module.prefix}/
diff --git a/src/site/markdown/components.md b/src/site/markdown/components.md
index fc69bda1..2ba09312 100644
--- a/src/site/markdown/components.md
+++ b/src/site/markdown/components.md
@@ -17,7 +17,7 @@ _Enhanced with_ MEDIA _Media Handler
* [Title (v3)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/title/v3) | [v2](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/title/v2) LINK
* [Text (v2)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/text/v2) RICHTEXT LINK
-* [Image (v2)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/image/v2) (deprecated) MEDIA LINK
+* [Image (v3)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/image/v3) | [(v2)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/image/v2) MEDIA LINK
* [Button (v2)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/button/v2) | [(v1)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/button/v1) LINK
* [Teaser (v2)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/teaser/v2) | [(v1)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/teaser/v1) MEDIA LINK RICHTEXT
* [List (v4)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/list/v4) | [(v3)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/list/v3) | [(v2)](https://github.com/wcm-io/wcm-io-wcm-core-components/tree/develop/bundles/core/src/main/webapp/app-root/components/list/v2) LINK
@@ -59,4 +59,3 @@ _Enhanced with_ MEDIA _Media Handler
**Page Authoring Components**
* Sharing (v1)
-* Image (v3)