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