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