View Javadoc

1   /**
2    * This file Copyright (c) 2008-2011 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.cms.util.ContentUtil;
37  import info.magnolia.context.MgnlContext;
38  import info.magnolia.jcr.RuntimeRepositoryException;
39  import info.magnolia.jcr.util.ContentMap;
40  import info.magnolia.jcr.util.NodeUtil;
41  import info.magnolia.jcr.util.PropertyUtil;
42  import info.magnolia.jcr.wrapper.I18nNodeWrapper;
43  import info.magnolia.module.templatingkit.STKModule;
44  import info.magnolia.module.templatingkit.categorization.CategorizationSupport;
45  import info.magnolia.module.templatingkit.dam.Asset;
46  import info.magnolia.module.templatingkit.dam.AssetNotFoundException;
47  import info.magnolia.module.templatingkit.dam.DAMException;
48  import info.magnolia.module.templatingkit.dam.DAMSupport;
49  import info.magnolia.module.templatingkit.dam.NoSuchVariationException;
50  import info.magnolia.module.templatingkit.sites.Site;
51  import info.magnolia.module.templatingkit.sites.SiteManager;
52  import info.magnolia.module.templatingkit.style.CssSelectorBuilder;
53  import info.magnolia.module.templatingkit.style.Theme;
54  import info.magnolia.module.templatingkit.style.ThemeImpl;
55  import info.magnolia.module.templatingkit.templates.AbstractSTKTemplateModel;
56  import info.magnolia.module.templatingkit.templates.category.TemplateCategory;
57  import info.magnolia.module.templatingkit.templates.category.TemplateCategoryUtil;
58  import info.magnolia.rendering.template.TemplateDefinition;
59  import info.magnolia.repository.RepositoryConstants;
60  import info.magnolia.templating.functions.TemplatingFunctions;
61  
62  import java.util.ArrayList;
63  import java.util.List;
64  import java.util.Set;
65  
66  import javax.inject.Inject;
67  import javax.inject.Provider;
68  import javax.inject.Singleton;
69  import javax.jcr.Node;
70  import javax.jcr.Property;
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   * @version $Id$
83   */
84  @Singleton
85  public class STKTemplatingFunctions{
86  
87      private static Logger log = LoggerFactory.getLogger(STKTemplatingFunctions.class);
88  
89      public static final String SITEROOT_TEMPLATE_CATEGORY = TemplateCategory.HOME;
90  
91      protected static final String TITLE_PROP_NAME = "title";
92      protected static final String SITE_TITLE_PROP_NAME = "siteTitle";
93      protected static final String METADATA_NODE_NAME = "MetaData";
94      protected static final String TEMPLATE_PROP_NAME = "mgnl:template";
95  
96      //Injected variables
97      private CssSelectorBuilder cssSelectorBuilder;
98      private CategorizationSupport categorizationSupport;
99      private SiteManager siteManager;
100     private TemplatingFunctions templatingFunctions;
101     private Provider<STKModule> moduleProvider;
102 
103     @Inject
104     public STKTemplatingFunctions(TemplatingFunctions templatingFunctions, CssSelectorBuilder cssSelectorBuilder, CategorizationSupport categorizationSupport, SiteManager siteManager, Provider<STKModule> moduleProvider) {
105         this.templatingFunctions = templatingFunctions;
106         this.cssSelectorBuilder = cssSelectorBuilder;
107         this.categorizationSupport = categorizationSupport;
108         this.siteManager = siteManager;
109         this.moduleProvider = moduleProvider;
110     }
111 
112     /**
113      * Method used to get the SearchPageLink.
114      *   This Search page Link is defined on the Main PageInfo/Site configuration (Search Page
115      * Path to search result page).
116      *   Method is used from a STKComponent and STKArea Model.
117      * @param content: Current Content node.
118      * @return relative path to the defined result page i.e.
119      *   '/magnolia-empty-webapp/demo-project/service/search-result.html'
120      */
121     public String searchPageLink(Node content) {
122         String searchResultPageUUID = PropertyUtil.getString(siteRoot(content), "searchUUID");
123         return (StringUtils.isEmpty(searchResultPageUUID)) ? null : templatingFunctions.link(RepositoryConstants.WEBSITE, searchResultPageUUID);
124     }
125     public String searchPageLink(ContentMap content) {
126         return searchPageLink(content.getJCRNode());
127     }
128 
129     /**
130      * Return the Home Site Name = the physical node's object name.
131      */
132     public String homeName(Node content) {
133         return NodeUtil.getName(siteRoot(content));
134     }
135     public String homeName(ContentMap content) {
136         return homeName(content.getJCRNode());
137     }
138 
139     /**
140      * Return the HomeTitle value.
141      * Referred by the  property 'name' of the root Node
142      */
143     public String homeTitle(Node content) {
144         return PropertyUtil.getString(siteRoot(content), TITLE_PROP_NAME );
145     }
146     public String homeTitle(ContentMap content) {
147         return homeTitle(content.getJCRNode());
148     }
149 
150     /**
151      * Return the SiteTitle value.
152      * Referred by the  property 'siteTitle' of the root Node
153      */
154     public String siteTitle(Node content) {
155         return PropertyUtil.getString(siteRoot(content), SITE_TITLE_PROP_NAME );
156     }
157     public String siteTitle(ContentMap content) {
158         return siteTitle(content.getJCRNode());
159     }
160 
161     /**
162      * Return the home link of this site.
163      */
164     public String homeLink(Node content) {
165         return templatingFunctions.link(siteRoot(content));
166     }
167 
168     public String homeLink(ContentMap content) {
169         return homeLink(content.getJCRNode());
170     }
171 
172     public Site site() {
173         return siteManager.getCurrentSite();
174     }
175 
176     /**
177      * Returns the according {@link Site} of the passed content {@link Node}.
178      *
179      * @param content The {@link Node} to determine its {@link Site} from
180      * @return {@link Site} The according {@link Site} of the passed @param content
181      */
182     public Site site(Node content){
183         try {
184             return (content == null) ? null : siteManager.getAssignedSite(content);
185         } catch (IllegalStateException e) {
186             log.error("Unable to access assigned site ({}) until following issue is resolved: {}", content.toString(), e.getMessage());
187             return null;
188         }
189     }
190 
191     // TODO dlipp/had: is that a good place to keep that code - you might have to access it from non-template related code. Maybe we shouldn't completely drop STKUtil but
192     // keep it for common stuff.
193     public Theme theme(Site site) {
194         final String theme = site.getTheme().getName();
195         return StringUtils.isBlank(theme) ? new ThemeImpl() : moduleProvider.get().getTheme(theme);
196 
197     }
198     /**
199      * Returns the according {@link Site} of the passed content {@link ContentMap}.
200      *
201      * @param content The {@link ContentMap} to determine its {@link Site} from
202      * @return {@link Site} The according {@link Site} of the passed @param content
203      */
204     public Site site(ContentMap content){
205         return site(content.getJCRNode());
206     }
207 
208     /**
209      * Returns the site's root {@link Node} of the @param content {@link Node}.
210      * The root {@link Node} is defined as the page {@link Node} having template category TemplateCategory.HOME.
211      * If no ancestor page exists with category TemplateCategory.HOME, the JCR workspace root is returned.
212      *
213      * @param content The {@link Node} to determine its site root
214      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
215      */
216     public Node siteRoot(Node content) {
217         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
218     }
219 
220     /**
221      * Returns the site's root {@link ContentMap} of the @param content {@link ContentMap}.
222      * The root {@link ContentMap} is defined as the page {@link Node} having template category TemplateCategory.HOME.
223      * If no ancestor page exists with category TemplateCategory.HOME, the JCR workspace root is returned.
224      *
225      * @param content The {@link ContentMap} to determine its site root
226      * @return {@link ContentMap} The site root {@link ContentMap} of the passed @param content
227      */
228     public ContentMap siteRoot(ContentMap content) {
229         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
230     }
231 
232     /**
233      * Returns the site's root {@link Node} of the passed @param content {@link Node}.
234      * The root {@link Node} is defined as the page {@link Node} with template its category as @param siteRootTemplateCategory.
235      * If no ancestor page exists with category TemplateCategory.HOME, the JCR workspace root is returned.
236      *
237      * @param content The {@link Node} to determine its site root
238      * @param siteRootTemplateCategory The {@link TemplateCategory} value of the site root to detect.
239      *        If null or EMPTY is passed, the default value SITEROOT_TEMPLATE_CATEGORY is used instead.
240      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
241      */
242     public ContentMap siteRoot(ContentMap content, String siteRootTemplateCategory) {
243         return templatingFunctions.asContentMap(siteRoot(content.getJCRNode(), siteRootTemplateCategory));
244     }
245 
246     /**
247      *
248      * Returns the site's root {@link Node} of the passed @param content {@link Node}.
249      * The root {@link Node} is defined as the page {@link Node} with template its category as @param siteRootTemplateCategory.
250      * If no ancestor page exists with category TemplateCategory.HOME, the JCR workspace root is returned.
251      *
252      * @param content The {@link Node} to determine its site root
253      * @param siteRootTemplateCategory The {@link TemplateCategory} value of the site root to detect.
254      *        If null or EMPTY is passed, the default value SITEROOT_TEMPLATE_CATEGORY is used instead.
255      * @return {@link Node} The site root {@link Node} of the passed content {@link Node}
256      */
257     public Node siteRoot(Node content, String siteRootTemplateCategory) {
258         if(StringUtils.isEmpty(siteRootTemplateCategory)){
259             siteRootTemplateCategory = SITEROOT_TEMPLATE_CATEGORY;
260         }
261         try {
262             Node page = this.templatingFunctions.page(content);
263             Node root = TemplateCategoryUtil.findParentWithTemplateCategory(page, siteRootTemplateCategory);
264             return (root == null) ? (Node)page.getAncestor(0) : root;
265         }
266         catch (RepositoryException e) {
267             throw new RuntimeException("Can't access site root.", e);
268         }
269     }
270 
271     public List<ContentMap> ancestorsInSite(ContentMap content) throws RepositoryException {
272         return ancestorsInSite(content, null);
273     }
274 
275     public List<Node> ancestorsInSite(Node content) throws RepositoryException {
276         return ancestorsInSite(content, null);
277     }
278 
279     public List<ContentMap> ancestorsInSite(ContentMap content, String nodeTypeName) throws RepositoryException {
280         return templatingFunctions.asContentMapList(ancestorsInSite(content.getJCRNode(), nodeTypeName));
281     }
282 
283     public List<Node> ancestorsInSite(Node content, String nodeTypeName) throws RepositoryException {
284         List<Node> allAncestors = templatingFunctions.ancestors(content, nodeTypeName);
285         Node siteRoot = siteRoot(content);
286 
287         List<Node> ancestoresInSite = new ArrayList<Node>();
288         for(Node current: allAncestors){
289             if(current.getDepth() >= siteRoot.getDepth()){
290                 ancestoresInSite.add(current);
291             }
292         }
293         return ancestoresInSite;
294     }
295 
296 
297     /**
298      * FIXME review: should this function be provided by this class?
299      */
300     public String getAssetLink(Node content, String nodeDataPrefix){
301         return getAssetLink( content,  nodeDataPrefix,  DAMSupport.VARIATION_ORIGINAL);
302     }
303 
304     /**
305      * FIXME review: should this function be provided by this class?
306      */
307     public String getAssetLink(ContentMap content, String nodeDataPrefix){
308         return getAssetLink(content.getJCRNode(), nodeDataPrefix);
309     }
310 
311     public String getAssetLink(ContentMap content, String nodeDataPrefix, AbstractSTKTemplateModel<? extends TemplateDefinition> model){
312         return getAssetLink(content.getJCRNode(), nodeDataPrefix, resolveImageVariationName(model));
313     }
314 
315     public String getAssetLink(ContentMap content, String nodeDataPrefix, String variationName){
316         return getAssetLink(content.getJCRNode(), nodeDataPrefix,variationName);
317     }
318 
319     public String getAssetLink(Node content, String nodeDataPrefix, String variationName){
320         final Asset asset = getAsset(content, nodeDataPrefix, variationName);
321         if(asset != null){
322             try {
323                 return asset.getLink();
324             }
325             catch (DAMException e) {
326                 log.warn("Failed to get asset link with exception: " + e.getMessage(), e);
327                 return null;
328             }
329         }
330         return null;
331     }
332 
333 
334     public String resolveImageVariationName(AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
335         final String cssSelector = cssSelectorBuilder.createCssSelector(model);
336         try {
337             return theme(site(model.getNode())).getImaging().resolveVariationName(cssSelector);
338         }
339         catch (NoSuchVariationException e) {
340             log.warn(e.getMessage());
341             return DAMSupport.VARIATION_ORIGINAL;
342         }
343     }
344 
345     public  Asset getAssetVariation(Asset original, String variationName){
346         if(!variationName.equals(DAMSupport.VARIATION_ORIGINAL)){
347             try {
348                 return original.getVariation(variationName);
349             }
350             catch (NoSuchVariationException e) {
351                 log.warn("Can't find the variation " + variationName + " for asset [" + original + "]. Will use the original instead.");
352             }
353             catch(DAMException e){
354                 log.warn("Failed to get asset variation " + variationName + " with exception: " + e.getMessage(), e);
355                 return null;
356             }
357         }
358         return original;
359     }
360 
361     /**
362      * FIXME review: should this function be provided by this class?
363      * FIXME I18nContentWrapper --> I18nNodeWrapper
364      */
365     public Node wrap(Node content){
366         return (content == null)  ? null :  templatingFunctions.encode(new I18nNodeWrapper(content));
367     }
368 
369     /**
370      * FIXME review: should this function be provided by this class?
371      */
372     public List<ContentMap> getCategories(Node page) {
373         return categorizationSupport.getCategories(page);
374     }
375 
376     /**
377      * FIXME review: should this function be provided by this class?
378      */
379     public String getCategoryLink(Node page, String categoryName) {
380         return categorizationSupport.getCategoryLink(page, categoryName);
381     }
382 
383     /**
384      * FIXME review: should this function be provided by this class?
385      */
386     public List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory) throws RepositoryException {
387         return getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, Integer.MAX_VALUE, null, null);
388     }
389 
390     /**
391      * FIXME review: should this function be provided by this class?
392      */
393     public static List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory, int maxResultSize, String andClause, String orderBy) throws RepositoryException {
394         return TemplateCategoryUtil.getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, maxResultSize, andClause, orderBy);
395     }
396 
397     /**
398      * FIXME review: should this function be provided by this class?
399      */
400     public Node getNearestContentByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory, Node current) throws RepositoryException {
401        return TemplateCategoryUtil.getNearestContentByTemplateCategorySubCategory(siteRoot,  category,  subCategory,current);
402     }
403     
404     /**
405      * FIXME review: should this function be provided by this class?
406      */
407     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName) throws RepositoryException {
408         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName);
409     }
410     
411     /**
412      * FIXME review: should this function be provided by this class?
413      */
414     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName, int maxResultSize, String andClause, String orderByClause) throws RepositoryException {
415         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName, maxResultSize, andClause, orderByClause);
416     }
417     
418     /**
419      * FIXME review: should this function be provided by this class?
420      */
421     public static List<Node> getContentListByTemplateNames(Node searchRoot, Set<String> templateIds, int maxResultSize, String andClause, String orderByClause) throws RepositoryException{
422         return TemplateCategoryUtil.getContentListByTemplateNames(searchRoot, templateIds, maxResultSize, andClause, orderByClause);
423     }
424 
425     /**
426      * Returns a {@link Node} object which is referenced by its id, stored in the @param propertyName.
427      * @param content the node with a property containing the referenced id value.
428      * @param idPropertyName The name of the property which contains the id of the referenced {@link Node}.
429      * @param referencedWorkspace The workspace in which the referenced {@link Node} exists.
430      * @return the referenced {@link Node}
431      */
432     public Node getReferencedContent(Node content, String idPropertyName, String referencedWorkspace){
433         try {
434             if(content.hasProperty(idPropertyName)){
435                 String identifier = PropertyUtil.getString(content, idPropertyName);
436                 return wrap(NodeUtil.getNodeByIdentifier(referencedWorkspace, identifier));
437             }
438         } catch (RepositoryException e) {
439             log.error("can't read value '" + idPropertyName + "' of the Node '" + content.toString() + "'.", e);
440             throw new RuntimeRepositoryException(e);
441         }
442         return null;
443     }
444 
445     /**
446      * FIXME review: should this function be provided by this class?
447      */
448     public Asset getAsset(Node content, String nodeDataPrefix, String variationName){
449         try{
450             final Asset asset = moduleProvider.get().getDamSupport().getAsset(ContentUtil.asContent(content), nodeDataPrefix);
451             return getAssetVariation(asset, variationName);
452         }
453         catch(AssetNotFoundException e){
454             return null;
455         }
456         catch(DAMException e){
457             log.warn("Failed to get asset with exception: " + e.getMessage(), e);
458             return null;
459         }
460     }
461 
462     public Asset getAsset(Node content, String nodeDataPrefix){
463         return getAsset(content, nodeDataPrefix, DAMSupport.VARIATION_ORIGINAL);
464     }
465 
466     public Asset getAsset(ContentMap content, String nodeDataPrefix){
467         return getAsset(content.getJCRNode(), nodeDataPrefix);
468     }
469 
470 
471     /**
472      * Return a Node List with a maximum number or result less or equal to maxResults.
473      */
474     public List<Node> cutList(List<Node> itemsList, final int maxResults){
475         if(itemsList.size() > maxResults) {
476             return itemsList.subList(0, maxResults);
477         }
478         return itemsList;
479     }
480 
481     // TODO : use WordUtils.abbreviate and/or add tests
482     public String abbreviateString(String stringToCut, int size) {
483         return abbreviateString(stringToCut, size, " ...");
484     }
485 
486     public String abbreviateString(String stringToCut, int size, String closureString) {
487         if(stringToCut.length() > size) {
488             int sizeMinusClosue= size - closureString.length();
489             String cuttedString = StringUtils.left(stringToCut, sizeMinusClosue);
490 
491             //If the character after the cut was a space, no word was cutted -> no cutting on last space needed.
492             String firstCharAfterCut = stringToCut.substring(sizeMinusClosue, sizeMinusClosue+1);
493             if(!firstCharAfterCut.equals(" ")){
494                 cuttedString = StringUtils.substringBeforeLast(cuttedString, " ");
495             }
496 
497             return cuttedString + closureString;
498 
499         }
500         return stringToCut;
501     }
502 
503     /**
504      * Increases the context bound counter and returns the value. This is for instance useful to create unique page wide ids.
505      */
506     public int count(String name){
507         String attributeName = STKTemplatingFunctions.class.getName() +  name;
508         if(!MgnlContext.hasAttribute(attributeName)){
509             MgnlContext.setAttribute(attributeName, new MutableInt(0));
510         }
511         MutableInt counter = (MutableInt) MgnlContext.getAttribute(attributeName);
512         counter.increment();
513         return counter.intValue();
514     }
515 
516     public String getDivIdAbbreviation(String divID){
517         return getDivIdAbbreviation(divID, "-");
518     }
519 
520     public String getDivIdAbbreviation(String divID, String delimiter){
521         String result = StringUtils.EMPTY;
522         if(StringUtils.isNotEmpty(divID) && StringUtils.isNotEmpty(delimiter)){
523             String[] values = divID.split("\\"+delimiter);
524             for(String value : values ){
525                 result += value.substring(0, 1);
526             }
527         }
528         return result;
529     }
530 
531     /**
532      * Return a property from the metaData of the node.
533      */
534     public String metaDataProperty(Node content, String property){
535         try {
536             if(content.hasNode(METADATA_NODE_NAME)){
537                 Node node = content.getNode(METADATA_NODE_NAME);
538                 Property metaDataProp = PropertyUtil.getProperty(node, property);
539                 if(metaDataProp != null){
540                     return metaDataProp.getString();
541                 }
542             }
543         } catch (RepositoryException e) {
544             throw new RuntimeException(e);
545         }
546         return null;
547     }
548 
549     public String metaDataProperty(ContentMap content, String property){
550         return metaDataProperty(content.getJCRNode(), property);
551     }
552 
553     /**
554      * Return the template id associated with this content.
555      */
556     public String metaDataTemplate(Node content){
557         return metaDataProperty(content,TEMPLATE_PROP_NAME);
558     }
559 
560     public String metaDataTemplate(ContentMap content){
561         return metaDataProperty(content,TEMPLATE_PROP_NAME);
562     }
563 
564     /**
565      * For test purpose only.
566      */
567     public void setCssSelectorBuilder(CssSelectorBuilder cssSelectorBuilder){
568         this.cssSelectorBuilder = cssSelectorBuilder;
569     }
570 
571     /**
572      * For test purpose only.
573      */
574     public void setCategorizationSupport(CategorizationSupport categorizationSupport){
575         this.categorizationSupport = categorizationSupport;
576     }
577 
578 }