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