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}