001/*
002 *  Copyright 2017 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.plugins.workspaces.editionfo;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.Set;
021
022import org.apache.avalon.framework.service.ServiceException;
023import org.apache.avalon.framework.service.ServiceManager;
024import org.apache.cocoon.components.ContextHelper;
025import org.apache.cocoon.environment.Request;
026import org.apache.commons.lang.IllegalClassException;
027
028import org.ametys.cms.FilterNameHelper;
029import org.ametys.cms.repository.Content;
030import org.ametys.cms.repository.ModifiableWorkflowAwareContent;
031import org.ametys.cms.transformation.xslt.ResolveURIComponent;
032import org.ametys.core.observation.Event;
033import org.ametys.core.right.RightManager.RightResult;
034import org.ametys.core.ui.Callable;
035import org.ametys.plugins.explorer.ExplorerNode;
036import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
037import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory;
038import org.ametys.plugins.repository.AmetysObject;
039import org.ametys.plugins.repository.AmetysRepositoryException;
040import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
041import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
042import org.ametys.plugins.workflow.AbstractWorkflowComponent;
043import org.ametys.plugins.workflow.support.WorkflowProvider;
044import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
045import org.ametys.plugins.workspaces.AbstractWorkspaceModule;
046import org.ametys.plugins.workspaces.project.ProjectConstants;
047import org.ametys.plugins.workspaces.project.objects.Project;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.web.ObservationConstants;
050import org.ametys.web.explorer.ExplorerResourcesDAO;
051import org.ametys.web.repository.page.ModifiablePage;
052import org.ametys.web.repository.page.ModifiableZone;
053import org.ametys.web.repository.page.ModifiableZoneItem;
054import org.ametys.web.repository.page.Page;
055import org.ametys.web.repository.page.Page.PageType;
056import org.ametys.web.repository.page.ZoneItem.ZoneType;
057import org.ametys.web.repository.site.Site;
058import org.ametys.web.repository.site.SiteManager;
059import org.ametys.web.repository.sitemap.Sitemap;
060import org.ametys.web.skin.Skin;
061import org.ametys.web.skin.SkinTemplate;
062import org.ametys.web.skin.SkinTemplateZone;
063import org.ametys.web.workflow.CreateContentFunction;
064
065import com.google.common.collect.ImmutableSet;
066import com.opensymphony.workflow.WorkflowException;
067
068/**
069 * Manager for the Edition FO
070 */
071public class EditionFOWorkspaceModule extends AbstractWorkspaceModule
072{
073    /** Avalon ROLE */
074    public static final String EDITIONFO_MODULE_ID = EditionFOWorkspaceModule.class.getName();
075    
076    /** ContentType for the editionFO contents */
077    public static final String EDITIONFO_CONTENT_TYPE = "org.ametys.plugins.workspaces.Content.editionFO";
078    
079    /** Tag on the main page holding the wiki module */
080    public static final String __WIKI_MODULE_TAG = "WORKSPACES_MODULE_WIKI";
081    
082    /** Workspaces tasks list node name */
083    private static final String __WORKSPACES_EDITIONFO_NODE_NAME = "wiki";
084    
085    private static final String __RIGHT_CREATE_PAGE = "Plugins_Workspaces_Right_Page_EditionFO_Create";
086    private static final String __RIGHT_DELETE_PAGE = "Plugins_Workspaces_Right_Page_EditionFO_Delete";
087
088    /** Module i18n title key */
089    private static final String __MODULE_TITLE_KEY = "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_EDITIONFO_LABEL";
090    
091    /** The workflow provider */
092    protected WorkflowProvider _workflowProvider;
093    
094    private SiteManager _siteManager;
095    private ExplorerResourcesDAO _explorerResourcesDAO;
096    
097    @Override
098    public void service(ServiceManager manager) throws ServiceException
099    {
100        super.service(manager);
101        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
102        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
103        _explorerResourcesDAO = (ExplorerResourcesDAO) manager.lookup(org.ametys.plugins.explorer.resources.actions.ExplorerResourcesDAO.ROLE);
104    }
105    
106    
107    @Override
108    public String getId()
109    {
110        return EDITIONFO_MODULE_ID;
111    }
112    
113    @Override
114    protected String getModulePageName()
115    {
116        return "wiki";
117    }
118    
119    @Override
120    public I18nizableText getModuleTitle()
121    {
122        return new I18nizableText("plugin." + _pluginName, __MODULE_TITLE_KEY);
123    }
124    
125    @Override
126    protected I18nizableText getModulePageTitle()
127    {
128        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_WIKI_TITLE");
129    }
130    
131    @Override
132    protected String getModulePageTemplate()
133    {
134        return ProjectConstants.EDITIONFO_TEMPLATE;
135    }
136    
137    @Override
138    protected String getModuleTagName()
139    {
140        return __WIKI_MODULE_TAG;
141    }
142    
143    @Override
144    protected void initializeModulePage(ModifiablePage page)
145    {
146        _initializeWikiDefaultZone(page, page.getSite(), page.getSitemapName(), "Wiki");
147        _initializeWikiLeftZone(page, page);
148    }
149    
150    /**
151     * Initialize the default zone for the wiki page
152     * @param wikiPage The wiki page
153     * @param site The site
154     * @param sitemapName The sitemap name
155     * @param title The content title
156     */
157    protected void _initializeWikiDefaultZone(ModifiablePage wikiPage, Site site, String sitemapName, String title)
158    {
159        ModifiableZone defaultZone = wikiPage.createZone("default");
160        
161        ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem();
162        defaultZoneItem.setType(ZoneType.CONTENT);
163        
164        try
165        {
166            ModifiableWorkflowAwareContent content = createNewWikiContent(site, sitemapName, title);
167            defaultZoneItem.setContent(content);
168            content.saveChanges();
169        }
170        catch (WorkflowException e)
171        {
172            getLogger().error("Unable to initialize the Wiki Page content for the new workspace, the wiki index page will not be editable until the content is manually created in the BackOffice", e);
173        }
174    }
175    
176    /**
177     * Initialize the left zone for the wiki page
178     * @param wikiPage The wiki page
179     * @param wikiRootPage The wiki root page
180     */
181    protected void _initializeWikiLeftZone(ModifiablePage wikiPage, Page wikiRootPage)
182    {
183        ModifiableZone leftZone = wikiPage.createZone("left");
184
185        String sitemapServiceId = "org.ametys.web.service.SitemapService";
186        ModifiableZoneItem sitemapZoneItem = leftZone.addZoneItem();
187        sitemapZoneItem.setType(ZoneType.SERVICE);
188        sitemapZoneItem.setServiceId(sitemapServiceId);
189        ModifiableCompositeMetadata serviceMetadata = sitemapZoneItem.getServiceParameters();
190        serviceMetadata.setMetadata("xslt", _getDefaultXslt(sitemapServiceId));
191        serviceMetadata.setMetadata("header", "Wiki");
192        serviceMetadata.setMetadata("all", "select-root");
193        serviceMetadata.setMetadata("rootPageId", wikiRootPage.getId());
194        
195        String serviceId = "org.ametys.plugins.workspaces.EditionFO";
196        ModifiableZoneItem defaultZoneItem = leftZone.addZoneItem();
197        defaultZoneItem.setType(ZoneType.SERVICE);
198        defaultZoneItem.setServiceId(serviceId);
199        serviceMetadata = defaultZoneItem.getServiceParameters();
200        serviceMetadata.setMetadata("xslt", _getDefaultXslt(serviceId));
201    }
202
203    /**
204     * Retrieves the rights for the current user in the project
205     * @return The project
206     */
207    @Callable
208    public Map<String, Object> getModuleRights()
209    {
210        Map<String, Object> rights = new HashMap<>();
211        
212        Request request = ContextHelper.getRequest(_context);
213        String projectName = (String) request.getAttribute("projectName");
214        Project project = _projectManager.getProject(projectName);
215        ModifiableResourceCollection editionFORoot = getModuleRoot(project, false);
216        
217        rights.put("create", editionFORoot != null && _rightManager.currentUserHasRight(__RIGHT_CREATE_PAGE, editionFORoot) == RightResult.RIGHT_ALLOW);
218        rights.put("delete", editionFORoot != null && _rightManager.currentUserHasRight(__RIGHT_DELETE_PAGE, editionFORoot) == RightResult.RIGHT_ALLOW);
219        
220        return rights;
221    }
222    
223    /**
224     * Create a new content for a wiki page of the wiki module
225     * @param site The site
226     * @param sitemapName the name of the sitemap
227     * @param title The content title
228     * @return The content
229     * @throws WorkflowException if an error occurred
230     */
231    public ModifiableWorkflowAwareContent createNewWikiContent(Site site, String sitemapName, String title) throws WorkflowException
232    {
233        Map<String, Object> inputs = new HashMap<>();
234        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_TITLE_KEY, title);
235        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_NAME_KEY, FilterNameHelper.filterName(title));
236        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_TYPES_KEY, new String[] {EDITIONFO_CONTENT_TYPE});
237        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_LANGUAGE_KEY, sitemapName);
238        inputs.put(CreateContentFunction.SITE_KEY, site.getName());
239        
240        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow();
241        workflow.initialize("editionfo", 1, inputs);
242    
243        @SuppressWarnings("unchecked")
244        Map<String, Object> results = (Map<String, Object>) inputs.get(AbstractWorkflowComponent.RESULT_MAP_KEY);
245        ModifiableWorkflowAwareContent content = (ModifiableWorkflowAwareContent) results.get(Content.class.getName());
246        
247        return content;
248    }
249    
250    /**
251     * Create a new wiki page
252     * @param data The data
253     * @return The url
254     * @throws WorkflowException If an error occurred
255     * @throws IllegalAccessException If a user with insufficient rights try to create a page
256     */
257    @Callable
258    public Map<String, Object> createPage(Map<String, Object> data) throws WorkflowException, IllegalAccessException
259    {
260        Map<String, Object> result = new HashMap<>();
261        
262        Request request = ContextHelper.getRequest(_context);
263        String siteName = (String) request.getAttribute("siteName");
264        String sitemapLanguage = (String) request.getAttribute("sitemapLanguage");
265        String projectName = (String) request.getAttribute("projectName");
266        Project project = _projectManager.getProject(projectName);
267       
268        ModifiableResourceCollection editionFORoot = getModuleRoot(project, false);
269        _explorerResourcesDAO.checkUserRight(editionFORoot, __RIGHT_CREATE_PAGE);
270        
271        Site site = _siteManager.getSite(siteName);
272        Sitemap sitemap = site.getSitemap(sitemapLanguage);
273        String title = (String) data.get("title");
274        
275       
276        ModifiablePage wikiRootPage = sitemap.getChild(getModulePageName());
277        
278        String originalPageName = FilterNameHelper.filterName(title);
279        String pageName = originalPageName;
280        int index = 2;
281        while (wikiRootPage.hasChild(pageName))
282        {
283            pageName = originalPageName + "-" + index++;
284        }
285        ModifiablePage page = wikiRootPage.createChild(pageName, "ametys:defaultPage");
286        page.setTitle(title);
287        page.setSiteName(siteName);
288        page.setSitemapName(sitemapLanguage);
289        
290        _initializeNewPage(page, site, sitemapLanguage, title, wikiRootPage);
291        
292        wikiRootPage.saveChanges();
293        
294        Map<String, Object> eventParams = new HashMap<>();
295        eventParams.put(ObservationConstants.ARGS_PAGE, page);
296        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams));
297        
298        eventParams.put(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PROJECT, project);
299        
300        _observationManager.notify(new Event(org.ametys.plugins.workspaces.ObservationConstants.EVENT_EDITIONFO_CREATED, _currentUserProvider.getUser(), eventParams));
301
302        result.put("url", ResolveURIComponent.resolve("page", page.getId()));
303        return result;
304    }
305
306    
307    /**
308     * Initialize the new wiki page
309     * @param wikiPage The page
310     * @param site The site
311     * @param sitemapName The sitemap name 
312     * @param title The page title
313     * @param parentPage The parent page
314     */
315    protected void _initializeNewPage(ModifiablePage wikiPage, Site site, String sitemapName, String title, Page parentPage)
316    {
317        Skin skin = _skinsManager.getSkin(site.getSkinId());
318        SkinTemplate template = skin.getTemplate(ProjectConstants.EDITIONFO_TEMPLATE);
319        
320        if (template != null)
321        {
322            // Set the type and template.
323            wikiPage.setType(PageType.CONTAINER);
324            wikiPage.setTemplate(ProjectConstants.EDITIONFO_TEMPLATE);
325            
326            // Initialize the zones.
327            Map<String, SkinTemplateZone> templateZones = template.getZones();
328            if (templateZones.containsKey("default"))
329            {
330                _initializeWikiDefaultZone(wikiPage, site, sitemapName, title);
331            }
332            else
333            {
334                getLogger().error("A 'default' zone is mandatory in the editionfo template!");
335                return;
336            }
337            if (templateZones.containsKey("left"))
338            {
339                _initializeWikiLeftZone(wikiPage, parentPage);
340            }
341            else
342            {
343                getLogger().error("A 'left' zone is mandatory in the editionfo template!");
344                return;
345            }
346            
347            // Tag page as a sitemap section.
348            wikiPage.tag("SECTION");
349        }
350        else
351        {
352            String errorMsg = String.format(
353                    "The project workspace  '%s' was created with the skin '%s'  which doesn't possess the mandatory template '%s'.\nThe '%s' page of the project workspace could not be initialized.",
354                    site.getName(), site.getSkinId(), ProjectConstants.EDITIONFO_TEMPLATE, wikiPage.getName());
355            
356            getLogger().error(errorMsg);
357        }
358    }
359    
360    @Override
361    public ModifiableResourceCollection getModuleRoot(Project project, boolean create)
362    {
363        try
364        {
365            ExplorerNode projectRootNode = project.getExplorerRootNode();
366            
367            if (projectRootNode instanceof ModifiableResourceCollection)
368            {
369                ModifiableResourceCollection projectRootNodeRc = (ModifiableResourceCollection) projectRootNode;
370                return _getAmetysObject(projectRootNodeRc, __WORKSPACES_EDITIONFO_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create);
371            }
372            else
373            {
374                throw new IllegalClassException(ModifiableResourceCollection.class, projectRootNode.getClass());
375            }
376        }
377        catch (AmetysRepositoryException e)
378        {
379            throw new AmetysRepositoryException("Error getting the documents root node.", e);
380        }
381    }
382
383    
384    /**
385     * Utility method to get or create an ametys object
386     * @param <A> A sub class of AmetysObject
387     * @param parent The parent object
388     * @param name The ametys object name
389     * @param type The ametys object type
390     * @param create True to create the object if it does not exist
391     * @return ametys object
392     * @throws AmetysRepositoryException if an repository error occurs
393     */
394    protected <A extends AmetysObject> A _getAmetysObject(ModifiableTraversableAmetysObject parent, String name, String type, boolean create) throws AmetysRepositoryException
395    {
396        A object = null;
397        
398        if (parent.hasChild(name))
399        {
400            object = parent.getChild(name);
401        }
402        else if (create)
403        {
404            object = parent.createChild(name, type);
405            parent.saveChanges();
406        }
407        
408        return object;
409    }
410    
411    /**
412     * Delete a page in the edition FO module
413     * @param pageId The page to delete
414     * @return The result
415     * @throws IllegalAccessException If a user with insufficient rights try to delete a page
416     */
417    @Callable
418    public Map<String, Object> deletePage(String pageId) throws IllegalAccessException
419    {
420        Map<String, Object> result = new HashMap<>();
421        
422        Request request = ContextHelper.getRequest(_context);
423        String siteName = (String) request.getAttribute("siteName");
424        String sitemapLanguage = (String) request.getAttribute("sitemapLanguage");
425        String projectName = (String) request.getAttribute("projectName");
426        Project project = _projectManager.getProject(projectName);
427        ModifiablePage page = _resolver.resolveById(pageId);
428        String pageTitle = page.getTitle();
429        
430        ModifiableResourceCollection editionFORoot = getModuleRoot(project, false);
431        _explorerResourcesDAO.checkUserRight(editionFORoot, __RIGHT_DELETE_PAGE);
432        
433        _pageDAO.deletePage(page, true);
434        
435        Site site = _siteManager.getSite(siteName);
436        Sitemap sitemap = site.getSitemap(sitemapLanguage);
437        if (sitemap.hasChild(getModulePageName()))
438        {
439            result.put("url",  ResolveURIComponent.resolve("page", sitemap.getChild(getModulePageName()).getId()));
440        }
441        
442        Map<String, Object> eventParams = new HashMap<>();
443        eventParams.put(ObservationConstants.ARGS_PAGE_ID, pageId);
444        eventParams.put(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PAGE_TITLE, pageTitle);
445        eventParams.put(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PROJECT, project);
446        _observationManager.notify(new Event(org.ametys.plugins.workspaces.ObservationConstants.EVENT_EDITIONFO_DELETED, _currentUserProvider.getUser(), eventParams));
447        
448        return result;
449    }
450    
451    @Override
452    public Set<String> getAllowedEventTypes()
453    {
454        return ImmutableSet.of("wiki.page.created", "wiki.page.updated", "wiki.page.renamed", "wiki.page.deleted");
455    }
456}