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