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