Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all 512 articles
Browse latest View live

AEM 6420 - Add Sort by Name, Type in Assets List view, set Default Sort to Name

0
0

Goal


In all 3 views (Card, List, Column) of Assets UI, set Default Sort by column to Name. Configure the Name and Type columns to be sortable in List View

Demo | Source Code | Package Install | Github

Extension



List view Sort by Name




Solution


1) Login to CRXDe Lite, overlay the name and type nodes of /libs/dam/gui/content/commons/availablecolumns, set the property sortable=true

                                  /apps/dam/gui/content/commons/availablecolumns/name
                                  /apps/dam/gui/content/commons/availablecolumns/type

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured">
<name
jcr:primaryType="nt:unstructured"
jcr:title="Name"
configurable="{Boolean}false"
default="{Boolean}true"
sortable="{Boolean}true"/>
<type
jcr:primaryType="nt:unstructured"
jcr:title="Type"
columnGroup="metadata"
default="{Boolean}true"
sortable="{Boolean}true"/>
</jcr:root>


2) Create a Sling Filter to intercept request URIs starting with /mnt/overlay/dam/gui/content/assets/jcr:content/views, set the sort property to name if sortName parameter is empty (overriding the default orderby=@jcr:created with orderby=nodename). NameSortSlingServletRequestWrapper, a sling request wrapper in the filter returns name if sortName parameter value is empty

package apps.experienceaem.assets;

import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;

@Component(
metatype = true,
description = "Experience AEM Request filter for AssetsDataSourceServlet",
label = "EAEM Datasource Sort Filter")
@Service({Filter.class})
@Properties({
@Property(name = "sling.filter.scope",value = {"REQUEST"},propertyPrivate = true),
@Property( name = "sling.filter.pattern",
value = {"/mnt/overlay/dam/gui/content/assets/jcr:content/views/.*"},
propertyPrivate = true),
@Property(name = "service.ranking",intValue = {-99},propertyPrivate = true)})
public class EAEMAssetDataSourceFilter implements Filter {
private static Logger log = LoggerFactory.getLogger(EAEMAssetDataSourceFilter.class);

public static String SORT_NAME = "sortName";

public static String SORT_NAME_NAME = "name";


@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
SlingHttpServletRequest slingRequest = (SlingHttpServletRequest)request;

String orderBy = slingRequest.getParameter(SORT_NAME);

if(StringUtils.isNotEmpty(orderBy)){
chain.doFilter(request, response);
return;
}

SlingHttpServletRequest nameSortRequest = new NameSortSlingServletRequestWrapper(slingRequest);
chain.doFilter(nameSortRequest, response);
}

@Override
public void destroy() {
}

private class NameSortSlingServletRequestWrapper extends SlingHttpServletRequestWrapper {
public NameSortSlingServletRequestWrapper(final SlingHttpServletRequest request) {
super(request);
}

@Override
public String getParameter(String paramName) {
if(!EAEMAssetDataSourceFilter.SORT_NAME.equals(paramName)){
return super.getParameter(paramName);
}

return EAEMAssetDataSourceFilter.SORT_NAME_NAME;
}
}
}


AEM 6420 - Touch UI Show / Hide Dialog Fields based on the Layout Container of Component

0
0

Goal


Hide component dialog fields, not supported by the Editable template Layout container policy, component was added into...

In the following demo /libs/foundation/components/image/cq:dialog/content/items/column/items/size of Image component is relevant only if the component is added in layout container wcm/foundation/components/responsivegrid with policy say wcm/foundation/components/responsivegrid/eaem_container_a

Foundation Image component in /libs was modified for demonstration purposes only

Demo | Package Install | Github


Get the layout container policy



Set the layout container policy on dialog field



Field shown if the policy matches layout container



Field hidden if the policy does not match layout container policy



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-policy-based-dialog-widgets-visibility

2) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-policy-based-dialog-widgets-visibility/clientlib and set property categories of String[] type to cq.authoring.dialog.all and dependencies String[] to underscore

3) Create file ( type nt:file ) /apps/eaem-policy-based-dialog-widgets-visibility/clientlib/js.txt, add the following

  policy-based-widgets.js

4) Create file (type nt:file) /apps/eaem-policy-based-dialog-widgets-visibility/clientlib/policy-based-widgets.js, add the following code

(function ($, $document) {
var EAEM_SHOW_FOR_CONTAINER_POLICY = "eaemshowforcontainerpolicy",
HIDE_HTML = "<div style='font-weight: bold; text-align: center; margin: 10px 0 10px 0'>"
+ "Widget not supported in root layout container"
+ "</div>";

$document.on('dialog-ready', handleEditablePolicies);

function handleEditablePolicies(){
var containerPolicyRelPath = getParentResponsiveGridPolicyPath(getCurrentEditable());

if(_.isEmpty(containerPolicyRelPath)){
console.error("Experience AEM - Container Relative Policy Path in page not available");
return;
}

var eTemplatePath = getEditableTemplatePath();

if(_.isEmpty(eTemplatePath)){
console.error("Experience AEM - Editable template path not available for current page");
return;
}

var containerPolicyMappingPath = eTemplatePath + "/policies/jcr:content/" + containerPolicyRelPath;

$.get(containerPolicyMappingPath + ".json").done(checkWidgetsAndPolicies);
}

function checkWidgetsAndPolicies(data){
var policyRelPath = data["cq:policy"];

if(_.isEmpty(policyRelPath)){
console.error("Experience AEM - Policy Relative Path in template not available");
return;
}

handleDialogWidgetsVisibility(policyRelPath);
}

function handleDialogWidgetsVisibility(policyRelPath){
var $widgetsWithPolicySet = $("[data-" + EAEM_SHOW_FOR_CONTAINER_POLICY + "]"),
$widget, value;

_.each($widgetsWithPolicySet, function(widget){
$widget = $(widget);

value = $widget.data(EAEM_SHOW_FOR_CONTAINER_POLICY);

if(value != policyRelPath){
$widget.closest(".coral-Form-fieldwrapper").append(HIDE_HTML);
$widget.remove();
}
});
}

function getEditableTemplatePath(){
var gAuthor = Granite.author;

if(!gAuthor || !gAuthor.pageInfo){
return "";
}

return gAuthor.pageInfo.editableTemplate;
}

function getCurrentEditable(){
var gAuthor = Granite.author;

if(!gAuthor || !gAuthor.DialogFrame){
return;
}

return gAuthor.DialogFrame.currentDialog.editable;
}

function getParentResponsiveGridPolicyPath(editable){
if(!editable){
return "";
}

var parent = editable, containerPolicyPath;

do{
if(!parent){
break;
}

if(parent.config && parent.config.isResponsiveGrid){
containerPolicyPath = parent.config.policyPath;
break;
}

parent = parent.getParent();
}while(true);

return containerPolicyPath;
}
}(jQuery, jQuery(document)));



AEM 6420 - Sling Servlet Filter (or sample how-to Virus Scan) to decode file streams for CreateAssetServlet

0
0

Goal


Add a filter executing prior to com.day.cq.dam.core.impl.servlet.CreateAssetServlet, intercepting the file upload stream and decode it. CreateAssetServlet will then create a dam:Asset using the decoded input stream

A common business case is, scanning streams for viruses before adding them as assets in AEM. The uploaded stream should be read twice, first for virus scan and second time by CreateAssetServlet to create the asset

Snippets for apps.experienceaem.assets.EAEMDecryptRequestPart  taken from org/apache/sling/engine/impl/parameters/RequestPartsIterator.java

Code in this post was not production harnessed, make sure you test (and improve it) for large files, small files, binaries, text files etc..

Demo | Package Install | Source Code | Github


Uploading an Encoded file


                                                Encoded files are decoded prior to asset creation




Uploading a Normal file

                                                Unencoded files upload is not supported with this filter installed



Solution


1) For test purposes encode an image using below code

        FileInputStream fileIn = new FileInputStream(inputPath);

byte[] bytes = IOUtils.toByteArray(fileIn);

String encoded = Base64.getEncoder().encodeToString(bytes);

FileOutputStream fileOut = new FileOutputStream(new File(encPath));

fileOut.write(encoded.getBytes());

fileOut.close();

2) Following code in filter decodes input stream and writes to a temp file, stream later read (by CreateAssetServlet ) is from this temp file

        private InputStream getDecodedStream(Part part) throws IOException{
File tmpFile = File.createTempFile(TEMP_PREFIX, ".tmp");

byte[] decoded = Base64.getDecoder().decode(IOUtils.toByteArray(part.getInputStream()));

FileOutputStream decodedStream = new FileOutputStream(tmpFile);

decodedStream.write(decoded);

decodedStream.close();

return new FileInputStream(tmpFile);
}

3) apps.experienceaem.assets.DecryptAssetsFilter for decoding the uploaded files

package apps.experienceaem.assets;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Part;

import javax.servlet.*;
import java.io.*;
import java.util.*;

@Component(
metatype = true,
description = "Experience AEM Request Decrypt Filter for CreateAssetServlet",
label = "EAEM CreateAssetServlet InputStream Decrypt Filter")
@Service({Filter.class})
@Properties({
@Property(name = "sling.filter.scope",value = {"REQUEST"}, propertyPrivate = true)
})
public class DecryptAssetsFilter implements Filter {
private static Logger log = LoggerFactory.getLogger(DecryptAssetsFilter.class);

public void init(FilterConfig filterConfig) throws ServletException {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!(request instanceof SlingHttpServletRequest) || !(response instanceof SlingHttpServletResponse)) {
chain.doFilter(request, response);
return;
}

final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

if (!StringUtils.equals("POST", slingRequest.getMethod()) || !isCreateAssetRequest(slingRequest) ) {
chain.doFilter(request, response);
return;
}

log.info("Decoding create asset request - " + slingRequest.getRequestURI());

Iterator parts = (Iterator)request.getAttribute("request-parts-iterator");

if( (parts == null) || !parts.hasNext()){
chain.doFilter(request, response);
return;
}

List<Part> otherParts = new ArrayList<Part>();
Part part = null;

while(parts.hasNext()) {
part = (Part) parts.next();

otherParts.add(new EAEMDecryptRequestPart(part));
}

request.setAttribute("request-parts-iterator", otherParts.iterator());

chain.doFilter(request, response);
}

private boolean isCreateAssetRequest(SlingHttpServletRequest slingRequest){
String[] selectors = slingRequest.getRequestPathInfo().getSelectors();

if(ArrayUtils.isEmpty(selectors) || (selectors.length > 1)){
return false;
}

return selectors[0].equals("createasset");
}

public void destroy() {
}

private static class EAEMDecryptRequestPart implements Part {
private final Part part;
private final InputStream inputStream;
private static final String TEMP_PREFIX = "eaem_decrypt_";

public EAEMDecryptRequestPart(Part part) throws IOException {
this.part = part;

if(!isFilePart(part)){
this.inputStream = new ByteArrayInputStream(IOUtils.toByteArray(part.getInputStream()));
}else{
this.inputStream = this.getDecodedStream(part);
}
}

private InputStream getDecodedStream(Part part) throws IOException{
File tmpFile = File.createTempFile(TEMP_PREFIX, ".tmp");

byte[] decoded = Base64.getDecoder().decode(IOUtils.toByteArray(part.getInputStream()));

FileOutputStream decodedStream = new FileOutputStream(tmpFile);

decodedStream.write(decoded);

decodedStream.close();

return new FileInputStream(tmpFile);
}

private boolean isFilePart(Part part) {
return StringUtils.isNotEmpty(part.getSubmittedFileName());
}

public InputStream getInputStream() throws IOException {
return inputStream;
}

public String getContentType() {
return part.getContentType();
}

public String getName() {
return part.getName();
}

public long getSize() {
return 0;
}

public void write(String s) throws IOException {
throw new UnsupportedOperationException("Writing parts directly to disk is not supported by this implementation, use getInputStream instead");
}

public void delete() throws IOException {
}

public String getHeader(String headerName) {
return part.getHeader(headerName);
}

public Collection<String> getHeaders(String headerName) {
return part.getHeaders(headerName);
}

public Collection<String> getHeaderNames() {
return part.getHeaderNames();
}

public String getSubmittedFileName() {
return part.getSubmittedFileName();
}

private <T> Collection<T> toCollection(Iterator<T> i) {
if ( i == null ) {
return Collections.emptyList();
} else {
List<T> c = new ArrayList<T>();
while(i.hasNext()) {
c.add(i.next());
}
return c;
}
}
}
}

AEM 6420 - Touch UI Validate Asset file names on Upload

0
0

Goal


Validate asset file names when they are uploaded to Assets Touch UI

For similar validation of sites page name check this post

Demo | Package Install | Github




Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-assets-file-name-validation

2) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-assets-file-name-validation/clientlib and set property categories of String[] type to dam.gui.coral.fileupload and dependencies String[] to lodash

3) Create file ( type nt:file ) /apps/eaem-touchui-assets-file-name-validation/clientlib/js.txt, add the following

  file-name-validate.js

4) Create file (type nt:file) /apps/eaem-touchui-assets-file-name-validation/clientlib/file-name-validate.js, add the following code

(function ($, $document) {
"use strict";

var _ = window._,
PATTERN = /^[a-z0-9_\-.]+$/,
ERROR_MSG = "Acceptable characters in file name are lowercase alphabets, numbers, underscore and hyphen <b>" + PATTERN + "</b>",
UPLOAD_DIALOG_CLASS = ".uploadListDialog",
registered = false;

$document.on("foundation-contentloaded", handleFileNames);

function handleFileNames() {
if(registered){
return;
}

registered = true;

var $fileUpload = $("coral-chunkfileupload");

$fileUpload.on('change', checkFileNames);
}

function checkFileNames(event) {
var $uploadDialog = $(UPLOAD_DIALOG_CLASS);

if(_.isEmpty($uploadDialog)){
return;
}

var fileUpload = this;

$uploadDialog.each(function(index, uploadDialog){
if(!uploadDialog.open){
return;
}

if(_.isEmpty(getInvalidFileNames(fileUpload))){
return;
}

uploadDialog.hide();

showAlert(ERROR_MSG);
});
}

function getInvalidFileNames(fileUpload){
var invalidFileNames = [];

if(!fileUpload || _.isEmpty(fileUpload.uploadQueue)){
return invalidFileNames;
}

_.each(fileUpload.uploadQueue, function(file){
if(!PATTERN.test(file.name)){
invalidFileNames.push(file.name)
}
});

return invalidFileNames;
}

function showAlert(message, title, callback){
var fui = $(window).adaptTo("foundation-ui"),
options = [{
id: "ok",
text: "OK",
primary: true
}];

message = message || "Unknown Error";
title = title || "Error";

fui.prompt(title, message, "warning", options, callback);
}
}(jQuery, jQuery(document)));



AEM 6420 - Touch UI Sites validate page name

0
0

Goal


In AEM Sites, validate name entered for page during creation (here the validator registered checks for lowercase letters, numbers and underscore)

For similar validation of asset file names check this post

Demo | Package Install | Github




Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-sites-page-validation

2) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-sites-page-validation/clientlib and set property categories of String[] type to cq.sites.validations and dependencies String[] to lodash

3) Create file ( type nt:file ) /apps/eaem-touchui-sites-page-validation/clientlib/js.txt, add the following

  page-name-validate.js

4) Create file (type nt:file) /apps/eaem-touchui-sites-page-validation/clientlib/page-name-validate.js, add the following code

(function ($, $window) {
"use strict";

var PATTERN = /^[a-z0-9_]+$/,
ERROR_MSG = "Acceptable characters in page name are lowercase alphabets, numbers, underscore <b>" + PATTERN + "</b>";

$window.adaptTo("foundation-registry").register("foundation.validation.validator", {
selector: '[data-foundation-validation~="admin.pagename"]',
validate: function(el) {
if (!PATTERN.test(el.value)) {
return ERROR_MSG;
}
}
});
}(jQuery, jQuery(window)));

InDesign - Creating Mac specific paths in InDesign docs using Windows InDesign Server 13.1

0
0

Goal


If you are ever in a situation where using Windows InDesign Server for creating documents with links to external images (linked artwork and not embedded) fails with Missing links error message, when the document is opened on Mac (and are not interested in installing InDesign extensions to fix link paths) ,this post is for you...

Solution


1) Use Windows InDesign Server 13.1 or higher

2) Run the script to create InDesign doc and link to external image. As the customer is using Windows InDesign server, it creates a windows specific path to image. When the document is opened on Mac, it fails with the obvious Missing Links error (path is windows specific).





3) To workaround this problem, the following script creates an InDesign document with link to external image using file:/// prefix and reinitLink() api.

         eg. file:///Users/nalabotu/dev/temp/exists_in_mac_not_windows.jpeg

         Make sure the path does NOT exist on InDesign server windows fs (say in C:\Users\nalabotu\dev\temp\exists_in_mac_not_windows.jpeg)

(function () {
var TEST_FILE = "C:/dev/projects/files/test.indd",
IMAGE_FILE = "C:/Users/nalabotu/Pictures/placeholder.jpg";

function addImageInPage(){
var doc = app.documents.add(),
rectangle = doc.rectangles.add();

rectangle.geometricBounds = [10,10,30,30];
rectangle.frameFittingOptions.autoFit = true;

try{
//add a placeholder image
rectangle.place(IMAGE_FILE);
}catch(err){
$.writeln(err);
}

var links = doc.links,
PATH_EXISTS_IN_MAC_NOT_WINDOWS = "file:///Users/nalabotu/dev/temp/exists_in_mac_not_windows.jpeg";

for(var i = 0; i < links.length; i++ ){
var link = links[0];

try{
//link.relink(new File(PATH_EXISTS_IN_MAC_NOT_WINDOWS));

// relink to a file that exists on mac, but not windows where the doc was created
link.reinitLink(PATH_EXISTS_IN_MAC_NOT_WINDOWS);
}catch(err){
$.writeln(err);
return;
}
}

doc.save(new File(TEST_FILE));

doc.close();
}
}());


4) The linked images exist on Mac



5) Open the created document on Mac, InDesign shows following alert. click Update Links



6) Fully resolved links in InDesign


AEM 6420 - TouchUI Increase the number of Sites / Pages loaded by default to 100

0
0

Goal


In Column view, increase the number of sites / pages loaded by default to 100 (from 40) so a user doesn't have to scroll each time...

Before adjusting default size please take into account the performance implications

Solution


1) Overlay /libs/wcm/core/content/sites/jcr:content/views/column into /apps/wcm/core/content/sites/jcr:content/views/column and set the properties @limit, @size



2) Overlay /libs/wcm/core/content/sites/jcr:content/views/column/datasource into /apps/wcm/core/content/sites/jcr:content/views/column/datasource and set the properties @limit



3) Similarly, the default load can be adjusted for....

                                Card view by overlaying - /libs/wcm/core/content/sites/jcr:content/views/card 

                                List view by overlaying - /libs/wcm/core/content/sites/jcr:content/views/list

AEM 6420 - CRXDE Environment Indicator

0
0

Goal


ACS Commons AEM Environment Indicator is a great tool for showing env specific visual pointers when a user has multiple tabs open in browser accessing different AEM servers, however it's only for Touch and not Classic or CRXDe Lite..

If you need a similar indicator for CRXDe Lite use the following chrome extension based method, avoiding adding server side code (If you need the script added on server and so to execute for all users try the method in this post, NOT RECOMMENDED, or modify the CRXDE Lite response using servlet filters)

Demo | Github



Solution


1) Install a chrome extension for injecting custom javascript eg. CJS - Custom JavaScript for websites



2) Click the CJS extension icon to open script editor; for specific AEM env, check the option enable cjs for this host




3) Add the following script, modify variable message accordingly

(function(){
function changeNavColorCRXDELite(switcher){
var message = "!!! CAUTION PUBLISH !!!";

var inner = switcher.firstChild;

inner.style.background = "#FFFF33";

var publishText = '<span style="font-size:30px; font-weight: bold; color:red; margin-left: 700px">' + message +'</span>';

inner.insertAdjacentHTML('beforeend', publishText);
}

Ext.onReady(function(){
var INTERVAL = setInterval(function(){
var switcher = document.getElementsByClassName("crx-switcher");

if(switcher.length > 0){
clearInterval(INTERVAL);
changeNavColorCRXDELite(switcher[0]);
}
}, 250);
});
}())



AEM - Random Package Filters

0
0
1) Package filter to include all numeric level 1 nodes (relative to parent folder) and exclude sub nodes

<?xml version="1.0" encoding="UTF-8"?>
<workspaceFilter version="1.0">
<filter root="/content/dam/experience-aem">
<include pattern="/content/dam/experience-aem/[1-9].*"/>
<exclude pattern="/content/dam/experience-aem/[1-9].*/.*"/>
</filter>
</workspaceFilter>




2) 

AEM 6420 - Generate High resolution thumbnails of docs using InDesign server for zoom in AEM

0
0

Goal


Enterprises with AEM Assets as central repository for images can use AEM Desktop App for CC collaboration. Using the app, assets within AEM are easily accessible on local desktop and can be used in desktop applications like InDesign for placing on pages and create documents, upload created documents to AEM

Using AEM and InDesign server integration, the Media extraction workflow step of DAM Update Asset workflow creates thumbnail renditions, adds extracted pages as subassets etc. The web rendition thumbnail cq5dam.web.1280.1280.jpeg, generated with otb scripts is of low resolution (created from embedded page preview) and results in blurry/pixelated image when zoomed in AEM /assetdetails.html (adding AEM desktop app on InDesign server also does not help)

This post is on adding necessary scripts to Media Extraction, for downloading original binaries of referenced assets in AEM, relink and generate high resolution web rendition thumbnail and subassets for better zoom experience (eg. to read fine print in marketing approval workflow process)

Thank you Kiran Murugulla for the bug fixes

Demo | Package Install | Github


Product (exports Low resolution thumbnail)



Custom Script (exports High resolution thumbnail)



Solution


1) Media extraction step picks the script from /libs for relative path dam/indesign/scripts/ThumbnailExport.jsx specified in Extend Scripts multifield, if the same path does not exist in /apps, so otb way of generating thumbnails



2) Override otb thumbnail export script /libs/settings/dam/indesign/scripts/ThumbnailExport.jsx by creating /apps/settings/dam/indesign/scripts/ThumbnailExport.jsx, add the following code

                         a. eaemGetAllLinks() - get all link paths (placing images using desktop app, they all are AEM paths)

                         b. eaemDownloadAndGetHighResMap() - download the original binaries of referenced images from AEM, to a temp folder

                         c. eaemReplaceLowResWithHighRes() - relink to downloaded high res images

                         d. eaemExportThumbnailUsingIDSIndd() - export and upload the high res web thumbnail to AEM

app.consoleout('EAEM - Relinking to High-Res Originals downloading from AEM...');

eaemReplaceLowResWithHighRes(document);

eaemExportThumbnailUsingIDSIndd(document);

function eaemDownloadAndGetHighResMap(aemLinks){
var aemToLocalMap = {}, aemLinkPath = "", fileName, localPath;

try{
app.consoleout('EAEM - START downloading High-Res renditions...');

for(var i = 0; i < aemLinks.length; i++ ) {
aemLinkPath = aemLinks[i];

fileName = aemLinkPath.substring(aemLinkPath.lastIndexOf("/"));

localPath = new File(sourceFolder.fullName + fileName);

app.consoleout('EAEM - Downloading : ' + aemLinkPath + ", to : " + localPath.fsName);

fetchResource (host, credentials, aemLinks[i], localPath);

app.consoleout('EAEM - Download complete : ' + aemLinkPath + ", to : " + localPath.fsName);

aemToLocalMap[aemLinkPath] = localPath;
}

app.consoleout('EAEM - END downloading High-Res Renditions...');
}catch(err){
app.consoleout('EAEM - ERROR downloading : ' + aemLinkPath);
}

return aemToLocalMap;
}

function eaemReplaceLowResWithHighRes(document){
var links = document.links, aemToLocalMap, link, filePath, highResPath;

aemToLocalMap = eaemDownloadAndGetHighResMap(eaemGetAllLinks(document));

try{
app.consoleout('EAEM - START relinking with High-Res renditions...');

for(var l = 0; l < links.length; l++ ) {
link = links[l];

filePath = eaemNormalizePath(link.filePath);

filePath = eaemAddDAMRootToPath(filePath.substring(filePath.indexOf("/")));

highResPath = aemToLocalMap[filePath];

app.consoleout('EAEM - RELINKING High-Res : ' + highResPath);

link.relink(highResPath);

link.update();
}

app.consoleout('EAEM - END relinking with High-Res renditions...');
}catch(err){
app.consoleout('EAEM - ERROR relinking : ' + highResPath);
}
}

function eaemNormalizePath(path){
if(!path){
return path;
}

path = path.replace(/\\/g, "/");

if(path.indexOf(":/") == 1){
//windows paths are like X:/content/dam/wip/myfile.jpg
}else{
path = path.replace(/:/g, "/");

if(path.indexOf("/") > 0){
path = "/" + path;
}
}

return path;
}

function eaemAddDAMRootToPath(path){
var EAEM_CONTENT_DAM_ROOT = "/content/dam",
MAC_CONTENT_DAM_ROOT = "/DAM",
MAC_CONTENT_DAM_VOLUME_ROOT = "/Volumes/DAM";

if(!path){
return path;
}

if(path.indexOf(MAC_CONTENT_DAM_ROOT) == 0){
path = EAEM_CONTENT_DAM_ROOT + path.substring(MAC_CONTENT_DAM_ROOT.length);
}else if(path.indexOf(MAC_CONTENT_DAM_VOLUME_ROOT) == 0){
path = EAEM_CONTENT_DAM_ROOT + path.substring(MAC_CONTENT_DAM_VOLUME_ROOT.length);
}else if(path.indexOf(EAEM_CONTENT_DAM_ROOT) != 0){
path = EAEM_CONTENT_DAM_ROOT + path;
}

return path;
}

function eaemGetAllLinks(document){
var linkAEMPaths = [],
links = document.links, link, filePath;

for(var i = 0; i < links.length; i++ ) {
link = links[i];

filePath = eaemNormalizePath(link.filePath);

filePath = eaemAddDAMRootToPath(filePath.substring(filePath.indexOf("/")));

linkAEMPaths.push(filePath);
}

return linkAEMPaths;
}

function eaemExportThumbnailUsingIDSIndd(document){
app.consoleout('EAEM - Generating thumbnail renditions...');

var tnFolder = new Folder(exportFolder.fullName + "/thumbnail"), thumbnail;

tnFolder.create();

with (app.jpegExportPreferences) {
exportResolution = 300;
jpegColorSpace = JpegColorSpaceEnum.RGB;
jpegQuality = JPEGOptionsQuality.MAXIMUM;
jpegRenderingStyle = JPEGOptionsFormat.PROGRESSIVE_ENCODING;
viewDocumentAfterExport = false;
pageString = document.pages.item(0).name;
jpegExportRange = ExportRangeOrAllPages.EXPORT_RANGE;
}

thumbnail = new File(tnFolder.fullName + '/cq5dam.web.1280.1280.jpeg');

document.exportFile(ExportFormat.JPG, thumbnail);

app.consoleout('EAEM - Posting file cq5dam.web.1280.1280.jpeg to location: ' + target + '/jcr:content/renditions');

putResource(host, credentials, thumbnail, 'cq5dam.web.1280.1280.jpeg', 'image/jpeg', target);

app.jpegExportPreferences.exportResolution = 72;

app.jpegExportPreferences.jpegQuality = JPEGOptionsQuality.LOW;

thumbnail = new File(tnFolder.fullName + '/thumbnail.jpg');

document.exportFile(ExportFormat.JPG, thumbnail);

app.consoleout('EAEM - Posting file thumbnail.jpg to location: ' + target + '/jcr:content/renditions');

putResource(host, credentials, thumbnail, 'thumbnail.jpg', 'image/jpeg', target);
}

AEM 6420 - Content Fragment Editor add Font size, Color, Background color RTE Plugin

0
0

Goal


Add an RTE plugin eaemTextFont to apply size, color, background color to the text in content fragment editor

Demo | Package Install | Github


Minimized



Toolbar Plugin



Maximized



Saved in CRX



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-cfm-font-size-plugin

2) Create a Font selector cq:Page /apps/eaem-touchui-cfm-font-size-plugin/font-selector with the following code

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:Page">
<jcr:content
jcr:mixinTypes="[sling:VanityPath]"
jcr:primaryType="nt:unstructured"
jcr:title="EAEM Font Selector"
sling:resourceType="granite/ui/components/coral/foundation/page">
<head jcr:primaryType="nt:unstructured">
<favicon
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
<viewport
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
<clientlibs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
categories="[coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral, eaem-cfm.rte.plugin]"/>
</head>
<body
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/body">
<items jcr:primaryType="nt:unstructured">
<form
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
class="foundation-form content-container"
maximized="{Boolean}true"
style="vertical">
<items jcr:primaryType="nt:unstructured">
<wizard
jcr:primaryType="nt:unstructured"
jcr:title="Select Text Font Color..."
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<text
jcr:primaryType="nt:unstructured"
jcr:title="Select Text Font Color..."
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<fixedColumns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<fontSize
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldDescription="Select text size"
fieldLabel="Size"
name="./size">
<items jcr:primaryType="nt:unstructured">
<def
jcr:primaryType="nt:unstructured"
text="Select Size"
value=""/>
<small
jcr:primaryType="nt:unstructured"
text="Small (15px)"
value="15px"/>
<medium
jcr:primaryType="nt:unstructured"
text="Medium (30px)"
value="30px"/>
<large
jcr:primaryType="nt:unstructured"
text="Large (40px)"
value="40px"/>
</items>
</fontSize>
<color
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
fieldDescription="Select text color"
fieldLabel="Text Color"
name="./color"
showProperties="{Boolean}true"/>
<bgColor
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
fieldDescription="Select background color"
fieldLabel="Background Color"
name="./bgColor"
showProperties="{Boolean}true"/>
</items>
</column>
</items>
</fixedColumns>
</items>
<parentConfig jcr:primaryType="nt:unstructured">
<prev
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/anchorbutton"
text="Cancel">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="cancel"/>
</prev>
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
disabled="{Boolean}true"
text="Apply"
type="submit"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</text>
</items>
</wizard>
</items>
</form>
</items>
</body>
</jcr:content>
</jcr:root>

3) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-cfm-font-size-plugin/clientlib and set property categories of String[] type to [dam.cfm.authoring.contenteditor.v2, eaem-cfm.rte.plugin] and dependencies String[] to [lodash]

4) Create file ( type nt:file ) /apps/eaem-touchui-cfm-font-size-plugin/clientlib/css.txt, add the following

  cfm-rte-font-size-plugin.css

5) Create file (type nt:file) /apps/eaem-touchui-cfm-font-size-plugin/clientlib/cfm-rte-font-size-plugin.css, add the following code

.eaem-cfm-font-size {
width: 50%;
margin-left: -50%;
height: 53%;
margin-top: -50%;
box-sizing: content-box;
z-index: 10100;
}

.eaem-cfm-font-size > iframe {
width: 100%;
height: 100%;
border: 1px solid #888;
}

6) Create file ( type nt:file ) /apps/eaem-touchui-cfm-font-size-plugin/clientlib/js.txt, add the following

  cfm-rte-font-size-plugin.js

7) Create file (type nt:file) /apps/eaem-touchui-cfm-font-size-plugin/clientlib/cfm-rte-font-size-plugin.js, add the following code

(function ($, $document) {
var EAEM_PLUGIN_ID = "eaemfont",
EAEM_TEXT_FONT_FEATURE = "eaemTextFont",
EAEM_TEXT_FONT_ICON = EAEM_PLUGIN_ID + "#" + EAEM_TEXT_FONT_FEATURE,
CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
FONT_SELECTOR_URL = "/apps/eaem-touchui-cfm-font-size-plugin/font-selector.html",
SENDER = "experience-aem", REQUESTER = "requester", $eaemFontPicker,
url = document.location.pathname;

if( url.indexOf("/editor.html") == 0 ){
extendStyledTextEditor();
registerPlugin();
}else if(url.indexOf(FONT_SELECTOR_URL) == 0){
handlePicker();
}

function handlePicker(){
$document.on("foundation-contentloaded", fillDefaultValues);

$document.on("click", CANCEL_CSS, sendCancelMessage);

$document.submit(sentTextAttributes);
}

function setWidgetValue(form, selector, value){
Coral.commons.ready(form.querySelector(selector), function (field) {
field.value = _.isEmpty(value) ? "" : decodeURIComponent(value);
});
}

function rgbToHex(color){
if(_.isEmpty(color)){
return color;
}

if(color.indexOf("rgb") == 0){
color = CUI.util.color.RGBAToHex(color);
}

return color;
}

function fillDefaultValues(){
var queryParams = queryParameters(),
form = $("form")[0];

setWidgetValue(form, "[name='./color']", queryParams.color);

setWidgetValue(form, "[name='./size']", queryParams.size);

setWidgetValue(form, "[name='./bgColor']", queryParams.bgColor);
}

function sentTextAttributes(){
var message = {
sender: SENDER,
action: "submit",
data: {}
}, $form = $("form"), $field;

_.each($form.find("[name^='./']"), function(field){
$field = $(field);
message.data[$field.attr("name").substr(2)] = $field.val();
});

parent.postMessage(JSON.stringify(message), "*");
}

function queryParameters() {
var result = {}, param,
params = document.location.search.split(/\?|\&/);

params.forEach( function(it) {
if (_.isEmpty(it)) {
return;
}

param = it.split("=");
result[param[0]] = param[1];
});

return result;
}

function sendCancelMessage(){
var message = {
sender: SENDER,
action: "cancel"
};

getParent().postMessage(JSON.stringify(message), "*");
}

function getParent() {
if (window.opener) {
return window.opener;
}

return parent;
}

function closePicker(event){
event = event.originalEvent || {};

if (_.isEmpty(event.data)) {
return;
}

var message, action;

try{
message = JSON.parse(event.data);
}catch(err){
return;
}

if (!message || message.sender !== SENDER) {
return;
}

action = message.action;

if(action === "submit"){
$eaemFontPicker.eaemFontPlugin.editorKernel.execCmd(EAEM_TEXT_FONT_FEATURE, message.data);
}

var modal = $eaemFontPicker.data('modal');
modal.hide();
modal.$element.remove();
}

function extendStyledTextEditor(){
var origFn = Dam.CFM.StyledTextEditor.prototype._start;

Dam.CFM.StyledTextEditor.prototype._start = function(){
addTextFontPluginSettings(this);
origFn.call(this);
}
}

function addTextFontPluginSettings(editor){
var config = editor.$editable.data("config");

config.rtePlugins[EAEM_PLUGIN_ID] = {
features: "*"
};

config.uiSettings.cui.multieditorFullscreen.toolbar.push(EAEM_TEXT_FONT_ICON);
}

function registerPlugin(){
var EAEM_CFM_TEXT_FONT_PLUGIN = new Class({
toString: "eaemCFMTextFontPlugin",

extend: CUI.rte.plugins.Plugin,

textFontUI: null,

getFeatures: function () {
return [ EAEM_TEXT_FONT_FEATURE ];
},

notifyPluginConfig: function (pluginConfig) {
var defaults = {
tooltips: {}
};

defaults.tooltips[EAEM_TEXT_FONT_FEATURE] = {
title: "Set text font size, color, background color..."
};

CUI.rte.Utils.applyDefaults(pluginConfig, defaults);

this.config = pluginConfig;
},

initializeUI: function (tbGenerator) {
if (!this.isFeatureEnabled(EAEM_TEXT_FONT_FEATURE)) {
return;
}

this.textFontUI = new tbGenerator.createElement(EAEM_TEXT_FONT_FEATURE, this, false,
this.config.tooltips[EAEM_TEXT_FONT_FEATURE]);

tbGenerator.addElement(EAEM_TEXT_FONT_FEATURE, 999, this.textFontUI, 999);

if (tbGenerator.registerIcon) {
tbGenerator.registerIcon(EAEM_TEXT_FONT_ICON, "textColor");
}

$(window).off('message', closePicker).on('message', closePicker);
},

isValidSelection: function(){
var winSel = window.getSelection();
return winSel && winSel.rangeCount == 1 && winSel.getRangeAt(0).toString().length > 0;
},

execute: function (pluginCommand, value, envOptions) {
var context = envOptions.editContext;

if (pluginCommand != EAEM_TEXT_FONT_FEATURE) {
return;
}

if(!this.isValidSelection()){
return;
}

var selection = CUI.rte.Selection.createProcessingSelection(context),
ek = this.editorKernel, startNode = selection.startNode;

if ( (selection.startOffset === startNode.length) && (startNode != selection.endNode)) {
startNode = startNode.nextSibling;
}

var $tag = $(CUI.rte.Common.getTagInPath(context, startNode, "span")),
color, size = $tag.css("font-size");

color = this.getColorAttributes($tag);

this.showFontModal(this.getPickerIFrameUrl(size, color.color, color.bgColor));
},

getColorAttributes: function($tag){
var key, color = { color: "", bgColor : ""};

if(!$tag.attr("style")){
return color;
}

//donot use .css("color"), it returns default font color, if color is not set
var parts = $tag.attr("style").split(";");

_.each(parts, function(value){
value = value.split(":");

key = value[0] ? value[0].trim() : "";
value = value[1] ? value[1].trim() : "";

if(key == "color"){
color.color = rgbToHex(value);
}else if(key == "background-color"){
color.bgColor = rgbToHex(value);
}
});

return color;
},

showFontModal: function(url){
var self = this, $iframe = $('<iframe>'),
$modal = $('<div>').addClass('eaem-cfm-font-size coral-Modal');

$iframe.attr('src', url).appendTo($modal);

$modal.appendTo('body').modal({
type: 'default',
buttons: [],
visible: true
});

$eaemFontPicker = $modal;

$eaemFontPicker.eaemFontPlugin = self;

$modal.nextAll(".coral-Modal-backdrop").addClass("cfm-coral2-backdrop");
},

getPickerIFrameUrl: function(size, color, bgColor){
var url = Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;

if(!_.isEmpty(color)){
url = url + "&color=" + encodeURIComponent(color);
}

if(!_.isEmpty(bgColor)){
url = url + "&bgColor=" + encodeURIComponent(bgColor);
}

if(!_.isEmpty(size)){
url = url + "&size=" + size;
}

return url;
}
});

var EAEM_CFM_TEXT_FONT_CMD = new Class({
toString: "eaemCFMTextFontCmd",

extend: CUI.rte.commands.Command,

isCommand: function (cmdStr) {
return (cmdStr.toLowerCase() == EAEM_TEXT_FONT_FEATURE);
},

getProcessingOptions: function () {
var cmd = CUI.rte.commands.Command;
return cmd.PO_SELECTION | cmd.PO_BOOKMARK | cmd.PO_NODELIST;
},

getTagObject: function(textData) {
var style = "";

if(!_.isEmpty(textData.color)){
style = "color: " + textData.color + ";";
}

if(!_.isEmpty(textData.size)){
style = style + "font-size: " + textData.size + ";";
}

if(!_.isEmpty(textData.bgColor)){
style = style + "background-color: " + textData.bgColor;
}

return {
"tag": "span",
"attributes": {
"style" : style
}
};
},

execute: function (execDef) {
var textData = execDef.value, selection = execDef.selection,
nodeList = execDef.nodeList;

if (!selection || !nodeList) {
return;
}

var common = CUI.rte.Common,
context = execDef.editContext,
tagObj = this.getTagObject(textData);

if(_.isEmpty(textData.size) && _.isEmpty(textData.color) && _.isEmpty(textData.bgColor)){
nodeList.removeNodesByTag(execDef.editContext, tagObj.tag, undefined, true);
return;
}

var tags = common.getTagInPath(context, selection.startNode, tagObj.tag);

//remove existing color before adding new color
if (tags != null) {
nodeList.removeNodesByTag(execDef.editContext, tagObj.tag, tags.attributes ? tags.attributes : undefined, true);
}

nodeList.surround(execDef.editContext, tagObj.tag, tagObj.attributes);
},

queryState: function(selectionDef, cmd) {
return false;
}
});

CUI.rte.plugins.PluginRegistry.register(EAEM_PLUGIN_ID, EAEM_CFM_TEXT_FONT_PLUGIN);

CUI.rte.commands.CommandRegistry.register(EAEM_TEXT_FONT_FEATURE, EAEM_CFM_TEXT_FONT_CMD);
}
}(jQuery, jQuery(document)));

AEM 6420 - Alphabetically sort tags in Tag picker column view

0
0

Goal


Sort the tags alphabetically, in tags picker column view

Demo | Package Install | Source Code | Github


Product



Extension



Solution


1) Create a Sling Filter to intercept request URIs starting with /mnt/overlay/cq/gui/content/coral/common/form/tagfield/picker.*, use a sling servlet request wrapper TagSortSlingServletRequestWrapper to return a sorted datasource iterator

package apps.experienceaem.assets;

import com.adobe.granite.ui.components.ds.AbstractDataSource;
import com.adobe.granite.ui.components.ds.DataSource;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;

import javax.servlet.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

@Component( description = "Experience AEM Request filter for libs.cq.gui.components.coral.common.form.tagfield.datasources.children",
label = "Experience AEM Tags Sort Filter")
@Service({Filter.class})
@Properties({
@Property(name = "sling.filter.scope", value = {"REQUEST"}, propertyPrivate = true),
@Property( name = "sling.filter.pattern",
value = {"/mnt/overlay/cq/gui/content/coral/common/form/tagfield/picker.*"},
propertyPrivate = true),
@Property(name = "service.ranking", intValue = {-99}, propertyPrivate = true)})
public class EAEMTagsSortFilter implements Filter {
public static String DATA_SOURCE_NAME = DataSource.class.getName();

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new TagSortSlingServletRequestWrapper((SlingHttpServletRequest)request), response);
}

@Override
public void destroy() {
}

private class TagSortSlingServletRequestWrapper extends SlingHttpServletRequestWrapper {
public TagSortSlingServletRequestWrapper(final SlingHttpServletRequest request) {
super(request);
}

@Override
public Object getAttribute(String attrName) {
if(!EAEMTagsSortFilter.DATA_SOURCE_NAME.equals(attrName)){
return super.getAttribute(attrName);
}

DataSource ds = (DataSource)super.getAttribute(attrName);

if(ds == null){
return ds;
}

final List<Resource> sortedList = new ArrayList<Resource>();
Iterator<Resource> items = ds.iterator();

while(items.hasNext()){
sortedList.add(items.next());
}

//sortedList.sort(Comparator.comparing(Resource::getName));

sortedList.sort(Comparator.comparing(Resource::getValueMap, (v1, v2) -> {
return v1.get("jcr:title", "").compareTo(v2.get("jcr:title", ""));
}));

ds = new AbstractDataSource() {
public Iterator<Resource> iterator() {
return sortedList.iterator();
}
};

return ds;
}
}
}


AEM 6420 - Configure Dynamic Media Scene7 (DMS7), render videos in Author, Publish, extend Viewer, Video Player

0
0


Goal


This post is on configuring AEM in DMS7 (Dynamic Media Scene 7 mode), a cover to cover step-by-step guide with details on getting your AEM up and running in DMS7 mode for uploading, encoding, rendering videos in author, publish pages created using Static HTL Templates.

Additionally, extend the video player to add a text overlay using the s7sdk api

For more details on DMS7 check this post (details pertaining to DMS7 only and not other configuration types like Hybrid)

Demo | Package Install | Github



Solution


Configuring Dynamic Media Scene7

1) As always, everything starts with an email, if you are provisioned for DM Scene7, you must have received an email with the credentials necessary for configuring AEM with DMS7, something like below (make sure you change the password, for safety reasons and also to get the connection to Scene7 from AEM works)



2) Start AEM with dynamicmedia_scene7 run mode

java -Xdebug -Xrunjdwp:transport=dt_socket,address=5005,suspend=n,server=y
-XX:MaxPermSize=512m -Xmx1024M -Doak.queryLimitInMemory=500000 -Doak.queryLimitReads=500000
-jar cq-quickstart-6.4.0-p4502.jar -r dynamicmedia_scene7 -nofork

3) Goto Tools -> Cloud Services -> Dynamic Media Configuration (tip - use omnisearch) and in the /conf/global folder create a configuration for connecting to Scene7; make sure the connection works and add Company Root Folder Path (all folders in /content/dam with valid video profiles are synced as subfolders of root folder)

                                   http://localhost:4502/libs/dam/content/configurations/dmscene7.html/conf



4) With a successful configuration, AEM Assets creates hidden system folders (property hidden = true) /content/dam/_CSS/content/dam/_DMSAMPLE for storing viewer specific css and images. At this point let error.log stabilize a bit (AEM Scene7 sync under the hood)



5) Create a sample folder for uploading videos, make sure this folder has a video profile associated eg. Adaptive Video Encoding, there is no default. Video profiles are available in Tools -> Assets -> Video Profileshttp://localhost:4502/mnt/overlay/dam/gui/content/s7dam/videoprofiles/videoprofiles.html





6) Upload a video to play in sample site page and later publish, the encoding progress is shown (under the covers, video is synced to Scene7 and the renditions, thumbnails are dynamically pulled from Scene7, shown in AEM)





Playing Video in Sites

1) Login to CRXDe Lite and create a static HTL or JSP template. Package Install has necessary code for sample page component (extending core components)

                    /apps/eaem-basic-htl-page-template



2) Create a test page, open and enter Design mode, enable Dynamic Media components in ParSys configuration (stored in /libs/settings/wcm/designs/default/jcr:content/basic-htl-page-component/content)

                         http://localhost:4502/editor.html/content/test.html



3) In the Edit mode drag and drop Dynamic Media -> Dynamic Media component, drag drop the test video and click Settings for configuring the component. Enter width and height, select Viewer Preset (video player settings). Viewer presets are available in Tools -> Assets -> Viewer Presets (http://localhost:4502/mnt/overlay/dam/gui/content/s7dam/viewerpresets/viewerpresets.html)



4) To extend the Video player UI, you can create a custom viewer preset, but this post is more on exploring the s7sdk api, so for adding a text overlay using javascript register a clientlib based extension /apps/eaem-scene7-video-viewer-text-overlay/clientlib of type cq:ClientLibraryFoldercategories set to [cq.dam.components.scene7.common.viewer] and dependencies set to [jquery, lodash]

5) Create a file /apps/eaem-scene7-video-viewer-text-overlay/clientlib/css.txt with the following text

                         text-overlay.css

6) Add file /apps/eaem-scene7-video-viewer-text-overlay/clientlib/text-overlay.css with the following style

.eaem-video-text-overlay {
position: relative;
top: 60%;
left: 18%;
color: #AAA;
font-size: 30px;
background-color: #EEE;
width: 400px;
padding: 5px;
text-align: center;
}


7) Create a file /apps/eaem-scene7-video-viewer-text-overlay/clientlib/js.txt with the following text

            text-overlay.js

8) Add file /apps/eaem-scene7-video-viewer-text-overlay/clientlib/text-overlay.js with the following code

(function($, $document){
var COMP_SELECTOR = '.s7dm-dynamic-media',
EAEM_TEXT_OVERLAY = "eaem-video-text-overlay";

$document.ready(findViewer);

function findViewer(){
$(COMP_SELECTOR).each(function(){
getViewer($(this).attr('id')).then(handleViewer);
});
}

function handleViewer(viewer){
if(!viewer instanceof s7viewers.VideoViewer){
return;
}

var s7sdk = window.s7classic.s7sdk;

viewer.s7params.addEventListener(s7sdk.Event.SDK_READY, function(){
s7sdkReady(viewer, s7sdk);
}, false);
}

function s7sdkReady(viewer, s7sdk){
viewer.videoplayer.addEventListener(s7sdk.event.CapabilityStateEvent.NOTF_VIDEO_CAPABILITY_STATE, function(event){
addTextOverlay(viewer, event.s7event);
}, false);

//viewer.playPauseButton.addEventListener("click", function(){});
}

function addTextOverlay(viewer, s7event){
var $vpComponent = $("#" + viewer.videoplayer.compId),
$textOverlay = $("." + EAEM_TEXT_OVERLAY);

if(!_.isEmpty($textOverlay)){
if(s7event && s7event.state && (s7event.state.state == 13)){
$textOverlay.hide();
}else{
$textOverlay.show();
}
return;
}

$vpComponent.append("<div class='" + EAEM_TEXT_OVERLAY + "'>click anywhere to watch video</div>")
}

function getViewer(compId){
if(!compId){
return;
}

return new Promise(function(resolve, reject){
var INTERVAL = setInterval(function(){
var viewer = S7dmUtils[compId];

if(!viewer || !viewer.initializationComplete){
return;
}

clearInterval(INTERVAL);

resolve(viewer);
}, 100);
});
}
}(jQuery, jQuery((document))));


9) Refresh test page and check the player in Published mode http://localhost:4502/content/test.html?wcmmode=disabled

10) Add the necessary sling authenticator configuration to access the extension code in unauthenticated envs like publish
                                                     http://localhost:4502/system/console/configMgr/org.apache.sling.engine.impl.auth.SlingAuthenticator



11) Start your Publish AEM (dynamicmedia_scene7 runmode is NOT necessary in publish), publish everything, template, page, components, viewer presets etc. and check the video player text overlay click anywhere to watch video

                         /apps/eaem-scene7-video-viewer-text-overlay
                         /apps/eaem-basic-htl-page-template
                         /apps/system/config/org.apache.sling.engine.impl.auth.SlingAuthenticator.config



AEM 6420 - Add custom search filters in Pathfield Picker

0
0

Goal


Search results filtering in picker of Pathfield (granite/ui/components/coral/foundation/form/pathfield") by default is based on path and fulltext only. This post is on extending the picker to add a Tag based filter

Demo | Package Install | Github


Product



Extension



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-pathfield-picker-search-fields

2) Create nt:file /apps/eaem-touchui-pathfield-picker-search-fields/custom-search/custom-search.jsp, add the following code for generating custom filters html

<%@include file="/libs/granite/ui/global.jsp"%>

<%@ page import="org.apache.sling.api.resource.Resource" %>
<%@ page import="java.util.Iterator" %>

<%
for (Iterator<Resource> it = resource.listChildren(); it.hasNext();) {
%>

<div class="coral-Form-fieldwrapper eaem-touchui-pathfield-picker-search-field">

<sling:include resource="<%= it.next() %>" />

</div>

<%
}
%>

3) Create nt:unstructured /apps/eaem-touchui-pathfield-picker-search-fields/custom-search/form with  sling:resourceType set to /apps/eaem-touchui-pathfield-picker-search-fields/custom-search containing the nodes for tag based filter. The name property of filter nodes should match querybuilder predicate, here tagid and tagid.property. (the default path filter is specified in /libs/granite/ui/content/coral/foundation/form/pathfield/picker/search/form, so you can get the tag filter working without any additional javascript by adding it in the /libs path, however its not recommended and also marked granite:InternalArea)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/eaem-touchui-pathfield-picker-search-fields/custom-search">
<tagid
jcr:primaryType="nt:unstructured"
sling:resourceType="cq/gui/components/coral/common/form/tagfield"
fieldLabel="Tags"
multiple="{Boolean}true"
name="tagid"/>
<tagid-prop
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/hidden"
name="tagid.property"
value="jcr:content/metadata/cq:tags"/>
</jcr:root>


2) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-pathfield-picker-search-fields/clientlib and set property categories of String[] type to granite.ui.coral.foundation and dependencies String[] to lodash

3) Create file ( type nt:file ) /apps/eaem-touchui-pathfield-picker-search-fields/clientlib/js.txt, add the following

  search-fields-in-picker.js

4) Create file (type nt:file) /apps/eaem-touchui-pathfield-picker-search-fields/clientlib/search-fields-in-picker.js, add the following code

(function ($, $document) {
var PATH_FIELD_PICKER = "#granite-ui-pathfield-picker-collection",
CUSTOM_SEARCH_FIELD = ".eaem-touchui-pathfield-picker-search-field",
CUSTOM_FIELDS_URL = "/apps/eaem-touchui-pathfield-picker-search-fields/custom-search/form.html";

$document.on("coral-cyclebutton:change", ".granite-toggleable-control", handlePathFieldPicker);

function handlePathFieldPicker(event){
if(_.isEmpty($(PATH_FIELD_PICKER))){
return;
}

var selectedEl = event.originalEvent.detail.selection,
target = selectedEl.dataset.graniteToggleableControlTarget;

if(!_.isEmpty($(target).find(CUSTOM_SEARCH_FIELD))){
return;
}

addSearchFields(target);
}

function addSearchFields(target){
var ui = $(window).adaptTo("foundation-ui");

ui.wait();

$.ajax(CUSTOM_FIELDS_URL).done(function(html){
$(target).find(".coral-Form-fieldwrapper:last").after(html);
ui.clearWait();
});
}
}(jQuery, jQuery(document)));

AEM 6420 - Adding Composite Multifield Items based on Coral Select

0
0

Goal


Add composite multifield items dynamically based on coral select change. In sample below, the value of coral select "./count" defines the count of multi field items "./products"

Demo | Package Install | Github



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-select-change-dynamic-multifield

2) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-select-change-dynamic-multifield/clientlib and set property categories of String[] type to cq.authoring.dialog.all and dependencies String[] to lodash

3) Create file ( type nt:file ) /apps/eaem-touchui-select-change-dynamic-multifield/clientlib/js.txt, add the following

  select-change-dynamic-multifield.js

4) Create file (type nt:file) /apps/eaem-touchui-select-change-dynamic-multifield/clientlib/select-change-dynamic-multifield.js, add the following code

(function ($, $document, gAuthor) {
var COUNT_SELECT = "./count",
PRODUCTS_MF = "./products";

$document.on("dialog-ready", addSelectListener);

function addSelectListener(){
var $countSelect = $("coral-select[name='" + COUNT_SELECT + "']");

if(_.isEmpty($countSelect)){
return;
}

var countSelect = $countSelect[0];

countSelect.on("change", adjustMultifieldItems);

}

function adjustMultifieldItems(event){
var countSelect = event.target,
$productsMF = $("coral-multifield[data-granite-coral-multifield-name='" + PRODUCTS_MF + "']");

if(_.isEmpty($productsMF)){
return;
}

var maxCount = parseInt(countSelect.value),
productsMF = $productsMF[0],
mfItems = productsMF.items.getAll();

if(mfItems.length <= maxCount){
for(var c = mfItems.length; c < maxCount; c++){
productsMF.items.add();
}
}else{
for(var c = (mfItems.length - 1) ; c >= maxCount; c--){
productsMF.items.remove(mfItems[c]);
}
}
}
}(jQuery, jQuery(document), Granite.author));



AEM 6420 - Touch UI Sites Content Tree double click to open page for Authoring

0
0

Goal


Register a click listener on sites /sites.html content tree nodes to open the page for authoring on double click (imitatating Classic UI)

Demo | Package Install | Github



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-content-tree-dbl-click-open

2) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-content-tree-dbl-click-open/clientlib and set property categories of String[] type to granite.ui.shell and dependencies String[] to lodash

3) Create file ( type nt:file ) /apps/eaem-touchui-content-tree-dbl-click-open/clientlib/js.txt, add the following

  content-tree-dbl-click.js

4) Create file (type nt:file) /apps/eaem-touchui-content-tree-dbl-click-open/clientlib/content-tree-dbl-click.js, add the following code

(function ($, $document) {
var SITES_CONSOLE_URL = "/sites.html";

$(registerDblClickForContentTree);

function registerDblClickForContentTree(){
if(!isSitesConsole()){
return;
}

var tree = $(".shell-collectionpage-tree").first(),
treeEl = tree[0];

if (!treeEl) {
return;
}

tree.on("change", function(event) {
tree.find("[class^=fnd-Cell-text]").each(registerDblClick);
});

function registerDblClick(index, element){
var $element = $(element);

if($element.data("eaemRegistered") == "true" ){
return;
}

$element.on("dblclick", function(event){
window.open("/editor.html" + treeEl.selectedItem.id + ".html", "_blank");
});

$element.data("eaem-registered", "true");
}
}

function isSitesConsole() {
return (window.location.pathname.indexOf(SITES_CONSOLE_URL) >= 0);
}
}(jQuery, jQuery(document)));




AEM - Random CURL Commands

0
0
1) Update properties of an OSGI service


curl -v -u admin:admin -X POST
--data "apply=true&di3dUploadRenderUrl=ABC&di3dDownloadUrl=DEF&propertylist=di3dUploadRenderUrl%2Cdi3dDownloadUrl"
http://localhost:4502/system/console/configMgr/apps.assets.di3d.impl.DI3DCloudRenderingServiceImpl

AEM 64 - Content Fragment Insights - Configuring Adobe Launch and Analytics in AEM

0
0

Goal


Configure Adobe Launch and Analytics in Experience Cloud to capture AEM Content Fragment Analytics displayed on page

The following steps assume your organization is enabled in Experience Cloud https://experiencecloud.adobe.com


Create Analytics Report Suite

To capture site metrics like Page Views and Content Fragment Views create a Report Suite in Adobe Analytics

1) In your experience cloud click on the Analytics icon and create a new Report suite e.g. ags959eaempopularcfxf



2) Configure list variable - list1 to capture the content fragment paths on page



Configure Adobe Launch

1) In your experience cloud click on Activation> Launch (or https://launch.adobe.com), create a New Property e.g. Content Fragment Insights, add the Adobe Analytics extension and add your report suite id created in step above



2) Add the Adobe Context Hub extension for surfacing data layer in Launch. This custom data layer provides content fragment paths rendered on page for analytics. Add the following data layer schema and data layer root window.eaemData

{
"type": "object",
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"page": {
"type": "object",
"properties": {
"pageInfo": {
"type": "object",
"properties": {
"pagePath": {
"type": "string"
}
}
},
"contentFraments": {
"type": "object",
"properties": {
"paths": {
"type": "string"
}
}
}
}
}
}
}




3) With the above data layer schema, json objects like the following can be exposed on your page so launch can understand it


<script>
window.eaemData = {
page : {
pageInfo : {
pagePath : "/content/we-retail/language-masters/en"
},
contentFraments:{
paths : [ "/content/dam/experience-aem/adobe-experience-manager" ]
}
}
}
</script>


4) Create data element page_path for mapping page path and content_fragments_on_page for mapping content fragment paths from data layer 



5) Add a Rule to load the Analytics library on page load event. It is common to load Analytics at the page bottom, as this allows page to fully load and data layer to be fully populated. The rule is configured to Set Variables Page Name (using data element mapping) and List Variable s.list1 (comma separated values set using custom script collected from the data layer)

for(var i = 0; i < window.eaemData.page.contentFraments.paths.length; i++){
s.list1 = s.list1 || "";
s.list1 = s.list1 + ( (s.list1 == "") ? "" : "," ) + window.eaemData.page.contentFraments.paths[i];
}




6) Configure the Analytics Send Beacon action to capture and send data to Analytics for tracking



7) At this point we've configured Analytics in Adobe Launch to capture and track AEM pages and content fragment on the pages; Publish these changes to Development, Stage and Production launch environments (pushing the code to production should happen after thorough testing, here we are doing it for demo purposes... )



8) Following is a launch configuration published to production, we are now ready to use it in AEM sites



AEM - Adobe I/O Integration for IMS

Create an Adobe IMS (Identity Management System) configuration in AEM to integrate with Launch via Adobe I/O

1) Login to AEM and access the IMS configuration screen http://localhost:4502/libs/cq/adobeims-configuration/content/configurations.html, create a configuration, public key certificate...



2) Save the downloaded public key and proceed to Adobe IO console https://console.adobe.io to add the downloaded public key



3) Continue the IO configuration in AEM



AEM and Adobe Launch Integration

1) With the Adobe IO integration now available in AEM, configure AEM to integrate with Launch, allowing us to deploy the Analytics configuration. Navigate to page http://localhost:4502/libs/cq/dtm-reactor/content/configurations.html/conf/we-retail and create a new configuration


2) Quick Publish the site We.Retail to publish the Launch configuration and verify if Launch library is loaded, using chrome developer tools



Import Analytics data for AEM Content Insights

Configure Content Insights to server side import the Analytics data directly into AEM. Launch integration controls the client-side loading of the Analytics library and tracking of impression data, while the Analytics integration controls the server-side import of reporting data from Analytics

1) Navigate to Legacy Cloud Services http://localhost:4502/libs/cq/core/content/tools/cloudservices.html and create an Adobe Analytics configuration



2) Create a Analytics Framework (this is for selecting report suite, mapping context hub variables etc.) In a realistic scenario you'd be selecting two different report suites for author and publish, but for the sake of simplicity we are selecting all to use the same report suite ags959eaempopularcfxf. As we are using a custom data layer (ContextHub extension in Adobe Launch configuration above), we are not mapping any CQ variables to Analytics variables




Analytics Cloud Service in We.Retail

For attaching the cloud service to a site, navigate to properties page of  site, say We.Retail - http://localhost:4502/mnt/overlay/wcm/core/content/sites/properties.html?item=/content/we-retail and select the configuration in Cloud Services tab



















AEM 6420 - Content Fragment Insights - Importing CF Analytics into AEM

0
0

Goal


Before importing analytics data into AEM you need to set up AEM for Adobe Analytics. For configuring Adobe Launch and Adobe Analytics in AEM check this post


AEM is now successfully integrated with Analytics, the next steps are...

1. Create the data layer in AEM page template head for storing page path and content fragments paths rendered on page

2.  Extend otb content fragment component to add the content fragment path rendered by the component

3. Refresh page and make sure the cf paths are sent to Analytics in list variable list1 (l1)

4. Setup a polling importer to import Analytics data into AEM at regular intervals

5. Extend Assets List View to show the Fragment Views fetched from Analytics


Demo | Package Install | Github


List View in AEM




Content Fragments Report in Analytics





Add Data Layer in Page Template

1) Time to setup the data layer window.eaemData in your page head.html with the page path and content fragments paths data structure (the data layer root window.eaemData was setup in Adobe Launch extension Context Hub in the previous exercise



2) For simplicity, attached package install contains the core components page /apps/core/wcm/components/page/v2/page/head.html modified to add the data layer, ideally you'd be extending the core components page component or add it a new page component

<script>
window.eaemData = {
page : {
pageInfo : {
pagePath : "${currentPage.path @ context='scriptString'}"
},
contentFraments:{
paths : []
}
},

addContentFragmentPath: function(cfPath){
if(!cfPath){
return;
}

var cfPaths = window.eaemData.page.contentFraments.paths;

if(cfPaths.indexOf(cfPath) >= 0){
return;
}

cfPaths.push(cfPath);
}
}
</script>


3) The content fragment component rendered on page can now add CF path using api window.eaemData.addContentFragmentPath()



Extend Content Fragment Component

1) Extend the otb content fragment component /libs/dam/cfm/components/contentfragment to create a custom content fragment component EAEM Content Fragment Component - /apps/eaem-most-popular-content-fragments/components/content-fragment




2) Add the following code in /apps/eaem-most-popular-content-fragments/components/content-fragment/content-fragment.html

<section data-sly-include="/libs/dam/cfm/components/contentfragment/contentfragment.html" data-sly-unwrap></section>

<div data-sly-use.fragment="helper.js">
<script>
window.eaemData.addContentFragmentPath("${fragment.cfPath @ context='scriptString'}");
</script>
</div>


3) Add the following code in /apps/eaem-most-popular-content-fragments/components/content-fragment/helper.js

"use strict";

use( ["/libs/wcm/foundation/components/utils/ResourceUtils.js" ], function(ResourceUtils){
return { cfPath : granite.resource.properties.fileReference };
} );


Verify CF Paths sent to Analytics

1) Drag and drop the content fragment component created above EAEM Content Fragment Component and select a CF



2) Refresh the page in published mode (wcmmode=disabled) and check the data sent to Analytics




Setup Polling Importer

1) Create Polling Importer apps.experienceaem.assets.EAEMListVariablesReportImporter extending com.day.cq.polling.importer.Importer with custom scheme eaemReport, add the following code...

package apps.experienceaem.assets;

import com.day.cq.analytics.sitecatalyst.SitecatalystUtil;
import com.day.cq.analytics.sitecatalyst.SitecatalystWebservice;
import com.day.cq.polling.importer.ImportException;
import com.day.cq.polling.importer.Importer;
import com.day.cq.wcm.webservicesupport.ConfigurationManager;
import com.day.cq.wcm.webservicesupport.ConfigurationManagerFactory;
import com.day.cq.wcm.webservicesupport.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.settings.SlingSettingsService;
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.Node;
import javax.jcr.Session;
import java.text.SimpleDateFormat;
import java.util.*;

@Component(
immediate = true,
service = { Importer.class },
property = {
Importer.SCHEME_PROPERTY + "=" + EAEMListVariablesReportImporter.SCHEME_VALUE
}
)
@Designate(ocd = EAEMListVariablesReportImporter.EAEMListVariablesReportConfiguration.class)
public class EAEMListVariablesReportImporter implements Importer{
private Logger log = LoggerFactory.getLogger(getClass());

public static final String SCHEME_VALUE = "eaemReport";
private static final String USE_ANALYTICS_FRAMEWORK = "use-analytics-framework";
private static final String GET_ANALYTICS_FOR_LAST_DAYS = "get-analytics-for-last-days";
private static final String EAEM_ANALYTICS_VIEWS = "eaemAnalyticsViews";
private static final String UPDATE_ANALYTICS_SERVICE = "eaem-update-cf-with-analytics";
private static final long DEFAULT_REPORT_FETCH_DELAY = 10000;
private static final long DEFAULT_REPORT_FETCH_ATTEMPTS = 10;
private long reportFetchDelay = DEFAULT_REPORT_FETCH_DELAY;
private long reportFetchAttempts = DEFAULT_REPORT_FETCH_ATTEMPTS;

@Reference
private SitecatalystWebservice sitecatalystWebservice;

@Reference
private ResourceResolverFactory resolverFactory;

@Reference
private ConfigurationManagerFactory cfgManagerFactory;

@Reference
private SlingSettingsService settingsService;

@Activate
protected void activate(EAEMListVariablesReportConfiguration configuration) {
reportFetchDelay = configuration.reportFetchDelay();
reportFetchAttempts = configuration.reportFetchAttempts();
}

public void importData(String scheme, String dataSource, Resource target) throws ImportException {
log.info("Importing analytics data for list var - " + dataSource);

try {
String useAnalyticsFrameworkPath = target.getValueMap().get(USE_ANALYTICS_FRAMEWORK, String.class);

if(StringUtils.isEmpty(useAnalyticsFrameworkPath)){
log.warn("Analytics framework path property " + USE_ANALYTICS_FRAMEWORK + " missing on " + target.getPath());
return;
}

Configuration configuration = getConfiguration(useAnalyticsFrameworkPath, target.getResourceResolver());

String reportSuiteID = SitecatalystUtil.getReportSuites(settingsService, configuration);

log.debug("Report Suite ID - " + reportSuiteID);

JSONObject reportDescription = getReportDescription(target, dataSource, reportSuiteID);

String queueReportResponse = sitecatalystWebservice.queueReport(configuration, reportDescription);

log.debug("queueReportResponse - " + queueReportResponse);

JSONObject jsonObj = new JSONObject(queueReportResponse);

Long queuedReportId = jsonObj.optLong("reportID");

if(queuedReportId == 0L) {
log.error("Could not queue report, queueReportResponse - " + queueReportResponse);
return;
}

boolean reportReady = false;
JSONObject report = null;

for(int attemptNo = 1; attemptNo <= reportFetchAttempts; ++attemptNo) {
log.debug("Attempt number " + attemptNo + " to fetch queued report " + queuedReportId);

String reportData = sitecatalystWebservice.getReport(configuration, queuedReportId.toString());

log.debug("Get report " + queuedReportId + " result: " + reportData);

jsonObj = new JSONObject(reportData);
String errorResponse = jsonObj.optString("error");

reportReady = ((errorResponse == null) || !"report_not_ready".equalsIgnoreCase(errorResponse));

if(reportReady) {
report = jsonObj.optJSONObject("report");
break;
}

Thread.sleep(reportFetchDelay);
}

if(report == null) {
log.error("Could not fetch queued report [" + queuedReportId + "] after " + reportFetchAttempts + " attempts");
}

ResourceResolver rResolverForUpdate = resolverFactory.getServiceResourceResolver(
Collections.singletonMap("sling.service.subservice", (Object)UPDATE_ANALYTICS_SERVICE));

saveAnalyticsData(report, rResolverForUpdate);

log.info("Successfully imported analytics data with report id - " + queuedReportId);
}catch(Exception e){
log.error("Error importing analytics data for list var - " + dataSource, e);
}
}

private void saveAnalyticsData(JSONObject report, ResourceResolver resolver ) throws Exception{
JSONArray data = report.optJSONArray("data");
JSONObject metrics = null;
String cfPath;

Resource cfResource;
Node cfJcrContent;

for(int d = 0, len = data.length(); d < len; d++){
metrics = data.getJSONObject(d);
cfPath = metrics.getString("name");

cfResource = resolver.getResource(cfPath);

if(cfResource == null){
log.warn("Content fragment not found - " + cfPath);
continue;
}

cfJcrContent = cfResource.getChild("jcr:content").adaptTo(Node.class);

cfJcrContent.setProperty(EAEM_ANALYTICS_VIEWS, metrics.getJSONArray("counts").getString(0));
}

resolver.adaptTo(Session.class).save();
}

private JSONObject getReportDescription(Resource target, String dataSource, String reportSuiteID) throws Exception{
Calendar cal = new GregorianCalendar();
cal.setTime(new Date());

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

Integer daysCount = target.getValueMap().get(GET_ANALYTICS_FOR_LAST_DAYS, Integer.class);

if(daysCount == null){
daysCount = -90;
}

JSONObject reportDescription = new JSONObject();

reportDescription.put("elements", getElements(dataSource));
reportDescription.put("metrics", getMetrics());
reportDescription.put("reportSuiteID", reportSuiteID);
reportDescription.put("dateTo", formatter.format(cal.getTime()));

cal.add(Calendar.DAY_OF_MONTH, daysCount);
reportDescription.put("dateFrom", formatter.format(cal.getTime()));

return reportDescription;
}

private JSONArray getElements(String dataSource) throws Exception{
JSONObject elements = new JSONObject();

elements.put("id", dataSource);
elements.put("top", 10000);
elements.put("startingWith", 1);

return new JSONArray().put(elements);
}

private JSONArray getMetrics() throws Exception{
JSONObject metrics = new JSONObject();

metrics.put("id", "pageviews");

return new JSONArray().put(metrics);
}

private Configuration getConfiguration(String analyticsFrameworkPath, ResourceResolver resourceResolver)
throws Exception {
ConfigurationManager cfgManager = cfgManagerFactory.getConfigurationManager(resourceResolver);

String[] services = new String[]{ analyticsFrameworkPath };

return cfgManager.getConfiguration("sitecatalyst", services);
}


public void importData(String scheme,String dataSource,Resource target,String login,String password)
throws ImportException {
importData(scheme, dataSource, target);
}

@ObjectClassDefinition(
name = "EAEM Analytics Report Importer",
description = "Imports Analytics List Variable Reports periodically into AEM"
)
public @interface EAEMListVariablesReportConfiguration {

@AttributeDefinition(
name = "Fetch delay",
description = "Number in milliseconds between attempts to fetch a queued report. Default is set to 10000 (10s)",
defaultValue = { "" + DEFAULT_REPORT_FETCH_DELAY },
type = AttributeType.LONG
)
long reportFetchDelay() default DEFAULT_REPORT_FETCH_DELAY;

@AttributeDefinition(
name = "Fetch attempts",
description = "Number of attempts to fetch a queued report. Default is set to 10",
defaultValue = { "" + DEFAULT_REPORT_FETCH_ATTEMPTS },
type = AttributeType.LONG
)
long reportFetchAttempts() default DEFAULT_REPORT_FETCH_ATTEMPTS;
}
}


2) Create a managed poll configuration sling:OsgiConfig /apps/eaem-most-popular-content-fragments/config/com.day.cq.polling.importer.impl.ManagedPollConfigImpl-eaem-popular-cf and set id as eaem-list-var-report-days source as eaemReport:listvar1 among other values. Here we trying to pull a report created, with data collected in list variable list1 which is configured for Content Fragments in Analytics (list1 would have made more sense, but for some reason analytics api expects listvar1)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
enabled="{Boolean}true"
id="eaem-list-var-report-days"
interval="{Long}86400"
reference="{Boolean}true"
source="eaemReport:listvar1"/>


3) Create cq:PollConfigFolder /etc/eaem-most-popular-content-fragments/analytics-list-vars-report-poll-config with a reference to the managed poll configuration created above eaem-list-var-report-days

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
jcr:mixinTypes="[rep:AccessControllable]"
jcr:primaryType="cq:PollConfigFolder"
managedPollConfig="eaem-list-var-report-days"
use-analytics-framework="/etc/cloudservices/sitecatalyst/ags-959-analytics/experience-aem-analytics-framework"
get-analytics-for-last-days="{Long}-90"
source="eaemReport:listvar1"/>


4) In the above poll config folder configuration, we are also adding analytics framework path /etc/cloudservices/sitecatalyst/ags-959-analytics/experience-aem-analytics-framework set with use-analytics-framework created in previous exercise and configuring property get-analytics-for-last-days to get the last 90 days data

5) Create a service user mapping/apps/eaem-most-popular-content-fragments/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-eaem-popular-cf with write access to update content fragment nodes

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
user.mapping="[apps.experienceaem.assets.eaem-most-popular-content-fragments-bundle:eaem-update-cf-with-analytics=dam-update-service]"/>


6) The polling importer configuration is set to import every 24 hours, if you'd like to change it for quick testing, change it in OSGI config manager http://localhost:4502/system/console/configMgr



Extend Assets List View

1)  Create node /apps/dam/gui/content/commons/availablecolumns/views for adding a list view column header



2) Create cq:ClientLibraryFolder /apps/eaem-most-popular-content-fragments/clientlib with categories set to dam.gui.admin.util and dependencies set to lodash

3) Create file /apps/eaem-most-popular-content-fragments/clientlib/js.txt with content

                show-fragment-views.js

4) Create file /apps/eaem-most-popular-content-fragments/clientlib/show-fragment-views.js with the following code

(function ($, $document) {
"use strict";

var firstLoad = true,
LAYOUT_LIST_VIEW = "list",
VIEWS_COUNT = "VIEWS_COUNT",
EAEM_ANALYTICS_VIEWS = "eaemAnalyticsViews",
EAEM_MARKER_CSS = "eaem-fragment-cell",
FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
FRAGMENT_VIEWS_TEXT = "Fragment Views",
SEL_DAM_ADMIN_CHILD_PAGES = ".cq-damadmin-admin-childpages";

$document.on("cui-contentloaded", function (e) {
if(!firstLoad){
return;
}

var $childPages = $(e.currentTarget).find(SEL_DAM_ADMIN_CHILD_PAGES);

if(_.isEmpty($childPages)){
return;
}

firstLoad = false;

$childPages.trigger(FOUNDATION_CONTENT_LOADED);
});

$document.on(FOUNDATION_CONTENT_LOADED, SEL_DAM_ADMIN_CHILD_PAGES, addFragmentViews);

function addFragmentViews(e){
if(!e.currentTarget || !isFragmentViewsEnabled()){
return;
}

var $currentTarget = $(e.currentTarget),
foundationLayout = $currentTarget.data("foundation-layout");

if(_.isEmpty(foundationLayout)){
return;
}

var layoutId = foundationLayout.layoutId,
folderPath = getFolderPath();

if(layoutId !== LAYOUT_LIST_VIEW){
return;
}

$.ajax(folderPath + ".2.json").done(function(data){
$(".foundation-collection-item").each(function(index, item){
itemHandler(data, layoutId, $(item) );
});
});
}

function itemHandler(data, layoutId, $item){
if(isFragmentViewsAdded($item)){
return;
}

var itemPath = $item.data("foundation-collection-item-id"),
itemName = getStringAfterLastSlash(itemPath);

var eaemAnalyticsViews = "";

if(data[itemName] && data[itemName] && data[itemName]["jcr:content"]
&& data[itemName]["jcr:content"][EAEM_ANALYTICS_VIEWS]){
eaemAnalyticsViews = data[itemName]["jcr:content"][EAEM_ANALYTICS_VIEWS];
}

$item.append(getListCellHtml().replace("VIEWS_COUNT", eaemAnalyticsViews));

/*
if($item.find(".coral3-Icon--dragHandle").length > 0){
$item.find(".coral3-Icon--dragHandle").closest("td").before(getListCellHtml().replace("VIEWS_COUNT", eaemAnalyticsViews));
}else{
$item.append(getListCellHtml().replace("VIEWS_COUNT", eaemAnalyticsViews));
}
*/
}

function getStringAfterLastSlash(str){
if(!str || (str.indexOf("/") == -1)){
return "";
}

return str.substr(str.lastIndexOf("/") + 1);
}

function getFolderPath(){
return $(SEL_DAM_ADMIN_CHILD_PAGES).data("foundationCollectionId");
}

function getListCellHtml(){
return '<td is="coral-table-cell" class="coral-Table-cell ' + EAEM_MARKER_CSS + '" alignment="column">VIEWS_COUNT</td>';
}

function isFragmentViewsEnabled(){
var $viewsTd = $(SEL_DAM_ADMIN_CHILD_PAGES).find("thead")
.find("coral-table-headercell-content:contains('" + (FRAGMENT_VIEWS_TEXT) + "')");

return ($viewsTd.length > 0);
}

function isFragmentViewsAdded($item){
return ($item.find("td." + EAEM_MARKER_CSS).length > 0);
}
})(jQuery, jQuery(document));




AEM 6420 - Lookup JNDI Objects

0
0

Goal


Look up JNDI objects in AEM servlet. This post checks if a FQDN (fully qualified domain name)  exists in local OpenLDAP

Package Install

                  http://localhost:4502/bin/experienceaem/jndi/check-user-exists?dn=cn=sreek,ou=People,dc=experienceaem,dc=com





Solution


1) Create a servlet apps.experienceaem.jndi.ListLDAPUsers with the following code

package apps.experienceaem.jndi;

import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.servlets.post.JSONResponse;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.Context;
import javax.naming.directory.*;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Properties;

@SlingServlet(
label = "Experience AEM - List LDAP Users",
description = "Experience AEM - List LDAP Users Servlet.",
paths = { "/bin/experienceaem/jndi/check-user-exists" },
methods = { "GET", "POST" },
extensions = { "json" }
)
public class ListLDAPUsers extends SlingAllMethodsServlet{
private static final Logger log = LoggerFactory.getLogger(ListLDAPUsers.class);

private static String ldapServer = "localhost:389";
private static String rootDn = "cn=Manager,dc=experienceaem,dc=com";
private static String rootPass = "secret";

@Override
protected final void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws
ServletException, IOException {
try {
addJSONHeaders(response);

JSONObject jsonObject = new JSONObject();

Properties env = new Properties();
env.put( Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory" );
env.put( Context.PROVIDER_URL, "ldap://" + ldapServer);
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put( Context.SECURITY_PRINCIPAL, rootDn );
env.put( Context.SECURITY_CREDENTIALS, rootPass );

String dn = request.getParameter("dn");

DirContext ctx = new InitialDirContext(env);

Object user = ctx.lookup(dn);

if(user != null){
jsonObject.put(dn, user);
}

ctx.close();

jsonObject.write(response.getWriter());
} catch (Exception e) {
log.error("Could not formulate JSON response");
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}

@Override
protected final void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws
ServletException, IOException {
doGet(request, response);
}

public static void addJSONHeaders(SlingHttpServletResponse response){
response.setContentType(JSONResponse.RESPONSE_CONTENT_TYPE);
response.setHeader("Cache-Control", "nocache");
response.setCharacterEncoding("utf-8");
}
}



Viewing all 512 articles
Browse latest View live




Latest Images