View Javadoc

1   /**
2    * This file Copyright (c) 2008-2012 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.templatingkit.functions;
35  
36  import info.magnolia.context.MgnlContext;
37  import info.magnolia.dam.DamManager;
38  import info.magnolia.dam.asset.Asset;
39  import info.magnolia.dam.asset.functions.DamTemplatingFunctions;
40  import info.magnolia.dam.asset.renderer.NoSuchRenditionException;
41  import info.magnolia.dam.provider.AssetProviderNotFoundException;
42  import info.magnolia.dam.providers.jcr.JcrAssetProvider;
43  import info.magnolia.jcr.util.ContentMap;
44  import info.magnolia.jcr.util.NodeTypes;
45  import info.magnolia.jcr.util.NodeUtil;
46  import info.magnolia.jcr.util.PropertyUtil;
47  import info.magnolia.jcr.wrapper.I18nNodeWrapper;
48  import info.magnolia.module.templatingkit.STKModule;
49  import info.magnolia.module.templatingkit.categorization.CategorizationSupport;
50  import info.magnolia.module.templatingkit.imaging.ImagingSupport;
51  import info.magnolia.module.templatingkit.sites.Site;
52  import info.magnolia.module.templatingkit.sites.SiteManager;
53  import info.magnolia.module.templatingkit.style.CssSelectorBuilder;
54  import info.magnolia.module.templatingkit.style.Theme;
55  import info.magnolia.module.templatingkit.style.ThemeImpl;
56  import info.magnolia.module.templatingkit.templates.AbstractSTKTemplateModel;
57  import info.magnolia.module.templatingkit.templates.category.TemplateCategory;
58  import info.magnolia.module.templatingkit.templates.category.TemplateCategoryUtil;
59  import info.magnolia.rendering.template.TemplateDefinition;
60  import info.magnolia.repository.RepositoryConstants;
61  import info.magnolia.templating.functions.TemplatingFunctions;
62  
63  import java.util.ArrayList;
64  import java.util.List;
65  import java.util.Set;
66  
67  import javax.inject.Inject;
68  import javax.inject.Provider;
69  import javax.inject.Singleton;
70  import javax.jcr.Node;
71  import javax.jcr.RepositoryException;
72  
73  import org.apache.commons.lang.StringUtils;
74  import org.apache.commons.lang.mutable.MutableInt;
75  import org.slf4j.Logger;
76  import org.slf4j.LoggerFactory;
77  
78  /**
79   * This is an component exposing a couple of methods useful for STK templates.
80   * It's exposed in templates as "stkfn".
81   */
82  @Singleton
83  public class STKTemplatingFunctions {
84  
85      private static Logger log = LoggerFactory.getLogger(STKTemplatingFunctions.class);
86  
87      public static final String SITEROOT_TEMPLATE_CATEGORY = TemplateCategory.HOME;
88  
89      protected static final String TITLE_PROP_NAME = "title";
90      protected static final String SITE_TITLE_PROP_NAME = "siteTitle";
91  
92      // Injected variables
93      private CssSelectorBuilder cssSelectorBuilder;
94      private CategorizationSupport categorizationSupport;
95      private final SiteManager siteManager;
96      private final TemplatingFunctions templatingFunctions;
97      private final Provider<STKModule> moduleProvider;
98      private final DamTemplatingFunctions damFunctions;
99      private final DamManager damManager;
100 
101     @Inject
102     public STKTemplatingFunctions(TemplatingFunctions templatingFunctions, CssSelectorBuilder cssSelectorBuilder, CategorizationSupport categorizationSupport, SiteManager siteManager,
103             Provider<STKModule> moduleProvider, DamManager damManager, DamTemplatingFunctions damFunctions) {
104         this.templatingFunctions = templatingFunctions;
105         this.cssSelectorBuilder = cssSelectorBuilder;
106         this.categorizationSupport = categorizationSupport;
107         this.siteManager = siteManager;
108         this.moduleProvider = moduleProvider;
109         this.damFunctions = damFunctions;
110         this.damManager = damManager;
111     }
112 
113     /**
114      * Method used to get the SearchPageLink. This Search page Link is defined
115      * on the Main PageInfo/Site configuration (Search Page Path to search
116      * result page). Method is used from a STKComponent and STKArea Model.
117      * 
118      * @param content
119      * : Current Content node.
120      * @return relative path to the defined result page i.e.
121      * '/magnolia-empty-webapp/demo-project/service/search-result.html'
122      */
123     public String searchPageLink(Node content) {
124         String searchResultPageUUID = PropertyUtil.getString(siteRoot(content), "searchUUID");
125         return (StringUtils.isEmpty(searchResultPageUUID)) ? null : templatingFunctions.link(RepositoryConstants.WEBSITE, searchResultPageUUID);
126     }
127 
128     public String searchPageLink(ContentMap content) {
129         return searchPageLink(content.getJCRNode());
130     }
131 
132     /**
133      * Return the Home Site Name = the physical node's object name.
134      */
135     public String homeName(Node content) {
136         return NodeUtil.getName(siteRoot(content));
137     }
138 
139     public String homeName(ContentMap content) {
140         return homeName(content.getJCRNode());
141     }
142 
143     /**
144      * Return the HomeTitle value. Referred by the property 'name' of the root
145      * Node
146      */
147     public String homeTitle(Node content) {
148         return PropertyUtil.getString(siteRoot(content), TITLE_PROP_NAME);
149     }
150 
151     public String homeTitle(ContentMap content) {
152         return homeTitle(content.getJCRNode());
153     }
154 
155     /**
156      * Return the SiteTitle value. Referred by the property 'siteTitle' of the
157      * root Node
158      */
159     public String siteTitle(Node content) {
160         return PropertyUtil.getString(siteRoot(content), SITE_TITLE_PROP_NAME);
161     }
162 
163     public String siteTitle(ContentMap content) {
164         return siteTitle(content.getJCRNode());
165     }
166 
167     /**
168      * Return the home link of this site.
169      */
170     public String homeLink(Node content) {
171         return templatingFunctions.link(siteRoot(content));
172     }
173 
174     public String homeLink(ContentMap content) {
175         return homeLink(content.getJCRNode());
176     }
177 
178     public Site site() {
179         return siteManager.getCurrentSite();
180     }
181 
182     /**
183      * Returns the according {@link Site} of the passed content {@link Node}.
184      * 
185      * @param content
186      * The {@link Node} to determine its {@link Site} from
187      * @return {@link Site} The according {@link Site} of the passed @param
188      * content
189      */
190     public Site site(Node content) {
191         try {
192             return (content == null) ? null : siteManager.getAssignedSite(content);
193         } catch (IllegalStateException e) {
194             log.error("Unable to access assigned site ({}) until following issue is resolved: {}", content.toString(), e.getMessage());
195             return null;
196         }
197     }
198 
199     // TODO dlipp/had: is that a good place to keep that code - you might have
200     // to access it from non-template related code. Maybe we shouldn't
201     // completely drop STKUtil but
202     // keep it for common stuff.
203     public Theme theme(Site site) {
204         final String theme = site.getTheme().getName();
205         return StringUtils.isBlank(theme) ? new ThemeImpl() : moduleProvider.get().getTheme(theme);
206 
207     }
208 
209     /**
210      * Returns the according {@link Site} of the passed content {@link ContentMap}.
211      * 
212      * @param content
213      * The {@link ContentMap} to determine its {@link Site} from
214      * @return {@link Site} The according {@link Site} of the passed @param
215      * content
216      */
217     public Site site(ContentMap content) {
218         return site(content.getJCRNode());
219     }
220 
221     /**
222      * Returns the site's root {@link Node} of the @param content {@link Node}.
223      * The root {@link Node} is defined as the page {@link Node} having template
224      * category TemplateCategory.HOME. If no ancestor page exists with category
225      * TemplateCategory.HOME, the JCR workspace root is returned.
226      * 
227      * @param content
228      * The {@link Node} to determine its site root
229      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
230      */
231     public Node siteRoot(Node content) {
232         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
233     }
234 
235     /**
236      * Returns the site's root {@link ContentMap} of the @param content {@link ContentMap}. The root {@link ContentMap} is defined as the page {@link Node} having template category TemplateCategory.HOME. If no
237      * ancestor page exists with category TemplateCategory.HOME, the JCR
238      * workspace root is returned.
239      * 
240      * @param content
241      * The {@link ContentMap} to determine its site root
242      * @return {@link ContentMap} The site root {@link ContentMap} of the passed @param
243      * content
244      */
245     public ContentMap siteRoot(ContentMap content) {
246         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
247     }
248 
249     /**
250      * Returns the site's root {@link Node} of the passed @param content {@link Node}. The root {@link Node} is defined as the page {@link Node} with template its category as @param siteRootTemplateCategory. If no
251      * ancestor page exists with category TemplateCategory.HOME, the JCR
252      * workspace root is returned.
253      * 
254      * @param content
255      * The {@link Node} to determine its site root
256      * @param siteRootTemplateCategory
257      * The {@link TemplateCategory} value of the site root to detect.
258      * If null or EMPTY is passed, the default value
259      * SITEROOT_TEMPLATE_CATEGORY is used instead.
260      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
261      */
262     public ContentMap siteRoot(ContentMap content, String siteRootTemplateCategory) {
263         return templatingFunctions.asContentMap(siteRoot(content.getJCRNode(), siteRootTemplateCategory));
264     }
265 
266     /**
267      * Returns the site's root {@link Node} of the passed @param content {@link Node}. The root {@link Node} is defined as the page {@link Node} with template its category as @param siteRootTemplateCategory. If no
268      * ancestor page exists with category TemplateCategory.HOME, the JCR
269      * workspace root is returned.
270      * 
271      * @param content
272      * The {@link Node} to determine its site root
273      * @param siteRootTemplateCategory
274      * The {@link TemplateCategory} value of the site root to detect.
275      * If null or EMPTY is passed, the default value
276      * SITEROOT_TEMPLATE_CATEGORY is used instead.
277      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
278      */
279     public Node siteRoot(Node content, String siteRootTemplateCategory) {
280         if (StringUtils.isEmpty(siteRootTemplateCategory)) {
281             siteRootTemplateCategory = SITEROOT_TEMPLATE_CATEGORY;
282         }
283         try {
284             Node page = this.templatingFunctions.page(content);
285             Node root = TemplateCategoryUtil.findParentWithTemplateCategory(page, siteRootTemplateCategory);
286             return (root == null) ? (Node) page.getAncestor(0) : root;
287         } catch (RepositoryException e) {
288             throw new RuntimeException("Can't access site root.", e);
289         }
290     }
291 
292     public List<ContentMap> ancestorsInSite(ContentMap content) throws RepositoryException {
293         return ancestorsInSite(content, null);
294     }
295 
296     public List<Node> ancestorsInSite(Node content) throws RepositoryException {
297         return ancestorsInSite(content, null);
298     }
299 
300     public List<ContentMap> ancestorsInSite(ContentMap content, String nodeTypeName) throws RepositoryException {
301         return templatingFunctions.asContentMapList(ancestorsInSite(content.getJCRNode(), nodeTypeName));
302     }
303 
304     public List<Node> ancestorsInSite(Node content, String nodeTypeName) throws RepositoryException {
305         List<Node> allAncestors = templatingFunctions.ancestors(content, nodeTypeName);
306         Node siteRoot = siteRoot(content);
307 
308         List<Node> ancestoresInSite = new ArrayList<Node>();
309         for (Node current : allAncestors) {
310             if (current.getDepth() >= siteRoot.getDepth()) {
311                 ancestoresInSite.add(current);
312             }
313         }
314         return ancestoresInSite;
315     }
316 
317     /**
318      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String)}
319      */
320     public String getAssetLink(Node content, String assetProperty) {
321         return getAssetLink(content, assetProperty, ImagingSupport.VARIATION_ORIGINAL);
322     }
323 
324     /**
325      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String)}
326      */
327     public String getAssetLink(ContentMap content, String assetProperty) {
328         return getAssetLink(content.getJCRNode(), assetProperty);
329     }
330 
331     /**
332      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String)
333      * (String)}
334      */
335     public String getAssetLink(ContentMap content, String assetProperty, AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
336         return getAssetLink(content.getJCRNode(), assetProperty, resolveImageVariationName(model));
337     }
338 
339     /**
340      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String, String)}
341      */
342     public String getAssetLink(ContentMap content, String assetProperty, String renditionName) {
343         return getAssetLink(content.getJCRNode(), assetProperty, renditionName);
344     }
345 
346     /**
347      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String, String)}
348      */
349     public String getAssetLink(Node content, String assetProperty, String renditionName) {
350 
351         String assetIdentifier = PropertyUtil.getString(content, assetProperty);
352         if (StringUtils.isNotBlank(assetIdentifier)) {
353             return damFunctions.getAssetLinkForId(assetIdentifier, renditionName);
354         }
355         return null;
356     }
357 
358     public String resolveImageVariationName(AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
359         final String cssSelector = cssSelectorBuilder.createCssSelector(model);
360         try {
361             return theme(site(model.getNode())).getImaging().resolveVariationName(cssSelector);
362         } catch (NoSuchRenditionException e) {
363             log.warn(e.getMessage());
364             return ImagingSupport.VARIATION_ORIGINAL;
365         }
366     }
367 
368     /**
369      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetRendition(Asset, String)}
370      */
371     public Asset getAssetVariation(Asset asset, String renditionName) {
372         return damFunctions.getAssetRendition(asset, renditionName);
373     }
374 
375     /**
376      * FIXME review: should this function be provided by this class? FIXME
377      * I18nContentWrapper --> I18nNodeWrapper
378      */
379     public Node wrap(Node content) {
380         return (content == null) ? null : templatingFunctions.encode(new I18nNodeWrapper(content));
381     }
382 
383     /**
384      * FIXME review: should this function be provided by this class?
385      */
386     public List<ContentMap> getCategories(Node page) {
387         return categorizationSupport.getCategories(page);
388     }
389 
390     /**
391      * FIXME review: should this function be provided by this class?
392      */
393     public String getCategoryLink(Node page, String categoryName) {
394         return categorizationSupport.getCategoryLink(page, categoryName);
395     }
396 
397     /**
398      * FIXME review: should this function be provided by this class?
399      */
400     public List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory) throws RepositoryException {
401         return getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, Integer.MAX_VALUE, null, null);
402     }
403 
404     /**
405      * FIXME review: should this function be provided by this class?
406      */
407     public static List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory, int maxResultSize, String andClause, String orderBy)
408             throws RepositoryException {
409         return TemplateCategoryUtil.getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, maxResultSize, andClause, orderBy);
410     }
411 
412     /**
413      * FIXME review: should this function be provided by this class?
414      */
415     public Node getNearestContentByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory, Node current) throws RepositoryException {
416         return TemplateCategoryUtil.getNearestContentByTemplateCategorySubCategory(siteRoot, category, subCategory, current);
417     }
418 
419     /**
420      * FIXME review: should this function be provided by this class?
421      */
422     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName) throws RepositoryException {
423         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName);
424     }
425 
426     /**
427      * FIXME review: should this function be provided by this class?
428      */
429     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName, int maxResultSize, String andClause, String orderByClause) throws RepositoryException {
430         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName, maxResultSize, andClause, orderByClause);
431     }
432 
433     /**
434      * FIXME review: should this function be provided by this class?
435      */
436     public static List<Node> getContentListByTemplateNames(Node searchRoot, Set<String> templateIds, int maxResultSize, String andClause, String orderByClause) throws RepositoryException {
437         return TemplateCategoryUtil.getContentListByTemplateNames(searchRoot, templateIds, maxResultSize, andClause, orderByClause);
438     }
439 
440     /**
441      * Returns a {@link Node} object which is referenced by its id, stored in
442      * the @param propertyName.
443      * 
444      * @param content
445      * the node with a property containing the referenced id value.
446      * @param idPropertyName
447      * The name of the property which contains the id of the
448      * referenced {@link Node}.
449      * @param referencedWorkspace
450      * The workspace in which the referenced {@link Node} exists.
451      * @return the referenced {@link Node}
452      */
453     public Node getReferencedContent(Node content, String idPropertyName, String referencedWorkspace) throws RepositoryException {
454         if (content.hasProperty(idPropertyName)) {
455             String identifier = PropertyUtil.getString(content, idPropertyName);
456             return wrap(NodeUtil.getNodeByIdentifier(referencedWorkspace, identifier));
457         }
458         return null;
459     }
460 
461     /**
462      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetRenditionForAssetId(String, String)} Get the Asset specified by 'nodeDataPrefix' property name
463      * linked to 'node' for the specified 'rendition'.
464      * @param node
465      * Content Node
466      * @param nodeDataPrefix
467      * Property name of the node containing the Asset identifier
468      * (composite key containing a reference of the Asset Provider)
469      * @param renditionName
470      * Name of the rendition. If not specified, the default rendition
471      * will be applied.
472      * @return The targeted Asset or null if not found or in case of Exception.
473      */
474     public Asset getAsset(Node node, String assetProperty, String renditionName) {
475         Asset damAsset = null;
476         String providerId = null;
477         try {
478             if (node.hasProperty(assetProperty)) {
479                 String assetNodeIdentifier = node.getProperty(assetProperty).getString();
480                 return damFunctions.getAssetRenditionForAssetId(assetNodeIdentifier, renditionName);
481             }
482         } catch (RepositoryException e) {
483             log.warn("Could not find asset for '{}' at '{}'", new Object[] { assetProperty, NodeUtil.getNodePathIfPossible(node), e });
484             return null;
485         }
486         return null;
487     }
488 
489     /**
490      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetForId(String)}
491      */
492     public Asset getAsset(Node content, String assetProperty) {
493         return getAsset(content, assetProperty, ImagingSupport.VARIATION_ORIGINAL);
494     }
495 
496     /**
497      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetForId(String)}
498      */
499     public Asset getAsset(ContentMap content, String assetProperty) {
500         return getAsset(content.getJCRNode(), assetProperty);
501     }
502 
503     /**
504      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetForId(String)}
505      */
506     public Asset getAsset(ContentMap content, String nodeDataPrefix, String variationName) {
507         return getAsset(content.getJCRNode(), nodeDataPrefix, variationName);
508     }
509 
510     /**
511      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetsFromFolderId(String)}
512      * @return A List of Assets found as element of the folder or an Empty list
513      * in case of exception or if no Asset are found under this folder.
514      */
515     public List<Asset> getAssetsFromFolder(Node node, String assetProperty) {
516 
517         try {
518             if (node.hasProperty(assetProperty)) {
519                 String folderIdentifier = node.getProperty(assetProperty).getString();
520                 return damFunctions.getAssetsFromFolderId(folderIdentifier);
521             }
522         } catch (RepositoryException e) {
523             log.warn("Failed to get assets from folder.", e);
524         }
525 
526         return new ArrayList<Asset>();
527     }
528 
529     /**
530      * Only supports InternalAssets because only InternalAssets support JCR
531      * queries.
532      * 
533      * @deprecated Since 2.5. Use {@link DamManager#getAssetsForFilter(info.magnolia.dam.asset.AssetFilter)}
534      */
535     public List<Asset> getAssetsByQuery(String statement) {
536         try {
537             JcrAssetProvider assetProvider = (JcrAssetProvider) damManager.getAssetProvider(JcrAssetProvider.PROVIDER_ID);
538             return assetProvider.getAssetsByQuery(statement, false);
539         } catch (AssetProviderNotFoundException e) {
540             log.warn("Failed to query for assets.", e);
541             return new ArrayList<Asset>();
542         }
543     }
544 
545     /**
546      * Return a Node List with a maximum number or result less or equal to
547      * maxResults.
548      */
549     public List<Node> cutList(List<Node> itemsList, final int maxResults) {
550         if (itemsList.size() > maxResults) {
551             return itemsList.subList(0, maxResults);
552         }
553         return itemsList;
554     }
555 
556     // TODO : use WordUtils.abbreviate and/or add tests
557     public String abbreviateString(String stringToCut, int size) {
558         return abbreviateString(stringToCut, size, " ...");
559     }
560 
561     public String abbreviateString(String stringToCut, int size, String closureString) {
562         if (stringToCut.length() > size) {
563             int sizeMinusClosure = size - closureString.length();
564             String cutString = StringUtils.left(stringToCut, sizeMinusClosure);
565 
566             // If the character after the cut was a space, no word was cut ->
567             // no cutting on last space needed.
568             String firstCharAfterCut = stringToCut.substring(sizeMinusClosure, sizeMinusClosure + 1);
569             if (!firstCharAfterCut.equals(" ")) {
570                 cutString = StringUtils.substringBeforeLast(cutString, " ");
571             }
572 
573             return cutString + closureString;
574 
575         }
576         return stringToCut;
577     }
578 
579     /**
580      * Increases the context bound counter and returns the value. This is for
581      * instance useful to create unique page wide ids.
582      */
583     public int count(String name) {
584         String attributeName = STKTemplatingFunctions.class.getName() + name;
585         if (!MgnlContext.hasAttribute(attributeName)) {
586             MgnlContext.setAttribute(attributeName, new MutableInt(0));
587         }
588         MutableInt counter = (MutableInt) MgnlContext.getAttribute(attributeName);
589         counter.increment();
590         return counter.intValue();
591     }
592 
593     public String getDivIdAbbreviation(String divID) {
594         return getDivIdAbbreviation(divID, "-");
595     }
596 
597     public String getDivIdAbbreviation(String divID, String delimiter) {
598         String result = StringUtils.EMPTY;
599         if (StringUtils.isNotEmpty(divID) && StringUtils.isNotEmpty(delimiter)) {
600             String[] values = divID.split("\\" + delimiter);
601             for (String value : values) {
602                 result += value.substring(0, 1);
603             }
604         }
605         return result;
606     }
607 
608     /**
609      * Return a property from the metaData of the node.
610      */
611     public String metaDataProperty(Node content, String property) {
612         return templatingFunctions.metaData(content, property);
613     }
614 
615     public String metaDataProperty(ContentMap content, String property) {
616         return templatingFunctions.metaData(content, property);
617     }
618 
619     /**
620      * Return the template id associated with this content.
621      */
622 
623     public String metaDataTemplate(Node content) {
624         try {
625             return NodeTypes.Renderable.getTemplate(content);
626         } catch (RepositoryException e) {
627             return null;
628         }
629     }
630 
631     public String metaDataTemplate(ContentMap content) {
632         return metaDataTemplate(content.getJCRNode());
633     }
634 
635     /**
636      * For test purpose only.
637      */
638     public void setCssSelectorBuilder(CssSelectorBuilder cssSelectorBuilder) {
639         this.cssSelectorBuilder = cssSelectorBuilder;
640     }
641 
642     /**
643      * For test purpose only.
644      */
645     public void setCategorizationSupport(CategorizationSupport categorizationSupport) {
646         this.categorizationSupport = categorizationSupport;
647     }
648 
649 }