001/* 002 * Copyright 2011 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.blog; 017 018import java.util.HashMap; 019import java.util.Map; 020 021import javax.jcr.Node; 022import javax.jcr.RepositoryException; 023 024import org.apache.avalon.framework.logger.AbstractLogEnabled; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028import org.apache.commons.lang.StringUtils; 029 030import org.ametys.cms.FilterNameHelper; 031import org.ametys.cms.repository.Content; 032import org.ametys.cms.repository.DefaultContent; 033import org.ametys.cms.repository.ModifiableContent; 034import org.ametys.cms.workflow.CreateContentFunction; 035import org.ametys.core.observation.Event; 036import org.ametys.core.observation.ObservationManager; 037import org.ametys.core.observation.Observer; 038import org.ametys.core.user.UserIdentity; 039import org.ametys.core.util.I18nUtils; 040import org.ametys.plugins.blog.repository.BlogRootPageFactory; 041import org.ametys.plugins.blog.repository.VirtualPostsPage; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 044import org.ametys.plugins.workflow.AbstractWorkflowComponent; 045import org.ametys.plugins.workflow.support.WorkflowProvider; 046import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 047import org.ametys.runtime.i18n.I18nizableText; 048import org.ametys.runtime.plugin.component.PluginAware; 049import org.ametys.web.ObservationConstants; 050import org.ametys.web.repository.content.shared.SharedContentManager; 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.PageType; 055import org.ametys.web.repository.page.ZoneItem.ZoneType; 056import org.ametys.web.repository.site.Site; 057import org.ametys.web.repository.sitemap.Sitemap; 058import org.ametys.web.service.Service; 059import org.ametys.web.service.ServiceExtensionPoint; 060import org.ametys.web.service.ServiceParameter; 061import org.ametys.web.site.SiteConfigurationExtensionPoint; 062import org.ametys.web.skin.Skin; 063import org.ametys.web.skin.SkinTemplate; 064import org.ametys.web.skin.SkinTemplateZone; 065import org.ametys.web.skin.SkinsManager; 066 067import com.opensymphony.workflow.WorkflowException; 068 069/** 070 * Initializes all blog pages when a site is created. 071 */ 072public class InitializeBlogSiteObserver extends AbstractLogEnabled implements Observer, Serviceable, PluginAware 073{ 074 /** The ametys object resolver. */ 075 protected AmetysObjectResolver _ametysResolver; 076 077 /** The skins manager. */ 078 protected SkinsManager _skinsManager; 079 080 /** The service extension point. */ 081 protected ServiceExtensionPoint _serviceEP; 082 083 /** The site configuration extension point. */ 084 protected SiteConfigurationExtensionPoint _siteConf; 085 086 /** The i18n utils. */ 087 protected I18nUtils _i18nUtils; 088 089 /** The observation manager. */ 090 protected ObservationManager _observationManager; 091 092 /** The workflow provider */ 093 protected WorkflowProvider _workflowProvider; 094 095 /** The shared content manager. */ 096 protected SharedContentManager _sharedContentManager; 097 098 /** The plugin name. */ 099 protected String _pluginName; 100 101 /** The i18n catalogue. */ 102 protected String _i18nCatalogue; 103 104 @Override 105 public void setPluginInfo(String pluginName, String featureName, String id) 106 { 107 _pluginName = pluginName; 108 _i18nCatalogue = "plugin." + pluginName; 109 } 110 111 @Override 112 public void service(ServiceManager manager) throws ServiceException 113 { 114 _ametysResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 115 _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE); 116 _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE); 117 _siteConf = (SiteConfigurationExtensionPoint) manager.lookup(SiteConfigurationExtensionPoint.ROLE); 118 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 119 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 120 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 121 _sharedContentManager = (SharedContentManager) manager.lookup(SharedContentManager.ROLE); 122 } 123 124 @Override 125 public boolean supports(Event event) 126 { 127 return event.getId().equals(ObservationConstants.EVENT_SITE_ADDED) 128 || event.getId().equals(ObservationConstants.EVENT_SITEMAP_ADDED); 129 } 130 131 @Override 132 public int getPriority(Event event) 133 { 134 // Not too big but not too small either. 135 return MAX_PRIORITY + 842708; 136 } 137 138 @Override 139 public void observe(Event event, Map<String, Object> transientVars) throws Exception 140 { 141 if (event.getId().equals(ObservationConstants.EVENT_SITE_ADDED)) 142 { 143 Site site = (Site) event.getArguments().get(ObservationConstants.ARGS_SITE); 144 if (site != null && BlogConstants.BLOG_SITE_TYPE.equals(site.getType())) 145 { 146 initializeSite(site); 147 } 148 } 149 else if (event.getId().equals(ObservationConstants.EVENT_SITEMAP_ADDED)) 150 { 151 // If a sitemap is created in a blog site, initialize it. 152 Sitemap sitemap = (Sitemap) event.getArguments().get(ObservationConstants.ARGS_SITEMAP); 153 if (sitemap != null) 154 { 155 if (BlogConstants.BLOG_SITE_TYPE.equals(sitemap.getSite().getType())) 156 { 157 initializeSitemap(sitemap); 158 } 159 } 160 } 161 } 162 163 /** 164 * Initialize the given site. 165 * @param site the Site object. 166 */ 167 protected void initializeSite(Site site) 168 { 169 for (Sitemap sitemap : site.getSitemaps()) 170 { 171 initializeSitemap(sitemap); 172 } 173 } 174 175 /** 176 * Initialize the given sitemap. 177 * @param sitemap the Sitemap object. 178 */ 179 protected void initializeSitemap(Sitemap sitemap) 180 { 181 Site site = sitemap.getSite(); 182 String siteName = site.getName(); 183 String sitemapName = sitemap.getName(); 184 185 try 186 { 187 // Create the index page. 188 ModifiablePage indexPage = createPage(sitemap, "index", translate("PLUGINS_BLOG_INDEX_PAGE_TITLE", sitemapName)); 189 190 // Create the profile page. 191 ModifiablePage profilePage = createPage(sitemap, "about", translate("PLUGINS_BLOG_ABOUT_PAGE_TITLE", sitemapName)); 192 193 Content profileContent = initializeProfilePage(profilePage); 194 195 initializeIndexPage(indexPage, profileContent); 196 197 Node sitemapNode = sitemap.getNode(); 198 199 String[] virtualValue = new String[] {BlogRootPageFactory.class.getName()}; 200 201 sitemapNode.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, virtualValue); 202 203 // Create the search page. 204 ModifiablePage searchPage = createPage(sitemap, "search", translate("PLUGINS_BLOG_SEARCH_PAGE_TITLE", sitemapName)); 205 initializeSearchPage(searchPage, profileContent); 206 207 sitemap.saveChanges(); 208 209 // Notify of the sitemap change. 210 Map<String, Object> eventParams = new HashMap<>(); 211 eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap); 212 _observationManager.notify(new Event(ObservationConstants.EVENT_SITEMAP_UPDATED, new UserIdentity("admin", "admin"), eventParams)); 213 } 214 catch (RepositoryException e) 215 { 216 getLogger().error("Error setting the virtual blog root property on the sitemap " + sitemapName + " of site " + siteName); 217 } 218 } 219 220 /** 221 * Create the blog root page. 222 * @param sitemap The sitemap 223 * @param name The page's name 224 * @param title The page's title 225 * @return the root page. 226 */ 227 protected ModifiablePage createPage(Sitemap sitemap, String name, String title) 228 { 229 if (!sitemap.hasChild(name)) 230 { 231 ModifiablePage page = sitemap.createChild(name, "ametys:defaultPage"); 232 233 page.setTitle(title); 234 page.setType(PageType.NODE); 235 page.setSiteName(sitemap.getSiteName()); 236 page.setSitemapName(sitemap.getName()); 237 238 sitemap.saveChanges(); 239 240 return page; 241 } 242 else 243 { 244 return sitemap.getChild(name); 245 } 246 } 247 248 /** 249 * Initialize the blog index page. 250 * @param indexPage the blog index page. 251 * @param profileContent the profile Content. 252 */ 253 protected void initializeIndexPage(ModifiablePage indexPage, Content profileContent) 254 { 255 Site site = indexPage.getSite(); 256 Skin skin = _skinsManager.getSkin(site.getSkinId()); 257 SkinTemplate template = skin.getTemplate(BlogConstants.BLOG_TEMPLATE); 258 259 if (template != null) 260 { 261 // Set the type and template. 262 indexPage.setType(PageType.CONTAINER); 263 indexPage.setTemplate(BlogConstants.BLOG_TEMPLATE); 264 265 // Initialize the zones. 266 Map<String, SkinTemplateZone> templateZones = template.getZones(); 267 if (templateZones.containsKey("default")) 268 { 269 initializeDefaultZone(indexPage); 270 } 271 else 272 { 273 getLogger().error("A 'default' zone is mandatory in the blog template!"); 274 return; 275 } 276 277 if (templateZones.containsKey("aside")) 278 { 279 initializeAsideZone(indexPage); 280 } 281 282 if (templateZones.containsKey("about")) 283 { 284 initializeIndexAboutZone(indexPage, profileContent); 285 } 286 287 indexPage.saveChanges(); 288 289 Map<String, Object> eventParams = new HashMap<>(); 290 eventParams.put(ObservationConstants.ARGS_PAGE, indexPage); 291 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, new UserIdentity("admin", "admin"), eventParams)); 292 } 293 else 294 { 295 getLogger().error("The blog site " + site.getName() + " was created with the skin " 296 + site.getSkinId() + " which doesn't possess the mandatory template '" + BlogConstants.BLOG_TEMPLATE 297 + ". The blog's default pages will not be initialized."); 298 } 299 } 300 301 /** 302 * Initialize the search page. 303 * @param page the search page. 304 * @param profileContent the profile Content. 305 */ 306 protected void initializeSearchPage(ModifiablePage page, Content profileContent) 307 { 308 Site site = page.getSite(); 309 Skin skin = _skinsManager.getSkin(site.getSkinId()); 310 SkinTemplate template = skin.getTemplate(BlogConstants.BLOG_TEMPLATE); 311 312 if (template != null) 313 { 314 // Set the type and template. 315 page.setType(PageType.CONTAINER); 316 page.setTemplate(BlogConstants.BLOG_TEMPLATE); 317 318 // Initialize the zones. 319 Map<String, SkinTemplateZone> templateZones = template.getZones(); 320 if (templateZones.containsKey("default")) 321 { 322 initializeSearchDefaultZone(page); 323 } 324 else 325 { 326 getLogger().error("A 'default' zone is mandatory in the blog template!"); 327 } 328 329 if (templateZones.containsKey("aside")) 330 { 331 initializeAsideZone(page); 332 } 333 334 if (templateZones.containsKey("about")) 335 { 336 initializeIndexAboutZone(page, profileContent); 337 } 338 339 page.saveChanges(); 340 341 Map<String, Object> eventParams = new HashMap<>(); 342 eventParams.put(ObservationConstants.ARGS_PAGE, page); 343 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, new UserIdentity("admin", "admin"), eventParams)); 344 } 345 else 346 { 347 getLogger().error("The blog site " + site.getName() + " was created with the skin " 348 + site.getSkinId() + " which doesn't possess the mandatory template '" + BlogConstants.BLOG_TEMPLATE 349 + ". The blog's default pages will not be initialized."); 350 } 351 352 } 353 354 /** 355 * Initialize the search page's default zone. 356 * @param page the search page. 357 */ 358 protected void initializeSearchDefaultZone(ModifiablePage page) 359 { 360 ModifiableZone defaultZone = page.createZone("default"); 361 362 ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem(); 363 defaultZoneItem.setType(ZoneType.SERVICE); 364 defaultZoneItem.setServiceId("org.ametys.web.service.FrontSearchService"); 365 366 String rootId = page.getSitemap().getId(); 367 String searchPageId = "blog-category://" + VirtualPostsPage.NAME + "?rootId=" + rootId; 368 369 ModifiableCompositeMetadata serviceMetadata = defaultZoneItem.getServiceParameters(); 370 serviceMetadata.setMetadata("advanced-search", false); 371 serviceMetadata.setMetadata("offset", 10); 372 serviceMetadata.setMetadata("search-mode", "criteria-and-results"); 373 serviceMetadata.setMetadata("search-by-content-types", BlogConstants.POST_CONTENT_TYPE); 374 serviceMetadata.setMetadata("search-by-content-types-choice", "none"); 375 serviceMetadata.setMetadata("search-by-pages", searchPageId); 376 serviceMetadata.setMetadata("search-multisite", false); 377 serviceMetadata.setMetadata("xslt", getDefaultXslt("org.ametys.web.service.FrontSearchService")); 378 } 379 380 /** 381 * Initialize the blog profile page. 382 * @param page the blog profile page. 383 * @return the profile content. 384 */ 385 protected Content initializeProfilePage(ModifiablePage page) 386 { 387 Site site = page.getSite(); 388 Skin skin = _skinsManager.getSkin(site.getSkinId()); 389 SkinTemplate template = skin.getTemplate(BlogConstants.BLOG_TEMPLATE); 390 391 Content profileContent = null; 392 393 if (template != null) 394 { 395 // Set the type and template. 396 page.setType(PageType.CONTAINER); 397 page.setTemplate(BlogConstants.BLOG_TEMPLATE); 398 399 // Initialize the zones. 400 Map<String, SkinTemplateZone> templateZones = template.getZones(); 401 if (templateZones.containsKey("default")) 402 { 403 profileContent = initializeProfileDefaultZone(page); 404 } 405 else 406 { 407 getLogger().error("A 'default' zone is mandatory in the blog template!"); 408 return null; 409 } 410 411 if (templateZones.containsKey("aside")) 412 { 413 initializeAsideZone(page); 414 } 415 416 page.saveChanges(); 417 418 Map<String, Object> eventParams = new HashMap<>(); 419 eventParams.put(ObservationConstants.ARGS_PAGE, page); 420 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, new UserIdentity("admin", "admin"), eventParams)); 421 } 422 else 423 { 424 getLogger().error("The blog site " + site.getName() + " was created with the skin " 425 + site.getSkinId() + " which doesn't possess the mandatory template '" + BlogConstants.BLOG_TEMPLATE 426 + ". The blog's default pages will not be initialized."); 427 } 428 429 return profileContent; 430 } 431 432 /** 433 * Initialize the profile page's default zone. 434 * @param page the profile page. 435 * @return the profile content. 436 */ 437 protected Content initializeProfileDefaultZone(ModifiablePage page) 438 { 439 try 440 { 441 String lang = page.getSitemapName(); 442 443 Content profile = createProfileContent(BlogConstants.BLOG_WORKFLOW_NAME, page.getSiteName(), page.getSitemapName(), translate("PLUGINS_BLOG_ABOUT_PAGE_TITLE", lang)); 444 445 ModifiableZone aboutZone = page.createZone("default"); 446 447 ModifiableZoneItem aboutZoneItem = aboutZone.addZoneItem(); 448 aboutZoneItem.setType(ZoneType.CONTENT); 449 450 aboutZoneItem.setContent(profile); 451 452 return profile; 453 } 454 catch (WorkflowException e) 455 { 456 getLogger().error("Error creating profile content", e); 457 return null; 458 } 459 } 460 461 462 /** 463 * Initialize the default zone. 464 * @param page The page 465 */ 466 protected void initializeDefaultZone(ModifiablePage page) 467 { 468 ModifiableZone defaultZone = page.createZone("default"); 469 470 // Archives service. 471 ModifiableZoneItem archivesZoneItem = defaultZone.addZoneItem(); 472 archivesZoneItem.setType(ZoneType.SERVICE); 473 archivesZoneItem.setServiceId(BlogConstants.POSTS_SERVICE_ID); 474 475 String maxCount = _siteConf.getValueAsString(page.getSiteName(), BlogConstants.MAX_COUNT_PARAM_ID); 476 if (StringUtils.isEmpty(maxCount)) 477 { 478 maxCount = Integer.toString(BlogConstants.DEFAULT_POST_COUNT_PER_PAGE); 479 } 480 481 ModifiableCompositeMetadata parameters = archivesZoneItem.getServiceParameters(); 482 parameters.setMetadata("header", ""); 483 parameters.setMetadata("type", "all"); 484 parameters.setMetadata("metadataSetName", "abstract"); 485 parameters.setMetadata("maxCount", maxCount); 486 parameters.setMetadata("xslt", getDefaultXslt(BlogConstants.POSTS_SERVICE_ID)); 487 } 488 489 /** 490 * Initialize the index page's "about" zone. 491 * @param indexPage the index Page. 492 * @param profileContent the profile content. 493 */ 494 protected void initializeIndexAboutZone(ModifiablePage indexPage, Content profileContent) 495 { 496 ModifiableZone aboutZone = indexPage.createZone("about"); 497 498 if (profileContent instanceof DefaultContent) 499 { 500 ModifiableZoneItem aboutZoneItem = aboutZone.addZoneItem(); 501 502 aboutZoneItem.setType(ZoneType.CONTENT); 503 aboutZoneItem.setContent(profileContent); 504 aboutZoneItem.setMetadataSetName("abstract"); 505 } 506 } 507 508 /** 509 * Initialize the "aside" zone. 510 * @param page the page on which to initialize the aside zone. 511 */ 512 protected void initializeAsideZone(ModifiablePage page) 513 { 514 String language = page.getSitemapName(); 515 516 ModifiableZone aboutZone = page.createZone("aside"); 517 518 // Archives service. 519 ModifiableZoneItem archivesZoneItem = aboutZone.addZoneItem(); 520 archivesZoneItem.setType(ZoneType.SERVICE); 521 archivesZoneItem.setServiceId(BlogConstants.ARCHIVES_SERVICE_ID); 522 523 ModifiableCompositeMetadata parameters = archivesZoneItem.getServiceParameters(); 524 parameters.setMetadata("service-title", translate("PLUGINS_BLOG_ARCHIVES_ZONEITEM_TITLE", language)); 525 parameters.setMetadata("xslt", getDefaultXslt(BlogConstants.ARCHIVES_SERVICE_ID)); 526 527 // Tags service. 528 ModifiableZoneItem tagsZoneItem = aboutZone.addZoneItem(); 529 tagsZoneItem.setType(ZoneType.SERVICE); 530 tagsZoneItem.setServiceId(BlogConstants.TAGS_SERVICE_ID); 531 532 parameters = tagsZoneItem.getServiceParameters(); 533 parameters.setMetadata("service-title", translate("PLUGINS_BLOG_TAGS_ZONEITEM_TITLE", language)); 534 parameters.setMetadata("xslt", getDefaultXslt(BlogConstants.TAGS_SERVICE_ID)); 535 } 536 537 /** 538 * Create a profile content. 539 * @param workflowName The workflow name 540 * @param siteName the site name. 541 * @param lang the language. 542 * @param title The content's title 543 * @return the created person content. 544 * @throws WorkflowException if an error occurs 545 */ 546 protected Content createProfileContent(String workflowName, String siteName, String lang, String title) throws WorkflowException 547 { 548 String contentName = FilterNameHelper.filterName(title); 549 550 Map<String, Object> params = new HashMap<>(); 551 552 // Workflow result 553 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(); 554 555 Map<String, Object> workflowResult = new HashMap<>(); 556 params.put(AbstractWorkflowComponent.RESULT_MAP_KEY, workflowResult); 557 558 // Workflow parameters. 559 params.put("workflowName", workflowName); 560 params.put("org.ametys.web.repository.site.Site", siteName); 561 params.put(CreateContentFunction.CONTENT_NAME_KEY, contentName); 562 params.put(CreateContentFunction.CONTENT_TITLE_KEY, title); 563 params.put(CreateContentFunction.CONTENT_TYPES_KEY, new String[] {BlogConstants.PROFILE_CONTENT_TYPE}); 564 params.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, lang); 565 566 workflow.initialize(workflowName, 1, params); 567 String contentId = (String) workflowResult.get("contentId"); 568 Content content = _ametysResolver.resolveById(contentId); 569 570 // FIXME API Check if modifiable. 571 ModifiableContent modifiableContent = (ModifiableContent) content; 572 573 modifiableContent.setTitle(title); 574 575 modifiableContent.saveChanges(); 576 577 return content; 578 } 579 580 /** 581 * Get the default value of the XSLT parameter of the given service. 582 * @param serviceId the service ID. 583 * @return the default XSLT parameter value. 584 */ 585 protected String getDefaultXslt(String serviceId) 586 { 587 Service archivesService = _serviceEP.getExtension(serviceId); 588 ServiceParameter xsltParam = (ServiceParameter) archivesService.getParameters().get("xslt"); 589 590 if (xsltParam != null) 591 { 592 return xsltParam.getDefaultValue().toString(); 593 } 594 595 return ""; 596 } 597 598 /** 599 * Translate the key in the plugin's catalogue. 600 * @param key the i18n key. 601 * @param language the language. 602 * @return the translated value. 603 */ 604 protected String translate(String key, String language) 605 { 606 return _i18nUtils.translate(new I18nizableText(_i18nCatalogue, key), language); 607 } 608 609}