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