Showing posts with label Customizing. Show all posts
Showing posts with label Customizing. Show all posts

January 23, 2021
Estimated Post Reading Time ~

How can I list only project specific workflow in dropdown list of "start workflow"

I am using AEM 6.4 want to list only project specific workflow in drop down list of "start workflow" and hide the out of the box workflows. Please suggest the best possible approach.

Solution:
Just add Workflow: System tag in the page properties of the OOTB workflow models. So it will hide all the OOTBS workflows from the drop-down list of Start Workflow option and it will only show the workflow models which do not contains workflow:System tag.

http://localhost:4502/libs/cq/workflow/admin/console/content/models.html

Here is the official documentation from Adobe in case if you want more on this https://helpx.adobe.com/in/experience-manager/kb/hide-workflow-models-start-workflow-list.html


By aem4beginner

January 5, 2021
Estimated Post Reading Time ~

Adding a Custom Field to the AEM Content Fragment Model Editor

Content Fragment Models are built with elements from various out-of-the-box data types, including single-line text, multi-line text, number, boolean (only for checkboxes), date/time, enumeration (only for static dropdown values), tags, and content reference.

Adding a Custom Field to the AEM Content Fragment Model Editor

After investigating the structure of the Content Fragment Model form builder configuration inside CRXDE,  I found that we can easily add most other data types (there are some restrictions for a few datatypes). To extend and customize this form builder configuration of Content Fragment Model Editor, we need to overlay the Content Fragment form builder resource.

In our client’s case, we needed to set the requirement to add the Color Picker field in the Content Fragment Model in AEM 6.5. In this post, I’m going to show you how extended the functionality of the Content Fragment Model to set the Color Picker data type.

How to Set the Color Picker Data Type:

  1. First, open the CRXDE Lite console http://localhost:4502/crx/de/index.jsp
  2. Enter the following path in the search bar and hit enter. /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items
  3. Right-click on the items node. This will open a pop-up menu.
  4. Click on Overlay Node Option from the pop-up menu. This will open another model dialog. Then click on select the Match Node Types option. Make sure the model dialog contains the following values.
    Path: /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items
    Overlay Location: /apps/
    Match Node Types: checked
  5. After verifying the above properties and values, click “OK.” This will overlay the location of /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items path inside /apps/ folder.
  6. Now go to /apps/settings/dam/cfm/models/formbuilderconfig/datatypes/items.
  7. Create one child node inside the items node with the name color-picker.
  8. Set the following properties on /apps/settings/dam/cfm/models/formbuilderconfig/datatypes/items/color-picker node.
    fieldIcon (String)=”colorPalette”
    fieldProperties (String [])=”[labelfield,maptopropertyfield,placeholderfield,textvaluefield,requiredfield]”
    fieldPropResourceType (String)=”dam/cfm/models/editor/components/datatypes/field”
    fieldResourceType (String [])=”[granite/ui/components/coral/foundation/form/colorfield]”
    fieldTitle (String)=”Color Picker”
    listOrder (String)=”9″
    renderType (String)=”text”
    valueType (String [])=”[string]”
  9. Color-picker Node xml should look like as follows
<?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"> <items jcr:primaryType="nt:unstructured"> 
<color-picker 
jcr:primaryType="nt:unstructured" 
fieldIcon="colorPalette" 
fieldProperties="[labelfield,maptopropertyfield,placeholderfield,textvaluefield,requiredfield]" fieldPropResourceType="dam/cfm/models/editor/components/datatypes/field" fieldResourceType="[granite/ui/components/coral/foundation/form/colorfield]" fieldTitle="Color Picker" 
listOrder="9" 
renderType="text" 
valueType="[string]"/> 
</items> 
</jcr:root>

Now you are successfully done with the node overlay and AEM customization process.

  1. In order to see the changes, we need to create a configuration for the Content Fragment Model. Go to AEM Start Menu > Tools > General > Configuration Browser and create a configuration for the Content Fragment Model. You can also use the OOTB global configuration if you don’t want to create a custom configuration (it depends upon your requirement). Here I am using AEM OOTB configuration with the name “Global.”
  2. Once you are done with the configuration steps, go to AEM Start Menu > Tools > Assets > Content Fragment Model > Global (or go to your custom configuration).
    Then create a new custom Content Fragment Model (In this case, I have created a model with the name “Color Picker CFM“) and click on open. Now, take a look at Data Types inside the sidebar. There you will see a new Data type is appearing with the name “Color Picker.
  3. To create your custom Content Fragment Model, drag and drop color picker data type in editor block, and set some properties on the color picker field. I set the following properties.
    Field Label: Set Color
    Property Name: color

Once you are done with this click on Save  Button.
  1. Now go to AEM Start Menu > Navigations > Assets > <any Folder> and Create a Content Fragment using Color Picker CFM model. Once it is created, click on the Open button. You will notice the Content Fragment has the color picker field with the label “Set Color.” Now you can select any predefined color from the color palette, or you can create your own color using a color mixer.

You can use these same steps to extend the functionality of the Content Fragment Model whenever you get such a requirement for other data types as well!

Download this package for reference, and don’t forget to drop a comment if you need more help on this.



By aem4beginner

January 4, 2021
Estimated Post Reading Time ~

Make List View As Default View In AEM Assets

AEM Assets allows customers to manage their digital assets for e.g., images, videos, documents, and audio clips in a web-based repository. It includes Metadata-support, Renditions, Digital Asset Management Finder, and AEM Assets Administration UI.

Most of the AEM Authors like to view their assets in List view compared to Column view and Card view. By default, AEM shows the assets in Card view. Through my experience in AEM, I had found an easy solution to make list view a default view for assets.

Solution:
1. Overlay the /libs/dam/gui/content/assets/jcr:content/views/list and under /apps/dam/gui/content/assets/jcr:content/views/list

2. Add a property sling:orderBefore to the list node, something like below

name: sling:orderBefore String card

3. The XML looks something like this

<views jcr:primaryType=”nt:unstructured”>
<list
    jcr:primaryType=”nt:unstructured”
    sling:orderBefore=”card”/>
</views>


4. Click on Save All.



Although there are multiple ways you can customize views in assets, this is one the simplest way to achieve this.


By aem4beginner

January 3, 2021
Estimated Post Reading Time ~

AEM - Custom Template'd Email

AEM provides OOTB email templates to send emails for workflow notification, completion, etc.
But those templates are plain text and cannot provide rich UI and limited to few dynamic properties.
Example - OOTB workflow notification email template contains plain text and other variables like an event, workitem, user, host properties, etc.

More info about Email Templates for Workflow Notification at https://helpx.adobe.com/experience-manager/6-3/sites/administering/using/notification.html#ConfiguringtheWorkflowEmailNotificationService


Custom Email
Custom Email Service/Servlet/Process Step:You can create a utility/Servlet/workflow Process, whichever trigger the email, and use com.day.cq.mailer.MessageGatewayService to send a template-based email.

Add Below dependencies in POM. or check dependencies using dependency finder at http://localhost:4504/system/console/depfinder

Pom dependencies

Create a utility class, In this class inject MessageGatewayService service using Reference annotation and Create a map with all the dynamic properties.

e.g.
@Reference
private MessageGatewayService messageGatewayService;

final Map<String, String> parameters = new HashMap<String, String>();
parameters.put("title", "Demo Email");

Create org.apache.commons.mail.HtmlEmail class object with template and map parameters.

HtmlEmail email = mailTemplate.getEmail(StrLookup.mapLookup(parameters), HtmlEmail.class);

Find the below Servlet Example for complete code -
https://github.com/arunpatidar02/aem63app-repo/blob/master/java/email/HTMLEmailServlet.java

Custom Email template.txtYou can create a custom HTML5 template like an HTML page and save it as .txt in CRX repository wherever you want.
All the variable values can be replaced with properties set in the code e.g. ${title}

Example template available at
https://github.com/arunpatidar02/aem63app-repo/blob/master/java/email/html5-template.txt

OSGi Config to Send an email:For AEM to be able to send emails, the Day CQ Mail Service needs to be properly configured. Please check at
https://helpx.adobe.com/experience-manager/6-3/sites/administering/using/notification.html#configmail

Sample configuration for sending email using Gmail SMTP server

That’s it. Find the sample email trigger by https://github.com/arunpatidar02/aem63app-repo/blob/master/java/email/HTMLEmailServlet.java servlet.


Find the complete code used in this blog at Github.


By aem4beginner

Add a button in site console's action toolbar to open pages in disabled mode

In the Author instance, sometimes we need to check pages in disabled mode to debug HTML/or to see pages how they will look in the publisher or other than edit, preview, design mode. AEM provides the way how to check pages in disabled mode, following are the ways -

1. Add wcmmode=disabled querystring in the page URL e.g.
http://localhost:4502/content/mfHTL63/en/demo1.html?wcmmode=disabled
2. Open a page as edit e.g. http://localhost:4502/editor.html/content/mfHTL63/en/demo1.html and then click on the page information button, it will show popover menu from there click on 'View as Published', a page will be opened in new tab with the disabled mode.


But if you are using the 'View as Published' option frequently then you can add a button in the site console to view page(s) in a disabled mode in one click.

Follow below step to add a new button/option :
overlay /libs/wcm/core/content/sites/jcr:content/actions/selection using Resource Merger (Overlay Node option from CRXDE).

CRXDE to overlay nodes

Create a node i.e 'view' and 'data' node child of 'view' node, as below screenshot
overlayed selection and view & data node

add the following properties to view and data nodes

<view
granite:rel="cq-siteadmin-admin-actions-edit-activator"
granite:title="View as Published"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/collection/action"
action="cq.wcm.open"
activeSelectionCount="multiple"
icon="devicePreview"
target=".cq-siteadmin-admin-childpages"
text="View"
variant="actionBar">
<data
jcr:primaryType="nt:unstructured"
href.uritemplate.abs="\{+item}.html?wcmmode=disabled"/>
</view>


That's it. The new option 'View' will be shown in the site consoles action toolbar when one or many pages are selected.


View button is added in site console

Enable the View button from the package
You can install the below package to enable/add the view option in site console in Author mode.
https://github.com/arunpatidar02/demo/blob/master/view-as-published-2.zip


By aem4beginner

January 2, 2021
Estimated Post Reading Time ~

SunEditor: An Alternative to the AEM RTE

Ever since I first worked with the AEM Rich Text Editor, it was clear to me that it was buggy, not easily extensible, and sometimes unusable. But the main issue for me was extensibility. There is no official API documentation or any documentation on how to build RTE Extensions. This is the reason I started searching for an alternative. Two years ago, I integrated CKEditor as an AEM Dialog Widget. This editor works, but was mainly a Proof of concept and not to be used in production.

SunEditor
Recently, I stumbled upon SunEditor, which the author describes as “Pure javascript based WYSIWYG HTML editor, with no dependencies”. You can play with an example here. It seems to work really well and has a simple API that allows you to build plugins to your heart’s desire. The documentation and plugin examples are pretty good as well.

Integrating SunEditor with AEM
Since I liked it very much, I decided to build an integration for it with AEM. It turned out to be extremely simple! SunEditor starts with a <textarea> and initializes the editor around it. Additionally, the editor saves the HTML value into the <textarea>making Sun ideal as an AEM dialog widget!

I’ve created a simple project on Github, that you can deploy and test for yourself: https://github.com/ahmed-musallam/AEM-SunEditor

Once you have it deployed to your AEM instance and have a component dialog that uses it, you should see something like this:


The editor allows you to edit almost everything the AEM RTE allows you to. It even allows you to author in full screen!

The editor does not support adding/authoring AEM images, but integrating that with AEM should not be difficult since SunEditor does support images. But that might be for a later post. For now, test this out and let me know how you like it!


By aem4beginner

Creating a Taxonomy

Taxonomy is a categorization framework used to identify or tag content. It sets the foundation for classification using a controlled vocabulary which leads to better findability and easier information management. A taxonomy should be agreed upon by business and content owners, and flexible enough to respond to changing business needs.

Using the Taxonomy Features in AEM Assets

Let’s get nomenclature out of the way. In AEM, taxonomy is referred to as “tagging” or “tags.” And a top-level set of tags is referred to as a “namespace.” These tags can be applied as metadata on an asset or page. In AEM, you can manage tags here: /libs/cq/tagging/gui/content/tags.html/etc/tags

For help on using tags in AEM, see: https://helpx.adobe.com/experience-manager/6-3/sites/authoring/using/tags.html

For help on administering tags in AEM, see: https://helpx.adobe.com/experience-manager/6-3/sites/administering/using/tags.html

Tips for Assembling a Taxonomy
Tip #1: Taxonomies are both internal-facing and external facing. To assemble a list of namespaces, think about how the organization classifies the documents or assets that will be stored in the DAM. Categories like Fund Type, Region, or Audience might be appropriate for a financial institution. Whereas for a manufacturer, Brand, Product Family, and Year are typical.

Tip #2: Facets enable the categorization of nested, independent categories, allowing the author to narrow the choices, making it easier to navigate the taxonomy tree. It’s likely the organization already has a hierarchy of products, for instance, that can be adopted as nested facets in a namespace, so be sure to look internally first, especially with the marketing department.

Tip #3: When identifying metadata, look for patterns that might be better represented as taxonomy. This also reduces the number of metadata fields and can lead to better identification of content.

More Perficient posts about taxonomy:


By aem4beginner

Non-AEM relative links in RTE and Path Browser in Touch UI

In AEM 6.0, 6.1 & 6.2 the Touch UI RTE link plugin and path browser adds a content keyword automatically. This begins as soon as the author starts typing/. It can be difficult for authors to enter non-AEM relative links in the content, and some environments require both AEMand non-AEM links in the same domain.

A CSS class js-coral-pathbrowser-input does exist which validates on RTE and path browsers. If this class is removed when the dialog is loaded, authors can enter relative URLs in the path browser and RTE link dialog.

Path Browser has this validation coming from class js-coral-pathbrowser-input in client lib/etc/clientlibs/granite/coralui2/optional/rte/js/components/cui-rte.templates.js. RTE has the same class coming from /libs/cq/ui/widgets/source/widgets/form/rte/plugins/LinkDialog.js

These are out of the box (OOTB) files that cannot be modified. I have created a custom extension that removes this class when the dialog is loaded. However, this extension only removes the validation. There are other configurations that need to be done on the RTE side in order for non-AEM internal links to be added.

AEM automatically appends .html for any links that don’t start with HTTP in RTE. In order to prevent non-AEM links from appending .html RTE needs to have htmlRules which have few configuration properties as defined in https://docs.adobe.com/docs/en/aem/6-1/administer/operations/page-authoring/rich-text-editor.html

You can then add this node to the current RTE in Text Component. But remember that this node needs to have links subnode with property ensureInternalLinkExt set to false to avoid changing .html to non-AEM or external links.

For path browser links, I extended the OOTB image component to stop appending .html to external links or any non-AEM path starting with /.

This class can be removed by the following statement when the dialog loads:
Path Browser
$(".js-coral-pathbrowser-input").removeClass("js-coral-pathbrowser-input");

For RTE, when the link dialog loads, we need to find this attribute from the element and remove from the DOM
RTE Link Dialog
this.$rteDialog = this.$container.find("[data-rte-dialog=link]");
this.$rteDialog.find(".js-coral-pathbrowser-input").removeClass("js-coral-pathbrowser-input");

Code samples are provided in my GitHub repository, and I created a sample page that can be accessed after the package is installed in your local AEM repository at http://localhost:4502/editor.html/content/invalidatejspathrte/en.html

Below are sample screenshots of the page with the OOTB issue(left) and its fix(right):



The code can be downloaded from the GitHub repository: https://github.com/shajiahmed/AEMjsinvalidatepathrte.

Let us know how this solution worked for you!


By aem4beginner

Markdown in AEM with Flexmark

Markdown is a light markup language with a text-based syntax that can be converted to HTML, PDFs, or any number of different styled formats. Markdown is a popular format for text-heavy content such as documentation, wiki content, and comments as it is easy to maintain and read the content without the added complexity of including formatting.

Unfortunately, AEM did not provide a mechanism to interact with Markdown content, nor were any of the Java markdown libraries compatible with OSGi. Recently, I worked with the Flexmark team to produce an OSGi bundle version of the Flexmark markdown library.

Now that this library is released, integrating markdown content into AEM is an easy process.

Using Flexmark in AEM
To use the Flexmark library in AEM, first you need to get the bundle installed. It can either be downloaded directly from Maven Central (not recommended) or integrated into your Content Package POM with the following:
<!-- within /project/dependencies -->
<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.11.3</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.vladsch.flexmark</groupId>
  <artifactId>flexmark-osgi</artifactId>
  <version>0.34.22</version>
  <scope>provided</scope>
</dependency>
<!-- within /project/build/plugins/plugin (content-package-maven-plugin) -->
<embedded>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <target>/apps/myapp/install</target>
</embedded>
<embedded>
  <groupId>com.vladsch.flexmark</groupId>
  <artifactId>flexmark-osgi</artifactId>
  <target>/apps/myapp/install</target>
</embedded>
Once the bundle is installed you can then use the Flexmark API to retrieve and leverage the Markdown content from the AEM repository, for example using a Sling Model like this one which retrieves Markdown content from the property markdownfield and converts the Markdown content to HTML:
package com.myorg.site.models;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.Self;
import com.vladsch.flexmark.ast.Node;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.options.MutableDataSet;
@Model(adaptables = Resource.class)
public class DiscussionMarkdownModel {
    @Self
    private Resource resource;
    public String getHtml() {
        MutableDataSet options = new MutableDataSet();
        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();
        Node node = parser.parse(resource.getValueMap().get("markdownfield", String.class));
        return renderer.render(node);
    }
}
With the new Flexmark OSGi bundle, integrating Markdown in AEM is painless, as we can see in this simple example.

Robert Munteanu is also working on a Sling Resource Provider backed by Markdown files, which is an interesting concept if someone wanted to do something like publish documentation in Markdown, but display it on a site with the dynamic, marketing features AEM. So who knows, maybe Adobe could convert their documentation site into Markdown + AEM?

EDIT – 31-09-2018 – Indicating that JSoup is also required.


By aem4beginner

Customized Logging using SLF4J / MDC in AEM

Out of the box, AEM provides a pattern-based logging system that comes pre-configured with a MessageFormat pattern for logging. This is a somewhat legacy messaging format, which, for most applications, has been updated with a “Logback” implementation. The main reason for said replacement is the flexibility that logs back techniques can offer, including a more customized log pattern support, in addition to the ability to add in mapped variables, or in log back terminology, Mapped Diagnostic Context, or MDC. 

AEM comes bundled with SLF4J, and therefore already has support for log back patterns, and more importantly, MDC. What is missing here is how to populate the variable which needs to be logged on each request, and how to manipulate your logging patterns to accommodate said variable.

Step 1: Create a Sling Filter
To utilize MDC, there are two main steps:
  • Populating the MDC Object (Map)
  • Referencing the MDC Properties (Logging Pattern)
In AEM, every request flows through a filter chain, meaning we need to populate the MDC variable early enough in the filter that we can successfully log those variables later. The easiest way to do this is to set the scope of a custom filter to be at the request level, order 0:

/**
* The SlingFilterScope.REQUEST is important as MDC needs to be configured prior
* to any logger being executed in the page rendering.
*/
@SlingFilter(
label = "Sample MDC Filter",
description = "Sample implementation of custom MDC properties.",
order = 0,
scope = SlingFilterScope.REQUEST)


Then you need to import the MDC object into your class:
import org.slf4j.MDC;

The rest of the logic is up to you! The request object gives you direct access to the following:
  • ResourceResolver (Access to JCR)
  • Request Headers
    • You can use a RequestWrapper to customize
  • Request Cookies
  • Request Parameters
And of course, you can always use a customized service user for more targeted JCR access. For sake of simplicity, this example includes logic to read a request parameter named “appId”.

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
final SlingHttpServletRequest request = (SlingHttpServletRequest) servletRequest;
try {
insertIntoMDC(request);
filterChain.doFilter(request, servletResponse);
} finally {
clearMDC();
}
}
private void clearMDC() {
for (String key : MDC_CUSTOM_KEYS) {
MDC.remove(key);
}
}
private void insertIntoMDC(SlingHttpServletRequest request) {
//logic can go here to take value from request object.
// Can also utilize ResourceResolver from same request.
// If needed, can also write a generic OSGi Configuration
// (String Array Properties) to read from standard request objects,
// i.e cookies, headers, and parameters.
MDC.put(APPLICATION_ID,request.getParameter(APPLICATION_ID));
}

That is it! Deploy the filter and we will have (the ability) to log a passed in request parameter into your log.

Here’s the full class for those copy/pasters:
package com.perficient.commons.core.filters;
import org.apache.felix.scr.annotations.sling.SlingFilter;
import org.apache.felix.scr.annotations.sling.SlingFilterScope;
import org.apache.sling.api.SlingHttpServletRequest;
import org.slf4j.MDC;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* The SlingFilterScope.REQUEST is important as MDC needs to be configured prior
* to any logger being executed in the page rendering.
*/
@SlingFilter(
label = "Sample MDC Filter",
description = "Sample implementation of custom MDC properties.",
order = 0,
scope = SlingFilterScope.REQUEST)
public class MDCLoggerFilter implements javax.servlet.Filter{
public static final String APPLICATION_ID = "appId";
private static final String[] MDC_CUSTOM_KEYS = {
APPLICATION_ID,
};
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
final SlingHttpServletRequest request = (SlingHttpServletRequest) servletRequest;
try {
insertIntoMDC(request);
filterChain.doFilter(request, servletResponse);
} finally {
clearMDC();
}
}
private void clearMDC() {
for (String key : MDC_CUSTOM_KEYS) {
MDC.remove(key);
}
}
private void insertIntoMDC(SlingHttpServletRequest request) {
//logic can go here to take value from request object.
// Can also utilize ResourceResolver from same request.
// If needed, can also write a generic OSGi Configuration
// (String Array Properties) to read from standard request objects,
// i.e cookies, headers, and parameters.
MDC.put(APPLICATION_ID,request.getParameter(APPLICATION_ID));
}
public void destroy() {
}
}

Step 2: Updating the Logging Pattern
This step actually helped uncover an issue in the latest AEM versions. At the time of writing, AEM “Logging Logger Configurations” do not properly leverage the “Apache Sling Logging Logger” pattern variable. Instead, it references the “default” pattern, found in the configuration for org.apache.sling.commons.log.LogManager. As a work-around, we will update the “default” LogManager instead of updating the application-specific “Logging Logger” pattern. You can perform this update directly in AEM:



Or, my recommendation, create an associated OSGi configuration within your code repository’s associated config folder ( /apps/<project>/config) named org.apache.sling.commons.log.LogManager.xml with the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/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="sling:OsgiConfig"
org.apache.sling.commons.log.pattern="%d{dd.MM.yyyy HH:mm:ss.SSS} *%level* [%X{appId:-NoAppId}] [%thread] %logger %msg %ex%n"
org.apache.sling.commons.log.file.size="'.'yyyy-MM-dd"
org.apache.sling.commons.log.file="logs/error.log"
org.apache.sling.commons.log.file.number="7"
org.apache.sling.commons.log.level="info"
org.apache.sling.commons.log.maxOldFileCountInDump="3"
org.apache.sling.commons.log.numOfLines="10000"
org.apache.sling.commons.log.maxCallerDataDepth="7"
org.apache.sling.commons.log.packagingDataEnabled="{Boolean}false"/>


You will notice that in the above example I use a different format for the pattern string. One that may look more familiar is the default MessagePattern format:

{0,date,yyyy-MM-dd HH:mm:ss.SSS} {4} [{3}] {5}

In this default pattern, each number corresponds with a given data point, as described in the official docs. In our case, you see variables prefixed with a percentage sign. Odd! Well, fortunately, there is also a mapping for these, which we can also map using the official logback documentation.

The most interesting of the group is the %X variable. This is what exposes your custom MDC variables. In the above example, %X{appId:-NoAppID}” defines a placeholder for a property named “appId” as well as its default text, “NoAppId”. The standard for supplying defaults for any MDC object is to use the “:-” delimiter followed by the default text. Therefore, %X{appId} would output an empty string if null, whereas %X{appId:-NoAppId} would output “NoAppId” if null.

Similarly, to output, all of the configured MDC variables, simply do not specify the variable you want to output: %X. If configured this way, the entire MDC map would be output as a comma-separated list.

For a much deeper look at all possible variable mappings, I would highly suggest taking a look at the official logback documentation, which I will link to again here: https://logback.qos.ch/manual/layouts.html#conversionWord

Step 3: Check out the results
Once you’ve pushed the updated configuration and your custom sling filter, the changes should be active! If you followed the exact steps above, you’ll see a lot of “NoAppID” messages. Do not fret – this is because we never requested a page with the “appId” request parameter. For example, a simple we-retail request ( http://localhost:4502/content/we-retail/ca/en/experience.html ) should result in:

24.09.2018 12:41:36.819 *WARN* [NoAppId] [0:0:0:0:0:0:0:1 [1537818095824] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /etc/designs/we-retail
24.09.2018 12:41:36.915 *INFO* [NoAppId] [0:0:0:0:0:0:0:1 [1537818095824] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.day.cq.wcm.core.impl.designer.SystemDesign Initialized system design at /etc/designs/default in 13ms
24.09.2018 12:41:38.432 *WARN* [NoAppId] [0:0:0:0:0:0:0:1 [1537818095824] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /apps/clientlibs/granite/jquery-ui
24.09.2018 12:41:40.303 *INFO* [NoAppId] [0:0:0:0:0:0:0:1 [1537818100301] GET /home/users/Z/ZxNe0kWWedMwcd2ZAFhp.infinity.json HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl service: Resource /home/users/Z/ZxNe0kWWedMwcd2ZAFhp.infinity.json not found

However, if we change the URL to add our appId, http://localhost:4502/content/we-retail/ca/en/experience.html?kp.appId=RMAPP, we get the following:

24.09.2018 12:43:39.305 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /apps/clientlibs/granite/jquery-ui
24.09.2018 12:43:39.305 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /etc/cloudsettings/default/contexthub.kernel
24.09.2018 12:43:39.309 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /etc/designs/we-retail
24.09.2018 12:43:39.436 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /apps/clientlibs/granite/jquery-ui


Obviously, the true power of these entries will show when leveraging JCR and the Resource Resolver. Potentially a topic for another day. Have fun and good luck tinkering!
Kudos

I want to give appropriate recognition to the initial implementation in which this was derived: https://github.com/chetanmeh/sling-logback. Unfortunately, this package no longer functions without modification on a 6.3 instance. The logic for setting OSGi configuration properties for header, cookie, and parameters could be mirrored in this example if desired.

Source:


By aem4beginner

Log Tailer Plus: A Client-Side Log Tailer for AEM

In the past, while working on client engagements, the simple task of viewing the logs has been problematic. Without system access, analyzing AEM logs in real-time on a non-local environment is almost impossible using the provided AEM tools. This is because the out of the box client-side logging with AEM is a reflection of the plain text logs on the system. However, those who have viewed logs directly on their system typically leverage other software to view log files. On the client-side, unfortunately, there are no such applications available out of the box with AEM. Log Tailer Plus was created to fill that void – providing a client-side application for viewing logs within AEM.

As a reminder, here is a screenshot of two different log consoles found in AEM out of the box:

/system/console/slinglog/tailer.txt


CRX/DE Logger (actually uses tailer.txt under the hood)


A New Challenger has Appeared
Log Tailer Plus solves the above problem and allows users to view the AEM Logs without the need for system-level permissions. Because it is built around the existing /system/console/slinglog/tailer.txt servlet, it already included (server-side) support for leveraging the sling configurations appenders. On top of the existing functionality, Log Tailer Plus includes additional client-side functionality in order to display the data in a much more consumable format.
Features

Standard Message Format Highlighting / Hover


Coming Soon: User/Custom highlight rules
Multiple Logs in the Same Window


Add Log Form Control


Note: Each logger currently requires 600px width. Only enormous screens will support more than three concurrent loggers in the same window.

Pin/Follow Toggle


Clear Log, Advanced Settings, Remove Tailer


Advanced Settings Dialog


Stack trace collapsing


Look! The stack trace disappears!

Installation
Requirements:

AEM 6.2+

Notes:
Do not install Log Tailer Plus in production deployments. Exposing your logs can be a security risk.

Steps:
To install, simply download and install the provided AEM package, or build it yourself from the source (see below for links). The tool installs to /apps/log-tailer-plus and is completely self-contained. Once installed, access the Log Tailer Plus by navigating to the pre-configured vanity URL /log-tailer-plus. The top-level apps page is also directly accessible: /apps/log-tailer-plus.html.

Download/More Information
For more information about the project, visit the projects:
  1. Github Repository.
  2. Releases


By aem4beginner

Getting Started with the Netcentric Access Control Tool and Adding Service Users to Your YAML Files

Keeping permissions in sync across environments is an issue for most organizations. In AEM, you can export permissions using packages but this becomes a tedious process if you need to do this on a regular basis.

I won’t say that the AC Tool solves the problem completely but it’s a good place to start. In future posts, I will tell you how to extend the functionality to give you more control over what you need for your specific organization.

What this tool does give you is a way to retrieve your permission information from your environments in the form of YAML files. It also provides an installation hook to deploy your YAML files to environments based on run modes. This means that you can have permissions for all of your environments in one code repository, it will only deploy the relevant permissions to the targeted environments with the matching run modes.

This is already a huge step forward from the manual process.

Installation
There are two packages you will need for installation. The first is the AC Tool package, the second is the oak index file for the same version. Though the index file is optional, it’s recommended for those who have a large number of groups. Personally, I don’t see a reason for not installing it either way.

One thing you want to keep in mind is that you should install the package only once per environment. This means you do not want to make a part of your regular code deployment, which can cause issues with your deployments.

Creating YAML Files
Once you have the packages installed you can access the tool in two ways, either through the JMX console or through the tools navigation in AEM Tools Console.



Using the Netcentric Dashboard can pull the latest dump file or upload a package with your YAML files for testing.

Deploying Updates


Once you have your files retrieved and modified for import you can deploy them to your environment; remember, this is run mode based so make sure your run modes are valid for the environment you’re targeting. If you are only deploying to a single environment, you don’t have to use run modes.

To deploy you can create a maven project that packages your YAML file structure. If you add the Netcentric hook, it will automatically take effect. If you would rather double-check things, leave out the hook, and use the “Apply” feature in the Netcentric Dashboard for your changes to take effect. Remember to put in the path to your YAML files before you try to apply the updates.



This configuration will deploy only to environments with an author and a localdev run mode.

Once you have deployed your files, you can check the logs to see if it successfully updated the permissions you expected. If you aren’t seeing anything in the logs, you may want to check the package installation to make sure it was successful. If there are any errors in the YAML files, it will create an error and stop the installation.

Tips
  1. Whenever possible, don’t redeploy OOTB system users or groups. There’s really no need to unless you’re using them for a specific reason.
  2. Don’t create new users other than test users or system users.
  3. Do use this tool for removing obsolete users and groups. This way you can remove them from all environments consistently.

Netcentric AC Tool – Adding Service Users to Your YAML Files

By default, these files do not contain any user information, however, the tool does give you a pretty easy way to include these by using an OSGi configuration. The only drawback to this approach is that you can’t change it without changing the config. In the next post, I’ll show you how to create your own custom servlet to generate a custom YAML file.

In AEM, go to the Configuration Web Console (<host:port>/system/console/configMgr). Then, open the “AC Tool Dump Service” configuration. Check the box by “Include users in dumps”. Also note that even though it says “users”, it only includes Service Users, not regular users. Hopefully, they will rename it to say Service Users, as it’s a bit misleading.



Save your changes. What this does is adds a new section to your YAML files called “user_config”, and it includes service users’ information. It also adds service user ACLs to the ace_config section of the file.

References:
You can find more information on the AC Tool, including example files on their Github website.

https://github.com/Netcentric/accesscontroltool

Installation package files and oak index files are managed in maven, which can be found here:

https://repo1.maven.org/maven2/biz/netcentric/cq/tools/accesscontroltool/accesscontroltool-package/

https://repo1.maven.org/maven2/biz/netcentric/cq/tools/accesscontroltool/accesscontroltool-oakindex-package/


Source:



By aem4beginner

One Tool to Configure Them All: Sling RepoInit

The core paradigm for the Java Content Repository (JCR), the repository for Adobe Experience Manager (AEM) is Everything is Content. This principal drives the flexibility which made AEM a market-leading solution. It does, however, come with a downside, managing the initial repository state is challenging since the repository state is a combination of the content, code and configuration for an AEM application.

Managing this initial state is important for developers and administrators to be able to stand up local instances, standing up new environments and keeping environments in sync.

Multiple teams have build parts of a solution to this problem, including:
Netcentric Access Control Tool
ACS AEM Commons Ensure Service User
ACS AEM Commons Authorizable Packager
Apache Sling Service User Web Console
AEM Content Packages

Recently though, another solution has come to the fore for configuring the initial repository state.

Apache Sling RepoInit
Apache Sling RepoInit has been a feature of Apache Sling since 2016 but historically has been used for base repository initialization as a part of slingstart / launchpad starter, not for project-level repository initialization.Version 1.1.6+ of Sling JCR RepoInit includes the ability for registering configurations with RepoInit scripts or references. This brings Apache Sling Repoint out from just being a base repository initializer to enable use by project teams.

With Sling Repoint, we have a consolidated grammar to:
  • Create groups
  • Assign permissions
  • Create paths
  • Set properties
  • Assign group membership
  • Create OSGi configurations
  • Create service users
Enabling RepoInit in AEM
Note: this is based on AEM 6.5.5, newer service packs and AEM Cloud Service may have different dependency versions.

AEM includes a fairly old version of Sling RepoInit, in order to leverage the full power of Sling RepoInit, you need to upgrade to the following dependencies for AEM 6.5.5:
org.apache.sling.jcr.repoinit – 1.1.24
org.apache.sling.repoinit.parser – 1.6.2

Configuring RepositoryInitializer
We can configure RepoInit scripts to be executed by registering a configuration of the org.apache.sling.jcr.repoinit.impl.RepositoryInitializer Service Factory, however there is a challenge with how this service resolves the references. Each reference is expected to be in the form of a URL and OSGi supports exposing bundles as URLs, however when Apache Felix resolves the URL of a bundle in the URLHandlersBundleStreamHandler, it expects the URL host to be the UUID of the bundle, not a stable ID such as the Bundle’s Symbolic Name.

Note: as per Oliver Lietz’s comment I would recommend following the PAX URL Classpath method rather than the approach below.

I have opened a ticket to get this resolved, but until that’s complete and Generally Available, the best option is to create an OSGi Component to resolve the Bundle URL and initialize the configurations when the bundle starts.

Below is a sample implementation showing how this could be done, showing how to support multiple RepoInit files in a single bundle:

package com.company;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import org.osgi.framework.BundleContext;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Register the repoint scripts in the bundle's /repoinits context path.
*/
@Component(immediate = true)
public class RegisterRepoInits {

public static final String PID = "org.apache.sling.jcr.repoinit.RepositoryInitializer";
private static final Logger log = LoggerFactory.getLogger(RegisterRepoinits.class);

@Reference
private ConfigurationAdmin configAdmin;

private Configuration config;

/**
* Install the Sling RepoInit configuration based on the RepoInit scripts
* embedded in this bundle
*
* @param ctx the OSGi ComponentContext
* @throws IOException an exception occurs resolving the RepoInit scripts
*/
@Activate
@Modified
protected void activate(ComponentContext ctx) throws IOException {
log.info("activate");
BundleContext bundleContext = ctx.getBundleContext();

// Get or create the configuration if it does not already exist
config = configAdmin.getConfiguration(PID + "-company");

List<String> urls = new ArrayList<>();
// Find all of the RepoInit text files in ./src/main/resources/repoinits by
// enumerating the Bundle's entries
Enumeration<?> repoinits = bundleContext.getBundle().getEntryPaths("/repoinits");
while (repoinits.hasMoreElements()) {
Object repoinit = repoinits.nextElement();
log.info("Adding RepoInit: {}", repoinit);
// Resolve the Bundle URL for the Bundle Resource
URL repointUrl = bundleContext.getBundle().getResource(repoinit.toString());
urls.add(repointUrl.toString());
}

// Create and register an OSGi configuration with an Array of Bundle Resource
// URLs
@SuppressWarnings("java:S1149")
Dictionary<String,Object> properties = new Hashtable<>();
properties.put(ConfigurationAdmin.SERVICE_FACTORYPID, PID);
properties.put("references", urls.toArray(new String[urls.size()]));
properties.put("scripts", new String[] {});
config.update(properties);

log.info("RepoInit Registered");
}

/*
* Remove the config when the component is deactivated
*/
@Deactivate
protected void deactivate() throws IOException {
log.info("deactivate");
if (config != null) {
config.delete();
log.info("Repoinit De-registered");
}
}
}


Now that you have Sling RepoInit setup and working, you can create any number of RepoInit scripts in the ./src/main/resources/repoinits folder and on bundle installation they’ll be live reloaded.

With the RepoInit scripts, you can set up your repository completely with commands like:# Create paths create path /content/my-site(cq:Page) # Create a group create group my-site-admin set ACL for my-site-admin allow crx:replicate, jcr:lockManagement, jcr:versionManagement, rep:write on /content/my-site end # Add group members add my-site-admin to group administrators # And More!

Apache Sling RepoInit is the consolidated way to initialize a repository for any Apache Sling-based system including AEM 6.5, AEM as a Cloud Service and Apache Sling CMS. With RepoInit, you can be assured that your solution will work on both the current version of AEM as well as AEM as a Cloud Service.

Source:
https://blogs.perficient.com/2020/06/17/one-tool-to-configure-them-all-sling-repoinit/


By aem4beginner

Adding a Color Picker to the AEM Content Fragment Model Editor

Recently, one of our clients looked to add a custom field in the Adobe Experience Manager (AEM) Content Fragment Model Editor. If you’re wondering what AEM Content Fragments are, check out Dan Klco’s post, as he does a great job explaining.

Content Fragment Models are built with elements from various out-of-the-box data types, including single-line text, multi-line text, number, boolean (only for checkboxes), date/time, enumeration (only for static dropdown values), tags, and content reference.

Adding a Custom Field to the AEM Content Fragment Model Editor
After investigating the structure of the Content Fragment Model form builder configuration inside CRXDE, I found that we can easily add most other data types (there are some restrictions for a few datatypes). To extend and customize this form builder configuration of Content Fragment Model Editor, we need to overlay the Content Fragment form builder resource.

In our client’s case, we needed to set the requirement to add the Color Picker field in the Content Fragment Model in AEM 6.5. In this post, I’m going to show you how extended the functionality of the Content Fragment Model to set the Color Picker data type.

How to Set the Color Picker Data Type:
  1. First, open the CRXDE Lite console http://localhost:4502/crx/de/index.jsp
  2. Enter the following path in the search bar and hit enter. /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items
  3. Right-click on the items node. This will open a pop-up menu.
  4. Click on Overlay Node Option from the pop-up menu. This will open another model dialog. Then click on select the Match Node Types option. Make sure the model dialog contains the following values.
Path: /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items
Overlay Location: /apps/
Match Node Types: checked

After verifying the above properties and values, click “OK.” This will overlay the location of /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items path inside /apps/ folder.

5. Now go to /apps/settings/dam/cfm/models/formbuilderconfig/datatypes/items.
6. Create one child node inside the items node with the name color-picker.
7. Set the following properties on /apps/settings/dam/cfm/models/formbuilderconfig/datatypes/items/color-picker node.

fieldIcon (String)=”colorPalette”
fieldProperties (String [])=”[labelfield,maptopropertyfield,placeholderfield,textvaluefield,requiredfield]”
fieldPropResourceType (String)=”dam/cfm/models/editor/components/datatypes/field”
fieldResourceType (String [])=”[granite/ui/components/coral/foundation/form/colorfield]”
fieldTitle (String)=”Color Picker”
listOrder (String)=”9″
renderType (String)=”text”
valueType (String [])=”[string]”


8. Color-picker Node xml should look like as follows

<?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">
<items jcr:primaryType="nt:unstructured">
<color-picker
jcr:primaryType="nt:unstructured"
fieldIcon="colorPalette"
fieldProperties="[labelfield,maptopropertyfield,placeholderfield,textvaluefield,requiredfield]"
fieldPropResourceType="dam/cfm/models/editor/components/datatypes/field"
fieldResourceType="[granite/ui/components/coral/foundation/form/colorfield]"
fieldTitle="Color Picker"
listOrder="9"
renderType="text"
valueType="[string]"/>
</items>
</jcr:root>



Now you are successfully done with the node overlay and AEM customization process.

9. In order to see the changes, we need to create a configuration for the Content Fragment Model. Go to AEM Start Menu > Tools > General > Configuration Browser and create a configuration for the Content Fragment Model. You can also use the OOTB global configuration if you don’t want to create a custom configuration (it depends upon your requirement). Here I am using AEM OOTB configuration with the name “Global.”
10. Once you are done with the configuration steps, go to AEM Start Menu > Tools > Assets > Content Fragment Model > Global (or go to your custom configuration).

Then create a new custom Content Fragment Model (In this case, I have created a model with the name “Color Picker CFM“) and click on open. Now, take a look at Data Types inside the sidebar. There you will see a new Data type is appearing with the name “Color Picker.“

11. To create your custom Content Fragment Model, drag and drop color picker data type in editor block, and set some properties on the color picker field. I set the following properties.

Field Label: Set Color
Property Name: color


Once you are done with this click on Save Button.

12. Now go to AEM Start Menu > Navigations > Assets > <any Folder> and Create a Content Fragment using Color Picker CFM model. Once it is created, click on the Open button. You will notice the Content Fragment has the color picker field with the label “Set Color.” Now you can select any predefined color from the color palette, or you can create your own color using a color mixer.



You can use these same steps to extend the functionality of the Content Fragment Model whenever you get such a requirement for other data types as well!

Download this package for reference, and don’t forget to drop a comment if you need more help on this.


Source:


By aem4beginner

Case Insensitive Queries with the AEM Query Builder

Recently, I needed to perform a query using the AEM Query Builder which was case insensitive. While I normally prefer using JCR SQL2 queries, in this case, Query Builder was a better fit as I wanted consuming applications to be able to manipulate the query, and doing so as a map is significantly easier than doing so as a string.

I was surprised to find that there was no native Query Builder Predicate for doing case insensitive queries so I ended up writing my own.
/*
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to 
*/
package com.perficient.adobe.predicates;

import java.util.Locale;
import java.util.Optional;

import com.day.cq.search.Predicate;
import com.day.cq.search.eval.AbstractPredicateEvaluator;
import com.day.cq.search.eval.EvaluationContext;

import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(factory = "com.day.cq.search.eval.PredicateEvaluator/equalsIgnoreCase")
public class CaseInsensitiveEquals extends AbstractPredicateEvaluator {

    private static final Logger log = LoggerFactory.getLogger(CaseInsensitiveEquals.class);

    static final String PREDICATE_PROPERTY = "property";
    static final String PREDICATE_VALUE = "value";
    static final String PREDICATE_LOCALE = "locale";

    @Override
    public String getXPathExpression(Predicate predicate, EvaluationContext context) {
        log.debug("Evaluating predicate: {}", predicate);
        String property = predicate.get(PREDICATE_PROPERTY);
        Locale locale = Optional.ofNullable(predicate.get(PREDICATE_LOCALE)).map(lt -> Locale.forLanguageTag(lt))
                .orElse(Locale.getDefault());
        String value = predicate.get(PREDICATE_VALUE).toLowerCase(locale).replace("'", "''");
        String query = String.format("fn:lower-case(@%s)='%s'", property, value);
        log.debug("Generated query: {}", query);
        return query;
    }
} 
Once the custom predicate is available in your application, it can be used in any Query Builder query as such:
path=/content/test
equalsIgnoreCase.property=test
equalsIgnoreCase.value=test
equalsIgnoreCase.locale=en_US 
The locale parameter is not required, but would generally be recommended unless the input will always be in the system default locale.

Source:


By aem4beginner

How to Show Unpublished Reference Alert for Tagpicker

In this post, I was able to showcase how to create a notification for the content author as to whether the selected content path is published or not. I found it to be a useful feature for one of our clients while they were authoring a number of AEM content pages. After the successful delivery of this feature, the client requested that we implement a similar kind of feature while selecting AEM tags in cq:dialog.

Unfortunately, AEM does not have the OOTB functionality to show unpublished notifications for selected AEM Tags while authoring the components. In this blog, I am going to show how you can extend the functionality of tagspicker resourceType to show unpublished reference alerts while selecting AEM tags.

Steps to extend the functionality of tagspicker resourceType.
  1. First, open CRXDE Lite console i.e. http://localhost:4502/crx/de/index.jsp
  2. Create one node named clientlibs with the following properties. After that, create one folder named js inside clientlibs node and also one file named js.txt.
<?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"
    jcr:primaryType="cq:ClientLibraryFolder"
    categories="[cq.authoring.editor]" />


The structure should look as follows:

3. Now, create one file named unpublished-reference-alert-for-tags.js inside /clientlibs/js folder and paste the following script:

(function ($, window) {
// cq:tag path validation only for cq:dialog
// Tagfield : cq/gui/components/common/tagspicker
$(window).adaptTo("foundation-registry").register("foundation.validation.validator", {
selector: ".js-cq-TagsPickerField .js-coral-pathbrowser-input",
validate: function (element) {
var tagPath = element.value;
if (tagPath !== '' && tagPath != null) {
checkReplicationStatus(tagPath);
}
}
});

// Check when coral-Pathbrowser-picker dialog is open.
$(document).on("click", ".coral-ColumnView-item.is-active", function () {
var selectedPath = $(this).data("value");
checkReplicationStatus(selectedPath);
});

// Check the replication status of the selected tag.
function checkReplicationStatus(tagPath){
const CONTENT_PATH_PREFIX = "/content/cq:tags/";
const INFINITY_JSON = ".infinity.json";
const CQ_LAST_REPLICATION_ACTION = "cq:lastReplicationAction";
const ACTIVATE = "Activate";
if (tagPath.indexOf(CONTENT_PATH_PREFIX) !== -1) {
var tagPathUrl = tagPath.concat(INFINITY_JSON);
$.getJSON(tagPathUrl)
.done(function (data) {
var lastReplicationAction = data[CQ_LAST_REPLICATION_ACTION];
if (lastReplicationAction !== ACTIVATE) {
var message = " You have Selected Unpublished Tag : " + tagPath.bold();
$(window).adaptTo("foundation-ui").notify('', message, 'notice');
}
});
}
}
})($, window);


4. Next, modify the js.txt file as follows:

Once you are done with all the above-mentioned steps, your next step is to test the functionality of tagpicker resourceType.

You can go to any page and drag/add components into a layout container whose cq:dialog has the tagpicker field. Select any tag that has not yet published. For testing purposes, you can create a new tag.

While authoring the component you will get the following result:



To ensure this would help, I tested this extended functionality on AEM 6.5. It will work only work for cq/gui/components/common/tagspicker resourceType.

Download this package for reference, and don’t forget to drop a comment if you need more help on this.

Source:


By aem4beginner

January 1, 2021
Estimated Post Reading Time ~

Custom xfpage component/template for Experience fragment

Experience fragment(XF) is a page in AEM like we have our project site pages of the type cq:Page.

Given that it is a page, it is backed by a template and hence a page component.

Site specific XF Page component(inheriting from OOB xfpage component) -> Template Type -> Editable Template -> XF page -> Use in site pages via OOB "Experience fragment" component(from the componentGroup - General)

When we develop a new site, we do create page component with supertype as OOB page component(wcm/foundation/components/page) or core v1/v2 page component(core/wcm/components/page)
Likewise, for XF specific to our site, we are creating one inheriting from the OOB XF page component.

OOB XF page component : /libs/cq/experience-fragments/components/xfpage.

Site-specific XF page component:
Create a component with super type being /libs/cq/experience-fragments/components/xfpage.
We need to override two files customheaderlibs.html and customfooterlibs.html as we need to use our project clientlibs. (We can use the same that we already have in the page component used for site pages)
Other than this, if it calls for any other customizations, we can override the respective files accordingly.



Site-specific XF page template:
Creating template type, an editable template, defining policies are detailed in separate posts. It is the same thing we do for our site pages. Below highlights the slight change specific to XF.
  • While creating template type for XF page component, initial/jcr:content and structure/jcr:content will point to xf page component that we created above specific to the site.
  • Additionally, add a property named "cq:xfVariantType" of type "String" with value "web" on initial/jcr:content node.
  • This property will then be available in all XF pages that we create out of this. It indicates our XF page variation to be of the type of web variation.
  • The reason for adding the same is explained a little later as we proceed below.


XF creation :
  • Navigate to General -> Experience Fragments -> create project-specific folder.
  • In order to use the newly created editable template for XF creation, allow this template in the XF folder. (Allow at root project-specific folder.
  • Eg. /content/experience-fragments/learnings-xf-pages)
  • We can create meaningful folders under the main project folder with respect to the project content structure.
  • I have created two more folders named "language-masters" and "en"
  • Being on the desired folder("en" in this case), Click Create -> Experience fragment -> Newly created XF editable template should be available.
  • Select and then create. (In this case, Title is given for XF: XF Header)
  • As we create the XF page, the page that we land in is considered as a "master" variation as evident from the URL - /content/experience-fragments/learnings-xf-pages/language-masters/en/xf-header/master.html. 



With this, we are done with XF creation using project-specific xfpage component/template. We are good to author site-specific components as desired to be part of XF(site-specific components would have already been allowed while defining policies, as part of editable template creation.)

Usage on a page.
  • We have a component titled "Experience Fragment" from componentGroup "General". We can make use of the same or we do have one from core components(core/wcm/components/experiencefragment) -> Can create a proxy component out of it and use it in site pages.
  • The component has the option to let us provide XF page variation.


Need for cq:xfVariantType property inclusion:
  • Experience Fragment component from "General" group(/libs/cq/experience-fragments/editor/components/experiencefragment) has a dialog field named "Variation" which looks like a pathfield whose resource is /libs/cq/experience-fragments/editor/components/xffield (supertype as Coral UI 3 pathfield)
  • If we observe this dialog field, it has a property named "variant" with a value of "web"
  • As we select the pathfield, it has to display all the XFs available under the root "/content/experience-fragments" along with its variation. Per the above property, pathfield will list all the XFs variation which has the variant type to be "web"
  • In the example we considered, the master will be displayed only if we have cq:xfVariantType(web variant property), Else, it will display paths till "/xf-header"
  • /content/experience-fragments/learnings-xf-pages/language-masters/en/experience-fragment-content-components/master.html. 


(Property that we added in template type will be available in master variation)


XF variation availability in pathfield picker.


Use case:
Most common is for the Header and Footer of the site (Site logo component + Navigation and related components can be part of XF)
Other related screenshots for reference:
We can cross-check by removing this property at the XF variant level and observe the behavior in pathfield picker.

cq:xfVariantType property is removed from master variation.


master variation is not available in pathfield picker.



By aem4beginner