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