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