Goal
Assuming AEM Assets is the image repository and the usecase is to make use of these assets managed in AEM, in an external CMS (not AEM sites) or a thirdparty website, Assets Selector provides a nice interface for browse/search & selecting the assets
Combined with Dynamic Media Smart Crop, you can generate dynamic rendition urls (scene7 delivery urls) for creating intelligent responsive crops for various device breakpoints, replacing the need for static renditions (eg. doing manual cropping using photoshop)...
For setting up AEM with Dynamic Media Scene7 check this post
Demo | Source Code | Github
Solution
1) Assuming AEM is setup with Dynamic Media Scene7 (check this post for how to) create the Image Profiles by accessing Tools > Assets > Image Profiles or http://localhost:4502/mnt/overlay/dam/gui/content/processingprofilepage/imageprocessingprofiles.html/conf/global/settings/dam/adminui-extension/imageprofile
2) Click Create to create an Image Profile, give it a name, select the type as Smart Crop, add Responsive Image Crop breakpoints and click Save
3) Optionally create few processing profiles for customer specific purposes (only one image processing profile can be active on a folder ). The profiles are shown in asset selector page created to show image dynamic renditions...
4) Assign the Image Profile to a folder by selecting it and clicking Apply Processing Profile to Folder(s)
5) For accessing and showing the profiles in an external page, create read only authentication handler apps.experienceaem.assets.EAEMReadonlyAuthenticationHandler with the following code
package apps.experienceaem.assets; import org.apache.commons.lang3.StringUtils; import org.apache.sling.auth.core.spi.AuthenticationHandler; import org.apache.sling.auth.core.spi.AuthenticationInfo; import org.apache.sling.jcr.api.SlingRepository; import org.apache.sling.jcr.resource.api.JcrResourceConstants; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.Designate; import org.osgi.service.metatype.annotations.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import static org.osgi.framework.Constants.SERVICE_RANKING; @Component( service = { AuthenticationHandler.class, Filter.class }, immediate = true, property = { SERVICE_RANKING + ":Integer=" + 9999, AuthenticationHandler.PATH_PROPERTY + "=/content/dam", AuthenticationHandler.PATH_PROPERTY + "=/conf", "service.description=Experience AEM Ready only Authentication Handler", "sling.filter.scope=REQUEST" }) @Designate(ocd = EAEMReadonlyAuthenticationHandler.Configuration.class) public class EAEMReadonlyAuthenticationHandler implements AuthenticationHandler, Filter { private static final Logger log = LoggerFactory.getLogger(EAEMReadonlyAuthenticationHandler.class); private static String AUTH_TYPE_ASSET_SELECTOR = "EAEM_ASSET_SELECTOR"; private static final String SESSION_REQ_ATTR = EAEMReadonlyAuthenticationHandler.class.getName() + ".session"; private String readOnlyUser = ""; @Reference private SlingRepository repository; @Reference(target = "(service.pid=com.day.crx.security.token.impl.impl.TokenAuthenticationHandler)") private AuthenticationHandler wrappedAuthHandler; @Activate protected void activate(final Configuration config) { readOnlyUser = config.read_only_user(); } public AuthenticationInfo extractCredentials(HttpServletRequest request, HttpServletResponse response) { AuthenticationInfo authInfo = null; String authType = request.getParameter("authType"); if(StringUtils.isEmpty(authType) || !authType.equals(AUTH_TYPE_ASSET_SELECTOR)){ return wrappedAuthHandler.extractCredentials(request, response); } Session adminSession = null; try{ adminSession = repository.loginAdministrative(null); Session userSession = adminSession.impersonate(new SimpleCredentials(readOnlyUser, new char[0])); request.setAttribute(SESSION_REQ_ATTR, userSession); authInfo = new AuthenticationInfo(AUTH_TYPE_ASSET_SELECTOR); authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, userSession); }catch(Exception e){ log.error("Error could not create session for authType - " + authType, e); return AuthenticationInfo.FAIL_AUTH; }finally { if (adminSession != null) { adminSession.logout(); } } return authInfo; } public boolean requestCredentials(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException { return wrappedAuthHandler.requestCredentials(httpServletRequest, httpServletResponse); } public void dropCredentials(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException { wrappedAuthHandler.dropCredentials(httpServletRequest, httpServletResponse); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Session userSession = (Session) request.getAttribute(EAEMReadonlyAuthenticationHandler.SESSION_REQ_ATTR); if (userSession != null) { request.removeAttribute(EAEMReadonlyAuthenticationHandler.SESSION_REQ_ATTR); } chain.doFilter(request, response); if (userSession != null) { userSession.logout(); } } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } @ObjectClassDefinition(name = "EAEMReadonlyAuthenticationHandler Configuration") public @interface Configuration { @AttributeDefinition( name = "Read only user", description = "Read only user for accessing asset metadata json", type = AttributeType.STRING) String read_only_user() default "eaem-read-only-user"; } }
6) The authentication handler provides readonly access to nodes in paths /content/dam and /conf, when the url contains query parameter authType=EAEM_ASSET_SELECTOR.It needs a read only user to impersonate, so create the user eg. eaem-read-only-user, provide read only permission on /content/dam and /conf eg. http://localhost:4502/security/permissions.html/principal/eaem-read-only-user?filter=user
7) Configure the read only user in the EAEMReadonlyAuthenticationHandler osgi properties http://localhost:4502/system/console/configMgr/apps.experienceaem.assets.EAEMReadonlyAuthenticationHandler
8) Authentication handler uses repository.loginAdministrative(null) to get an admin session. To allow administrative login in code whitelist the bundle (best practice is to get a session using service user)
9) Since the calls to asset metadata in AEM are made from an external site, set the CORS policy to allow origin Access-Control-Allow-Origin
10) Create the standalone page for loading asset selector and showing dynamic renditions eg. eaem-asset-selector-dynamic-renditions\asset-selector\asset-selector.html. It can be written using any javascript framework (eg. just jQuery), the following sample uses CoralUI
<html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale = 1.0,maximum-scale = 1.0"> <title>Experience AEM Asset Selector</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script> <script src="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/js/libs/moment.js"></script> <script src="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/js/libs/jquery.js"></script> <script src="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/js/coral.js"></script> <script src="asset-selector.js"></script> <link rel="stylesheet" type="text/css" href="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/css/coral.css"> <link rel="stylesheet" type="text/css" href="asset-selector.css"> </head> <body> <div> <h1 class="coral-Heading" style="text-align: center">AEM Dynamic Renditions Selector</h1> </div> <div> <h4 class="coral-Heading"> AEM Asset selector lets you search for, filter, and browse assets within Adobe Experience Manager (AEM) Assets. Combined with Dynamic Media Smart Crop for Responsive images, you can select AEM assets in third party tools without the need to create manually cropped static renditions... <br><br> Enter the AEM URL eg. http://localhost:4502. Select Image profile, click on the button below to open AEM asset picker and select an asset... <br><br> <font color="red"> If you see a blank image the asset might not have been published in AEM or the asset was uploaded before applying a image processing profile to the folder in AEM</font> </h4> </div> <div> <div style="padding: 0 0 15px 0"> <input is="coral-textfield" type="text" value="http://localhost:4502" id="aem-host-url" size="100" style="width: 30rem"> </div> </div> <div> <label>Select Image Profile</label> <div class="coral-RadioGroup" id="dyn-image-profiles-grp"> </div> </div> <div> <label>Select a Dynamic Rendition</label> <div class="coral-RadioGroup" id="dyn-renditions-grp"> </div> </div> <div> <button is="coral-button" id="open-asset-picker" style="text-align: center" variant="primary"> Open Asset Selector </button> </div> <div> <h4 class="coral-Heading">Asset Info : </h4> <div id="asset-info">No asset selected</div> <img id="aem-image" src=""/> </div> </body> </html>
11) Add the necessary JS code in eaem-asset-selector-dynamic-renditions\asset-selector\asset-selector.js to load the asset picker, select an asset and show the dynamic renditions
(function(){ var ASSET_SELECTOR_ID = "aem-asset-selector", AUTH_TYPE_ASSET_SELECTOR = "authType=EAEM_ASSET_SELECTOR", ASSET_SELECTOR_URL = "/aem/assetpicker", IMAGE_PROFILES_URL = "/conf/global/settings/dam/adminui-extension/imageprofile.1.json", assetsSelected, imageProfiles = {}; $(document).ready(initComponents); function addHostAndAuthType(url){ if(!url || url.includes(AUTH_TYPE_ASSET_SELECTOR)){ return url; } var host = $("#aem-host-url").val(); if(!url.startsWith(host)){ url = $("#aem-host-url").val() + url; } if(url.includes("?")){ return (url + "&" + AUTH_TYPE_ASSET_SELECTOR); } return (url + "?" + AUTH_TYPE_ASSET_SELECTOR); } function loadImageProfiles(){ $("#dyn-image-profiles-grp").html(""); $("#dyn-renditions-grp").html(""); $.ajax(addHostAndAuthType(IMAGE_PROFILES_URL)).done(handler).fail(function(){ alert("Error getting image profile. Check AEM host value") }); function handler(data){ if(!data["jcr:primaryType"]){ alert("Error getting image profiles"); return; } _.each(data, function(imgProfile, key){ if(key.startsWith("jcr:") || ( imgProfile["crop_type"] != "crop_smart" ) ){ return; } var crops = []; _.each(imgProfile["banner"].split("|"), function(crop){ crops.push(crop.substring(0,crop.indexOf(","))); }); imageProfiles[key] = crops; }); addImageProfilesRadioGroup(imageProfiles); } } function addImageProfilesRadioGroup(){ var html = ""; _.each(imageProfiles, function(crops, profileName){ html = html + '<coral-radio name="dyn-image-profiles" value="' + profileName + '">' + profileName + '</coral-radio>'; }); $("#dyn-image-profiles-grp").html(html); $("[name='dyn-image-profiles']").change(addSmartCropsRadioGroup); } function addSmartCropsRadioGroup(){ var selProfile = $("input[name='dyn-image-profiles']:checked").val(); var html = '<coral-radio name="dyn-renditions" value="original" checked>Original</coral-radio>'; _.each(imageProfiles, function(crops, profileName){ if(profileName != selProfile){ return; } _.each(crops, function(crop){ html = html + '<coral-radio name="dyn-renditions" value="' + crop + '">' + crop + '</coral-radio>'; }); }); $("#dyn-renditions-grp").html(html); $("[name='dyn-renditions']").change(setDynamicRenditionImage); setDynamicRenditionImage(); } function createDialog(){ var selUrl = $("#aem-host-url").val() + ASSET_SELECTOR_URL, html = "<iframe width='1500px' height='800px' frameBorder='0' src='" + selUrl + "'></iframe>"; var dialog = new Coral.Dialog().set({ id: ASSET_SELECTOR_ID, content: { innerHTML: html } }); document.body.appendChild(dialog); } function hideDialog() { var dialog = document.querySelector('#' + ASSET_SELECTOR_ID); dialog.hide(); } function showDialog(){ var dialog = document.querySelector('#' + ASSET_SELECTOR_ID); dialog.show(); var $dialog = $(dialog); adjustHeader($dialog); } function adjustHeader($dialog){ $dialog.find(".coral3-Dialog-header").remove().find(".coral3-Dialog-footer").remove(); } function registerReceiveDataListener(handler) { if (window.addEventListener) { window.addEventListener("message", handler, false); } else if (window.attachEvent) { window.attachEvent("onmessage", handler); } } function receiveMessage(event) { var message = JSON.parse(event.data); if(message.config.action == "close"){ hideDialog(); return; } assetsSelected = message.data; setAssetInfo("original"); $("#aem-image").attr("src", assetsSelected[0].url); $("input[name='dyn-renditions']")[0].checked = true; var dialog = document.querySelector('#' + ASSET_SELECTOR_ID); dialog.hide(); } function setAssetInfo(dynRendition, dynRendUrl){ var assetInfo = "Path - " + assetsSelected[0].path; if(dynRendition != "original"){ assetInfo = assetInfo + "<BR><BR>" + "Dynamic Media URL - <a href='" + dynRendUrl+ "' target='_blank'>" + dynRendUrl + "</a>"; } $("#asset-info").html(assetInfo); } function setDynamicRenditionImage(){ var dynRendition = $("input[name='dyn-renditions']:checked").val(), $aemImage = $("#aem-image"); if(!$aemImage.attr("src")){ return; } if(dynRendition == "original"){ $aemImage.attr("src", addHostAndAuthType(assetsSelected[0].url)); setAssetInfo(dynRendition); return; } $.ajax(addHostAndAuthType(assetsSelected[0].url + ".2.json")).done(handler); function handler(data){ if(!data["jcr:content"]){ alert("Error getting asset metadata"); return; } var metadata = data["jcr:content"]["metadata"], dynRendUrl = metadata["dam:scene7Domain"] + "is/image/" + metadata["dam:scene7File"] + ":" + dynRendition; $aemImage.attr("src", dynRendUrl); setAssetInfo(dynRendition, dynRendUrl); } } function initComponents(){ loadImageProfiles(); registerReceiveDataListener(receiveMessage); $("#open-asset-picker").click(function(){ createDialog(); showDialog(); }); $("#aem-host-url").change(loadImageProfiles); } }());
12) The asset picker url /aem/assetpicker is loaded in an iFrame added in a Coral.Dialog. when user selects an asset, the picker fires message event, captured by the receiveMessage() function and selected assets specific data is extracted from event object...
13) The dynamic rendition of an asset in AEM asset details eg. http://localhost:4502/assetdetails.html/content/dam/experience-aem/room-green.jpg
14) The asset picker shown in an external site/page eg. file:///C:/dev/projects/eaem-extensions/eaem-65-extensions/eaem-asset-selector-dynamic-renditions/asset-selector/asset-selector.html
15) Selected AEM asset dynamic renditions shown in the external site / page
16) Test the image responsiveness using embed code provided in AEM asset details page and adding it in a standalone html
No comments:
Post a Comment