001/*
002 *  Copyright 2016 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016
017package org.ametys.web.transformation.xslt;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.MalformedURLException;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030import java.util.Optional;
031import java.util.Set;
032
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.cocoon.environment.Response;
038import org.apache.cocoon.xml.dom.DOMBuilder;
039import org.apache.commons.lang.StringUtils;
040import org.apache.excalibur.source.Source;
041import org.w3c.dom.Element;
042import org.w3c.dom.Node;
043import org.w3c.dom.NodeList;
044import org.xml.sax.SAXException;
045
046import org.ametys.cms.CmsConstants;
047import org.ametys.cms.repository.Content;
048import org.ametys.cms.repository.ContentQueryHelper;
049import org.ametys.cms.repository.LanguageExpression;
050import org.ametys.cms.transformation.ImageResolverHelper;
051import org.ametys.core.right.RightManager.RightResult;
052import org.ametys.core.user.population.PopulationContextHelper;
053import org.ametys.core.util.I18nUtils;
054import org.ametys.core.util.dom.AmetysNodeList;
055import org.ametys.core.util.dom.EmptyElement;
056import org.ametys.core.util.dom.MapElement;
057import org.ametys.core.util.dom.MapElement.MapNode;
058import org.ametys.core.util.dom.SourceElement;
059import org.ametys.core.util.dom.StringElement;
060import org.ametys.plugins.explorer.resources.Resource;
061import org.ametys.plugins.explorer.resources.ResourceCollection;
062import org.ametys.plugins.explorer.resources.dom.ResourceCollectionElement;
063import org.ametys.plugins.explorer.resources.dom.ResourceElement;
064import org.ametys.plugins.repository.AmetysObject;
065import org.ametys.plugins.repository.AmetysObjectIterable;
066import org.ametys.plugins.repository.AmetysObjectResolver;
067import org.ametys.plugins.repository.UnknownAmetysObjectException;
068import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
069import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeater;
070import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeaterEntry;
071import org.ametys.plugins.repository.data.type.ModelItemTypeConstants;
072import org.ametys.plugins.repository.model.RepeaterDefinition;
073import org.ametys.plugins.repository.query.expression.AndExpression;
074import org.ametys.plugins.repository.query.expression.Expression;
075import org.ametys.plugins.repository.query.expression.Expression.Operator;
076import org.ametys.plugins.repository.query.expression.MetadataExpression;
077import org.ametys.plugins.repository.query.expression.NotExpression;
078import org.ametys.plugins.repository.query.expression.StringExpression;
079import org.ametys.runtime.model.ElementDefinition;
080import org.ametys.runtime.model.ModelHelper;
081import org.ametys.runtime.model.ModelItem;
082import org.ametys.runtime.model.type.DataContext;
083import org.ametys.runtime.model.type.ElementType;
084import org.ametys.runtime.model.type.ModelItemType;
085import org.ametys.web.URIPrefixHandler;
086import org.ametys.web.WebConstants;
087import org.ametys.web.parameters.view.ViewParametersManager;
088import org.ametys.web.parameters.view.ViewParametersManager.ViewParameterHolder;
089import org.ametys.web.parameters.view.ViewParametersModel;
090import org.ametys.web.renderingcontext.RenderingContext;
091import org.ametys.web.renderingcontext.RenderingContextHandler;
092import org.ametys.web.repository.content.WebContent;
093import org.ametys.web.repository.dom.PageElement;
094import org.ametys.web.repository.dom.SitemapElement;
095import org.ametys.web.repository.page.Page;
096import org.ametys.web.repository.page.Page.PageType;
097import org.ametys.web.repository.page.PageDAO;
098import org.ametys.web.repository.page.Zone;
099import org.ametys.web.repository.page.ZoneItem;
100import org.ametys.web.repository.page.ZoneItem.ZoneType;
101import org.ametys.web.repository.site.Site;
102import org.ametys.web.repository.site.SiteManager;
103import org.ametys.web.repository.sitemap.Sitemap;
104import org.ametys.web.service.Service;
105import org.ametys.web.service.ServiceExtensionPoint;
106import org.ametys.web.skin.SkinsManager;
107import org.ametys.web.tags.TagExpression;
108import org.ametys.web.url.UrlPreview;
109import org.ametys.web.url.UrlPreviewComponent;
110
111/**
112 * Helper component to be used from XSL stylesheets.
113 */
114public class AmetysXSLTHelper extends org.ametys.cms.transformation.xslt.AmetysXSLTHelper
115{
116    static final int _PAGINATION_CURRENT = -1;
117    static final int _PAGINATION_SEPARATOR = -2;
118    static final int _PAGINATION_SPACE = -3;
119    
120    private static final String __NAME_ATTRIBUTE = "name";
121    private static final String __TYPE_ATTRIBUTE = "type";
122    private static final String __REPEATER_ENTRY_TYPE = ModelItemTypeConstants.REPEATER_TYPE_ID + "_entry";
123    
124    private static SiteManager _siteManager;
125    private static RenderingContextHandler _renderingContextHandler;
126    private static URIPrefixHandler _prefixHandler;
127    private static ServiceExtensionPoint _serviceEP;
128    private static PopulationContextHelper _populationContextHelper;
129    private static UrlPreviewComponent _urlPreview;
130    private static ViewParametersManager _viewParametersManager;
131    private static PageDAO _pageDAO;
132    private static SkinsManager _skinsManager;
133    
134    @Override
135    public void service(ServiceManager manager) throws ServiceException
136    {
137        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
138        _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE);
139        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
140        _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
141        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
142        _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE);
143        _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
144        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
145        _urlPreview = (UrlPreviewComponent) manager.lookup(UrlPreviewComponent.ROLE);
146        _viewParametersManager = (ViewParametersManager) manager.lookup(ViewParametersManager.ROLE);
147        _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE);
148    }
149    
150    /**
151     * Returns the current URI prefix, depending on the rendering context.
152     * @return the current URI prefix.
153     */
154    public static String uriPrefix()
155    {
156        return _prefixHandler.getUriPrefix();
157    }
158    
159    /**
160     * Returns the URI prefix corresponding to the current site, depending on the rendering context.
161     * @return the URI prefix corresponding to the current site.
162     */
163    public static String siteUriPrefix()
164    {
165        Request request = ContextHelper.getRequest(_context);
166        String siteName = (String) request.getAttribute("site");
167        return _prefixHandler.getUriPrefix(siteName);
168    }
169    
170    /**
171     * Returns the absolute URI prefix, depending on the rendering context.
172     * @return the absolute URI prefix.
173     */
174    public static String absoluteUriPrefix()
175    {
176        return _prefixHandler.getAbsoluteUriPrefix();
177    }
178    
179    /**
180     * Returns the absolute URI prefix corresponding to the current site, depending on the rendering context.
181     * @return the absolute URI prefix corresponding to the current site.
182     */
183    public static String absoluteSiteUriPrefix()
184    {
185        Request request = ContextHelper.getRequest(_context);
186        String siteName = (String) request.getAttribute("site");
187        return _prefixHandler.getAbsoluteUriPrefix(siteName);
188    }
189    
190    /**
191     * Returns the absolute URI prefix corresponding to the given site, depending on the rendering context.
192     * @param siteName The site name. Can be null to get the current site.
193     * @return the absolute URI prefix corresponding to the current site.
194     */
195    public static String absoluteSiteUriPrefix(String siteName)
196    {
197        if (StringUtils.isEmpty(siteName))
198        {
199            return absoluteSiteUriPrefix();
200        }
201        return _prefixHandler.getAbsoluteUriPrefix(siteName);
202    }
203    
204    /**
205     * Returns the current skin name.
206     * @return the current skin name.
207     */
208    public static String skin()
209    {
210        return _skinsManager.getSkinNameFromRequest();
211    }
212    
213    /**
214     * Returns the current template name.
215     * @return the current template name.
216     */
217    public static String template()
218    {
219        Request request = ContextHelper.getRequest(_context);
220        return (String) request.getAttribute("template");
221    }
222    
223    /**
224     * Returns the current sitemap name.
225     * @return the current sitemap name.
226     */
227    public static String lang()
228    {
229        Request request = ContextHelper.getRequest(_context);
230        return (String) request.getAttribute("sitemapLanguage");
231    }
232    
233    /**
234     * Returns the current sitemap name.
235     * @param pageId The page identifier to get sitemap on
236     * @return the current sitemap name.
237     */
238    public static String lang(String pageId)
239    {
240        try
241        {
242            Page page = _getPage(pageId);
243            return page.getSitemapName();
244        }
245        catch (UnknownAmetysObjectException e)
246        {
247            _logger.error("Can not get sitemap lang on page '" + pageId + "'", e);
248            return "";
249        }
250    }
251    
252    /**
253     * Computes the URI for the given resource in the current site's skin.<br>
254     * If the URI is requested by the front-office, it will be absolutized.
255     * @param path the resource path.
256     * @return the URI for the given resource.
257     */
258    public static String skinURL(String path)
259    {
260        Request request = ContextHelper.getRequest(_context);
261        String siteName = (String) request.getAttribute("site");
262        Site site = _siteManager.getSite(siteName);
263        String skin = (String) request.getAttribute("skin");
264        
265        String resourcePath = "/skins/" + skin + "/resources/" + path;
266        
267        return _getResourceURL(request, site, resourcePath);
268    }
269    
270    /**
271     * Computes the URI for the given image with a given heigth and width in the current site's skin.<br>
272     * If the URI is requested by the front-office, it will be absolutized.
273     * @param path the resource path
274     * @param height the height for the resource to get
275     * @param width the width for the resource to get
276     * @return the URI of the given resource
277     */
278    public static String skinImageURL(String path, int height, int width)
279    {
280        String skinPath = skinURL(path);
281        return StringUtils.substringBeforeLast(skinPath, ".") + "_" + height + "x" + width + "." + StringUtils.substringAfterLast(skinPath, "."); 
282    }
283    
284    /**
285     * Computes the base 64 representation of the image at the specified path. <br>
286     * @param path the path of the image
287     * @return the base 64-encoded image
288     * @throws IOException if an error occurs while trying to get the file
289     */
290    public static String skinImageBase64 (String path) throws IOException
291    {
292        Source source = null;
293        try
294        {
295            source = _sourceResolver.resolveURI("skin://resources/" + path);
296            return _getResourceBase64(source); 
297        }
298        finally 
299        {
300            if (source != null)
301            {
302                _sourceResolver.release(source);
303            }
304        }
305    }
306    
307    /**
308     * Computes the URI for the given image with a given heigth and width in the current site's skin.<br>
309     * If the URI is requested by the front-office, it will be absolutized.
310     * @param path the resource path
311     * @param maxHeight the maximum height for the resource to get
312     * @param maxWidth the maximum width for the resource to get
313     * @return the URI of the given resource
314     */
315    public static String skinBoundedImageURL(String path, int maxHeight, int maxWidth)
316    {
317        String skinPath = skinURL(path);
318        return StringUtils.substringBeforeLast(skinPath, ".") + "_max" + maxHeight + "x" + maxWidth + "." + StringUtils.substringAfterLast(skinPath, "."); 
319    }
320    
321    /**
322     * Computes the URI for the given resource in the current template.<br>
323     * If the URI is requested by the front-office, it will be absolutized.
324     * @param path the resource path.
325     * @return the URI for the given resource.
326     */
327    public static String templateURL(String path)
328    {
329        Request request = ContextHelper.getRequest(_context);
330        String siteName = (String) request.getAttribute("site");
331        Site site = _siteManager.getSite(siteName);
332        String skin = (String) request.getAttribute("skin");
333        String template = (String) request.getAttribute("template");
334        
335        String resourcePath = "/skins/" + skin + "/templates/" + template + "/resources/" + path;
336        
337        return _getResourceURL(request, site, resourcePath);
338    }
339    
340    /**
341     * Computes the URI for the given resource in the given plugin.<br>
342     * If the URI is requested by the front-office, it will be absolutized.
343     * @param plugin the plugin name.
344     * @param path the resource path.
345     * @return the URI for the given resource.
346     */
347    public static String pluginResourceURL(String plugin, String path)
348    {
349        Request request = ContextHelper.getRequest(_context);
350        String siteName = (String) request.getAttribute("site");
351        Site site = _siteManager.getSite(siteName);
352        
353        String resourcePath = "/plugins/" + plugin + "/resources/" + path;
354        
355        return _getResourceURL(request, site, resourcePath);
356    }
357    
358    /**
359     * Computes the base 64 representation of the image at the specified path in the given plugin.
360     * @param plugin the plugin's name.
361     * @param path the resource path.
362     * @return the base 64 encoding for the given resource.
363     * @throws IOException if an error occurs when trying to get the file
364     * @throws MalformedURLException if the url is invalid
365     */
366    public static String pluginImageBase64(String plugin, String path) throws MalformedURLException, IOException
367    {
368        Source source = null;
369        try
370        {
371            source = _sourceResolver.resolveURI("plugin:" + plugin + "://resources/" + path);
372            return _getResourceBase64(source);
373        }
374        finally
375        {
376            if (source != null)
377            {
378                _sourceResolver.release(source);
379            }
380        }
381    }
382
383    
384    private static String _getResourceURL(Request request, Site site, String resourcePath)
385    {
386        String prefix;
387        switch (_renderingContextHandler.getRenderingContext())
388        {
389            case FRONT:
390                String[] aliases = site.getUrlAliases();
391                int position = Math.abs(resourcePath.hashCode()) % aliases.length;
392                
393                boolean absolute = request.getAttribute("forceAbsoluteUrl") != null ? (Boolean) request.getAttribute("forceAbsoluteUrl") : false;
394                prefix = position == 0 && !absolute ? siteUriPrefix() : aliases[position];  
395                return prefix + resourcePath;
396                
397            default:
398                prefix = StringUtils.trimToEmpty((String) request.getAttribute(CmsConstants.PATH_PREFIX_ATTRIBUTE));
399                return request.getContextPath() + prefix + resourcePath;
400        }
401    }
402    
403    /**
404     * Get the base 64 encoding for the given source
405     * @param source the source 
406     * @return the base 64 encoding of the source
407     */
408    private static String _getResourceBase64(Source source)
409    {
410        if (source.exists())
411        {
412            
413            try (InputStream dataIs = source.getInputStream())
414            {
415                return ImageResolverHelper.resolveImageAsBase64(dataIs, source.getMimeType(), 0, 0, 0, 0);
416            }
417            catch (Exception e)
418            {
419                throw new IllegalStateException(e);
420            }
421        }
422
423        return "";
424    }
425    
426    /**
427     * Returns the current {@link RenderingContext}.
428     * @return the current {@link RenderingContext}.
429     */
430    public static String renderingContext()
431    {
432        return _renderingContextHandler.getRenderingContext().toString();
433    }
434    
435    /**
436     * Return the name of the zone beeing handled
437     * @param defaultValue If no page is handled currently, this value is returned (can be null, empty...)
438     * @return the name or the default value (so can be null or empty)
439     */
440    public static String zone(String defaultValue)
441    {
442        Request request = ContextHelper.getRequest(_context);
443        
444        return StringUtils.defaultIfEmpty((String) request.getAttribute(Zone.class.getName()), defaultValue);
445    }
446
447    /**
448     * Return the value of a site parameter as a String.
449     * @param parameter the parameter ID.
450     * @return the parameter value as a String.
451     */
452    public static String siteParameter(String parameter)
453    {
454        Request request = ContextHelper.getRequest(_context);
455        
456        String siteName = (String) request.getAttribute("site");
457        if (StringUtils.isBlank(siteName))
458        {
459            // In BO xsl
460            siteName = (String) request.getAttribute("siteName");
461        }
462        
463        return siteParameter(siteName, parameter);
464    }
465    
466    /**
467     * Return the value of a site parameter as a String.
468     * @param siteName the site name
469     * @param parameter the parameter ID.
470     * @return the parameter value as a String.
471     */
472    public static String siteParameter(String siteName, String parameter)
473    {
474        try
475        {
476            Site site = _siteManager.getSite(siteName);
477            Object value = site.getValue(parameter);
478            if (value != null)
479            {
480                return value.toString();
481            }
482            else
483            {
484                return null;
485            }
486        }
487        catch (Exception e)
488        {
489            String message = "Error retrieving the value of the site parameter " + parameter;
490            _logger.error(message, e);
491            throw new RuntimeException(message, e);
492        }
493    }
494    
495    /**
496     * Get the service parameters as a {@link Node}.
497     * @return the service parameters as a {@link Node}.
498     */
499    public static Node serviceParameters()
500    {
501        ZoneItem zoneItem = _getZoneItemFromRequest();
502        return _serviceParameters(zoneItem);
503    }
504    
505    static Node _serviceParameters(ZoneItem zoneItem)
506    {
507        Map<String, MapNode> values = new HashMap<>();
508        
509        ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters();
510        String serviceId = zoneItem.getServiceId();
511        Service service = _serviceEP.getExtension(serviceId);
512        
513        for (String parameterName : dataHolder.getDataNames())
514        {
515            if (service.hasModelItem(parameterName) && dataHolder.hasValueOrEmpty(parameterName))
516            {
517                ModelItem modelItem = service.getModelItem(parameterName);
518                values.putAll(_getParameterValue(parameterName, modelItem, dataHolder, null));
519            }
520        }
521        
522        return new MapElement("serviceParameters", values);
523    }
524    
525    /**
526     * Returns the value of the given parameter for the current service, or the empty string if the parameter does not exist.
527     * @param parameterPath the parameter path.
528     * @return the value of the given parameter for the current service.
529     */
530    public static Node serviceParameter(String parameterPath)
531    {
532        return serviceParameter(parameterPath, "");
533    }
534    
535    /**
536     * Returns the value of the given parameter for the current service, or the provided default value if the parameter does not exist.
537     * @param parameterPath the parameter path.
538     * @param defaultValue the default value. Note that default value is ignored if the parameter is a composite parameter.
539     * @return the value of the given parameter for the current service.
540     */
541    public static Node serviceParameter(String parameterPath, Object defaultValue)
542    {
543        ZoneItem zoneItem = _getZoneItemFromRequest();
544        return _serviceParameter(zoneItem, parameterPath, defaultValue);
545    }
546    
547    static Node _serviceParameter(ZoneItem zoneItem, String parameterPath, Object defaultValue)
548    {
549        ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters();
550        
551        String serviceId = zoneItem.getServiceId();
552        String definitionPath = ModelHelper.getDefinitionPathFromDataPath(parameterPath);
553        
554        Service service = _serviceEP.getExtension(serviceId);
555        ModelItem paramDef;
556        if (service.hasModelItem(definitionPath))
557        {
558            paramDef = service.getModelItem(definitionPath);
559        }
560        else
561        {
562            // The parameter is unknown
563            if (defaultValue == null
564                || defaultValue instanceof String && StringUtils.isEmpty((String) defaultValue))
565            {
566                return null;
567            }
568            else
569            {
570                return new StringElement(parameterPath, Collections.EMPTY_MAP, defaultValue.toString());
571            }
572        }
573        
574        Map<String, MapNode> value = _getParameterValue(parameterPath, paramDef, dataHolder, defaultValue);
575        
576        if (!value.containsKey(parameterPath))
577        {
578            return null;
579        }
580        else if (paramDef instanceof RepeaterDefinition
581                || paramDef instanceof ElementDefinition && ((ElementDefinition) paramDef).isMultiple())
582        {
583            MapNode node = value.get(parameterPath);
584            @SuppressWarnings("unchecked")
585            Map<String, ? extends Object> values = (Map<String, ? extends Object>) node.getValue();
586            return new MapElement(parameterPath, node.getAttributes(), values);
587        }
588        else 
589        {
590            return new StringElement(parameterPath, value.get(parameterPath).getAttributes(), (String) value.get(parameterPath).getValue());
591        }
592    }
593    
594    private static String _convertTagName(String name)
595    {
596        char c = name.charAt(0);
597        if (c >= '0' && c <= '9')
598        {
599            String hex = Integer.toHexString(c);
600            return "_x" + StringUtils.leftPad(hex, 4, '0') + "_" + name.substring(1);
601        }
602        else
603        {
604            return name;
605        }
606    }
607    
608    @SuppressWarnings("unchecked")
609    private static Map<String, MapNode> _getParameterValue(String parameterPath, ModelItem modelItem, ModelAwareDataHolder dataHolder, Object defaultValue)
610    {
611        String[] pathSegments = StringUtils.split(parameterPath, ModelItem.ITEM_PATH_SEPARATOR);
612        String parameterName = pathSegments[pathSegments.length - 1];
613        Map<String, MapNode> paramValues = new HashMap<>();
614        
615        if (modelItem instanceof RepeaterDefinition)
616        {
617            if (!dataHolder.hasValue(parameterPath))
618            {
619                return paramValues;
620            }
621                    
622            Map<String, String> attributes = new HashMap<>();
623            attributes.put(__NAME_ATTRIBUTE, parameterName);
624            attributes.put(__TYPE_ATTRIBUTE, ModelItemTypeConstants.REPEATER_TYPE_ID);
625            
626            Map<String, Object> children = new HashMap<>();
627            
628            ModelAwareRepeater repeater = dataHolder.getRepeater(parameterPath);
629            for (ModelAwareRepeaterEntry entry : repeater.getEntries())
630            {
631                Map<String, Object> entryValue = new HashMap<>();
632                
633                for (ModelItem childModelItem : ((RepeaterDefinition) modelItem).getChildren())
634                {
635                    // Default value is ignored if parameter is a repeater
636                    Map<String, MapNode> childParamValues = _getParameterValue(childModelItem.getName(), childModelItem, entry, null);
637                    entryValue.putAll(childParamValues);
638                }
639                
640                Map<String, String> entryAttributes = new HashMap<>();
641                entryAttributes.put(__NAME_ATTRIBUTE, String.valueOf(entry.getPosition()));
642                entryAttributes.put(__TYPE_ATTRIBUTE, __REPEATER_ENTRY_TYPE);
643                
644                MapNode entryNode = new MapNode(entryValue, entryAttributes);
645                children.put(_convertTagName(String.valueOf(entry.getPosition())), entryNode);
646            }
647            
648            MapNode node = new MapNode(children, attributes);
649            paramValues.put(parameterPath, node);
650        }
651        else 
652        {
653            if (!_hasParameterValueOrDefaultValue(parameterPath, dataHolder, defaultValue))
654            {
655                return paramValues;
656            }
657            
658            Map<String, String> attributes = new HashMap<>();
659            attributes.put(__NAME_ATTRIBUTE, parameterName);
660            ElementDefinition elementDefinition = (ElementDefinition) modelItem;
661            ElementType elementType = elementDefinition.getType();
662            attributes.put(__TYPE_ATTRIBUTE, elementType.getId());
663            
664            if (elementDefinition.isMultiple())
665            {
666                Map<String, List<String>> values = new HashMap<>();
667                List<String> value = new ArrayList<>();
668                Object[] valueArray = (Object[]) dataHolder.getValue(parameterPath, false, defaultValue);
669                for (Object arrayItem : valueArray)
670                {
671                    value.add(elementType.toString(arrayItem));
672                }
673                values.put("value", value);
674                
675                MapNode node = new MapNode(values, attributes);
676                paramValues.put(parameterPath, node);
677            }
678            else
679            {
680                
681                String value = elementType.toString(dataHolder.getValue(parameterPath, false, defaultValue));
682                MapNode node = new MapNode(value, attributes);
683                paramValues.put(parameterPath, node);
684            }
685        }
686        
687        return paramValues;
688    }
689    
690    private static boolean _hasParameterValueOrDefaultValue(String parameterPath, ModelAwareDataHolder dataHolder, Object defaultValue)
691    {
692        if (dataHolder.hasValue(parameterPath))
693        {
694            return true;
695        }
696        else
697        {
698            if (defaultValue != null)
699            {
700                return defaultValue instanceof String ? StringUtils.isNotEmpty((String) defaultValue) : true;
701            }
702            else
703            {
704                return false;
705            }
706        }
707    }
708    
709    /**
710     * Returns the current site
711     * @return the current site
712     */
713    public static String site()
714    {
715        Request request = ContextHelper.getRequest(_context);
716        return (String) request.getAttribute("site");
717    }
718    
719    /**
720     * Returns the current site
721     * @param pageId The identifier ot the page
722     * @return the current site
723     */
724    public static String site(String pageId)
725    {
726        try
727        {
728            Page page = _getPage(pageId);
729            return page.getSiteName();
730        }
731        catch (UnknownAmetysObjectException e)
732        {
733            _logger.error("Can not get site on page '" + pageId + "'", e);
734            return "";
735        }
736    }
737
738    /**
739     * Return the current sitemap as a {@link Node}.
740     * Invisible pages will not be displayed
741     * @return the current sitemap.
742     * @deprecated Use the version with depth argument
743     */
744    @Deprecated
745    public static Node sitemap()
746    {
747        throw new UnsupportedOperationException("DEPRECATED Use the ametys:sitemap(DEPTH) where DEPTH can be 1 to get root pages, 2 or more to get their children at n levels ; 0 to get only sitemap infos ; -1 to get infinite sitemap (to avoid)");
748    }
749    
750    /**
751     * Return the current sitemap as a {@link Node}.
752     * Invisible pages will not be displayed
753     * @param depth The depth to get. 1 for root pages, -1 for infinite.
754     * @return the current sitemap.
755     */
756    public static Node sitemap(long depth)
757    {
758        return sitemap(depth, false);
759    }
760    
761    /**
762     * Return the current sitemap as a {@link Node}.
763     * @param includeInvisiblePages Should return child invisible pages
764     * @return the current sitemap.
765     * @deprecated Use the version with depth argument
766     */
767    @Deprecated
768    public static Node sitemap(boolean includeInvisiblePages)
769    {
770        throw new UnsupportedOperationException("DEPRECATED Use the ametys:sitemap(DEPTH, includeInvisiblePages) where DEPTH can be 1 to get root pages, 2 or more to get their children at n levels ; 0 to get only sitemap infos ; -1 to get infinite sitemap (to avoid)");
771    }
772    
773    /**
774     * Return the current sitemap as a {@link Node}.
775     * @param depth The depth to get. 1 for root pages, -1 for infinite.
776     * @param includeInvisiblePages Should return child invisible pages
777     * @return the current sitemap.
778     */
779    public static Node sitemap(long depth, boolean includeInvisiblePages)
780    {
781        Request request = ContextHelper.getRequest(_context);
782        Sitemap sitemap = (Sitemap) request.getAttribute(Sitemap.class.getName());
783        
784        if (sitemap == null)
785        {
786            // Try to get sitemap from content
787            Content content = (Content) request.getAttribute(Content.class.getName());
788            if (content instanceof WebContent)
789            {
790                sitemap = ((WebContent) content).getSite().getSitemap(content.getLanguage());
791            }
792        }
793        
794        if (sitemap == null)
795        {
796            return new EmptyElement("sitemap");
797        }
798        
799        Page page = (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE);
800        
801        return new SitemapElement(sitemap, page != null ? page.getPathInSitemap() : null, _rightManager, _renderingContextHandler, _currentUserProvider.getUser(), depth, includeInvisiblePages, _tagProviderExtPt);
802    }
803
804    /**
805     * Return the subsitemap of the given page as a {@link Node}.
806     * Invisible child pages will not be returned;
807     * @param pageId The root page
808     * @return The page as node.
809     * @deprecated Use the version with depth argument
810     */
811    @Deprecated
812    public static Node sitemap(String pageId)
813    {
814        throw new UnsupportedOperationException("DEPRECATED Use the ametys:sitemap(pageId, DEPTH) where DEPTH can be 1 (or more) to get page and children at n levels ; 0 to get only this page infos ; -1 to get infinite sitemap (to avoid)");
815    }
816
817    /**
818     * Return the subsitemap of the given page as a {@link Node}.
819     * Invisible child pages will not be returned;
820     * @param pageId The root page
821     * @param depth The depth to get. 0 for the root page only, 1 for its child pages, -1 for infinite child pages.
822     * @return The page as node.
823     */
824    public static Node sitemap(String pageId, long depth)
825    {
826        return sitemap(pageId, depth, false);
827    }
828        
829    /**
830     * Return the subsitemap of the given page as a {@link Node}.
831     * @param pageId The root page
832     * @param includeInvisiblePages Should return child invisible pages
833     * @return The page as node.
834     */
835    public static Node sitemap(String pageId, boolean includeInvisiblePages)
836    {
837        return sitemap(pageId, -1, includeInvisiblePages);
838    }
839    
840    /**
841     * Return the subsitemap of the given page as a {@link Node}.
842     * @param pageId The root page
843     * @param depth The depth to get. 0 for the root page only, 1 for its child pages, -1 for infinite child pages.
844     * @param includeInvisiblePages Should return child invisible pages
845     * @return The page as node.
846     */
847    public static Node sitemap(String pageId, long depth, boolean includeInvisiblePages)
848    {
849        Page rootPage = null;
850        try
851        {
852            rootPage = _ametysObjectResolver.resolveById(pageId);
853        }
854        catch (UnknownAmetysObjectException e)
855        {
856            return new EmptyElement("page");
857        }
858        
859        Page page = _getPageFromRequest();
860        return new PageElement(rootPage, _rightManager, _renderingContextHandler, page != null ? page.getPathInSitemap() : null,  _currentUserProvider.getUser(), depth, includeInvisiblePages, _tagProviderExtPt);
861    }
862    
863    /**
864     * Computes the breadcrumb of the current page.
865     * @return a NodeList containing all ancestor pages, rooted at the sitemap.
866     */
867    public static NodeList breadcrumb()
868    {
869        Page page = _getPageFromRequest();
870  
871        List<Element> result = new ArrayList<>();
872
873        AmetysObject parent = page.getParent();
874        while (parent instanceof Page)
875        {
876            Element node = new StringElement("page", (Map<String, String>) null, parent.getId());
877            result.add(node);
878            parent = parent.getParent();
879        }
880        
881        Collections.reverse(result);
882        return new AmetysNodeList(result);
883    }
884
885    /**
886     * Returns a DOM {@link Element} representing files and folder of the resources explorer, 
887     * under the {@link ResourceCollection} corresponding to the given id.
888     * @param collectionId the id of the root {@link ResourceCollection}.
889     * @return an Element containing files and folders.
890     */
891    public static Node resourcesById(String collectionId)
892    {
893        ResourceCollection collection = _ametysObjectResolver.resolveById(collectionId);
894        return new ResourceCollectionElement(collection);
895    }
896    
897    /**
898     * Returns a DOM {@link Element} representing files and folder of the resources explorer, 
899     * under the {@link ResourceCollection} corresponding to the given path. <br>
900     * This path is intended to be relative to the current site's resource explorer.
901     * @param path the path of the root {@link ResourceCollection}, relative to the current site's resource explorer.
902     * @return an Element containing files and folders or null if the specified resource does not exist.
903     */
904    public static Node resourcesByPath(String path)
905    {
906        Request request = ContextHelper.getRequest(_context);
907        String siteName = (String) request.getAttribute("site");
908        Site site = _siteManager.getSite(siteName);
909        
910        try
911        {
912            ResourceCollection collection = site.getRootResources().getChild(path);
913            return new ResourceCollectionElement(collection);
914        }
915        catch (UnknownAmetysObjectException ex)
916        {
917            return null;
918        }
919    }
920
921    /**
922     * Returns a DOM {@link Element} representing a single file of the resources explorer. <br>
923     * This path is intended to be relative to the current site's resource explorer.
924     * @param path the path of the {@link Resource}, relative to the current site's resource explorer.
925     * @return an Element containing a file or null if the specified resource does not exist.
926     */
927    public static Node resourceByPath(String path)
928    {
929        Request request = ContextHelper.getRequest(_context);
930        String siteName = (String) request.getAttribute("site");
931        Site site = _siteManager.getSite(siteName);
932
933        try
934        {
935            Resource resource = site.getRootResources().getChild(path);
936            return new ResourceElement(resource, null);
937        }
938        catch (UnknownAmetysObjectException ex)
939        {
940            return null;
941        }
942    }
943
944    /**
945     * Returns a DOM {@link Element} representing files and folder of a skin directory. <br>
946     * This path is intended to be relative to the current skin's 'resources' directory.
947     * @param path the path of the root {@link ResourceCollection}, relative to the current skin's 'resources' directory. Can be a file path to test its existance.
948     * @return an Element containing files and folders. node name is 'collection' or 'resource' with an attribute 'name'. Return null if the path does not exits.
949     * @throws IOException if an error occurred while listing files.
950     */
951    public static Node skinResources(String path) throws IOException
952    {
953        Source source = _sourceResolver.resolveURI("skin://resources/" + path);
954        if (source.exists())
955        {
956            return new SourceElement(source);
957        }
958        else
959        {
960            return null;
961        }
962    }
963    
964    //*************************
965    // Page methods
966    //*************************
967    
968    /**
969     * Get the site name of a page.
970     * @param pageId The page id.
971     * @return The name or empty if the page does not exist.
972     */
973    public static String pageSiteName(String pageId)
974    {
975        try
976        {
977            Page page = _getPage(pageId);
978            return page.getSiteName();
979        }
980        catch (UnknownAmetysObjectException e)
981        {
982            _logger.error("Can not get site name on page with id '" + pageId + "'", e);
983            return "";
984        }
985    }
986    
987    /**
988     * Get the title of a page.
989     * @param sitename the site name.
990     * @param lang the sitemap name.
991     * @param path the page path.
992     * @return The name or empty if the meta or the page does not exist.
993     */
994    public static String pageTitle(String sitename, String lang, String path)
995    {
996        try
997        {
998            Page page = _getPage(sitename, lang, path);
999            return page.getTitle();
1000        }
1001        catch (UnknownAmetysObjectException e)
1002        {
1003            _logger.warn("Unknown page at path '" + sitename + "/" + lang + "/" + path + "'", e);
1004            return "";
1005        }
1006    }
1007    
1008    /**
1009     * Determines if page exists
1010     * @param sitename the site name.
1011     * @param lang the sitemap name.
1012     * @param path the page path.
1013     * @return <code>false</code> the page does not exist.
1014     */
1015    public static boolean pageExists(String sitename, String lang, String path)
1016    {
1017        try
1018        {
1019            _getPage(sitename, lang, path);
1020            return true;
1021        }
1022        catch (UnknownAmetysObjectException e)
1023        {
1024            _logger.debug("Page at path '" + sitename + "/" + lang + "/" + path + "' does not exists", e);
1025            return false;
1026        }
1027    }
1028    
1029    /**
1030     * Get the title of a page.
1031     * @param pageId The page id.
1032     * @return The name or empty if the meta or the page does not exist.
1033     */
1034    public static String pageTitle(String pageId)
1035    {
1036        try
1037        {
1038            Page page = _getPage(pageId);
1039            return page.getTitle();
1040        }
1041        catch (UnknownAmetysObjectException e)
1042        {
1043            _logger.error("Can not get title on page with id '" + pageId + "'", e);
1044            return "";
1045        }
1046    }
1047
1048    /**
1049     * Get the long title of a page
1050     * @param sitename the site name
1051     * @param lang the page's language
1052     * @param path the page's path
1053     * @return The name or empty if the meta or the page does not exist
1054     */
1055    public static String pageLongTitle(String sitename, String lang, String path)
1056    {
1057        try
1058        {
1059            Page page = _getPage(sitename, lang, path);
1060            return page.getLongTitle();
1061        }
1062        catch (UnknownAmetysObjectException e)
1063        {
1064            _logger.error("Can not get long title on page '" + sitename + "/" + lang + "/" + path + "'", e);
1065            return "";
1066        }
1067    }
1068    /**
1069     * Get the long title of a page
1070     * @param pageId The page id
1071     * @return The name or empty if the meta or the page does not exist
1072     */
1073    public static String pageLongTitle(String pageId)
1074    {
1075        try
1076        {
1077            Page page = _getPage(pageId);
1078            return page.getLongTitle();
1079        }
1080        catch (UnknownAmetysObjectException e)
1081        {
1082            _logger.error("Can not get long title on page with id '" + pageId + "'", e);
1083            return "";
1084        }
1085    }
1086    
1087    /**
1088     * Get the type of a page.
1089     * @param pageId The page id.
1090     * @return The type or empty if the page does not exist.
1091     */
1092    public static String pageType(String pageId)
1093    {
1094        try
1095        {
1096            Page page = _getPage(pageId);
1097            return page.getType().name();
1098        }
1099        catch (UnknownAmetysObjectException e)
1100        {
1101            _logger.error("Can not get type of page with id '" + pageId + "'", e);
1102            return "";
1103        }
1104    }
1105    
1106    /**
1107     * Get the linked url of a page.
1108     * @param pageId The page id.
1109     * @return The linked url or empty if the page does not exist or if it is not a LINK page
1110     */
1111    public static String pageUrl(String pageId)
1112    {
1113        try
1114        {
1115            Page page = _getPage(pageId);
1116            return page.getType() == PageType.LINK ? page.getURL() : "";
1117        }
1118        catch (UnknownAmetysObjectException e)
1119        {
1120            _logger.error("Can not get type of page with id '" + pageId + "'", e);
1121            return "";
1122        }
1123    }
1124    
1125    /**
1126     * Get the linked URL type of a page.
1127     * @param pageId The page id.
1128     * @return The linked URL type or empty if the page does not exist or if it is not a LINK page
1129     */
1130    public static String pageUrlType(String pageId)
1131    {
1132        try
1133        {
1134            Page page = _getPage(pageId);
1135            return page.getType() == PageType.LINK ? page.getURLType().name() : "";
1136        }
1137        catch (UnknownAmetysObjectException e)
1138        {
1139            _logger.error("Can not get type of page with id '" + pageId + "'", e);
1140            return "";
1141        }
1142    }
1143
1144    /**
1145     * Get the data of a page at the given path
1146     * @param sitename the site name
1147     * @param lang the page's language
1148     * @param path the page's path
1149     * @param dataPath The data path (use '/' as separator for composites and repeaters)
1150     * @return The value or empty if the data or the page does not exist
1151     */
1152    public static String pageMetadata(String sitename, String lang, String path, String dataPath)
1153    {
1154        try
1155        {
1156            Page page = _getPage(sitename, lang, path);
1157            return _getPageData(page, dataPath);
1158        }
1159        catch (UnknownAmetysObjectException e)
1160        {
1161            _logger.error("Can not get data at path '" + dataPath + "' on page '" + sitename + "/" + lang + "/" + path + "'", e);
1162            return StringUtils.EMPTY;
1163        }
1164    }
1165    
1166    /**
1167     * Get the data of a page at the given path
1168     * @param pageId The page id
1169     * @param dataPath The data path (use '/' as separator for composites and repeaters)
1170     * @return The value or empty if the data or the page does not exist
1171     */
1172    public static String pageMetadata(String pageId, String dataPath)
1173    {
1174        try
1175        {
1176            Page page = _getPage(pageId);
1177            return _getPageData(page, dataPath);
1178        }
1179        catch (UnknownAmetysObjectException e)
1180        {
1181            _logger.error("Can not get data at path '" + dataPath + "' on page with id '" + pageId + "'", e);
1182            return StringUtils.EMPTY;
1183        }
1184    }
1185    
1186    @SuppressWarnings("unchecked")
1187    private static String _getPageData(Page page, String dataPath)
1188    {
1189        try
1190        {
1191            Object value = page.getValue(dataPath);
1192            if (value != null)
1193            {
1194                ModelItemType type = page.getType(dataPath);
1195                if (type instanceof ElementType)
1196                {
1197                    return ((ElementType) type).toString(value);
1198                }
1199            }
1200
1201            _logger.error("Can not get data at path '" + dataPath + "' on page with id '" + page.getId() + "'");
1202            return StringUtils.EMPTY;
1203        }
1204        catch (Exception e)
1205        {
1206            _logger.error("Can not get data at path '" + dataPath + "' on page with id '" + page.getId() + "'", e);
1207            return StringUtils.EMPTY;
1208        }
1209    }
1210
1211    /**
1212     * Returns true if the given page is visible into navigation elements
1213     * @param pageId the page id.
1214     * @return true if the page is visible
1215     */
1216    public static boolean pageIsVisible (String pageId)
1217    {
1218        try
1219        {
1220            Page page = _getPage(pageId);
1221            return page.isVisible();
1222        }
1223        catch (UnknownAmetysObjectException e)
1224        {
1225            _logger.error("Can not get visibility status on page with id '" + pageId + "'", e);
1226            return false;
1227        }
1228    }
1229    
1230    /**
1231     * Returns true if the given page is visible into navigation elements
1232     * @param sitename the site name
1233     * @param lang the page's language
1234     * @param path the page's path
1235     * @return true if the page is visible
1236     */
1237    public static boolean pageIsVisible (String sitename, String lang, String path)
1238    {
1239        try
1240        {
1241            Page page = _getPage(sitename, lang, path);
1242            return page.isVisible();
1243        }
1244        catch (UnknownAmetysObjectException e)
1245        {
1246            _logger.error("Can not get visibility status on page with id '" + sitename + "/" + lang + "/" + path + "'", e);
1247            return false;
1248        }
1249    }
1250    
1251    /**
1252     * Returns true if the given page has restricted access.
1253     * @param pageId the page id.
1254     * @return true if the page exists and has restricted access.
1255     */
1256    public static boolean pageHasRestrictedAccess(String pageId)
1257    {
1258        try
1259        {
1260            Page page = _getPage(pageId);
1261            return !_rightManager.hasAnonymousReadAccess(page);
1262        }
1263        catch (UnknownAmetysObjectException e)
1264        {
1265            _logger.error("Can not get page access info on page with id '" + pageId + "'", e);
1266            return false;
1267        }
1268    }
1269
1270    /**
1271     * Returns true if the current user has read access on the specified page
1272     * @param pageId Page Id
1273     * @return true if the current user has read access on the specified page
1274     */
1275    public static boolean hasReadAccessOnPage(String pageId)
1276    {
1277        Page page = _getPage(pageId);
1278        return _rightManager.currentUserHasReadAccess(page); 
1279    }
1280    
1281    /**
1282     * Returns true if the current user has the specified right on the current page
1283     * @param rightId Right Id
1284     * @return true if the current user has the specified right on the current page
1285     */
1286    public static boolean hasRightOnPage(String rightId)
1287    {
1288        Page page = _getPageFromRequest();
1289        return _hasRightOnPage(rightId, page);
1290    }
1291
1292    /**
1293     * Returns true if the current user has the specified right on the specified page
1294     * @param rightId Right Id
1295     * @param pageId Page Id
1296     * @return true if the current user has the specified right on the specified page
1297     */
1298    public static boolean hasRightOnPage(String rightId, String pageId)
1299    {
1300        Page page = _getPage(pageId);
1301        return _hasRightOnPage(rightId, page);
1302    }
1303
1304    /**
1305     * Returns true if the current user has the specified right on the specified page
1306     * @param rightId Right Id
1307     * @param page Page
1308     * @return true if the current user has the specified right on the specified page
1309     */
1310    private static boolean _hasRightOnPage(String rightId, Page page)
1311    {
1312        RightResult rightResult = _rightManager.currentUserHasRight(rightId, page);
1313        return rightResult == RightResult.RIGHT_ALLOW;
1314    }
1315
1316    /**
1317     * Returns true if the given page has restricted access.
1318     * @param sitename the site name
1319     * @param lang the page's language
1320     * @param path the page's path
1321     * @return true if the page exists and has restricted access.
1322     */
1323    public static boolean pageHasRestrictedAccess(String sitename, String lang, String path)
1324    {
1325        try
1326        {
1327            Page page = _getPage(sitename, lang, path);
1328            return !_rightManager.hasAnonymousReadAccess(page);
1329        }
1330        catch (UnknownAmetysObjectException e)
1331        {
1332            _logger.error("Can not get page access info on page with id '" + sitename + "/" + lang + "/" + path + "'", e);
1333            return false;
1334        }
1335    }
1336    
1337    private static Page _getPage(String id)
1338    {
1339        return _ametysObjectResolver.resolveById(id);
1340    }
1341    
1342    private static Page _getPage(String sitename, String lang, String path)
1343    {
1344        Site site = _siteManager.getSite(sitename);
1345        Sitemap sitemap = site.getSitemap(lang);
1346        return sitemap.getChild(path);
1347    }
1348    
1349    private static Page _getPageFromRequest()
1350    {
1351        Request request = ContextHelper.getRequest(_context);
1352        return (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE);
1353    }
1354    
1355    /**
1356     * Returns the path of the current page, relative to the sitemap's root.
1357     * @return the path of the current Page, or empty if there's no current page.
1358     */
1359    public static String pagePath()
1360    {
1361        Page page = _getPageFromRequest();
1362        return page == null ? "" : page.getPathInSitemap();
1363    }
1364    
1365    /**
1366     * Returns the path in sitemap of a page
1367     * @param pageId The id of page
1368     * @return the path of the Page, or empty if not exists
1369     */
1370    public static String pagePath(String pageId)
1371    {
1372        try
1373        {
1374            Page page = _getPage(pageId);
1375            return page.getPathInSitemap();
1376        }
1377        catch (UnknownAmetysObjectException e)
1378        {
1379            _logger.error("Can not get title on page with id '" + pageId + "'", e);
1380            return "";
1381        }
1382    }
1383    
1384    /**
1385     * Returns the id of the current page.
1386     * @return the id of the current Page, or empty if there's no current page.
1387     */
1388    public static String pageId()
1389    {
1390        Page page = _getPageFromRequest();
1391        return page == null ? "" : page.getId();
1392    }
1393    
1394    private static Zone _getZone(Page page, String zoneName)
1395    {
1396        if (StringUtils.isNotBlank(zoneName) && page.hasZone(zoneName))
1397        {
1398            return page.getZone(zoneName);
1399        }
1400        
1401        return null;
1402    }
1403    
1404    private static Zone _getZoneFromRequest(Page page)
1405    {
1406        Request request = ContextHelper.getRequest(_context);
1407        String currentZoneName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_ZONE_NAME);
1408        if (StringUtils.isNotBlank(currentZoneName) && page.hasZone(currentZoneName))
1409        {
1410            return page.getZone(currentZoneName);
1411        }
1412        
1413        return null;
1414    }
1415    
1416    /**
1417     * Returns the id of the current zone item id.
1418     * @return the id of the current zone item id, or empty if there's no current zone item.
1419     */
1420    public static String zoneItemId()
1421    {
1422        ZoneItem zoneItem = _getZoneItemFromRequest();
1423        return zoneItem == null ? "" : StringUtils.defaultString(zoneItem.getId());
1424    }
1425
1426    private static ZoneItem _getZoneItem(String id)
1427    {
1428        return _ametysObjectResolver.resolveById(id);
1429    }
1430    
1431    private static ZoneItem _getZoneItemFromRequest()
1432    {
1433        Request request = ContextHelper.getRequest(_context);
1434        return (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM);
1435    }
1436    
1437    private static Content _getContentFromRequest()
1438    {
1439        Request request = ContextHelper.getRequest(_context);
1440        return (Content) request.getAttribute(Content.class.getName());
1441    }
1442    
1443    /**
1444     * Determines if the current zone item or (if there is no current zone item) the current page is cacheable
1445     * This method is only valid for a page.
1446     * @return true if the current zone item or page is cacheable.
1447     */
1448    public static boolean isCacheable()
1449    {
1450        Request request = ContextHelper.getRequest(_context);
1451        if (request.getAttribute("IsZoneItemCacheable") != null)
1452        {
1453            return (Boolean) request.getAttribute("IsZoneItemCacheable");
1454        }
1455        
1456        // The method was called from the skin, out of a zone item
1457        Response response = ContextHelper.getResponse(_context);
1458        if (response.containsHeader("X-Ametys-Cacheable"))
1459        {
1460            return true;
1461        }
1462        return false;
1463    }
1464    
1465    /**
1466     * Determines if we are in an edition mode
1467     * @return true if we are in edition mode
1468     */
1469    public static boolean isEditionMode()
1470    {
1471        RenderingContext renderingContext = _renderingContextHandler.getRenderingContext();
1472        Request request = ContextHelper.getRequest(_context);
1473        if (renderingContext == RenderingContext.FRONT && request.getParameter("_edition") != null && "true".equals(request.getParameter("_edition")))
1474        {
1475            return true;
1476        }
1477        return false;
1478    }
1479
1480    /**
1481     * Returns the id of pages referencing the content and for which the Front-office user can access
1482     * @param contentId The content's id
1483     * @return The pages' id
1484     */
1485    public static NodeList accessibleReferencedPages (String contentId)
1486    {
1487        RenderingContext renderingContext = _renderingContextHandler.getRenderingContext();
1488        boolean inBackOffice = renderingContext == RenderingContext.BACK || renderingContext == RenderingContext.PREVIEW;
1489        
1490        List<StringElement> pages = new ArrayList<>(); 
1491        
1492        Content content = _ametysObjectResolver.resolveById(contentId);
1493        if (content instanceof WebContent)
1494        {
1495            Collection<ZoneItem> zoneItems = ((WebContent) content).getReferencingZoneItems();
1496            
1497            for (ZoneItem zoneItem : zoneItems)
1498            {
1499                String metadataSetName = zoneItem.getViewName();
1500                Page page = zoneItem.getZone().getPage();
1501                
1502                if (inBackOffice || _rightManager.hasReadAccess(_currentUserProvider.getUser(), page))
1503                {
1504                    Map<String, String> attrs = new HashMap<>();
1505                    attrs.put("id", page.getId());
1506                    attrs.put("metadataSetName", metadataSetName);
1507                    pages.add(new StringElement("page", attrs));
1508                }
1509            }
1510        }
1511        
1512        return new AmetysNodeList(pages);
1513    }
1514    
1515    /**
1516     * Returns the id of pages referencing the content
1517     * @param contentId The content's id
1518     * @return The pages' id
1519     */
1520    public static NodeList referencedPages (String contentId)
1521    {
1522        List<StringElement> pages = new ArrayList<>(); 
1523        
1524        Content content = _ametysObjectResolver.resolveById(contentId);
1525        if (content instanceof WebContent)
1526        {
1527            Collection<ZoneItem> zoneItems = ((WebContent) content).getReferencingZoneItems();
1528            
1529            for (ZoneItem zoneItem : zoneItems)
1530            {
1531                String metadataSetName = zoneItem.getViewName();
1532                Page page = zoneItem.getZone().getPage();
1533                
1534                Map<String, String> attrs = new HashMap<>();
1535                attrs.put("id", page.getId());
1536                attrs.put("metadataSetName", metadataSetName);
1537                pages.add(new StringElement("page", attrs));
1538            }
1539        }
1540        
1541        return new AmetysNodeList(pages);
1542    }
1543    
1544    /**
1545     * Returns the ids of the contents
1546     * @param tag The tag id
1547     * @return Array of contents ids
1548     */
1549    public static NodeList findContentsIdsByTag(String tag)
1550    {
1551        Request request = ContextHelper.getRequest(_context);
1552        String siteName = (String) request.getAttribute("site");
1553
1554        String lang = (String) request.getAttribute("sitemapLanguage");
1555        if (lang == null)
1556        {
1557            // Try to get current language from content
1558            Content content = (Content) request.getAttribute(Content.class.getName());
1559            if (content != null)
1560            {
1561                lang = content.getLanguage();
1562            }
1563        }
1564        return findContentsIdsByTag(siteName, lang, tag);
1565    }
1566
1567    /**
1568     * Returns the ids of the contents
1569     * @param sitename site name. '+' for any site, '*'/null for any site, including contents without site, '^' for only contents without site
1570     * @param lang lang of the contents
1571     * @param tag The tag id
1572     * @return Array of contents ids
1573     */
1574    public static NodeList findContentsIdsByTag(String sitename, String lang, String tag)
1575    {
1576        List<Expression> expressions = new ArrayList<>();
1577        TagExpression tagExpression = new TagExpression(Operator.EQ, tag);
1578        expressions.add(tagExpression);
1579
1580        if (lang != null && !lang.equals("*"))
1581        {
1582            LanguageExpression le = new LanguageExpression(Operator.EQ, lang);
1583            expressions.add(le);
1584        }
1585
1586        if (sitename != null)
1587        {
1588            if (sitename.equals("+"))
1589            {
1590                MetadataExpression me = new MetadataExpression("site");
1591                expressions.add(me);
1592            }
1593            else if (sitename.equals("*"))
1594            {
1595                // no filter
1596            }
1597            else if (sitename.equals("^"))
1598            {
1599                MetadataExpression me = new MetadataExpression("site");
1600                expressions.add(new NotExpression(me));
1601            }
1602            else
1603            {
1604                StringExpression se = new StringExpression("site", Operator.EQ, sitename);
1605                expressions.add(se);
1606            }
1607        }
1608
1609        Expression[] expressionsArray = expressions.toArray(new Expression[expressions.size()]);
1610
1611        String xpath = ContentQueryHelper.getContentXPathQuery(new AndExpression(expressionsArray));
1612        AmetysObjectIterable<Content> contents = _ametysObjectResolver.query(xpath);
1613        Iterator<Content> it = contents.iterator();
1614
1615        List<StringElement> list = new ArrayList<>();
1616        while (it.hasNext())
1617        {
1618            list.add(new StringElement("content", "id", it.next().getId()));
1619        }
1620        return new AmetysNodeList(list);
1621    }
1622
1623    /**
1624     * Returns the ids of the pages tagged with the specified tag
1625     * @param sitename The site id
1626     * @param lang The language code
1627     * @param tag The tag id
1628     * @return Array of pages ids
1629     */
1630    public static NodeList findPagesIdsByTag(String sitename, String lang, String tag)
1631    {
1632        return findPagesIdsByTag(sitename, lang, tag, false);
1633    }
1634    
1635    /**
1636     * Returns the ids of the pages tagged with the specified tag
1637     * @param sitename The site id
1638     * @param lang The language code
1639     * @param tag The tag id
1640     * @param checkReadAccess true to return only pages with read access for current user
1641     * @return Array of pages ids
1642     */
1643    public static NodeList findPagesIdsByTag(String sitename, String lang, String tag, boolean checkReadAccess)
1644    {
1645        List<StringElement> list = new ArrayList<>(); 
1646        for (String pageId : _pageDAO.findPagedIdsByTag(sitename, lang, tag))
1647        {
1648            if (!checkReadAccess || _hasReadAccess(pageId))
1649            {
1650                list.add(new StringElement("page", "id", pageId));
1651            }
1652        }
1653
1654        return new AmetysNodeList(list);
1655    }
1656    
1657    private static boolean _hasReadAccess(String pageId)
1658    {
1659        try
1660        {
1661            Page page = _ametysObjectResolver.resolveById(pageId);
1662            return _rightManager.currentUserHasReadAccess(page);
1663        }
1664        catch (UnknownAmetysObjectException e)
1665        {
1666            return false;
1667        }
1668    }
1669    
1670    /**
1671     * Returns the ids of the pages tagged with the specified tag
1672     * @param tag The tag id
1673     * @return Array of pages ids
1674     */
1675    public static NodeList findPagesIdsByTag(String tag)
1676    {
1677        return findPagesIdsByTag(tag, false);
1678    }
1679    
1680    /**
1681     * Returns the ids of the pages tagged with the specified tag
1682     * @param tag The tag id
1683     * @param checkReadAccess true to return only pages with read access for current user
1684     * @return Array of pages ids
1685     */
1686    public static NodeList findPagesIdsByTag(String tag, boolean checkReadAccess)
1687    {
1688        Request request = ContextHelper.getRequest(_context);
1689        String siteName = (String) request.getAttribute("site");
1690        
1691        String lang = (String) request.getAttribute("sitemapLanguage");
1692        if (lang == null)
1693        {
1694            // Try to get current language from content
1695            Content content = (Content) request.getAttribute(Content.class.getName());
1696            if (content != null)
1697            {
1698                lang = content.getLanguage();
1699            }
1700        }
1701        
1702        return findPagesIdsByTag(siteName, lang, tag, checkReadAccess);
1703    }
1704    
1705    
1706    /**
1707     * Return the given user by its email over all authorized population on current site
1708     * @param email the concerned user's email
1709     * @return The informations about the given user
1710     * @throws SAXException If an error occurred while saxing the user
1711     */
1712    public static Node userByMail(String email) throws SAXException
1713    {
1714        String siteName = site();
1715        
1716        Set<String> userPopulationsOnSite = _populationContextHelper.getUserPopulationsOnContexts(Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName), false, false);
1717        
1718        for (String populationId : userPopulationsOnSite)
1719        {
1720            Node user = userByMail(email, populationId);
1721            if (user != null)
1722            {
1723                return user;
1724            }
1725        }
1726        
1727        return null;
1728    }
1729    
1730    /**
1731     * Get the preview of a url (title, description, image, favico)
1732     * @param url the web link
1733     * @return the url preview
1734     * @throws SAXException If an error occurred while saxing
1735     */
1736    public static Node urlPreview(String url) throws SAXException
1737    {
1738        try
1739        {
1740            String lang = StringUtils.defaultIfEmpty(lang(), "en");
1741            UrlPreview urlPreview = _urlPreview.getUrlPreview(url, lang);
1742            DOMBuilder domBuilder = new DOMBuilder();
1743            
1744            urlPreview.toSAX(domBuilder, "preview");
1745            
1746            return domBuilder.getDocument();
1747        }
1748        catch (IOException e)
1749        {
1750            _logger.error("Unable to get preview URL at " + url, e);
1751            return null;
1752        }
1753    }
1754    
1755    /**
1756     * Generates a XML structure to help to creates a complex pagination. 
1757     * <br>
1758     * <br>Example: pagination(39, 19, 2, 5, 2) will return
1759     * 
1760     * <pre>
1761     *   &lt;gotofirstpage enabled="true"&gt;1&lt;/gotofirstpage&gt;
1762     *   &lt;gotopreviouspage enabled="true"&gt;18&lt;/gotopreviouspage&gt;
1763     *   &lt;page&gt;1&lt;/page&gt;
1764     *   &lt;page&gt;2&lt;/page&gt;
1765     *   &lt;separator/&gt;
1766     *   &lt;page&gt;17&lt;/page&gt;
1767     *   &lt;page&gt;18&lt;/page&gt;
1768     *   &lt;current&gt;19&lt;/current&gt;
1769     *   &lt;page&gt;20&lt;/page&gt;
1770     *   &lt;page&gt;21&lt;/page&gt;
1771     *   &lt;separator/&gt;
1772     *   &lt;page&gt;38&lt;/page&gt;
1773     *   &lt;page&gt;39&lt;/page&gt;
1774     *   &lt;gotonextpage enabled="true"&gt;20&lt;/gotonextpage&gt;
1775     *   &lt;gotolastpage enabled="true"&gt;39&lt;/gotonextpage&gt;
1776     * </pre>
1777     *   
1778     * Example: pagination(5, 2, 2, 5, 2) will return
1779     *   
1780     * <pre>
1781     *   &lt;gotofirstpage enabled="true"&gt;1&lt;/gotofirstpage&gt;
1782     *   &lt;gotopreviouspage enabled="true"&gt;1&lt;/gotopreviouspage&gt;
1783     *   &lt;page&gt;1&lt;/page&gt;
1784     *   &lt;current&gt;2&lt;/page&gt;
1785     *   &lt;page&gt;3&lt;/page&gt;
1786     *   &lt;page&gt;4&lt;/page&gt;
1787     *   &lt;page&gt;5&lt;/page&gt;
1788     *   &lt;space/&gt;
1789     *   &lt;space/&gt;
1790     *   &lt;space/&gt;
1791     *   &lt;space/&gt;
1792     *   &lt;gotonextpage enabled="true"&gt;3&lt;/gotonextpage&gt;
1793     *   &lt;gotolastpage enabled="true"&gt;5&lt;/gotonextpage&gt;
1794     * </pre>
1795     *   
1796     * @param nbPages The total number of pages
1797     * @param currentPage The currently displayed page (1 based)
1798     * @param nbFirstPages The max number of pages to display before the 1st separator
1799     * @param nbCentralPages The max number of pages to display around the current page
1800     * @param nbLastPages The max number of pages to display after the 2nd separator
1801     * @return The xml described
1802     */
1803    public static AmetysNodeList pagination(int nbPages, int currentPage, int nbFirstPages, int nbCentralPages, int nbLastPages)
1804    {
1805        List<Node> elements = new ArrayList<>();
1806
1807        
1808        elements.add(new StringElement("gotofirstpage", "enabled", Boolean.toString(currentPage > 1), "1"));
1809        elements.add(new StringElement("gotopreviouspage", "enabled", Boolean.toString(currentPage > 1), currentPage > 1 ? currentPage - 1 + "" : ""));
1810
1811        int[] pagination = _pagination(nbPages, currentPage, nbFirstPages, nbCentralPages, nbLastPages);
1812        for (int page : pagination)
1813        {
1814            if (page == _PAGINATION_SEPARATOR)
1815            {
1816                elements.add(new StringElement("separator", ""));
1817            }
1818            else if (page == _PAGINATION_SPACE)
1819            {
1820                elements.add(new StringElement("space", ""));
1821            }
1822            else if (page == _PAGINATION_CURRENT)
1823            {
1824                elements.add(new StringElement("current", Integer.toString(currentPage)));
1825            }
1826            else
1827            {
1828                elements.add(new StringElement("page", Integer.toString(page)));
1829            }
1830        }
1831        
1832        elements.add(new StringElement("gotonextpage", "enabled", Boolean.toString(currentPage < nbPages), currentPage < nbPages ? currentPage + 1 + "" : ""));
1833        elements.add(new StringElement("gotolastpage", "enabled", Boolean.toString(currentPage < nbPages), nbPages + ""));
1834
1835        return new AmetysNodeList(elements);
1836    }
1837    
1838    static int[] _pagination(int nbPages, int currentPage, int nbFirstPages, int nbCentralPages, int nbLastPages)
1839    {
1840        int displayedPages = nbFirstPages + 1 + nbCentralPages + 1 + nbLastPages;  // The +1 are the room for separators
1841        
1842        int[] values = new int[displayedPages];
1843
1844        int centerOfCentralPages = (int) Math.ceil(nbCentralPages / 2.0);
1845        int centralCursor = nbFirstPages + 1 + centerOfCentralPages;
1846        boolean firstSeparator = nbPages > displayedPages && currentPage > centralCursor;
1847        boolean secondSeparator = nbPages > displayedPages && currentPage <= nbPages - centralCursor;
1848        
1849        int cursor = 1;
1850        
1851        // Before first separator
1852        cursor = _paginationFirstPages(nbPages, nbFirstPages, currentPage, values, cursor);
1853        
1854        // First separator
1855        cursor = _paginationFirstSeparator(nbPages, firstSeparator, currentPage, values, cursor);
1856
1857        int offset = _paginationComputeOffsetAfterFirstSeparator(nbPages, currentPage, nbCentralPages, nbLastPages, centerOfCentralPages, firstSeparator, secondSeparator, cursor);
1858        
1859        // Middle part
1860        cursor = _paginationMiddle(nbPages, currentPage, nbFirstPages, nbCentralPages, values, cursor, offset);
1861        
1862        // Second separator
1863        cursor = _paginationSecondSeparator(nbPages, secondSeparator, currentPage, nbLastPages, values, cursor, offset);
1864        
1865        // After second separator
1866        cursor = _paginationLastPages(nbPages, currentPage, displayedPages, values, cursor, offset);
1867        
1868        return values;
1869    }
1870
1871    private static int _paginationMiddle(int nbPages, int currentPage, int nbFirstPages, int nbCentralPages, int[] values, int cursorP, int offset)
1872    {
1873        int cursor = cursorP;
1874        for (; cursor <= nbFirstPages + 1 + nbCentralPages; cursor++)
1875        {
1876            if (cursor + offset > nbPages)
1877            {
1878                values[cursor - 1] = _PAGINATION_SPACE;
1879            }
1880            else if (cursor + offset == currentPage)
1881            {
1882                values[cursor - 1] = _PAGINATION_CURRENT;
1883            }
1884            else
1885            {
1886                values[cursor - 1] = offset + cursor;
1887            }
1888        }
1889        return cursor;
1890    }
1891
1892    private static int _paginationComputeOffsetAfterFirstSeparator(int nbPages, int currentPage, int nbCentralPages, int nbLastPages, int centerOfCentralPages, boolean firstSeparator, boolean secondSeparator, int cursor)
1893    {
1894        if (!firstSeparator)
1895        {
1896            return 0;
1897        }
1898        else if (!secondSeparator)
1899        {
1900            return nbPages - nbLastPages - nbCentralPages - cursor;
1901        }
1902        else
1903        {
1904            return currentPage + 1 - centerOfCentralPages - cursor;
1905        }
1906    }
1907
1908    private static int _paginationLastPages(int nbPages, int currentPage, int displayedPages, int[] values, int cursorP, int offset)
1909    {
1910        int cursor = cursorP;
1911        for (; cursor <= displayedPages; cursor++)
1912        {
1913            if (cursor > nbPages)
1914            {
1915                values[cursor - 1] = _PAGINATION_SPACE;
1916            }
1917            else if (cursor + offset == currentPage)
1918            {
1919                values[cursor - 1] = _PAGINATION_CURRENT;
1920            }
1921            else 
1922            {
1923                values[cursor - 1] = nbPages - (displayedPages - cursor);
1924            }
1925        }
1926        return cursor;
1927    }
1928
1929    private static int _paginationSecondSeparator(int nbPages, boolean secondSeparator, int currentPage, int nbLastPages, int[] values, int cursor, int offset)
1930    {
1931        if (cursor + offset > nbPages)
1932        {
1933            values[cursor - 1] = _PAGINATION_SPACE;
1934        }
1935        else if (currentPage == cursor + offset)
1936        {
1937            values[cursor - 1] = _PAGINATION_CURRENT;
1938        }
1939        else if (secondSeparator)
1940        {
1941            values[cursor - 1] = _PAGINATION_SEPARATOR;
1942        }
1943        else 
1944        {
1945            values[cursor - 1] = nbPages - nbLastPages;
1946        }
1947        return cursor + 1;
1948    }
1949
1950    private static int _paginationFirstSeparator(int nbPages, boolean firstSeparator, int currentPage, int[] values, int cursor)
1951    {
1952        if (cursor > nbPages)
1953        {
1954            values[cursor - 1] = _PAGINATION_SPACE;
1955        }
1956        else if (currentPage == cursor)
1957        {
1958            values[cursor - 1] = _PAGINATION_CURRENT;
1959        }
1960        else if (firstSeparator)
1961        {
1962            values[cursor - 1] = _PAGINATION_SEPARATOR;
1963        }
1964        else 
1965        {
1966            values[cursor - 1] = cursor;
1967        }
1968        return cursor + 1;
1969    }
1970
1971    private static int _paginationFirstPages(int nbPages, int nbFirstPages, int currentPage, int[] values, int cursorP)
1972    {
1973        int cursor = cursorP;
1974        for (; cursor <= nbFirstPages; cursor++)
1975        {
1976            if (cursor > nbPages)
1977            {
1978                values[cursor - 1] = _PAGINATION_SPACE;
1979            }
1980            else if (cursor == currentPage)
1981            {
1982                values[cursor - 1] = _PAGINATION_CURRENT;
1983            }
1984            else 
1985            {
1986                values[cursor - 1] = cursor;
1987            }
1988        }
1989        return cursor;
1990    }
1991    
1992    /**
1993     * Get the parameters' values of the current template
1994     * @return the values of template's parameters
1995     */
1996    public static NodeList templateParameters()
1997    {
1998        List<Node> parameters = new ArrayList<>();
1999        
2000        Page page = _getPageFromRequest();
2001        if (page != null)
2002        {
2003            Optional<ViewParametersModel> viewParametersModelOptional = _viewParametersManager.getTemplateViewParametersModel(page.getSite().getSkinId(), page.getTemplate());
2004            if (viewParametersModelOptional.isPresent())
2005            {
2006                ViewParametersModel viewParametersModel = viewParametersModelOptional.get();
2007                
2008                Collection< ? extends ModelItem> modelItems = viewParametersModel.getModelItems();
2009                for (ModelItem modelItem : modelItems)
2010                {
2011                    Optional<ViewParameterHolder> templateParameterHolderOptional = _viewParametersManager.getTemplateViewParameterHolderWithInheritance(page, modelItem);
2012                    if (templateParameterHolderOptional.isPresent())
2013                    {
2014                        ViewParameterHolder templateParameterHolder = templateParameterHolderOptional.get();
2015                        List<Node> values = _getNodeValues(templateParameterHolder.getDataHolder(), templateParameterHolder.getPath(), null);
2016                        if (values != null)
2017                        {
2018                            parameters.addAll(values);
2019                        }
2020                    }
2021                }
2022            }
2023        }
2024        
2025        return new AmetysNodeList(parameters);
2026    }
2027    
2028    /**
2029     * Returns the value of the given parameter for the current page template, or the empty node if the parameter does not exist.
2030     * @param parameterPath the parameter path.
2031     * @return the value of the given parameter for the current page template.
2032     */
2033    public static NodeList templateParameter(String parameterPath)
2034    {
2035        return templateParameter(null, parameterPath);
2036    }
2037    
2038    /**
2039     * Returns the value of the given parameter for the given page template, or the empty node if the parameter does not exist.
2040     * @param pageId the page id
2041     * @param parameterPath the parameter path.
2042     * @return the value of the given parameter for the given page template.
2043     */
2044    public static NodeList templateParameter(String pageId, String parameterPath)
2045    {
2046        Page page = StringUtils.isNotBlank(pageId) ? _getPage(pageId) : _getPageFromRequest();
2047        if (page != null)
2048        {
2049            Optional<ViewParameterHolder> templateParameterHolderOptional = _viewParametersManager.getTemplateViewParameterHolderWithInheritance(page, parameterPath);
2050            if (templateParameterHolderOptional.isPresent())
2051            {
2052                ViewParameterHolder templateParameterHolder = templateParameterHolderOptional.get();
2053                List<Node> values = _getNodeValues(templateParameterHolder.getDataHolder(), templateParameterHolder.getPath(), null);
2054                if (values != null)
2055                {
2056                    return new AmetysNodeList(values);
2057                }
2058            }
2059        }
2060        
2061        return null;
2062    }
2063    
2064    /**
2065     * Get the parameters of the current zone
2066     * @return the zone's parameters
2067     */
2068    public static NodeList zoneParameters()
2069    {
2070        return zoneParameters(null);
2071    }
2072    
2073    /**
2074     * Get the parameters of a given zone
2075     * @param zoneName The zone's name. Can be null or empty to get the current zone.
2076     * @return the zone's parameters
2077     */
2078    public static NodeList zoneParameters(String zoneName)
2079    {
2080        List<Node> parameters = new ArrayList<>();
2081        
2082        Page page = _getPageFromRequest();
2083        if (page != null)
2084        {
2085            Zone zone = _getZoneForParameter(page, zoneName);
2086            if (zone != null)
2087            {
2088                Optional<ViewParametersModel> viewParametersModelOptional = _viewParametersManager.getZoneViewParametersModel(page.getSite().getSkinId(), page.getTemplate(), zone.getName());
2089                if (viewParametersModelOptional.isPresent())
2090                {
2091                    ViewParametersModel viewParametersModel = viewParametersModelOptional.get();
2092                    
2093                    Collection< ? extends ModelItem> modelItems = viewParametersModel.getModelItems();
2094                    for (ModelItem modelItem : modelItems)
2095                    {
2096                        Optional<ViewParameterHolder> zoneParameterHolderOptional = _viewParametersManager.getZoneViewParameterHolderWithInheritance(page, zone, modelItem);
2097                        if (zoneParameterHolderOptional.isPresent())
2098                        {
2099                            ViewParameterHolder zoneParameterHolder = zoneParameterHolderOptional.get();
2100                            List<Node> values = _getNodeValues(zoneParameterHolder.getDataHolder(), zoneParameterHolder.getPath(), null);
2101                            if (values != null)
2102                            {
2103                                parameters.addAll(values);
2104                            }
2105                        }
2106                    }
2107                }
2108            }
2109        }
2110        
2111        return new AmetysNodeList(parameters);
2112    }
2113    
2114    /**
2115     * Returns the value of the given parameter for the current page and current zone, or the empty node if the parameter does not exist.
2116     * @param parameterPath the parameter path.
2117     * @return the value of the given parameter for the current page and current zone.
2118     */
2119    public static NodeList zoneParameter(String parameterPath)
2120    {
2121        return zoneParameter(null, null, parameterPath);
2122    }
2123    
2124    /**
2125     * Returns the value of the given parameter for the current page and given zone, or the empty node if the parameter does not exist.
2126     * @param zoneName the zone name
2127     * @param parameterPath the parameter path.
2128     * @return the value of the given parameter for the current page and given zone.
2129     */
2130    public static NodeList zoneParameter(String zoneName, String parameterPath)
2131    {
2132        return zoneParameter(null, zoneName, parameterPath);
2133    }
2134    
2135    /**
2136     * Returns the value of the given parameter for the given page and given zone name, or the empty node if the parameter does not exist.
2137     * @param pageId the page id
2138     * @param zoneName the zone name
2139     * @param parameterPath the parameter path.
2140     * @return the value of the given parameter for the given page and given zone name.
2141     */
2142    public static NodeList zoneParameter(String pageId, String zoneName, String parameterPath)
2143    {
2144        Page page = StringUtils.isNotBlank(pageId) ? _getPage(pageId) : _getPageFromRequest();
2145        if (page != null)
2146        {
2147            Zone zone = _getZoneForParameter(page, zoneName);
2148            if (zone != null)
2149            {
2150                Optional<ViewParameterHolder> zoneParameterHolderOptional = _viewParametersManager.getZoneViewParameterHolderWithInheritance(page, zone, parameterPath);
2151                if (zoneParameterHolderOptional.isPresent())
2152                {
2153                    ViewParameterHolder zoneParameterHolder = zoneParameterHolderOptional.get();
2154                    List<Node> values = _getNodeValues(zoneParameterHolder.getDataHolder(), zoneParameterHolder.getPath(), null);
2155                    if (values != null)
2156                    {
2157                        return new AmetysNodeList(values);
2158                    }
2159                }
2160            }
2161        }
2162        
2163        return null;
2164    }
2165    
2166    /**
2167     * Get the parameters of the current zone item
2168     * @return the zone items's  parameters
2169     */
2170    public static NodeList zoneItemParameters()
2171    {
2172        return zoneItemParameters(null);
2173    }
2174    
2175    /**
2176     * Get the parameters of a given zone item
2177     * @param zoneItemId The id of zone item. Can be null or empty to get the current zone item
2178     * @return the zone items's  parameters
2179     */
2180    public static NodeList zoneItemParameters(String zoneItemId)
2181    {
2182        List<Node> parameters = new ArrayList<>();
2183        
2184        ZoneItem zoneItem = StringUtils.isNotBlank(zoneItemId) ? _getZoneItem(zoneItemId) : _getZoneItemFromRequest();
2185        if (zoneItem != null)
2186        {
2187            Optional<ViewParametersModel> viewParametersModelOptional = _viewParametersManager.getZoneItemViewParametersModel(zoneItem);
2188            if (viewParametersModelOptional.isPresent())
2189            {
2190                Collection< ? extends ModelItem> modelItems = viewParametersModelOptional.get().getModelItems();
2191                for (ModelItem modelItem : modelItems)
2192                {
2193                    List<Node> values = _getNodeValues(zoneItem.getZoneItemParametersHolder(), modelItem.getPath(), null);
2194                    if (values != null)
2195                    {
2196                        parameters.addAll(values);
2197                    }
2198                }
2199            }
2200        }
2201        
2202        return new AmetysNodeList(parameters);
2203    }
2204    
2205    /**
2206     * Returns the value of the given parameter for the current zone item, or the empty node if the parameter does not exist.
2207     * @param parameterPath the parameter path.
2208     * @return the value of the given parameter for the current zone item.
2209     */
2210    public static NodeList zoneItemParameter(String parameterPath)
2211    {
2212        return zoneItemParameter(null, parameterPath);
2213    }
2214    
2215    /**
2216     * Returns the value of the given parameter for the given zone item, or the empty node if the parameter does not exist.
2217     * @param zoneItemId the zone item id
2218     * @param parameterPath the parameter path.
2219     * @return the value of the given parameter for the given zone item.
2220     */
2221    public static NodeList zoneItemParameter(String zoneItemId, String parameterPath)
2222    {
2223        ZoneItem zoneItem = StringUtils.isNotBlank(zoneItemId) ? _getZoneItem(zoneItemId) : _getZoneItemFromRequest();
2224        if (zoneItem != null)
2225        {
2226            ModelAwareDataHolder dataHolder = zoneItem.getZoneItemParametersHolder();
2227            List<Node> values = _getNodeValues(dataHolder, parameterPath, null);
2228            if (values != null)
2229            {
2230                return new AmetysNodeList(values);
2231            }
2232        }
2233        
2234        return null;
2235    }
2236    
2237    /**
2238     * Get the parameters of the current service
2239     * @return the service's parameters
2240     */
2241    public static NodeList serviceViewParameters()
2242    {
2243        List<Node> parameters = new ArrayList<>();
2244        
2245        ZoneItem zoneItem = _getZoneItemFromRequest();
2246        if (zoneItem != null)
2247        {
2248            if (zoneItem.getType() == ZoneType.SERVICE)
2249            {
2250                Optional<ViewParametersModel> serviceViewParametersModel = _viewParametersManager.getServiceViewParametersModel(zoneItem);
2251                if (serviceViewParametersModel.isPresent())
2252                {
2253                    String viewName = zoneItem.getServiceParameters().getValue(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME);
2254                    
2255                    Collection< ? extends ModelItem> modelItems = serviceViewParametersModel.get().getModelItems();
2256                    for (ModelItem modelItem : modelItems)
2257                    {
2258                        List<Node> values = _getNodeValues(zoneItem.getServiceViewParametersHolder(viewName), modelItem.getPath(), null);
2259                        if (values != null)
2260                        {
2261                            parameters.addAll(values);
2262                        }
2263                    }
2264                }
2265            }
2266        }
2267        
2268        return new AmetysNodeList(parameters);
2269    }
2270    
2271    /**
2272     * Returns the value of the given parameter for the current service, or the empty node if the parameter does not exist.
2273     * @param parameterPath the parameter path.
2274     * @return the value of the given parameter for the current service.
2275     */
2276    public static NodeList serviceViewParameter(String parameterPath)
2277    {
2278        ZoneItem zoneItem = _getZoneItemFromRequest();
2279        if (zoneItem != null)
2280        {
2281            if (zoneItem.getType() == ZoneType.SERVICE)
2282            {
2283                Optional<ModelAwareDataHolder> serviceViewParametersHolder = _viewParametersManager.getServiceViewParametersHolder(zoneItem);
2284                if (serviceViewParametersHolder.isPresent())
2285                {
2286                    List<Node> values = _getNodeValues(serviceViewParametersHolder.get(), parameterPath, null);
2287                    if (values != null)
2288                    {
2289                        return new AmetysNodeList(values);
2290                    }
2291                }
2292            }
2293        }
2294        
2295        return null;
2296    }
2297    
2298    /**
2299     * Get the parameters of the current content
2300     * @return the content's parameters
2301     */
2302    public static NodeList contentViewParameters()
2303    {
2304        List<Node> parameters = new ArrayList<>();
2305        
2306        ZoneItem zoneItem = _getZoneItemFromRequest();
2307        if (zoneItem != null)
2308        {
2309            if (zoneItem.getType() == ZoneType.CONTENT)
2310            {
2311                Content content = _getContentFromRequest();
2312                Content zoneItemContent = zoneItem.getContent();
2313                
2314                // Test if the content in the zone item is the same in the request. For example, can have different content if a content call the method AmetysXSLTHelper#getContentView
2315                if (content != null && content.getId().equals(zoneItemContent.getId()))
2316                {
2317                    Optional<ViewParametersModel> contentViewParametersModel = _viewParametersManager.getContentViewParametersModel(zoneItem);
2318                    if (contentViewParametersModel.isPresent())
2319                    {
2320                        String viewName = zoneItem.getViewName();
2321                        Collection< ? extends ModelItem> modelItems = contentViewParametersModel.get().getModelItems();
2322                        for (ModelItem modelItem : modelItems)
2323                        {
2324                            List<Node> values = _getNodeValues(zoneItem.getContentViewParametersHolder(viewName), modelItem.getPath(), null, DataContext.newInstance().withObjectId(content.getId()));
2325                            if (values != null)
2326                            {
2327                                parameters.addAll(values);
2328                            }
2329                        }
2330                    }
2331                }
2332            }
2333        }
2334        
2335        return new AmetysNodeList(parameters);
2336    }
2337    
2338    /**
2339     * Returns the value of the given parameter for the current content, or the empty node if the parameter does not exist.
2340     * @param parameterPath the parameter path.
2341     * @return the value of the given parameter for the current content.
2342     */
2343    public static NodeList contentViewParameter(String parameterPath)
2344    {
2345        ZoneItem zoneItem = _getZoneItemFromRequest();
2346        if (zoneItem != null)
2347        {
2348            if (zoneItem.getType() == ZoneType.CONTENT)
2349            {
2350                Content content = _getContentFromRequest();
2351                Content zoneItemContent = zoneItem.getContent();
2352                
2353                // Test if the content in the zone item is the same in the request. For example, can have different content if a content call the method AmetysXSLTHelper#getContentView
2354                if (content != null && content.getId().equals(zoneItemContent.getId()))
2355                {
2356                    Optional<ModelAwareDataHolder> contentViewParametersHolder = _viewParametersManager.getContentViewParametersHolder(zoneItem);
2357                    if (contentViewParametersHolder.isPresent())
2358                    {
2359                        List<Node> values = _getNodeValues(contentViewParametersHolder.get(), parameterPath, null, DataContext.newInstance().withObjectId(content.getId()));
2360                        if (values != null)
2361                        {
2362                            return new AmetysNodeList(values);
2363                        }
2364                    }
2365                }
2366            }
2367        }
2368        
2369        return null;
2370    }
2371    
2372    private static Zone _getZoneForParameter(Page page, String zoneName)
2373    {
2374        Zone zone = _getZone(page, zoneName);
2375        if (zone == null)
2376        {
2377            zone = _getZoneFromRequest(page);
2378            if (zone == null)
2379            {
2380                ZoneItem zoneItem = _getZoneItemFromRequest();
2381                if (zoneItem != null)
2382                {
2383                    zone = zoneItem.getZone();
2384                }
2385            }
2386        }
2387        
2388        return zone;
2389    }
2390}