View Javadoc
1   /**
2    * This file Copyright (c) 2008-2015 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.api.Asset;
38  import info.magnolia.dam.api.AssetQuery;
39  import info.magnolia.dam.api.AssetRendition;
40  import info.magnolia.dam.api.DamException;
41  import info.magnolia.dam.asset.LegacyAssetWrapper;
42  import info.magnolia.dam.asset.renderer.NoSuchRenditionException;
43  import info.magnolia.dam.templating.functions.DamTemplatingFunctions;
44  import info.magnolia.jcr.util.ContentMap;
45  import info.magnolia.jcr.util.NodeTypes;
46  import info.magnolia.jcr.util.NodeUtil;
47  import info.magnolia.jcr.util.PropertyUtil;
48  import info.magnolia.jcr.wrapper.I18nNodeWrapper;
49  import info.magnolia.module.templatingkit.STKModule;
50  import info.magnolia.module.templatingkit.categorization.CategorizationSupport;
51  import info.magnolia.module.templatingkit.imaging.ImagingSupport;
52  import info.magnolia.module.templatingkit.sites.Site;
53  import info.magnolia.module.templatingkit.sites.SiteManager;
54  import info.magnolia.module.templatingkit.style.CssSelectorBuilder;
55  import info.magnolia.module.templatingkit.style.Theme;
56  import info.magnolia.module.templatingkit.style.ThemeImpl;
57  import info.magnolia.module.templatingkit.templates.AbstractSTKTemplateModel;
58  import info.magnolia.module.templatingkit.templates.category.TemplateCategory;
59  import info.magnolia.module.templatingkit.templates.category.TemplateCategoryUtil;
60  import info.magnolia.rendering.template.TemplateDefinition;
61  import info.magnolia.repository.RepositoryConstants;
62  import info.magnolia.templating.functions.TemplatingFunctions;
63  
64  import java.util.ArrayList;
65  import java.util.List;
66  import java.util.Set;
67  
68  import javax.inject.Inject;
69  import javax.inject.Provider;
70  import javax.inject.Singleton;
71  import javax.jcr.Node;
72  import javax.jcr.RepositoryException;
73  
74  import org.apache.commons.lang.StringUtils;
75  import org.apache.commons.lang.mutable.MutableInt;
76  import org.apache.jackrabbit.JcrConstants;
77  import org.apache.jackrabbit.util.Text;
78  import org.slf4j.Logger;
79  import org.slf4j.LoggerFactory;
80  
81  /**
82   * This is an component exposing a couple of methods useful for STK templates.
83   * It's exposed in templates as "stkfn".
84   */
85  @Singleton
86  public class STKTemplatingFunctions {
87  
88      private static Logger log = LoggerFactory.getLogger(STKTemplatingFunctions.class);
89  
90      public static final String SITEROOT_TEMPLATE_CATEGORY = TemplateCategory.HOME;
91  
92      protected static final String TITLE_PROP_NAME = "title";
93      protected static final String SITE_TITLE_PROP_NAME = "siteTitle";
94  
95      // Injected variables
96      private CssSelectorBuilder cssSelectorBuilder;
97      private CategorizationSupport categorizationSupport;
98      private final SiteManager siteManager;
99      private final TemplatingFunctions templatingFunctions;
100     private final Provider<STKModule> moduleProvider;
101     private final DamTemplatingFunctions damTemplatingFunctions;
102 
103     @Inject
104     public STKTemplatingFunctions(TemplatingFunctions templatingFunctions, CssSelectorBuilder cssSelectorBuilder, CategorizationSupport categorizationSupport, SiteManager siteManager,
105             Provider<STKModule> moduleProvider, DamTemplatingFunctions damTemplatingFunctions) {
106         this.templatingFunctions = templatingFunctions;
107         this.cssSelectorBuilder = cssSelectorBuilder;
108         this.categorizationSupport = categorizationSupport;
109         this.siteManager = siteManager;
110         this.moduleProvider = moduleProvider;
111         this.damTemplatingFunctions = damTemplatingFunctions;
112     }
113 
114     /**
115      * Method used to get the SearchPageLink. This Search page Link is defined
116      * on the Main PageInfo/Site configuration (Search Page Path to search
117      * result page). Method is used from a STKComponent and STKArea Model.
118      *
119      * @param content
120      * : Current Content node.
121      * @return relative path to the defined result page i.e.
122      * '/magnolia-empty-webapp/demo-project/service/search-result.html'
123      */
124     public String searchPageLink(Node content) {
125         String searchResultPageUUID = PropertyUtil.getString(siteRoot(content), "searchUUID");
126         return (StringUtils.isEmpty(searchResultPageUUID)) ? null : templatingFunctions.link(RepositoryConstants.WEBSITE, searchResultPageUUID);
127     }
128 
129     public String searchPageLink(ContentMap content) {
130         return searchPageLink(content.getJCRNode());
131     }
132 
133     /**
134      * Return the Home Site Name = the physical node's object name.
135      */
136     public String homeName(Node content) {
137         return NodeUtil.getName(siteRoot(content));
138     }
139 
140     public String homeName(ContentMap content) {
141         return homeName(content.getJCRNode());
142     }
143 
144     /**
145      * Return the HomeTitle value. Referred by the property 'name' of the root
146      * Node
147      */
148     public String homeTitle(Node content) {
149         return PropertyUtil.getString(siteRoot(content), TITLE_PROP_NAME);
150     }
151 
152     public String homeTitle(ContentMap content) {
153         return homeTitle(content.getJCRNode());
154     }
155 
156     /**
157      * Return the SiteTitle value. Referred by the property 'siteTitle' of the
158      * root Node
159      */
160     public String siteTitle(Node content) {
161         return PropertyUtil.getString(siteRoot(content), SITE_TITLE_PROP_NAME);
162     }
163 
164     public String siteTitle(ContentMap content) {
165         return siteTitle(content.getJCRNode());
166     }
167 
168     /**
169      * Return the home link of this site.
170      */
171     public String homeLink(Node content) {
172         return templatingFunctions.link(siteRoot(content));
173     }
174 
175     public String homeLink(ContentMap content) {
176         return homeLink(content.getJCRNode());
177     }
178 
179     public Site site() {
180         return siteManager.getCurrentSite();
181     }
182 
183     /**
184      * Returns the according {@link Site} of the passed content {@link Node}.
185      *
186      * @param content
187      * The {@link Node} to determine its {@link Site} from
188      * @return {@link Site} The according {@link Site} of the passed @param
189      * content
190      */
191     public Site site(Node content) {
192         try {
193             return (content == null) ? null : siteManager.getAssignedSite(content);
194         } catch (IllegalStateException e) {
195             log.error("Unable to access assigned site ({}) until following issue is resolved: {}", content.toString(), e.getMessage());
196             return null;
197         }
198     }
199 
200     // TODO dlipp/had: is that a good place to keep that code - you might have
201     // to access it from non-template related code. Maybe we shouldn't
202     // completely drop STKUtil but
203     // keep it for common stuff.
204     public Theme theme(Site site) {
205         final String theme = site.getTheme().getName();
206         return StringUtils.isBlank(theme) ? new ThemeImpl() : moduleProvider.get().getTheme(theme);
207 
208     }
209 
210     /**
211      * Returns the according {@link Site} of the passed content {@link ContentMap}.
212      *
213      * @param content
214      * The {@link ContentMap} to determine its {@link Site} from
215      * @return {@link Site} The according {@link Site} of the passed @param
216      * content
217      */
218     public Site site(ContentMap content) {
219         return site(content.getJCRNode());
220     }
221 
222     /**
223      * Returns the site's root {@link Node} of the @param content {@link Node}.
224      * The root {@link Node} is defined as the page {@link Node} having template
225      * category TemplateCategory.HOME. If no ancestor page exists with category
226      * TemplateCategory.HOME, the JCR workspace root is returned.
227      *
228      * @param content
229      * The {@link Node} to determine its site root
230      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
231      */
232     public Node siteRoot(Node content) {
233         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
234     }
235 
236     /**
237      * 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
238      * ancestor page exists with category TemplateCategory.HOME, the JCR
239      * workspace root is returned.
240      *
241      * @param content
242      * The {@link ContentMap} to determine its site root
243      * @return {@link ContentMap} The site root {@link ContentMap} of the passed @param
244      * content
245      */
246     public ContentMap siteRoot(ContentMap content) {
247         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
248     }
249 
250     /**
251      * 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
252      * ancestor page exists with category TemplateCategory.HOME, the JCR
253      * workspace root is returned.
254      *
255      * @param content
256      * The {@link Node} to determine its site root
257      * @param siteRootTemplateCategory
258      * The {@link TemplateCategory} value of the site root to detect.
259      * If null or EMPTY is passed, the default value
260      * SITEROOT_TEMPLATE_CATEGORY is used instead.
261      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
262      */
263     public ContentMap siteRoot(ContentMap content, String siteRootTemplateCategory) {
264         return templatingFunctions.asContentMap(siteRoot(content.getJCRNode(), siteRootTemplateCategory));
265     }
266 
267     /**
268      * 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
269      * ancestor page exists with category TemplateCategory.HOME, the JCR
270      * workspace root is returned.
271      *
272      * @param content
273      * The {@link Node} to determine its site root
274      * @param siteRootTemplateCategory
275      * The {@link TemplateCategory} value of the site root to detect.
276      * If null or EMPTY is passed, the default value
277      * SITEROOT_TEMPLATE_CATEGORY is used instead.
278      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
279      */
280     public Node siteRoot(Node content, String siteRootTemplateCategory) {
281         if (StringUtils.isEmpty(siteRootTemplateCategory)) {
282             siteRootTemplateCategory = SITEROOT_TEMPLATE_CATEGORY;
283         }
284         try {
285             Node page = this.templatingFunctions.page(content);
286             Node root = TemplateCategoryUtil.findParentWithTemplateCategory(page, siteRootTemplateCategory);
287             return (root == null) ? (Node) page.getAncestor(0) : root;
288         } catch (RepositoryException e) {
289             throw new RuntimeException("Can't access site root.", e);
290         }
291     }
292 
293     public List<ContentMap> ancestorsInSite(ContentMap content) throws RepositoryException {
294         return ancestorsInSite(content, null);
295     }
296 
297     public List<Node> ancestorsInSite(Node content) throws RepositoryException {
298         return ancestorsInSite(content, null);
299     }
300 
301     public List<ContentMap> ancestorsInSite(ContentMap content, String nodeTypeName) throws RepositoryException {
302         return templatingFunctions.asContentMapList(ancestorsInSite(content.getJCRNode(), nodeTypeName));
303     }
304 
305     public List<Node> ancestorsInSite(Node content, String nodeTypeName) throws RepositoryException {
306         List<Node> allAncestors = templatingFunctions.ancestors(content, nodeTypeName);
307         Node siteRoot = siteRoot(content);
308 
309         List<Node> ancestoresInSite = new ArrayList<Node>();
310         for (Node current : allAncestors) {
311             if (current.getDepth() >= siteRoot.getDepth()) {
312                 ancestoresInSite.add(current);
313             }
314         }
315         return ancestoresInSite;
316     }
317 
318     /**
319      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String)}
320      */
321     @Deprecated
322     public String getAssetLink(Node content, String assetProperty) {
323         return getAssetLink(content, assetProperty, ImagingSupport.VARIATION_ORIGINAL);
324     }
325 
326     /**
327      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String)}
328      */
329     @Deprecated
330     public String getAssetLink(ContentMap content, String assetProperty) {
331         return getAssetLink(content.getJCRNode(), assetProperty);
332     }
333 
334     /**
335      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String)
336      * (String)}
337      */
338     @Deprecated
339     public String getAssetLink(ContentMap content, String assetProperty, AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
340         return getAssetLink(content.getJCRNode(), assetProperty, resolveImageVariationName(model));
341     }
342 
343     /**
344      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String, String)}
345      */
346     @Deprecated
347     public String getAssetLink(ContentMap content, String assetProperty, String renditionName) {
348         return getAssetLink(content.getJCRNode(), assetProperty, renditionName);
349     }
350 
351     /**
352      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetLinkForId(String, String)}
353      */
354     @Deprecated
355     public String getAssetLink(Node content, String assetProperty, String renditionName) {
356 
357         String assetIdentifier = PropertyUtil.getString(content, assetProperty);
358         if (StringUtils.isNotBlank(assetIdentifier)) {
359             return damTemplatingFunctions.getAssetLinkForId(assetIdentifier, renditionName);
360         }
361         return null;
362     }
363 
364     /**
365      * Resolves an image variation name based on parameter values found in the model. If none is found, defaults to {@link ImagingSupport#VARIATION_ORIGINAL}.
366      * 
367      * @see CssSelectorBuilder
368      * @see info.magnolia.module.templatingkit.templates.components.ImageModel
369      */
370     public String resolveImageVariationName(AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
371         if (model == null) {
372             return ImagingSupport.VARIATION_ORIGINAL;
373         }
374         TemplateDefinition definition = model.getDefinition();
375         String variationName = (String) definition.getParameters().get("imageVariation");
376 
377         if (StringUtils.isNotEmpty(variationName)) {
378             return variationName;
379         }
380 
381         String cssSelector = cssSelectorBuilder.createCssSelector(model);
382         String imageClass = (String) definition.getParameters().get("imageClass");
383         if (StringUtils.isNotEmpty(imageClass)) {
384             cssSelector = cssSelector + " ." + imageClass.replace(" ", ".");
385         }
386 
387         try {
388             return getImagingSupport().resolveVariationName(cssSelector);
389         } catch (NoSuchRenditionException e) {
390             log.warn(e.getMessage());
391             return ImagingSupport.VARIATION_ORIGINAL;
392         }
393     }
394 
395     /**
396      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetRendition(Asset, String)}
397      * @see info.magnolia.module.templatingkit.dam.STKAssetRenderer . This implementation of AssetRenderer return a STKAssetRendition that extends {@link info.magnolia.dam.api.AssetDecorator}.
398      */
399     @Deprecated
400     public AssetRendition getAssetVariation(Asset asset, String renditionName) {
401         return damTemplatingFunctions.getRendition(asset.getItemKey().asString(), renditionName);
402     }
403 
404     /**
405      * FIXME review: should this function be provided by this class? FIXME
406      * I18nContentWrapper --> I18nNodeWrapper
407      */
408     public Node wrap(Node content) {
409         return (content == null) ? null : templatingFunctions.encode(new I18nNodeWrapper(content));
410     }
411 
412     /**
413      * FIXME review: should this function be provided by this class?
414      */
415     public List<ContentMap> getCategories(Node page) {
416         return categorizationSupport.getCategories(page);
417     }
418 
419     /**
420      * FIXME review: should this function be provided by this class?
421      */
422     public String getCategoryLink(Node page, String categoryName) {
423         return categorizationSupport.getCategoryLink(page, categoryName);
424     }
425 
426     /**
427      * FIXME review: should this function be provided by this class?
428      */
429     public List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory) throws RepositoryException {
430         return getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, Integer.MAX_VALUE, null, null);
431     }
432 
433     /**
434      * FIXME review: should this function be provided by this class?
435      */
436     public static List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory, int maxResultSize, String andClause, String orderBy)
437             throws RepositoryException {
438         return TemplateCategoryUtil.getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, maxResultSize, andClause, orderBy);
439     }
440 
441     /**
442      * FIXME review: should this function be provided by this class?
443      */
444     public Node getNearestContentByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory, Node current) throws RepositoryException {
445         return TemplateCategoryUtil.getNearestContentByTemplateCategorySubCategory(siteRoot, category, subCategory, current);
446     }
447 
448     /**
449      * FIXME review: should this function be provided by this class?
450      */
451     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName) throws RepositoryException {
452         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName);
453     }
454 
455     /**
456      * FIXME review: should this function be provided by this class?
457      */
458     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName, int maxResultSize, String andClause, String orderByClause) throws RepositoryException {
459         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName, maxResultSize, andClause, orderByClause);
460     }
461 
462     /**
463      * FIXME review: should this function be provided by this class?
464      */
465     public static List<Node> getContentListByTemplateNames(Node searchRoot, Set<String> templateIds, int maxResultSize, String andClause, String orderByClause) throws RepositoryException {
466         return TemplateCategoryUtil.getContentListByTemplateNames(searchRoot, templateIds, maxResultSize, andClause, orderByClause);
467     }
468 
469     /**
470      * Returns a {@link Node} object which is referenced by its id, stored in
471      * the @param propertyName.
472      *
473      * @param content
474      * the node with a property containing the referenced id value.
475      * @param idPropertyName
476      * The name of the property which contains the id of the
477      * referenced {@link Node}.
478      * @param referencedWorkspace
479      * The workspace in which the referenced {@link Node} exists.
480      * @return the referenced {@link Node}
481      */
482     public Node getReferencedContent(Node content, String idPropertyName, String referencedWorkspace) throws RepositoryException {
483         if (content.hasProperty(idPropertyName)) {
484             String identifier = PropertyUtil.getString(content, idPropertyName);
485             return wrap(NodeUtil.getNodeByIdentifier(referencedWorkspace, identifier));
486         }
487         return null;
488     }
489 
490     /**
491      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetRenditionForAssetId(String, String)} Get the Asset specified by 'nodeDataPrefix' property name
492      * linked to 'node' for the specified 'rendition'.
493      * @param node
494      * Content Node
495      * @param nodeDataPrefix
496      * Property name of the node containing the Asset identifier
497      * (composite key containing a reference of the Asset Provider)
498      * @param renditionName
499      * Name of the rendition. If not specified, the default rendition
500      * will be applied.
501      * @return The targeted Asset or null if not found or in case of Exception.
502      */
503     public Asset getAsset(Node node, String assetProperty, String renditionName) {
504         try {
505             if (node.hasProperty(assetProperty)) {
506                 String assetNodeIdentifier = node.getProperty(assetProperty).getString();
507                 return damTemplatingFunctions.getAssetRenditionForAssetId(assetNodeIdentifier, renditionName);
508             }
509         } catch (RepositoryException e) {
510             log.warn("Could not find asset for '{}' at '{}'", new Object[] { assetProperty, NodeUtil.getNodePathIfPossible(node), e });
511             return null;
512         }
513 
514         return null;
515     }
516 
517     /**
518      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAsset(String)}
519      */
520     public Asset getAsset(Node content, String assetProperty) {
521         try {
522             if (content.hasProperty(assetProperty)) {
523                 String assetNodeIdentifier = content.getProperty(assetProperty).getString();
524                 return new LegacyAssetWrapper(damTemplatingFunctions.getAssetForId(assetNodeIdentifier));
525             }
526         } catch (RepositoryException e) {
527             log.warn("Could not find asset for '{}' at '{}'", new Object[] { assetProperty, NodeUtil.getNodePathIfPossible(content), e });
528         }
529         return null;
530     }
531 
532     /**
533      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAsset(String)}
534      */
535     public Asset getAsset(ContentMap content, String assetProperty) {
536         return getAsset(content.getJCRNode(), assetProperty);
537     }
538 
539     /**
540      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetRenditionForAssetId(String, String)}
541      */
542     public Asset getAsset(ContentMap content, String nodeDataPrefix, String variationName) {
543         return getAsset(content.getJCRNode(), nodeDataPrefix, variationName);
544     }
545 
546     /**
547      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetsFromFolderId(String)}
548      * @return A List of Assets found as element of the folder or an Empty list
549      * in case of exception or if no Asset are found under this folder.
550      */
551     public List<Asset> getAssetsFromFolder(Node node, String assetProperty) {
552 
553         try {
554             if (node.hasProperty(assetProperty)) {
555                 String folderIdentifier = node.getProperty(assetProperty).getString();
556                 final List<Asset> assets = damTemplatingFunctions.getAssetsFromFolderId(folderIdentifier);
557                 for (int i = 0; i < assets.size(); i++) {
558                     assets.set(i, new LegacyAssetWrapper(assets.get(i)));
559                 }
560                 return assets;
561             }
562         } catch (RepositoryException e) {
563             log.warn("Failed to get assets from folder.", e);
564         }
565 
566         return new ArrayList<Asset>();
567     }
568 
569     /**
570      * Only supports InternalAssets because only InternalAssets support JCR
571      * queries.
572      *
573      * @deprecated Since 2.5. Use {@link DamTemplatingFunctions#getAssetsByQuery(String, boolean)}
574      */
575     @Deprecated
576     public List<Asset> getAssetsByQuery(String statement) {
577         AssetQuery assetQuery = new AssetQuery.Builder().withAdditionalQueryStatement(statement).build();
578         final List<Asset> assets = damTemplatingFunctions.getAssetsForFilter(assetQuery);
579         for (int i = 0; i < assets.size(); i++) {
580             assets.set(i, new LegacyAssetWrapper(assets.get(i)));
581         }
582         return assets;
583     }
584 
585     /**
586      * Return a Node List with a maximum number or result less or equal to
587      * maxResults.
588      */
589     public List<Node> cutList(List<Node> itemsList, final int maxResults) {
590         if (itemsList.size() > maxResults) {
591             return itemsList.subList(0, maxResults);
592         }
593         return itemsList;
594     }
595 
596     // TODO : use WordUtils.abbreviate and/or add tests
597     public String abbreviateString(String stringToCut, int size) {
598         return abbreviateString(stringToCut, size, " ...");
599     }
600 
601     public String abbreviateString(String stringToCut, int size, String closureString) {
602         if (stringToCut.length() > size) {
603             int sizeMinusClosure = size - closureString.length();
604             String cutString = StringUtils.left(stringToCut, sizeMinusClosure);
605 
606             // If the character after the cut was a space, no word was cut ->
607             // no cutting on last space needed.
608             String firstCharAfterCut = stringToCut.substring(sizeMinusClosure, sizeMinusClosure + 1);
609             if (!firstCharAfterCut.equals(" ")) {
610                 cutString = StringUtils.substringBeforeLast(cutString, " ");
611             }
612 
613             return cutString + closureString;
614 
615         }
616         return stringToCut;
617     }
618 
619     /**
620      * Increases the context bound counter and returns the value. This is for
621      * instance useful to create unique page wide ids.
622      */
623     public int count(String name) {
624         String attributeName = STKTemplatingFunctions.class.getName() + name;
625         if (!MgnlContext.hasAttribute(attributeName)) {
626             MgnlContext.setAttribute(attributeName, new MutableInt(0));
627         }
628         MutableInt counter = (MutableInt) MgnlContext.getAttribute(attributeName);
629         counter.increment();
630         return counter.intValue();
631     }
632 
633     public String getDivIdAbbreviation(String divID) {
634         return getDivIdAbbreviation(divID, "-");
635     }
636 
637     public String getDivIdAbbreviation(String divID, String delimiter) {
638         String result = StringUtils.EMPTY;
639         if (StringUtils.isNotEmpty(divID) && StringUtils.isNotEmpty(delimiter)) {
640             String[] values = divID.split("\\" + delimiter);
641             for (String value : values) {
642                 result += value.substring(0, 1);
643             }
644         }
645         return result;
646     }
647 
648     /**
649      * Return a property from the metaData of the node.
650      */
651     public String metaDataProperty(Node content, String property) {
652         return templatingFunctions.metaData(content, property);
653     }
654 
655     public String metaDataProperty(ContentMap content, String property) {
656         return templatingFunctions.metaData(content, property);
657     }
658 
659     /**
660      * Return the template id associated with this content.
661      */
662 
663     public String metaDataTemplate(Node content) {
664         try {
665             return NodeTypes.Renderable.getTemplate(content);
666         } catch (RepositoryException e) {
667             return null;
668         }
669     }
670 
671     public String metaDataTemplate(ContentMap content) {
672         return metaDataTemplate(content.getJCRNode());
673     }
674 
675     /**
676      * For test purpose only.
677      */
678     public void setCssSelectorBuilder(CssSelectorBuilder cssSelectorBuilder) {
679         this.cssSelectorBuilder = cssSelectorBuilder;
680     }
681 
682     /**
683      * For test purpose only.
684      */
685     public void setCategorizationSupport(CategorizationSupport categorizationSupport) {
686         this.categorizationSupport = categorizationSupport;
687     }
688 
689 
690     /**
691      * @see #getImageVariationLinkFromBinary(Node, String)
692      */
693     public String getImageVariationLinkFromBinary(ContentMap binaryContent, AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
694         if (binaryContent == null) {
695             return null;
696         }
697         return getImageVariationLinkFromBinary(binaryContent.getJCRNode(), model);
698     }
699 
700     /**
701      * @see #getImageVariationLinkFromBinary(Node, String)
702      */
703     public String getImageVariationLinkFromBinary(Node binaryContent, AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
704         return getImageVariationLinkFromBinary(binaryContent, resolveImageVariationName(model));
705     }
706 
707     /**
708      * @see #getImageVariationLinkFromBinary(Node, String)
709      */
710     public String getImageVariationLinkFromBinary(ContentMap binaryContent, String rendition) {
711         if (binaryContent == null) {
712             return null;
713         }
714         return getImageVariationLinkFromBinary(binaryContent.getJCRNode(), rendition);
715     }
716 
717     /**
718      * @return the link to an image variation for non asset nodes containing a binary
719      */
720     public String getImageVariationLinkFromBinary(Node binaryContent, String rendition) {
721         if (binaryContent == null || rendition == null) {
722             return null;
723         }
724 
725         try {
726             String link = getImagingSupport().createLink(binaryContent.getProperty(JcrConstants.JCR_DATA), rendition);
727             // URL encode the path because the user name node (=email address for ResearchAnt) in the path may contain illegal
728             // URL characters (like '+'). Fix for: https://jira.info.nl/browse/TOKUE-319
729             // for background info see: http://wiki.apache.org/jackrabbit/EncodingAndEscaping
730             return Text.escapePath(link);
731         } catch (Exception e) {
732             throw new DamException("AssetRendition exception", e) {
733             };
734         }
735     }
736 
737     protected ImagingSupport getImagingSupport() {
738         return theme(site()).getImaging();
739     }
740 
741 }