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