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