001/*
002 *  Copyright 2015 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 */
016package org.ametys.web.repository.page;
017
018import java.time.LocalDate;
019import java.time.ZonedDateTime;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Locale;
029import java.util.Map;
030import java.util.Set;
031
032import javax.jcr.Node;
033import javax.jcr.Repository;
034import javax.jcr.RepositoryException;
035import javax.jcr.Session;
036
037import org.apache.avalon.framework.component.Component;
038import org.apache.avalon.framework.logger.AbstractLogEnabled;
039import org.apache.avalon.framework.service.ServiceException;
040import org.apache.avalon.framework.service.ServiceManager;
041import org.apache.avalon.framework.service.Serviceable;
042import org.apache.commons.lang.StringUtils;
043
044import org.ametys.cms.FilterNameHelper;
045import org.ametys.cms.contenttype.ContentType;
046import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
047import org.ametys.cms.languages.Language;
048import org.ametys.cms.repository.Content;
049import org.ametys.cms.repository.ContentDAO.TagMode;
050import org.ametys.cms.repository.ModifiableContent;
051import org.ametys.cms.repository.TaggableAmetysObject;
052import org.ametys.cms.repository.WorkflowAwareContent;
053import org.ametys.cms.tag.CMSTag;
054import org.ametys.cms.tag.Tag;
055import org.ametys.cms.tag.TagProviderExtensionPoint;
056import org.ametys.core.observation.Event;
057import org.ametys.core.observation.ObservationManager;
058import org.ametys.core.right.RightManager;
059import org.ametys.core.right.RightManager.RightResult;
060import org.ametys.core.ui.Callable;
061import org.ametys.core.user.CurrentUserProvider;
062import org.ametys.core.user.UserIdentity;
063import org.ametys.plugins.explorer.ExplorerNode;
064import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
065import org.ametys.plugins.explorer.resources.Resource;
066import org.ametys.plugins.repository.AmetysObject;
067import org.ametys.plugins.repository.AmetysObjectIterable;
068import org.ametys.plugins.repository.AmetysObjectIterator;
069import org.ametys.plugins.repository.AmetysObjectResolver;
070import org.ametys.plugins.repository.AmetysRepositoryException;
071import org.ametys.plugins.repository.CopiableAmetysObject;
072import org.ametys.plugins.repository.ModifiableAmetysObject;
073import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
074import org.ametys.plugins.repository.RemovableAmetysObject;
075import org.ametys.plugins.repository.TraversableAmetysObject;
076import org.ametys.plugins.repository.UnknownAmetysObjectException;
077import org.ametys.plugins.repository.jcr.JCRAmetysObject;
078import org.ametys.plugins.repository.jcr.SimpleAmetysObject;
079import org.ametys.plugins.repository.lock.LockHelper;
080import org.ametys.plugins.repository.lock.LockableAmetysObject;
081import org.ametys.plugins.repository.version.VersionableAmetysObject;
082import org.ametys.plugins.workflow.support.WorkflowProvider;
083import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
084import org.ametys.runtime.i18n.I18nizableText;
085import org.ametys.runtime.model.type.ElementType;
086import org.ametys.runtime.model.type.ModelItemTypeConstants;
087import org.ametys.web.ObservationConstants;
088import org.ametys.web.WebConstants;
089import org.ametys.web.alias.Alias.TargetType;
090import org.ametys.web.alias.AliasHelper;
091import org.ametys.web.alias.DefaultAlias;
092import org.ametys.web.languages.WebLanguagesManager;
093import org.ametys.web.repository.content.SharedContent;
094import org.ametys.web.repository.content.WebContent;
095import org.ametys.web.repository.content.WebContentDAO;
096import org.ametys.web.repository.content.shared.SharedContentManager;
097import org.ametys.web.repository.page.Page.LinkType;
098import org.ametys.web.repository.page.Page.PageType;
099import org.ametys.web.repository.page.ZoneItem.ZoneType;
100import org.ametys.web.repository.page.jcr.DefaultPage;
101import org.ametys.web.repository.site.Site;
102import org.ametys.web.repository.sitemap.Sitemap;
103import org.ametys.web.rights.PageRightAssignmentContext;
104import org.ametys.web.service.Service;
105import org.ametys.web.service.ServiceExtensionPoint;
106import org.ametys.web.skin.Skin;
107import org.ametys.web.skin.SkinTemplate;
108import org.ametys.web.skin.SkinTemplateZone;
109import org.ametys.web.skin.SkinsManager;
110import org.ametys.web.skin.TemplatesAssignmentHandler;
111import org.ametys.web.synchronization.SynchronizeComponent;
112
113/**
114 * DAO for manipulating pages
115 *
116 */
117public class PageDAO extends AbstractLogEnabled implements Serviceable, Component
118{
119    /** Constant for untouched binary metadata. */
120    public static final String __SERVICE_PARAM_UNTOUCHED_BINARY = "untouched";
121    
122    /** Avalon Role */
123    public static final String ROLE = PageDAO.class.getName();
124    
125    private AmetysObjectResolver _resolver;
126    private ObservationManager _observationManager;
127    private CurrentUserProvider _currentUserProvider;
128    private SkinsManager _skinsManager;
129    private TemplatesAssignmentHandler _templatesHandler;
130    private ServicesAssignmentHandler _serviceHandler;
131    private ContentTypesAssignmentHandler _cTypeHandler;
132    private ContentTypeExtensionPoint _contentTypeExtensionPoint;
133    private ServiceExtensionPoint _serviceExtensionPoint;
134    private WorkflowProvider _workflowProvider;
135    private SharedContentManager _sharedContentManager;
136    private TagProviderExtensionPoint _tagProvider;
137    private WebLanguagesManager _webLanguagesManager;
138    private WebContentDAO _contentDAO;
139    private CopySiteComponent _copySiteComponent;
140    private RightManager _rightManager;
141    private SynchronizeComponent _synchronizeComponent;
142    private Repository _repository;
143    private PageDataTypeExtensionPoint _pageDataTypeExtensionPoint;
144    
145    @Override
146    public void service(ServiceManager smanager) throws ServiceException
147    {
148        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
149        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
150        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
151        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
152        _templatesHandler = (TemplatesAssignmentHandler) smanager.lookup(TemplatesAssignmentHandler.ROLE);
153        _cTypeHandler = (ContentTypesAssignmentHandler) smanager.lookup(ContentTypesAssignmentHandler.ROLE);
154        _serviceHandler = (ServicesAssignmentHandler) smanager.lookup(ServicesAssignmentHandler.ROLE);
155        _cTypeHandler = (ContentTypesAssignmentHandler) smanager.lookup(ContentTypesAssignmentHandler.ROLE);
156        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
157        _serviceExtensionPoint = (ServiceExtensionPoint) smanager.lookup(ServiceExtensionPoint.ROLE);
158        _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE);
159        _sharedContentManager = (SharedContentManager) smanager.lookup(SharedContentManager.ROLE);
160        _tagProvider = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE);
161        _webLanguagesManager = (WebLanguagesManager) smanager.lookup(WebLanguagesManager.ROLE);
162        _contentDAO = (WebContentDAO) smanager.lookup(WebContentDAO.ROLE);
163        _copySiteComponent = (CopySiteComponent) smanager.lookup(CopySiteComponent.ROLE);
164        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
165        _synchronizeComponent = (SynchronizeComponent) smanager.lookup(SynchronizeComponent.ROLE);
166        _repository = (Repository) smanager.lookup(Repository.class.getName());
167        _pageDataTypeExtensionPoint = (PageDataTypeExtensionPoint) smanager.lookup(PageDataTypeExtensionPoint.ROLE);
168    }
169    
170    /**
171     * Get the properties of given pages
172     * @param pageIds the id of pages
173     * @return the properties of pages in a result map
174     */ 
175    @Callable
176    public Map<String, Object> getPagesInfos (List<String> pageIds)
177    {
178        Map<String, Object> result = new HashMap<>();
179        
180        List<Map<String, Object>> pages = new ArrayList<>();
181        List<String> pagesNotFound = new ArrayList<>();
182        
183        for (String pageId : pageIds)
184        {
185            try
186            {
187                Page page = _resolver.resolveById(pageId);
188                pages.add(getPageInfos(page));
189            }
190            catch (UnknownAmetysObjectException e)
191            {
192                pagesNotFound.add(pageId);
193            }
194        }
195        
196        result.put("pages", pages);
197        result.put("pagesNotFound", pagesNotFound);
198        
199        return result;
200    }
201    
202    /**
203     * Get the page's properties
204     * @param pageId the page ID
205     * @return the properties
206     */
207    @Callable
208    public Map<String, Object> getPageInfos (String pageId)
209    {
210        Page page = _resolver.resolveById(pageId);
211        return getPageInfos(page);
212    }
213    
214    /**
215     * Get the page's properties
216     * @param page the page
217     * @return the properties
218     */
219    public Map<String, Object> getPageInfos (Page page)
220    {
221        Map<String, Object> infos = new HashMap<>();
222        
223        infos.put("id", page.getId());
224        infos.put("name", page.getName());
225        infos.put("parentId", page.getParent().getId());
226        infos.put("title", page.getTitle());
227        infos.put("longTitle", page.getLongTitle());
228        infos.put("path", page.getPathInSitemap());
229        infos.put("siteName", page.getSiteName());
230        infos.put("type", page.getType());
231        infos.put("lang", page.getSitemapName());
232        infos.put("isModifiable", page instanceof ModifiablePage);
233        infos.put("isMoveable", page instanceof MoveablePage);
234        infos.put("isTaggable", page instanceof TaggableAmetysObject);
235        infos.put("isVisible", page.isVisible());
236        infos.put("isParentInvisible", _isParentInvisible(page));
237        // Publication information
238        infos.put("publication", _publication2Json(page));
239        infos.put("isPreviewable", _isPreviewable(page));
240
241        String skinId = page.getSite().getSkinId();
242        Skin skin = _skinsManager.getSkin(skinId);
243        infos.put("isPageValid", _synchronizeComponent.isPageValid(page, skin));
244
245        Session liveSession = null;
246        try
247        {
248            liveSession = _repository.login(WebConstants.LIVE_WORKSPACE);
249            infos.put("isLiveHierarchyValid", _synchronizeComponent.isHierarchyValid(page, liveSession));
250        }
251        catch (RepositoryException e)
252        {
253            throw new RuntimeException("Unable to check live workspace", e);
254        }
255        finally
256        {
257            if (liveSession != null)
258            {
259                liveSession.logout();
260            }
261        }
262
263        PageType type = page.getType();
264        switch (type)
265        {
266            case CONTAINER:
267                infos.put("template", page.getTemplate());
268                
269                List<Map<String, Object>> zones = new ArrayList<>();
270                for (Zone zone : page.getZones())
271                {
272                    zones.add(_zone2json(zone));
273                }
274                infos.put("zones", zones);
275                break;
276            
277            case LINK:
278                infos.put("url", page.getURL());
279                
280                LinkType urlType = page.getURLType();
281                infos.put("urlType", urlType.toString());
282                
283                switch (urlType)
284                {
285                    case PAGE:
286                        try
287                        {
288                            Page targetPage = _resolver.resolveById(page.getURL());
289                            infos.put("urlTitle", targetPage.getTitle());
290                        }
291                        catch (UnknownAmetysObjectException e)
292                        {
293                            getLogger().error("Page '" + page.getId() + "' redirects to an unexisting page '" + page.getURL() + "'");
294                        }
295                        break;
296
297                    default:
298                        break;
299                }
300                break;
301            default:
302                AmetysObjectIterator< ? extends Page> iterator = page.getChildrenPages().iterator();
303                if (iterator.hasNext())
304                {
305                    Page firstSubPage = iterator.next();
306                    infos.put("url", firstSubPage.getId());
307                    infos.put("urlTitle", firstSubPage.getTitle());
308                    infos.put("urlType", LinkType.PAGE.toString());
309                    break;
310
311                }
312                break;
313        }
314        
315        
316        infos.put("rights", getUserRights(page));
317        
318        return infos;
319    }
320    
321    /**
322     * Get the page's properties
323     * @param pageId the id of page
324     * @return the properties
325     */
326    @Callable
327    public Map<String, Object> getPageProperties (String pageId)
328    {
329        Page page = _resolver.resolveById(pageId);
330        
331        Map<String, Object> infos = new HashMap<>();
332        
333        infos.put("id", page.getId());
334        infos.put("name", page.getName());
335        infos.put("parentId", page.getParent().getId());
336        infos.put("title", page.getTitle());
337        infos.put("longTitle", page.getLongTitle());
338        infos.put("path", page.getPathInSitemap());
339        infos.put("siteName", page.getSiteName());
340        infos.put("siteTitle", page.getSite().getTitle());
341        infos.put("type", page.getType());
342        
343        String lang = page.getSitemapName();
344        infos.put("lang", lang);
345        Language language = _webLanguagesManager.getAvailableLanguages().get(lang);
346        if (language != null)
347        {
348            infos.put("langIcon", language.getSmallIcon());
349            infos.put("langLabel", language.getLabel());
350        }
351        
352        PageType type = page.getType();
353        switch (type)
354        {
355            case CONTAINER:
356                infos.put("template", page.getTemplate());
357                break;
358            
359            case LINK:
360                infos.put("url", page.getURL());
361                LinkType urlType = page.getURLType();
362                infos.put("urlType", urlType.toString());
363                break;
364            default:
365                break;
366        }
367        
368        Map<String, Object> contextParameters = new HashMap<>();
369        contextParameters.put("siteName", page.getSiteName());
370        
371        // Tags
372        List<I18nizableText> tags = new ArrayList<>();
373        for (String tagName : page.getTags())
374        {
375            Tag tag = _tagProvider.getTag(tagName, contextParameters);
376            tags.add(tag.getTitle());
377        }
378        infos.put("tags", tags);
379        
380        // Incoming references
381        List<Map<String, Object>> incomingContents = new ArrayList<>();
382        AmetysObjectIterable<Content> contents = _getIncomingContentReferences (page.getId());
383        for (Content content : contents)
384        {
385            incomingContents.add(_content2Json(content, new Locale(page.getSitemapName())));
386        }
387        infos.put("inComingContents", incomingContents);
388        
389        List<Map<String, Object>> incomingPages = new ArrayList<>();
390        AmetysObjectIterable<Page> pages = _getIncomingPageReferences (page.getId());
391        for (Page pageRef : pages)
392        {
393            incomingPages.add(_page2Json(pageRef));
394        }
395        infos.put("inComingPages", incomingPages);
396        
397        // Publication information
398        infos.put("publication", _publication2Json(page));
399        
400        return infos;
401    }
402    
403    /**
404     * Check current user right on given page or sitemap
405     * @param id The id of the page or sitemap
406     * @param rightId The if of right to check
407     * @return true if user has right
408     */
409    @Callable
410    public boolean hasRight(String id, String rightId)
411    {
412        UserIdentity user = _currentUserProvider.getUser();
413        PagesContainer page = _resolver.resolveById(id);
414        
415        return _rightManager.hasRight(user, rightId, page) == RightResult.RIGHT_ALLOW;
416    }
417    
418    /**
419     * Create a new page
420     * @param parentId The parent id. Can not be null.
421     * @param title The page's title. Can not be null.
422     * @param longTitle The page's long title. Can be null or blank.
423     * @return The result map with id of created page
424     */
425    @Callable
426    public Map<String, Object> createPage (String parentId, String title, String longTitle)
427    {
428        Map<String, Object> result = new HashMap<>();
429        
430        PagesContainer parent = _resolver.resolveById(parentId);
431        
432        assert parent instanceof ModifiableTraversableAmetysObject;
433        
434        Site site = parent.getSite();
435        String originalPageName = "";
436        try
437        {
438            originalPageName = FilterNameHelper.filterName(title);
439        }
440        catch (IllegalArgumentException e)
441        {
442            result.put("invalid-name", title);
443            return result;
444        }
445        
446        String pageName = originalPageName;
447        int index = 2;
448        while (parent.hasChild(pageName))
449        {
450            pageName = originalPageName + "-" + (index++);
451        }
452        
453        ModifiablePage page = ((ModifiableTraversableAmetysObject) parent).createChild(pageName, "ametys:defaultPage");
454        
455        // Check rights
456        if (!_canCreate(parent))
457        {
458            throw new IllegalStateException("You do not have the rights to create a page under '/" + parent.getSitemapName() + "/" + parent.getPathInSitemap() + "'");
459        }
460        
461        page.setTitle(title);
462        page.setType(PageType.NODE);
463        page.setSiteName(site.getName());
464        page.setSitemapName(page.getSitemap().getName());
465        
466        if (!StringUtils.isBlank(longTitle))
467        {
468            page.setLongTitle(longTitle);
469        }
470        
471        site.saveChanges();
472        
473        Map<String, Object> eventParams = new HashMap<>();
474        eventParams.put(ObservationConstants.ARGS_PAGE, page);
475        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams));
476        
477        result.put("id", page.getId());
478        result.put("parentId", parent.getId());
479        result.put("lang", page.getSitemapName());
480        result.put("title", page.getTitle());
481        
482        return result;
483    }
484
485    /**
486     * Copy a page
487     * @param id The id of page to copy
488     * @param target The id of parent target page
489     * @param keepReferences true to keep references
490     * @return the result map
491     * @throws RepositoryException if an error occurred during copy
492     */
493    @Callable
494    public Map<String, String> copyPage (String id, String target, boolean keepReferences) throws RepositoryException
495    {
496        Map<String, String> result = new HashMap<>();
497        
498        Page page = _resolver.resolveById(id);
499        
500        if (!(page instanceof CopiableAmetysObject))
501        {
502            throw new IllegalArgumentException("The page '" + page.getId() + "' is not a copiable ametys object, it can not be copied"); 
503        }
504        
505        if (!(page instanceof JCRAmetysObject))
506        {
507            throw new IllegalArgumentException("The page '" + page.getId() + "' is not a JCR ametys object, it can not be copied"); 
508        }
509        
510        PagesContainer parent = _resolver.resolveById(target);
511        
512        // Check rights
513        if (!_canCreate(parent))
514        {
515            throw new IllegalStateException("You do not have the rights to create a page under '/" + parent.getSitemapName() + "/" + parent.getPathInSitemap() + "'");
516        }
517        
518        if (parent instanceof ModifiableTraversableAmetysObject)
519        {
520            Page cPage = null;
521            if (!keepReferences)
522            {
523                // Restrict the copy to the page and its current children to avoid infinitive loop
524                List<String> pagesToCopy = new ArrayList<>();
525                pagesToCopy.add(page.getId());
526                pagesToCopy.addAll(_getChildrenPageIds(page));
527                
528                // Copy and duplicate contents
529                cPage = (Page) ((CopiableAmetysObject) page).copyTo((ModifiableTraversableAmetysObject) parent, null, pagesToCopy);
530                _copySiteComponent.updateReferencesAfterCopy(page, cPage);
531                
532                // Creates the first version on all copied contents
533                _updateContentsAfterCopy (cPage);
534            }
535            else
536            {
537                
538                // Copy without duplicating contents (keep references)
539                String pageName = page.getName();
540                int index = 2;
541                while (parent.hasChild(pageName))
542                {
543                    pageName = page.getName() + "-" + (index++);
544                }
545                
546                String pagePath = ((SimpleAmetysObject) parent).getNode().getPath() + "/" + pageName;
547                Node node = ((JCRAmetysObject) page).getNode();
548                node.getSession().getWorkspace().copy(node.getPath(), pagePath);
549                
550                cPage = parent.getChild(pageName);
551            }
552            
553            ((ModifiableTraversableAmetysObject) parent).saveChanges();
554            result.put("id", cPage.getId());
555            
556            Map<String, Object> eventParams = new HashMap<>();
557            eventParams.put(ObservationConstants.ARGS_PAGE, cPage);
558            _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams));
559        }
560       
561        return result;
562    }
563    
564    /**
565     * Copy a page under another page
566     * @param targetId the page to copy in
567     * @param sourceId the page to copy
568     * @param keepReferences true to keep references for contents, or false to duplicate contents
569     * @return The id of created page is a result map.
570     * @throws RepositoryException if an error occurs
571     */
572    @Callable
573    @Deprecated
574    public Map<String, String> pastePage(String targetId, String sourceId, boolean keepReferences) throws RepositoryException
575    {
576        return copyPage(sourceId, targetId, keepReferences);
577    }
578    
579    /**
580     * Move a page
581     * @param id The page id
582     * @param parentId The id of parent destination
583     * @param index The position in parent child nodes where page will be inserted. -1 means as the last child.
584     * @return the result map
585     */
586    @Callable
587    public Map<String, Object> movePage (String id, String parentId, int index)
588    {
589        Map<String, Object> result = new HashMap<>();
590        
591        String oldPathInSitemap = null;
592        String newPathInSitemap = null;
593     
594        Page page = _resolver.resolveById(id);
595        PagesContainer srcParent = page.getParent();
596        oldPathInSitemap = page.getPathInSitemap();
597        Sitemap sitemap = page.getSitemap();
598        
599        // check rights of deletion on old parent page
600        if (!srcParent.getId().equals(parentId) && !_canDelete(page))
601        {
602            throw new IllegalStateException("You do not have the rights to delete the page '/" + page.getSitemapName() + "/" + oldPathInSitemap + "'");
603        }
604        
605        if (!(page instanceof MoveablePage))
606        {
607            throw new IllegalArgumentException("The page '/" + page.getSitemapName() + "/" + oldPathInSitemap + "' is not a moveable page");
608        }
609        
610        if (srcParent.getId().equals(parentId) && index != -1)
611        {
612            try
613            {
614                Page brother = srcParent.getChildPageAt(index);
615                ((MoveablePage) page).orderBefore(brother);
616            }
617            catch (UnknownAmetysObjectException e)
618            {
619                // Move the last child position
620                ((MoveablePage) page).orderBefore(null);
621            }
622            
623            // Path is not modified
624            newPathInSitemap = oldPathInSitemap;
625        }
626        else
627        {
628            PagesContainer newParentPage = _resolver.resolveById(parentId);
629
630            // check right on creation on new parent page
631            if (!_canCreate(newParentPage))
632            {
633                throw new IllegalStateException("You do not have the rights to create a page under '/" + newParentPage.getSitemapName() + "/" + newParentPage.getPathInSitemap() + "'");
634            }
635
636            ((MoveablePage) page).moveTo(newParentPage, true);
637            if (index != -1)
638            {
639                Page brother = newParentPage.getChildPageAt(index);
640                
641                ((MoveablePage) page).orderBefore(brother);
642            }
643            
644            // Path is modified
645            newPathInSitemap = page.getPathInSitemap();
646        }
647        
648        if (sitemap.needsSave())
649        {
650            sitemap.saveChanges();
651        }
652
653        // Notify observers that the page has been moved
654        Map<String, Object> eventParams = new HashMap<>();
655        eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap);
656        eventParams.put(ObservationConstants.ARGS_PAGE, page);
657        eventParams.put("page.old.path", oldPathInSitemap);
658        eventParams.put("page.old.parent", srcParent);
659        eventParams.put(ObservationConstants.ARGS_PAGE_PATH, newPathInSitemap);
660        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_MOVED, _currentUserProvider.getUser(), eventParams));
661        
662        result.put("id", page.getId());
663        result.put("parentId", page.getParent().getId());
664        
665        return result;
666    }
667    
668    private boolean _canCreate(PagesContainer parentPage)
669    {
670        UserIdentity user = _currentUserProvider.getUser();
671        if (_rightManager.hasRight(user, "Web_Rights_Page_Create", parentPage) == RightResult.RIGHT_ALLOW)
672        {
673            return true;
674        }
675        
676        if (getLogger().isInfoEnabled())
677        {
678            getLogger().info("The user '" + user + "' tried to create page under '/" + parentPage.getSitemapName() + "/" + parentPage.getPathInSitemap() + "' without sufficient rights");
679        }
680        
681        return false;
682    }
683    
684    private boolean _canDelete(Page page)
685    {
686        UserIdentity user = _currentUserProvider.getUser();
687        PagesContainer parent = page.getParent();
688        if (_rightManager.hasRight(user, "Web_Rights_Page_Delete", parent) == RightResult.RIGHT_ALLOW)
689        {
690            return true;
691        }
692        
693        if (getLogger().isInfoEnabled())
694        {
695            getLogger().info("The user '" + user + "' tried to move page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "' without sufficient rights");
696        }
697        
698        return false;
699    }
700    
701    /**
702     * Set pages as redirection
703     * @param pageIds the id of pages to modify
704     * @param url the url of redirection
705     * @param urlType the type of redirection
706     * @return the id of pages which succeeded or failed. 
707     */
708    @Callable
709    public Map<String, Object> setLink (List<String> pageIds, String url, String urlType)
710    {
711        Map<String, Object> result = new HashMap<>();
712        
713        if (StringUtils.isEmpty(url))
714        {
715            throw new IllegalArgumentException("Can not set page as a redirection with an empty url");
716        }
717        
718        List<String> successes = new ArrayList<>();
719        List<Map<String, Object>> failures = new ArrayList<>();
720        
721        for (String pageId : pageIds)
722        {
723            try
724            {
725                Page page = _resolver.resolveById(pageId);
726                if (!(page instanceof ModifiablePage))
727                {
728                    throw new IllegalArgumentException("Can not set page as a redirection on a non-modifiable page " + pageId);
729                }
730                
731                ModifiablePage mPage = (ModifiablePage) page;
732                
733                if (page.getType().equals(PageType.CONTAINER))
734                {
735                    // Remove zones
736                    for (ModifiableZone zone : mPage.getZones())
737                    {
738                        zone.remove();
739                    }
740                }
741                
742                if (pageId.equals(url))
743                {
744                    throw new IllegalArgumentException("A page can not redirect to itself");
745                }
746                
747                mPage.setType(PageType.LINK);
748                mPage.setURL(LinkType.valueOf(urlType), url);
749                mPage.getSitemap().saveChanges();
750                
751                successes.add(pageId);
752                
753                Map<String, Object> eventParams = new HashMap<>();
754                eventParams.put(ObservationConstants.ARGS_PAGE, page);
755                _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
756            }
757            catch (Exception e)
758            {
759                getLogger().error("Cannot set the page '" + pageId + "' as link [" + url + ", " + urlType.toString() + "]", e);
760                
761                Map<String, Object> failure = new HashMap<>();
762                failure.put("id", pageId);
763                failure.put("error", e.toString());
764                failures.add(failure);
765            }
766        }
767
768        result.put("success", successes);
769        result.put("failure", failures);
770        
771        return result;
772    }
773    
774    /**
775     * Get available template for specified page
776     * @param pageId The page's id
777     * @return the list of available template
778     */
779    @Callable
780    public List<Map<String, Object>> getAvailableTemplates (String pageId)
781    {
782        List<Map<String, Object>> templates = new ArrayList<>();
783        
784        Page page = _resolver.resolveById(pageId);
785        
786        Set<String> availableTemplateIds = _templatesHandler.getAvailablesTemplates(page);
787        for (String templateName : availableTemplateIds)
788        {
789            String skinId = page.getSite().getSkinId();
790            Skin skin = _skinsManager.getSkin(skinId);
791            
792            SkinTemplate template = skin.getTemplate(templateName);
793            
794            Map<String, Object> template2json = new HashMap<>();
795            template2json.put("id", template.getId());
796            template2json.put("label", template.getLabel());
797            template2json.put("description", template.getDescription());
798            template2json.put("iconSmall", template.getSmallImage());
799            template2json.put("iconMedium", template.getMediumImage());
800            template2json.put("iconLarge", template.getLargeImage());
801            template2json.put("zone", template.getDefaultZoneId());
802            
803            templates.add(template2json);
804        }
805        
806        return templates;
807    }
808    
809    /**
810     * Get available content types for specified page
811     * @param pageId The page's id
812     * @param zoneName the name of the zone
813     * @return the list of available content types
814     */
815    @Callable
816    public List<Map<String, Object>> getAvailableContentTypes (String pageId, String zoneName)
817    {
818        List<Map<String, Object>> contenttypes = new ArrayList<>();
819        
820        Page page = _resolver.resolveById(pageId);
821        
822        Set<String> contentTypeIds = _cTypeHandler.getAvailableContentTypes(page, zoneName);
823        for (String contentTypeId : contentTypeIds)
824        {
825            ContentType cType = _contentTypeExtensionPoint.getExtension(contentTypeId);
826            
827            if (cType != null && _hasRight(cType, page))
828            {
829                Map<String, Object> ctype2json = new HashMap<>();
830                ctype2json.put("id", cType.getId());
831                ctype2json.put("label", cType.getLabel());
832                ctype2json.put("description", cType.getDescription());
833                ctype2json.put("iconGlyph", cType.getIconGlyph());
834                ctype2json.put("iconDecorator", cType.getIconDecorator());
835                ctype2json.put("iconSmall", cType.getSmallIcon());
836                ctype2json.put("iconMedium", cType.getMediumIcon());
837                ctype2json.put("iconLarge", cType.getLargeIcon());
838                ctype2json.put("defaultTitle", cType.getDefaultTitle());
839                
840                contenttypes.add(ctype2json);
841            }
842        }
843        
844        return contenttypes;
845    }
846    
847    /**
848     * Get available content types for a page being created
849     * @param pageId The page's id. Can be null of the page is not yet created
850     * @param zoneName the name of the zone
851     * @param parentId The id of parent page
852     * @param pageTitle The title of page to create
853     * @param template The template of page to create
854     * @return the list of available services
855     */
856    @Callable
857    public List<Map<String, Object>> getAvailableContentTypesForCreation(String pageId, String zoneName, String parentId, String pageTitle, String template)
858    {
859        if (StringUtils.isNotEmpty(pageId))
860        {
861            // Get available services for a page
862            return getAvailableContentTypes(pageId, zoneName);
863        }
864        else if (StringUtils.isNotEmpty(parentId))
865        {
866            // Get available services for a not yet existing page
867            PagesContainer parent = _resolver.resolveById(parentId);
868            
869            // Create page temporarily
870            Page page = _createPage(parent, pageTitle, template);
871            
872            List<Map<String, Object>> availableServices = getAvailableContentTypes(page.getId(), zoneName);
873            
874            // Cancel page creation
875            page.getSitemap().revertChanges();
876            
877            return availableServices;
878        }
879        
880        return Collections.EMPTY_LIST;
881    }
882    
883    private boolean _hasRight(ContentType contentType, Page page)
884    {
885        String right = contentType.getRight();
886        
887        if (right == null)
888        {
889            return true;
890        }
891        else
892        {
893            UserIdentity user = _currentUserProvider.getUser();
894            return _rightManager.hasRight(user, right, page) == RightResult.RIGHT_ALLOW;
895        }
896    }
897    
898    /**
899     * Get available services for specified page
900     * @param pageId The page's id
901     * @param zoneName the name of the zone
902     * @return the list of available services
903     */
904    @Callable
905    public List<Map<String, Object>> getAvailableServices (String pageId, String zoneName)
906    {
907        List<Map<String, Object>> services = new ArrayList<>();
908        
909        Page page = _resolver.resolveById(pageId);
910        
911        Set<String> serviceIds = _serviceHandler.getAvailableServices(page, zoneName);
912        for (String serviceId : serviceIds)
913        {
914            Service service = _serviceExtensionPoint.getExtension(serviceId);
915            if (service != null && _hasRight(service, page))
916            {
917                Map<String, Object> serviceMap = new HashMap<>();
918                serviceMap.put("id", service.getId());
919                serviceMap.put("label", service.getLabel());
920                serviceMap.put("description", service.getDescription());
921                serviceMap.put("iconGlyph", service.getIconGlyph());
922                serviceMap.put("iconDecorator", service.getIconDecorator());
923                serviceMap.put("iconSmall", service.getSmallIcon());
924                serviceMap.put("iconMedium", service.getMediumIcon());
925                serviceMap.put("iconLarge", service.getLargeIcon());
926                serviceMap.put("parametersAction", service.getParametersScript().getScriptClassname());
927                
928                services.add(serviceMap);
929            }
930        }
931        
932        return services;
933    }
934    
935    /**
936     * Get available services for a page being created
937     * @param pageId The page's id. Can be null of the page is not yet created
938     * @param zoneName the name of the zone
939     * @param parentId The id of parent page
940     * @param pageTitle The title of page to create
941     * @param template The template of page to create
942     * @return the list of available services
943     */
944    @Callable
945    public List<Map<String, Object>> getAvailableServicesForCreation(String pageId, String zoneName, String parentId, String pageTitle, String template)
946    {
947        if (StringUtils.isNotEmpty(pageId))
948        {
949            // Get available services for a page
950            return getAvailableServices(pageId, zoneName);
951        }
952        else if (StringUtils.isNotEmpty(parentId))
953        {
954            // Get available services for a not yet existing page
955            PagesContainer parent = _resolver.resolveById(parentId);
956            
957            // Create page temporarily
958            Page page = _createPage(parent, pageTitle, template);
959            
960            List<Map<String, Object>> availableServices = getAvailableServices(page.getId(), zoneName);
961            
962            // Cancel page creation
963            page.getSitemap().revertChanges();
964            
965            return availableServices;
966        }
967        
968        return Collections.EMPTY_LIST;
969    }
970    
971    private Page _createPage (PagesContainer parent, String pageTitle, String template)
972    {
973        Site site = parent.getSite();
974        String originalPageName = FilterNameHelper.filterName(pageTitle);
975        
976        String pageName = originalPageName;
977        int index = 2;
978        while (parent.hasChild(pageName))
979        {
980            pageName = originalPageName + "-" + index++;
981        }
982        
983        ModifiablePage page = ((ModifiableTraversableAmetysObject) parent).createChild(pageName, "ametys:defaultPage");
984        
985        page.setTitle(pageTitle);
986        page.setType(PageType.NODE);
987        page.setSiteName(site.getName());
988        page.setSitemapName(page.getSitemap().getName());
989
990        if (template != null)
991        {
992            String skinId = page.getSite().getSkinId();
993            SkinTemplate tpl = _skinsManager.getSkin(skinId).getTemplate(template);
994            if (tpl == null)
995            {
996                throw new IllegalStateException("Template '" + template + "' does not exist on skin '" + skinId + "'");
997            }
998            
999            // Set temporary the template to get available services
1000            page.setType(PageType.CONTAINER); 
1001            page.setTemplate(template);
1002        }
1003        
1004        return page;
1005    }
1006    
1007    private boolean _hasRight(Service service, Page page)
1008    {
1009        String right = service.getRight();
1010        
1011        if (right == null)
1012        {
1013            return true;
1014        }
1015        else
1016        {
1017            UserIdentity user = _currentUserProvider.getUser();
1018            return _rightManager.hasRight(user, right, page) == RightResult.RIGHT_ALLOW;
1019        }
1020    }
1021    
1022    /**
1023     * Get available template for specified pages
1024     * @param pageIds The id of pages
1025     * @return the list of available template
1026     */
1027    @Callable
1028    public List<Map<String, Object>> getAvailableTemplates (List<String> pageIds)
1029    {
1030        List<String> templateIds = new ArrayList<>();
1031        
1032        List<Map<String, Object>> templates = new ArrayList<>();
1033        
1034        for (String pageId : pageIds)
1035        {
1036            List<Map<String, Object>> pageTemplates = getAvailableTemplates(pageId);
1037            
1038            for (Map<String, Object> template : pageTemplates)
1039            {
1040                String templateName = (String) template.get("id");
1041                
1042                if (!templateIds.contains(templateName))
1043                {
1044                    templateIds.add(templateName);
1045                    templates.add(template);
1046                }
1047            }
1048        }
1049        
1050        return templates;
1051    }
1052    
1053    /**
1054     * Get available content types for a page being created
1055     * @param pageId The page's id. Can be null of the page is not yet created
1056     * @param parentId The id of parent page
1057     * @param pageTitle The title of page to create
1058     * @return the list of available services
1059     */
1060    @Callable
1061    public List<Map<String, Object>> getAvailableTemplatesForCreation (String pageId, String parentId, String pageTitle)
1062    {
1063        if (StringUtils.isNotEmpty(pageId))
1064        {
1065            // Get available template for a page
1066            return getAvailableTemplates(pageId);
1067        }
1068        else if (StringUtils.isNotEmpty(parentId))
1069        {
1070            // Get available template for a not yet existing page
1071            PagesContainer parent = _resolver.resolveById(parentId);
1072            
1073            // Create page temporarily
1074            Page page = _createPage(parent, pageTitle, null);
1075            
1076            List<Map<String, Object>> availableTemplates = getAvailableTemplates(page.getId());
1077            
1078            // Cancel page creation
1079            page.getSitemap().revertChanges();
1080            
1081            return availableTemplates;
1082        }
1083        
1084        return Collections.EMPTY_LIST;
1085    }
1086    
1087    /**
1088     * Get service info
1089     * @param pageId Optional, the page id of the service. To get some basic info about the page.
1090     * @param serviceId The id of the service
1091     * @return a Map containing some info about the service (label, url..)
1092     */
1093    @Callable
1094    public Map<String, Object> getServiceInfo(String pageId, String serviceId)
1095    {
1096        Map<String, Object> info = new HashMap<>();
1097        
1098        if (StringUtils.isNotEmpty(pageId))
1099        {
1100            Page page = _resolver.resolveById(pageId);
1101            info.put("page-id", page.getId());
1102            info.put("page-title", page.getTitle());
1103        }
1104        
1105        Service service = _serviceExtensionPoint.getExtension(serviceId);
1106        info.put("id", service.getId());
1107        info.put("label", service.getLabel());
1108        info.put("url", service.getURL());
1109        info.put("smallIcon", service.getSmallIcon());
1110        info.put("iconGlyph", service.getIconGlyph());
1111        info.put("iconDecorator", service.getIconDecorator());
1112        return info;
1113    }
1114    
1115    /**
1116     * Rename a page
1117     * @param pageId The id of page to rename
1118     * @param title The page's  title
1119     * @param longTitle The page's long title.
1120     * @param updatePath true to update page's path
1121     * @param createAlias true to create a alias
1122     * @return the result map
1123     */
1124    @Callable (right = "Web_Rights_Page_Rename", rightContext = PageRightAssignmentContext.ID, paramIndex = 0)
1125    public Map<String, Object> renamePage (String pageId, String title, String longTitle, boolean updatePath, boolean createAlias)
1126    {
1127        Map<String, Object> result = new HashMap<>();
1128        
1129        Page page = _resolver.resolveById(pageId);
1130        
1131        if (!(page instanceof ModifiablePage))
1132        {
1133            throw new IllegalArgumentException("Can not rename a non-modifiable page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "'");
1134        }
1135        
1136        ModifiablePage mPage = (ModifiablePage) page;
1137        mPage.setTitle(title);
1138        mPage.setLongTitle(longTitle);
1139
1140        if (updatePath)
1141        {
1142            String oldPathInSitemap = page.getPathInSitemap();
1143            String oldPath = "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + ".html";
1144            String oldPathForChild = "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "/**.html";
1145
1146            String pageName = "";
1147            try
1148            {
1149                pageName = FilterNameHelper.filterName(title);
1150            }
1151            catch (IllegalArgumentException e)
1152            {
1153                result.put("invalid-name", title);
1154                return result;
1155            }
1156            
1157            if (!page.getName().equals(pageName))
1158            {
1159                int index = 1;
1160                String initialPageName = pageName;
1161                PagesContainer parent = page.getParent();
1162                while (parent.hasChild(pageName))
1163                {
1164                    pageName = initialPageName + "-" + (index++);
1165                }
1166
1167                mPage.rename(pageName);
1168                
1169                if (createAlias)
1170                {
1171                    ModifiableTraversableAmetysObject rootNode = AliasHelper.getRootNode(page.getSite());
1172                    
1173                    DefaultAlias alias = rootNode.createChild(AliasHelper.getAliasNextUniqueName(rootNode), "ametys:alias");
1174                    alias.setUrl(oldPath);
1175                    alias.setTarget(page.getId());
1176                    alias.setType(TargetType.PAGE);
1177                    alias.setCreationDate(new Date());
1178
1179                    // Alias for child pages
1180                    alias = rootNode.createChild(AliasHelper.getAliasNextUniqueName(rootNode), "ametys:alias");
1181                    alias.setUrl(oldPathForChild);
1182                    alias.setTarget("/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "/{1}.html");
1183                    alias.setType(TargetType.URL);
1184                    alias.setCreationDate(new Date());
1185
1186                    rootNode.saveChanges();
1187                }
1188                
1189                // Notify observers that the page has been renamed
1190                Map<String, Object> eventParams = new HashMap<>();
1191                eventParams.put(ObservationConstants.ARGS_PAGE, page);
1192                eventParams.put("path.old.path", oldPathInSitemap);
1193                eventParams.put(ObservationConstants.ARGS_PAGE_PATH, page.getPathInSitemap());
1194                _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_RENAMED, _currentUserProvider.getUser(), eventParams));
1195                
1196            }
1197            else
1198            {
1199                // Notify observers that the page's title has been modified
1200                Map<String, Object> eventParams = new HashMap<>();
1201                eventParams.put(ObservationConstants.ARGS_PAGE, page);
1202                _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
1203            }
1204                
1205        }
1206        else
1207        {
1208            // Notify observers that the page's title has been modified
1209            Map<String, Object> eventParams = new HashMap<>();
1210            eventParams.put(ObservationConstants.ARGS_PAGE, page);
1211            _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
1212        }
1213        
1214        Sitemap sitemap = page.getSitemap();
1215        if (sitemap.needsSave())
1216        {
1217            sitemap.saveChanges();
1218        }
1219        
1220        result.put("id", page.getId());
1221        result.put("path", page.getPath());
1222        result.put("title", page.getTitle());
1223        
1224        return result;
1225    }
1226    
1227    /**
1228     * Delete a page and its sub-pages.
1229     * The contents that belong only to the deleted page will be deleted if 'deleteBelongingContents' is set to true.
1230     * The newly created contents are deleted whatever the value if 'deleteBelongingContents'.
1231     * @param pageId the id of page to delete
1232     * @param deleteBelongingContents true to delete the contents that belong to the page and its sub-pages only
1233     * @return The id of deleted pages
1234     */
1235    @Callable
1236    public Map<String, Object> deletePage(String pageId, boolean deleteBelongingContents)
1237    {
1238        ModifiablePage page = _resolver.resolveById(pageId);
1239        
1240        // Check rights on parent
1241        if (!_canDelete(page))
1242        {
1243            throw new IllegalStateException("You do not have the rights to delete the page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "'");
1244        }
1245        
1246        return deletePage(page, deleteBelongingContents);
1247    }
1248    
1249    /**
1250     * Delete a page and its sub-pages.
1251     * The contents that belong only to the deleted page will be deleted if 'deleteBelongingContents' is set to true.
1252     * The newly created contents are deleted whatever the value if 'deleteBelongingContents'.
1253     * @param page the page to delete
1254     * @param deleteBelongingContents true to delete the contents that belong to the page and its sub-pages only
1255     * @return The id of deleted pages
1256     */
1257    public Map<String, Object> deletePage(ModifiablePage page, boolean deleteBelongingContents)
1258    {
1259        Map<String, Object> result = new HashMap<>();
1260        
1261        List<String> contentToDelete = getDeleteablePageContentIds(page.getId(), !deleteBelongingContents);
1262        
1263        Sitemap sitemap = page.getSitemap();
1264        PagesContainer parent = page.getParent();
1265        String pagePathInSitemap = page.getPathInSitemap();
1266        
1267        List<String> childPagesIds = _getChildrenPageIds(page);
1268        
1269        Map<String, Object> eventParams = new HashMap<>();
1270        eventParams.put(ObservationConstants.ARGS_PAGE_ID, page.getId());
1271        eventParams.put(ObservationConstants.ARGS_PAGE_PARENT, parent);
1272        eventParams.put(ObservationConstants.ARGS_PAGE_PATH, pagePathInSitemap);
1273        eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap);
1274        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_DELETING, _currentUserProvider.getUser(), eventParams));
1275        
1276        // FIXME API test if this is not modifiable
1277        page.getParent();
1278        page.remove();
1279        ((ModifiableAmetysObject) parent).saveChanges();
1280        
1281        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_DELETED, _currentUserProvider.getUser(), eventParams));
1282        
1283        result.put("id", page.getId());
1284        result.put("childPages", childPagesIds);
1285        
1286        result.putAll(_contentDAO.deleteContents(contentToDelete, true));
1287        return result;
1288    }
1289    
1290    /**
1291     * Set pages as blank page
1292     * @param pageIds the id of pages to modify
1293     * @return the id of pages which succeeded or failed. 
1294     */
1295    @Callable
1296    public Map<String, Object> setBlank (List<String> pageIds)
1297    {
1298        Map<String, Object> result = new HashMap<>();
1299        
1300        List<String> successes = new ArrayList<>();
1301        List<Map<String, Object>> failures = new ArrayList<>();
1302        
1303        for (String pageId : pageIds)
1304        {
1305            try
1306            {
1307                Page page = _resolver.resolveById(pageId);
1308                if (!(page instanceof ModifiablePage))
1309                {
1310                    throw new IllegalArgumentException("Can not set page as blank a non-modifiable page " + pageId);
1311                }
1312                
1313                ModifiablePage mPage = (ModifiablePage) page;
1314                
1315                if (page.getType().equals(PageType.CONTAINER))
1316                {
1317                    // Remove zones
1318                    for (ModifiableZone zone : mPage.getZones())
1319                    {
1320                        zone.remove();
1321                    }
1322                }
1323                
1324                mPage.setType(PageType.NODE);
1325                mPage.getSitemap().saveChanges();
1326                
1327                successes.add(pageId);
1328                
1329                Map<String, Object> eventParams = new HashMap<>();
1330                eventParams.put(ObservationConstants.ARGS_PAGE, page);
1331                _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
1332            }
1333            catch (Exception e)
1334            {
1335                getLogger().error("Cannot set the page '" + pageId + "' as blank page", e);
1336                
1337                Map<String, Object> failure = new HashMap<>();
1338                failure.put("id", pageId);
1339                failure.put("error", e.toString());
1340                failures.add(failure);
1341            }
1342        }
1343
1344        result.put("success", successes);
1345        result.put("failure", failures);
1346        
1347        return result;
1348    }
1349    
1350    /**
1351     * Set a template to pages
1352     * @param pageIds the id of pages to update
1353     * @param templateName The template name
1354     * @return the id of pages which succeeded
1355     */
1356    @Callable
1357    public Map<String, Object> setTemplate (List<String> pageIds, String templateName)
1358    {
1359        return setTemplate(pageIds, templateName, true);
1360    }
1361    
1362    /**
1363     * Set a template to pages
1364     * @param pageIds the id of pages to update
1365     * @param templateName The template name
1366     * @param checkAvailableTemplate true if you want to check available template
1367     * @return the id of pages which succeeded
1368     */
1369    public Map<String, Object> setTemplate (List<String> pageIds, String templateName, boolean checkAvailableTemplate)
1370    {
1371        Map<String, Object> result = new HashMap<>();
1372        
1373        List<String> successes = new ArrayList<>();
1374        
1375        String defaultZoneName = null;
1376        
1377        for (String pageId : pageIds)
1378        {
1379            Page page = _resolver.resolveById(pageId);
1380            if (!(page instanceof ModifiablePage))
1381            {
1382                throw new IllegalArgumentException("Can not set template a non-modifiable page " + pageId);
1383            }
1384            
1385            ModifiablePage mPage = (ModifiablePage) page;
1386            
1387            if (defaultZoneName == null)
1388            {
1389                String skinId = page.getSite().getSkinId();
1390                SkinTemplate tpl = _skinsManager.getSkin(skinId).getTemplate(templateName);
1391                if (tpl == null)
1392                {
1393                    throw new IllegalStateException("Template '" + templateName + "' does not exist on skin '" + skinId + "'");
1394                }
1395                
1396                defaultZoneName = tpl.getDefaultZoneId();
1397            }
1398            
1399            if (checkAvailableTemplate && !_templatesHandler.getAvailablesTemplates(mPage).contains(templateName))
1400            {
1401                throw new IllegalStateException("Template '" + templateName + "' is not available for page '" + pageId + "'");
1402            }
1403            
1404            if (page.getType().equals(PageType.CONTAINER))
1405            {
1406                _removeOldZones (mPage, templateName);
1407            }
1408            
1409            mPage.setTemplate(templateName);
1410            mPage.setType(PageType.CONTAINER);
1411            mPage.getSitemap().saveChanges();
1412            
1413            successes.add(pageId);
1414            
1415            Map<String, Object> eventParams = new HashMap<>();
1416            eventParams.put(ObservationConstants.ARGS_PAGE, page);
1417            _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
1418        }
1419        
1420        if (defaultZoneName != null)
1421        {
1422            result.put("zonename", defaultZoneName);
1423        }
1424        
1425        result.put("success", successes);
1426        
1427        return result;
1428    }
1429    
1430    /**
1431     * Get the script class name to execute to add or update this service 
1432     * @param serviceId the service id
1433     * @return the script class name. Can be empty
1434     */
1435    @Callable
1436    public String getServiceParametersAction (String serviceId)
1437    {
1438        try
1439        {
1440            Service service = _serviceExtensionPoint.getExtension(serviceId);
1441            return service.getParametersScript().getScriptClassname();
1442        }
1443        catch (IllegalArgumentException e)
1444        {
1445            throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e);
1446        }
1447    }
1448    
1449    private void _removeOldZones (ModifiablePage page, String templateName)
1450    {
1451        String skinId = page.getSite().getSkinId();
1452        
1453        SkinTemplate oldTemplate = _skinsManager.getSkin(skinId).getTemplate(templateName);
1454        
1455        Map<String, SkinTemplateZone> templateZones = oldTemplate.getZones();
1456        
1457        for (ModifiableZone zone : page.getZones())
1458        {
1459            if (!templateZones.containsKey(zone.getName()))
1460            {
1461                zone.remove();
1462            }
1463        }
1464    }
1465    
1466    /**
1467     * Get the tags from the pages
1468     * @param pageIds The ids of the pages
1469     * @return the tags of the pages
1470     */
1471    @Callable
1472    public Set<String> getTags (List<String> pageIds)
1473    {
1474        Set<String> tags = new HashSet<>();
1475        
1476        for (String pageId : pageIds)
1477        {
1478            Page page = _resolver.resolveById(pageId);
1479            tags.addAll(page.getTags());
1480        }
1481        
1482        return tags;
1483    }
1484    
1485    /**
1486     * Tag a list of pages
1487     * @param pageIds The ids of pages to tag
1488     * @param tagNames The tags
1489     * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags.
1490     * @param contextualParameters Contextual parameters. Must contain the site name
1491     * @return the result
1492     */
1493    @Callable
1494    public Map<String, Object> tag (List<String> pageIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters)
1495    {
1496        Map<String, Object> result = new HashMap<>();
1497        
1498        result.put("nomodifiable-pages", new ArrayList<Map<String, Object>>());
1499        result.put("invalid-tags", new ArrayList<String>());
1500        result.put("allright-pages", new ArrayList<Map<String, Object>>());
1501        
1502        for (String pageId : pageIds)
1503        {
1504            Page page = _resolver.resolveById(pageId);
1505            
1506            Map<String, Object> page2json = new HashMap<>();
1507            page2json.put("id", page.getId());
1508            page2json.put("title", page.getTitle());
1509            
1510            if (page instanceof ModifiablePage)
1511            {
1512                ModifiablePage mPage = (ModifiablePage) page;
1513                
1514                TagMode tagMode = TagMode.valueOf(mode);
1515                
1516                Set<String> oldTags = mPage.getTags();
1517                if (TagMode.REPLACE.equals(tagMode))
1518                {
1519                    // First delete old tags
1520                    for (String tagName : oldTags)
1521                    {
1522                        mPage.untag(tagName);
1523                    }
1524                }
1525                    
1526                
1527                // Then set new tags
1528                for (String tagName : tagNames)
1529                {
1530                    if (_isTagValid(page, tagName))
1531                    {
1532                        if (TagMode.REMOVE.equals(tagMode))
1533                        {
1534                            mPage.untag(tagName);
1535                        }
1536                        else if (TagMode.REPLACE.equals(tagMode) || !oldTags.contains(tagName))
1537                        {
1538                            mPage.tag(tagName);
1539                        }
1540                    }
1541                    else
1542                    {
1543                        @SuppressWarnings("unchecked")
1544                        List<String> invalidTags = (List<String>) result.get("invalid-tags");
1545                        invalidTags.add(tagName);
1546                    }
1547                }
1548                
1549                mPage.saveChanges();
1550                
1551                page2json.put("tags", page.getTags());
1552                @SuppressWarnings("unchecked")
1553                List<Map<String, Object>> allRightPages = (List<Map<String, Object>>) result.get("allright-pages");
1554                allRightPages.add(page2json);
1555                
1556                if (!oldTags.equals(page.getTags()))
1557                {
1558                    // Notify observers that the content has been tagged
1559                    Map<String, Object> eventParams = new HashMap<>();
1560                    eventParams.put(ObservationConstants.ARGS_PAGE, page);
1561                    eventParams.put("page.tags", page.getTags());
1562                    eventParams.put("page.old.tags", page);
1563                    _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
1564                }
1565            }
1566            else
1567            {
1568                @SuppressWarnings("unchecked")
1569                List<Map<String, Object>> nomodifiablePages = (List<Map<String, Object>>) result.get("nomodifiable-pages");
1570                nomodifiablePages.add(page2json);
1571            }
1572        }
1573        
1574        return result;
1575    }
1576    
1577    /**
1578     * Tag a list of contents with the given tags
1579     * @param pageIds The ids of pages to tag
1580     * @param contentIds The ids of contents to tag
1581     * @param tagNames The tags
1582     * @param contextualParameters The contextual parameters
1583     * @return the result map
1584     */
1585    @Callable
1586    public Map<String, Object> tag (List<String> pageIds, List<String> contentIds, List<String> tagNames, Map<String, Object> contextualParameters)
1587    {
1588        return tag(pageIds, contentIds, tagNames, TagMode.REPLACE.toString(), contextualParameters);
1589    }
1590    
1591    /**
1592     * Tag a list of contents and/org pages
1593     * @param pageIds The ids of pages to tag
1594     * @param contentIds The ids of contents to tag
1595     * @param tagNames The tags
1596     * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags.
1597     * @param contextualParameters The contextual parameters
1598     * @return the result
1599     */
1600    @Callable
1601    public Map<String, Object> tag (List<String> pageIds, List<String> contentIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters)
1602    {
1603        // Tag pages
1604        Map<String, Object> result = tag(pageIds, tagNames, mode, contextualParameters);
1605        
1606        // Tag contents
1607        result.putAll(_contentDAO.tag(contentIds, tagNames, mode, contextualParameters));
1608        
1609        // Invalid tags are ignored
1610        result.remove("invalid-tags");
1611        return result;
1612    }
1613    
1614    /**
1615     * Test if a tag is valid for a specific page
1616     * @param page The page
1617     * @param tagName The tag name
1618     * @return True if the tag is valid
1619     */
1620    public boolean _isTagValid (Page page, String tagName)
1621    {
1622        Map<String, Object> params = new HashMap<>();
1623        params.put("siteName", page.getSiteName());
1624        CMSTag tag = _tagProvider.getTag(tagName, params);
1625        
1626        return tag.getTarget().getName().equals("PAGE");
1627    }
1628    
1629    /**
1630     * Set the visible of pages
1631     * @param pageIds The id of pages
1632     * @param visible <code>true</code> to set pages as visible, <code>false</code> otherwise
1633     * @return The result map
1634     */
1635    @Callable
1636    public Map<String, Object> setVisibility (List<String> pageIds, boolean visible)
1637    {
1638        Map<String, Object> result = new HashMap<>();
1639        
1640        result.put("nomodifiable-pages", new ArrayList<Map<String, Object>>());
1641        result.put("allright-pages", new ArrayList<Map<String, Object>>());
1642        
1643        for (String id : pageIds)
1644        {
1645            Page page = _resolver.resolveById(id);
1646         
1647            Map<String, Object> page2json = new HashMap<>();
1648            page2json.put("id", page.getId());
1649            page2json.put("title", page.getTitle());
1650            
1651            if (page instanceof ModifiablePage)
1652            {
1653                ModifiablePage mPage = (ModifiablePage) page;
1654                mPage.setVisible(visible);
1655                mPage.saveChanges();
1656                
1657                Map<String, Object> eventParams = new HashMap<>();
1658                eventParams.put(ObservationConstants.ARGS_PAGE, page);
1659                eventParams.put(ObservationConstants.ARGS_PAGE_ID, page.getId());
1660                _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
1661                
1662                @SuppressWarnings("unchecked")
1663                List<Map<String, Object>> allRightPages = (List<Map<String, Object>>) result.get("allright-pages");
1664                allRightPages.add(page2json);
1665            }
1666            else
1667            {
1668                @SuppressWarnings("unchecked")
1669                List<Map<String, Object>> nomodifiablePages = (List<Map<String, Object>>) result.get("nomodifiable-pages");
1670                nomodifiablePages.add(page2json);
1671            }
1672        } 
1673        
1674        return result;
1675    }
1676    
1677    /**
1678     * Get the contents of a {@link Page} and its subpages which can be deleted.
1679     * A content is deleteable if user has right, the content is not locked and not referenced by other pages.
1680     * @param id The id of page
1681     * @return The list of deletable contents
1682     */
1683    @Callable
1684    public Map<String, Object> getDeleteablePageContents (String id)
1685    {
1686        Map<String, Object> results = new HashMap<>();
1687        
1688        results.put("deleteable-contents", new ArrayList<Map<String, Object>>());
1689        results.put("referenced-contents", new ArrayList<Map<String, Object>>());
1690        results.put("unauthorized-contents", new ArrayList<Map<String, Object>>());
1691        results.put("locked-contents", new ArrayList<Map<String, Object>>());
1692        
1693        Page page = _resolver.resolveById(id);
1694        List<Content> contents = getPageContents(page, true);
1695        
1696        for (Content content : contents)
1697        {
1698            Map<String, Object> contentParams = new HashMap<>();
1699            contentParams.put("id", content.getId());
1700            contentParams.put("title", content.getTitle(new Locale(page.getSitemapName())));
1701            contentParams.put("name", content.getName());
1702            
1703            if (_isReferenced(content))
1704            {
1705                // Content is referenced by at least another page
1706                @SuppressWarnings("unchecked")
1707                List<Map<String, Object>> referencedContents = (List<Map<String, Object>>) results.get("referenced-contents");
1708                referencedContents.add(contentParams);
1709            }
1710            else if (!_contentDAO.canDelete(content))
1711            {
1712                @SuppressWarnings("unchecked")
1713                List<Map<String, Object>> unauthorizedContents = (List<Map<String, Object>>) results.get("unauthorized-contents");
1714                unauthorizedContents.add(contentParams);
1715            }
1716            else if (_isLocked(content))
1717            {
1718                // If the content is locked by other
1719                @SuppressWarnings("unchecked")
1720                List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents");
1721                lockedContents.add(contentParams);
1722            }
1723            else
1724            {
1725                Map<String, Object> content2json = new HashMap<>();
1726                content2json.put("id", content.getId());
1727                content2json.put("name", content.getName());
1728                content2json.put("title", content.getTitle(new Locale(page.getSitemapName())));
1729                content2json.put("isNew", _isNew(content));
1730                content2json.put("isShared", content instanceof SharedContent);
1731                content2json.put("hasShared", _sharedContentManager.hasSharedContents(content));
1732                
1733                @SuppressWarnings("unchecked")
1734                List<Map<String, Object>> allrightContents = (List<Map<String, Object>>) results.get("deleteable-contents");
1735                allrightContents.add(content2json);
1736            }
1737        }
1738        
1739        return results;
1740    }
1741    
1742    /**
1743     * Get the contents that belong to the {@link Page} and its sub-pages and that can be deleted.
1744     * A content is deleteable if user has right, the content is not locked and it's not referenced by other pages.
1745     * If 'onlyNewlyCreatedContents' is set to 'true', only newly created contents will be returned
1746     * @param pageId The id of page
1747     * @param onlyNewlyCreatedContents true to return only the newly created contents
1748     * @return The ids of deleteable contents
1749     */
1750    public List<String> getDeleteablePageContentIds (String pageId, boolean onlyNewlyCreatedContents)
1751    {
1752        List<String> contentsId = new ArrayList<>();
1753        
1754        Page page = _resolver.resolveById(pageId);
1755        
1756        List<Content> contents = getPageContents(page, true);
1757        
1758        for (Content content : contents)
1759        {
1760            if (_contentDAO.canDelete(content) && !_isLocked(content) && !_isReferenced(content))
1761            {
1762                if (!onlyNewlyCreatedContents || _isNew(content))
1763                {
1764                    contentsId.add(content.getId());
1765                }
1766            }
1767        }
1768        
1769        return contentsId;
1770    }
1771        
1772    /**
1773     * Get the unreferenced contents of a {@link Page} or a {@link ZoneItem}
1774     * @param id The id of page or zone item
1775     * @return The list of unreferenced contents
1776     */
1777    @Callable
1778    public List<Map<String, Object>> getUnreferencedContents (String id)
1779    {
1780        List<Map<String, Object>> unreferencedContents = new ArrayList<>();
1781        
1782        Page page = _resolver.resolveById(id);
1783        List<Content> contents = getPageContents(page, true);
1784        
1785        for (Content content : contents)
1786        {
1787            if (!_isReferenced(content))
1788            {
1789                Map<String, Object> content2json = new HashMap<>();
1790                content2json.put("id", content.getId());
1791                content2json.put("name", content.getName());
1792                content2json.put("title", content.getTitle(new Locale(page.getSitemapName())));
1793                content2json.put("isNew", _isNew(content));
1794                content2json.put("isShared", content instanceof SharedContent);
1795                content2json.put("hasShared", _sharedContentManager.hasSharedContents(content));
1796                
1797                unreferencedContents.add(content2json);
1798            }
1799        }
1800        
1801        return unreferencedContents;
1802    }
1803    
1804    /**
1805     * Returns the page's attachments root node
1806     * @param id the page's id
1807     * @return The attachments' root node informations
1808     */
1809    @Callable
1810    public Map<String, Object> getAttachmentsRootNode (String id)
1811    {
1812        Map<String, Object> result = new HashMap<>();
1813        
1814        Page page = _resolver.resolveById(id);
1815        
1816        result.put("title", page.getTitle());
1817        result.put("contentId", page.getId());
1818        
1819        TraversableAmetysObject attachments = page.getRootAttachments();
1820        
1821        if (attachments != null)
1822        {
1823            result.put("id", attachments.getId());
1824            if (attachments instanceof ModifiableAmetysObject)
1825            {
1826                result.put("isModifiable", true);
1827            }
1828            if (attachments instanceof ModifiableResourceCollection)
1829            {
1830                result.put("canCreateChild", true);
1831            }
1832            
1833            boolean hasChildNodes = false;
1834            boolean hasResources = false;
1835
1836            for (AmetysObject child : attachments.getChildren())
1837            {
1838                if (child instanceof Resource)
1839                {
1840                    hasResources = true;
1841                }
1842                else if (child instanceof ExplorerNode)
1843                {
1844                    hasChildNodes = true;
1845                }
1846            }
1847
1848            if (hasChildNodes)
1849            {
1850                result.put("hasChildNodes", true);
1851            }
1852
1853            if (hasResources)
1854            {
1855                result.put("hasResources", true);
1856            }
1857            
1858            return result;
1859        }
1860        
1861        throw new IllegalArgumentException("Page with id '" + id + "' does not support attachments.");
1862    }
1863    /**
1864     * Returns the page's parents ids
1865     * @param id the page's id
1866     * @return The attachments' root node informations
1867     */
1868    @Callable
1869    public Map<String, Object> getPageParents (String id)
1870    {
1871        Map<String, Object> result = new HashMap<>();
1872        List<Map<String, Object>> pages = new ArrayList<>();
1873        Page page = _resolver.resolveById(id);
1874        pages.add(_page2Json(page));
1875        while (page.getParent() != null && page.getParent() instanceof Page)
1876        {
1877            page = page.getParent();
1878            pages.add(_page2Json(page));
1879        }
1880        result.put("parents", pages);
1881        return result;
1882    }
1883    
1884    /**
1885     * Get the contents of a page and its child pages
1886     * @param page The page
1887     * @return The list of contents
1888     */
1889    public List<Content> getPageContents (Page page)
1890    {
1891        return getPageContents(page, false);
1892    }
1893    
1894    /**
1895     * Get the contents of a page and its child pages
1896     * @param page The page
1897     * @param ignoreContentsOfNonRemovablePage true to ignore contents of non-removable pages (virtual pages)
1898     * @return The list of contents
1899     */
1900    public List<Content> getPageContents (Page page, boolean ignoreContentsOfNonRemovablePage)
1901    {
1902        List<Content> contents = new ArrayList<>();
1903        
1904        if ((!ignoreContentsOfNonRemovablePage || page instanceof RemovableAmetysObject) && page.getType() == Page.PageType.CONTAINER)
1905        {
1906            for (Zone zone : page.getZones())
1907            {
1908                for (ZoneItem zoneItem : zone.getZoneItems())
1909                {
1910                    if (zoneItem.getType() == ZoneItem.ZoneType.CONTENT)
1911                    {
1912                        contents.add(zoneItem.getContent());
1913                    }
1914                }
1915            }
1916        }
1917        
1918        AmetysObjectIterable< ? extends Page> childrenPages = page.getChildrenPages();
1919        for (Page childPage : childrenPages)
1920        {
1921            contents.addAll(getPageContents(childPage, ignoreContentsOfNonRemovablePage));
1922        }
1923        
1924        return contents;
1925    }
1926    
1927    /**
1928     * Get the user rights on page container (page or sitemap)
1929     * @param pagesCt The pages container
1930     * @return The user's rights
1931     */
1932    protected Set<String> getUserRights (PagesContainer pagesCt)
1933    {
1934        UserIdentity user = _currentUserProvider.getUser();
1935        
1936        Set<String> userRights = _rightManager.getUserRights(user, pagesCt);
1937        
1938        // Do some specific stuff here, because the right 'Web_Rights_Page_Delete' is a right to delete child pages and not the page itself.
1939        // So the right should be checked on parent context.
1940        if (pagesCt instanceof Page)
1941        {
1942            PagesContainer parent = pagesCt.getParent();
1943            boolean canDelete = _rightManager.hasRight(user, "Web_Rights_Page_Delete", parent) == RightResult.RIGHT_ALLOW;
1944            if (!canDelete)
1945            {
1946                // No right on parent page, so remove the right if exists.
1947                userRights.remove("Web_Rights_Page_Delete");
1948            }
1949        }
1950        else
1951        {
1952            // There is no right of deletion on the sitemap
1953            userRights.remove("Web_Rights_Page_Delete");
1954        }
1955        
1956        return userRights;
1957    }
1958    
1959    private boolean _isReferenced (Content content)
1960    {
1961        return content instanceof WebContent && ((WebContent) content).getReferencingPages().size() > 1;
1962    }
1963    
1964    private boolean _isLocked (Content content)
1965    {
1966        if (content instanceof LockableAmetysObject)
1967        {
1968            LockableAmetysObject lockableContent = (LockableAmetysObject) content;
1969            if (lockableContent.isLocked())
1970            {
1971                boolean canUnlockAll = _rightManager.hasRight(_currentUserProvider.getUser(), "CMS_Rights_UnlockAll", "/cms") == RightResult.RIGHT_ALLOW;
1972                if (!LockHelper.isLockOwner(lockableContent, _currentUserProvider.getUser()) && !canUnlockAll)
1973                {
1974                    return true;
1975                }
1976            }
1977        }
1978        
1979        return false;
1980    }
1981    
1982    private boolean _isNew (Content content)
1983    {
1984        boolean isNew = false;
1985        if (content instanceof WorkflowAwareContent)
1986        {
1987            WorkflowAwareContent waContent = (WorkflowAwareContent) content;
1988            long workflowId = waContent.getWorkflowId();
1989            
1990            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent);
1991            isNew = workflow.getHistorySteps(workflowId).isEmpty();
1992        }
1993        return isNew;
1994    }
1995
1996    private Map<String, Object> _page2Json (Page page)
1997    {
1998        Map<String, Object> page2json = new HashMap<>();
1999        page2json.put("id", page.getId());
2000        page2json.put("title", page.getTitle());
2001        page2json.put("siteName", page.getSiteName());
2002        page2json.put("path", page.getPathInSitemap());
2003        return page2json;
2004    }
2005    
2006    private Map<String, Object> _content2Json (Content content, Locale locale)
2007    {
2008        Map<String, Object> content2json = new HashMap<>();
2009        content2json.put("id", content.getId());
2010        content2json.put("title", content.getTitle(locale));
2011        content2json.put("name", content.getName());
2012        
2013        List<Map<String, Object>> pages = new ArrayList<>();
2014        if (content instanceof WebContent)
2015        {
2016            content2json.put("siteName", ((WebContent) content).getSiteName());
2017            Collection<Page> refPages = ((WebContent) content).getReferencingPages();
2018            for (Page refPage : refPages)
2019            {
2020                pages.add(_page2Json(refPage));
2021            }
2022            content2json.put("pages", pages);
2023        }
2024        
2025        return content2json;
2026    }
2027    
2028    private Map<String, Object> _publication2Json (Page page)
2029    {
2030        Map<String, Object> pub2json = new HashMap<>();
2031        @SuppressWarnings("unchecked")
2032        ElementType<ZonedDateTime> dateType = (ElementType<ZonedDateTime>) _pageDataTypeExtensionPoint.getExtension(ModelItemTypeConstants.DATETIME_TYPE_ID);
2033        
2034        ZonedDateTime startDate = page.getValue(DefaultPage.METADATA_PUBLICATION_START_DATE);
2035        if (startDate != null)
2036        {
2037            pub2json.put("startDate", dateType.valueToJSONForClient(startDate));
2038        }
2039
2040        ZonedDateTime endDate = page.getValue(DefaultPage.METADATA_PUBLICATION_END_DATE);
2041        if (endDate != null)
2042        {
2043            pub2json.put("endDate", dateType.valueToJSONForClient(endDate));
2044        }
2045        
2046        return pub2json;
2047        
2048    }
2049    
2050    private AmetysObjectIterable<Content> _getIncomingContentReferences (String pageId)
2051    {
2052        String xpathQuery = "//element(*, ametys:content)[ametys-internal:consistency/@ametys-internal:link = 'page:" + pageId + "']";
2053        return _resolver.query(xpathQuery);
2054    }
2055    
2056    private AmetysObjectIterable<Page> _getIncomingPageReferences (String pageId)
2057    {
2058        String xpathQuery = "//element(*, ametys:page)[@ametys-internal:type = 'LINK' and @ametys-internal:url= '" + pageId + "']";
2059        return _resolver.query(xpathQuery);
2060    }
2061    
2062    private Map<String, Object> _zone2json (Zone zone)
2063    {
2064        Map<String, Object> jsonObject = new HashMap<>();
2065        jsonObject.put("name", zone.getName());
2066        jsonObject.put("isModifiable", zone instanceof ModifiableZone);
2067        
2068        List<Map<String, Object>> zoneitems = new ArrayList<>();
2069        for (ZoneItem zoneItem : zone.getZoneItems())
2070        {
2071            zoneitems.add(_zoneitem2json(zoneItem));
2072        }
2073        
2074        jsonObject.put("zoneitems", zoneitems);
2075        
2076        return jsonObject;
2077    }
2078    
2079    private Map<String, Object> _zoneitem2json (ZoneItem zoneItem)
2080    {
2081        Map<String, Object> jsonObject = new HashMap<>();
2082        jsonObject.put("name", zoneItem.getName());
2083        jsonObject.put("id", zoneItem.getId());
2084        jsonObject.put("isModifiable", zoneItem instanceof ModifiableZoneItem);
2085        
2086        switch (zoneItem.getType())
2087        {
2088            case CONTENT:
2089                try
2090                {
2091                    jsonObject.put("metadataSetName", zoneItem.getMetadataSetName());
2092                    
2093                    Content content = zoneItem.getContent();
2094                    jsonObject.put("content", _contentDAO.getContentProperties(content));
2095                }
2096                catch (AmetysRepositoryException e)
2097                {
2098                    getLogger().error("Unable to get content property on zone item", e);
2099                }
2100                break;
2101                
2102            case SERVICE:
2103                jsonObject.put("service", zoneItem.getServiceId());
2104                break;
2105            default:
2106                break;
2107        }
2108        
2109        return jsonObject;
2110    }
2111    
2112    private void _updateContentsAfterCopy (Page createdPage) throws AmetysRepositoryException
2113    {
2114        for (Zone zone : createdPage.getZones())
2115        {
2116            for (ZoneItem zoneItem : zone.getZoneItems())
2117            {
2118                if (zoneItem.getType().equals(ZoneType.CONTENT))
2119                {
2120                    Content content = zoneItem.getContent();
2121                    
2122                    // Convert content language if necessary
2123                    if (content instanceof ModifiableContent && content.getLanguage() != null && content.getLanguage() != createdPage.getSitemapName())
2124                    {
2125                        ((ModifiableContent) content).setLanguage(createdPage.getSitemapName());
2126                        ((ModifiableContent) content).saveChanges();
2127                    }
2128                    
2129                    // Create the first version
2130                    if (content instanceof VersionableAmetysObject)
2131                    {
2132                        ((VersionableAmetysObject) content).checkpoint();
2133                    }
2134                }
2135            }
2136        }
2137        
2138        // Browse child pages
2139        for (Page childPage : createdPage.getChildrenPages())
2140        {
2141            _updateContentsAfterCopy (childPage);
2142        }
2143    }
2144    
2145    private List<String> _getChildrenPageIds (Page page)
2146    {
2147        List<String> childIds = new ArrayList<>();
2148        
2149        for (Page childPage : page.getChildrenPages())
2150        {
2151            childIds.add(childPage.getId());
2152            childIds.addAll(_getChildrenPageIds(childPage));
2153        }
2154        
2155        return childIds;
2156    }
2157
2158    /**
2159     * Check each parent and return true if one of them is invisible
2160     * @param page page to check
2161     * @return true if at least one parent is invisible
2162     */
2163    private boolean _isParentInvisible (Page page)
2164    {
2165        AmetysObject parent = page.getParent();
2166        while (parent != null && parent instanceof Page)
2167        {
2168            boolean invisible = !((Page) parent).isVisible();
2169            if (invisible)
2170            {
2171                return true;
2172            }
2173            parent = parent.getParent();
2174        }
2175        return false;
2176    }
2177
2178    /* start of a group of methods for _isPreviewable */
2179    /**
2180     * Determine if this page is previewable
2181     * @param page The page to look at
2182     * @return true if the page can be previewed
2183     */
2184    private boolean _isPreviewable(Page page)
2185    {
2186        // Check for infinitive loop redirection
2187        ArrayList<String> pagesSequence = new ArrayList<>();
2188        pagesSequence.add(page.getId());
2189        if (_isInfiniteRedirection (page, pagesSequence))
2190        {
2191            getLogger().error("An infinite loop redirection was detected for page '" + page.getPathInSitemap() + "'");
2192            return false;
2193        }
2194
2195        if (page.getType() == PageType.LINK && LinkType.PAGE.equals(page.getURLType()))
2196        {
2197            return _isPageExist(page.getURL()) && _isPreviewable((Page) _resolver.resolveById(page.getURL()));
2198        }
2199
2200        if (page.getType() != PageType.NODE)
2201        {
2202            return true;
2203        }
2204        else
2205        {
2206            for (Page subPage : page.getChildrenPages())
2207            {
2208                if (_isPreviewable(subPage))
2209                {
2210                    return true;
2211                }
2212            }
2213        }
2214        return false;
2215    }
2216
2217    private boolean _isPageExist (String id)
2218    {
2219        try
2220        {
2221            _resolver.resolveById(id);
2222            return true;
2223        }
2224        catch (UnknownAmetysObjectException e)
2225        {
2226            return false;
2227        }
2228    }
2229
2230    private boolean _isInfiniteRedirection (Page page, List<String> pagesSequence)
2231    {
2232        Page redirectPage = _getPageRedirection (page);
2233        if (redirectPage == null)
2234        {
2235            return false;
2236        }
2237
2238        if (pagesSequence.contains(redirectPage.getId()))
2239        {
2240            return true;
2241        }
2242
2243        pagesSequence.add(redirectPage.getId());
2244        return _isInfiniteRedirection (redirectPage, pagesSequence);
2245    }
2246
2247    private Page _getPageRedirection (Page page)
2248    {
2249        if (PageType.LINK.equals(page.getType()) && LinkType.PAGE.equals(page.getURLType()))
2250        {
2251            try
2252            {
2253                String pageId = page.getURL();
2254                return _resolver.resolveById(pageId);
2255            }
2256            catch (AmetysRepositoryException e)
2257            {
2258                return null;
2259            }
2260        }
2261        else if (PageType.NODE.equals(page.getType()))
2262        {
2263            AmetysObjectIterable<? extends Page> childPages = page.getChildrenPages();
2264            Iterator<? extends Page> it = childPages.iterator();
2265            if (it.hasNext())
2266            {
2267                return it.next();
2268            }
2269        }
2270
2271        return null;
2272    }
2273    /* end of a group of methods for _isPreviewable */
2274}