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.util.HashMap; 019import java.util.Locale; 020import java.util.Map; 021import java.util.Set; 022 023import javax.jcr.RepositoryException; 024 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.logger.AbstractLogEnabled; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.commons.lang.ArrayUtils; 031import org.apache.commons.lang.StringUtils; 032 033import org.ametys.cms.CmsConstants; 034import org.ametys.cms.contenttype.ContentType; 035import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 036import org.ametys.cms.repository.Content; 037import org.ametys.cms.repository.DefaultContent; 038import org.ametys.cms.repository.WorkflowAwareContent; 039import org.ametys.core.observation.Event; 040import org.ametys.core.observation.ObservationManager; 041import org.ametys.core.right.RightManager; 042import org.ametys.core.right.RightManager.RightResult; 043import org.ametys.core.ui.Callable; 044import org.ametys.core.user.CurrentUserProvider; 045import org.ametys.core.user.UserIdentity; 046import org.ametys.plugins.repository.AmetysObjectResolver; 047import org.ametys.plugins.repository.AmetysRepositoryException; 048import org.ametys.plugins.repository.UnknownAmetysObjectException; 049import org.ametys.plugins.repository.jcr.JCRAmetysObject; 050import org.ametys.plugins.workflow.support.WorkflowProvider; 051import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 052import org.ametys.web.ObservationConstants; 053import org.ametys.web.repository.content.SharedContent; 054import org.ametys.web.repository.content.WebContent; 055import org.ametys.web.repository.content.jcr.DefaultSharedContent; 056import org.ametys.web.repository.content.shared.SharedContentManager; 057import org.ametys.web.repository.page.ZoneItem.ZoneType; 058import org.ametys.web.service.Service; 059import org.ametys.web.service.ServiceExtensionPoint; 060 061/** 062 * DAO for manipulating {@link Zone} and {@link ZoneItem} 063 * 064 */ 065public class ZoneDAO extends AbstractLogEnabled implements Serviceable, Component 066{ 067 /** Avalon Role */ 068 public static final String ROLE = ZoneDAO.class.getName(); 069 070 private AmetysObjectResolver _resolver; 071 private ObservationManager _observationManager; 072 private CurrentUserProvider _currentUserProvider; 073 private WorkflowProvider _workflowProvider; 074 private SharedContentManager _sharedContentManager; 075 private ContentTypesAssignmentHandler _cTypesAssignmentHandler; 076 private ServicesAssignmentHandler _serviceAssignmentHandler; 077 private ContentTypeExtensionPoint _cTypeEP; 078 private ServiceExtensionPoint _serviceEP; 079 private RightManager _rightManager; 080 081 @Override 082 public void service(ServiceManager smanager) throws ServiceException 083 { 084 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 085 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 086 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 087 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 088 _sharedContentManager = (SharedContentManager) smanager.lookup(SharedContentManager.ROLE); 089 _cTypesAssignmentHandler = (ContentTypesAssignmentHandler) smanager.lookup(ContentTypesAssignmentHandler.ROLE); 090 _serviceAssignmentHandler = (ServicesAssignmentHandler) smanager.lookup(ServicesAssignmentHandler.ROLE); 091 _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 092 _serviceEP = (ServiceExtensionPoint) smanager.lookup(ServiceExtensionPoint.ROLE); 093 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 094 } 095 096 /** 097 * Insert a shared content into a zone 098 * @param pageId The page id 099 * @param zoneName The zone name 100 * @param contentId The content id to insert 101 * @param viewName The view name 102 * @return The result map 103 */ 104 @Callable 105 public Map<String, Object> addSharedContent (String pageId, String zoneName, String contentId, String viewName) 106 { 107 Map<String, Object> result = new HashMap<>(); 108 109 SitemapElement sitemapElement = _resolver.resolveById(pageId); 110 111 if (!(sitemapElement instanceof ModifiableSitemapElement mSitemapElement)) 112 { 113 throw new IllegalArgumentException("Can not add a shared content on a non-modifiable page " + pageId); 114 } 115 116 if (sitemapElement.getTemplate() == null) 117 { 118 throw new IllegalArgumentException("Page '" + pageId + "' is not a container page: unable to add a shared content"); 119 } 120 121 ModifiableZone zone; 122 if (mSitemapElement.hasZone(zoneName)) 123 { 124 zone = mSitemapElement.getZone(zoneName); 125 } 126 else 127 { 128 zone = mSitemapElement.createZone(zoneName); 129 } 130 131 Content content = _resolver.resolveById(contentId); 132 String contentSiteName = null; 133 if (content instanceof WebContent) 134 { 135 contentSiteName = ((WebContent) content).getSiteName(); 136 } 137 138 // Check that the content type is an authorized content type. 139 Set<String> validCTypes = _cTypesAssignmentHandler.getAvailableContentTypes(mSitemapElement, zoneName, true); 140 for (String contentTypeId : content.getTypes()) 141 { 142 if (!validCTypes.contains(contentTypeId)) 143 { 144 ContentType cType = _cTypeEP.getExtension(contentTypeId); 145 result.put("error", true); 146 result.put("invalid-contenttype", cType.getLabel()); 147 return result; 148 } 149 } 150 151 ZoneItem zoneItem = null; 152 if (contentSiteName == null || mSitemapElement.getSiteName().equals(contentSiteName)) 153 { 154 // The content comes from the same site as the page: insert the content as a new zone item. 155 zoneItem = addContentReference(zone, content, viewName); 156 } 157 else 158 { 159 // The content is from a different site: create a shared content in the zone. 160 if (!(content instanceof DefaultContent) || content instanceof SharedContent) 161 { 162 throw new IllegalArgumentException("The source content must be a DefaultContent but not a SharedContent."); 163 } 164 165 DefaultContent defaultContent = (DefaultContent) content; 166 167 if (!ArrayUtils.contains(defaultContent.getAllLabels(), CmsConstants.LIVE_LABEL)) 168 { 169 result.put("error", true); 170 result.put("unpublished-content", defaultContent.getTitle(new Locale(mSitemapElement.getSitemapName()))); 171 return result; 172 } 173 174 zoneItem = createSharedContent(zone, defaultContent, viewName); 175 } 176 177 mSitemapElement.saveChanges(); 178 179 String zoneItemId = zoneItem.getId(); 180 result.put("zoneItemId", zoneItemId); 181 182 Map<String, Object> eventParams = new HashMap<>(); 183 eventParams.put(ObservationConstants.ARGS_SITEMAP_ELEMENT, mSitemapElement); 184 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItemId); 185 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, ZoneType.CONTENT); 186 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_CONTENT, zoneItem.getContent()); 187 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_ADDED, _currentUserProvider.getUser(), eventParams)); 188 189 return result; 190 } 191 192 /** 193 * Add the given content as a zone item in the given zone. 194 * @param zone the zone to add the content in. 195 * @param content the content to add. 196 * @param viewName the view name. 197 * @return the added zone item 198 */ 199 protected ZoneItem addContentReference(ModifiableZone zone, Content content, String viewName) 200 { 201 ModifiableZoneItem zoneItem = zone.addZoneItem(); 202 zoneItem.setType(ZoneType.CONTENT); 203 204 zoneItem.setContent(content); 205 zoneItem.setViewName(viewName); 206 207 return zoneItem; 208 } 209 210 /** 211 * Create a shared content referencing the given content and add the shared one to the zone. 212 * @param zone the zone to create the shared content in. 213 * @param originalContent the original content. 214 * @param viewName the view name. 215 * @return the added zone item 216 */ 217 protected ZoneItem createSharedContent(ModifiableZone zone, DefaultContent originalContent, String viewName) 218 { 219 ModifiableZoneItem zoneItem = zone.addZoneItem(); 220 zoneItem.setType(ZoneType.CONTENT); 221 222 DefaultSharedContent content = _sharedContentManager.createSharedContent(zone.getSitemapElement().getSite(), originalContent); 223 224 zoneItem.setContent(content); 225 zoneItem.setViewName(viewName); 226 227 return zoneItem; 228 } 229 230 /** 231 * Remove a zone item from page 232 * @param zoneItemId The id of zone item to delete 233 */ 234 @Callable 235 public void removeZoneItem (String zoneItemId) 236 { 237 ZoneItem zoneItem = _resolver.resolveById(zoneItemId); 238 239 if (!(zoneItem instanceof ModifiableZoneItem)) 240 { 241 throw new IllegalArgumentException("Can not remove a non-modifiable zone item " + zoneItemId); 242 } 243 244 ModifiableSitemapElement sitemapElement = (ModifiableSitemapElement) zoneItem.getZone().getSitemapElement(); 245 246 Map<String, Object> eventParams = new HashMap<>(); 247 eventParams.put(ObservationConstants.ARGS_SITEMAP_ELEMENT, sitemapElement); 248 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItemId); 249 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, zoneItem.getType()); 250 if (zoneItem.getType() == ZoneType.CONTENT) 251 { 252 // In case of a zone item of type content, provide it. 253 // There should be no problem as the observers are processed synchronously and, 254 // if the user want it to be deleted, it will happen in a future client call. 255 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_CONTENT, zoneItem.getContent()); 256 } 257 else if (zoneItem.getType() == ZoneType.SERVICE) 258 { 259 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_SERVICE, zoneItem.getServiceId()); 260 } 261 262 ((ModifiableZoneItem) zoneItem).remove(); 263 sitemapElement.saveChanges(); 264 265 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_DELETED, _currentUserProvider.getUser(), eventParams)); 266 } 267 268 /** 269 * Get the unreferenced contents of a {@link Page} or a {@link ZoneItem} 270 * @param id The id of page or zone item 271 * @return The list of unreferenced contents 272 */ 273 @Callable 274 public Map<String, Object> getZoneItemElementInfo(String id) 275 { 276 ZoneItem zoneItem = _resolver.resolveById(id); 277 278 Map<String, Object> info = new HashMap<>(); 279 info.put("type", zoneItem.getType().toString().toLowerCase()); 280 281 Content content = getContent(zoneItem); 282 if (content != null) 283 { 284 info.put("id", content.getId()); 285 info.put("name", content.getName()); 286 info.put("title", content.getTitle(new Locale(zoneItem.getZone().getSitemapElement().getSitemapName()))); 287 info.put("isNew", _isNew(content)); 288 info.put("isShared", content instanceof SharedContent); 289 info.put("hasShared", _sharedContentManager.hasSharedContents(content)); 290 info.put("isReferenced", _isReferenced(content)); 291 } 292 else 293 { 294 Service service = getService(zoneItem); 295 if (service != null) 296 { 297 info.put("id", service.getId()); 298 info.put("label", service.getLabel()); 299 info.put("url", service.getURL()); 300 } 301 } 302 303 return info; 304 } 305 306 /** 307 * Get the content of a zone item. Can be null if zone item is a service zone item. 308 * @param zoneItem The zone item 309 * @return The content or null if zone item is a service zone item. 310 */ 311 public Content getContent (ZoneItem zoneItem) 312 { 313 if (zoneItem.getType() == ZoneItem.ZoneType.CONTENT) 314 { 315 return zoneItem.getContent(); 316 } 317 return null; 318 } 319 320 private boolean _isReferenced (Content content) 321 { 322 return content instanceof WebContent && ((WebContent) content).getReferencingPages().size() > 1; 323 } 324 325 private boolean _isNew (Content content) 326 { 327 boolean isNew = false; 328 if (content instanceof WorkflowAwareContent) 329 { 330 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 331 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 332 333 long workflowId = waContent.getWorkflowId(); 334 isNew = workflow.getHistorySteps(workflowId).isEmpty(); 335 } 336 return isNew; 337 } 338 339 /** 340 * Get the service of a zone item. Can be null if zone item is not a service zone item. 341 * @param zoneItem The zone item 342 * @return The service or null if zone item is not a service zone item. 343 */ 344 public Service getService (ZoneItem zoneItem) 345 { 346 if (zoneItem.getType() == ZoneItem.ZoneType.SERVICE) 347 { 348 return _serviceEP.getExtension(zoneItem.getServiceId()); 349 } 350 return null; 351 } 352 353 /** 354 * Move a zone item of a page before/after another zone item of the same page 355 * @param zoneItemId The identifier of the zone item to move 356 * @param zoneName The destination zone name 357 * @param beforeOrAfter true if before or false after 358 * @param beforeOrAfterItemId The target zone item can be null 359 * @param pageId The concerned page id 360 * @return true when success 361 * @throws UnknownAmetysObjectException If an error occurred 362 * @throws AmetysRepositoryException If an error occurred 363 * @throws RepositoryException If an error occurred 364 */ 365 @Callable 366 public boolean moveZoneItemTo(String zoneItemId, String zoneName, boolean beforeOrAfter, String beforeOrAfterItemId, String pageId) throws UnknownAmetysObjectException, AmetysRepositoryException, RepositoryException 367 { 368 UserIdentity user = _currentUserProvider.getUser(); 369 370 ModifiableSitemapElement sitemapElement = _resolver.resolveById(pageId); 371 372 Zone zone; 373 if (!sitemapElement.hasZone(zoneName)) 374 { 375 zone = sitemapElement.createZone(zoneName); 376 } 377 else 378 { 379 zone = sitemapElement.getZone(zoneName); 380 } 381 382 ZoneItem zoneItem = sitemapElement instanceof JCRAmetysObject ? (ZoneItem) _resolver.resolveById(zoneItemId, ((JCRAmetysObject) sitemapElement).getNode().getSession()) : (ZoneItem) _resolver.resolveById(zoneItemId); 383 384 _checkZoneItem(sitemapElement, zoneName, zoneItem, user); 385 386 _checkArguments(zoneItemId, zoneName, pageId, user, sitemapElement, zone, zoneItem); 387 388 ((ModifiableZoneItem) zoneItem).moveTo(zone, false); 389 390 // For the moment the zoneItem is at the end 391 if (StringUtils.isNotBlank(beforeOrAfterItemId)) 392 { 393 if (beforeOrAfter) 394 { 395 ZoneItem futureFollowingZoneItem = _resolver.resolveById(beforeOrAfterItemId); 396 ((ModifiableZoneItem) zoneItem).orderBefore(futureFollowingZoneItem); 397 } 398 else 399 { 400 boolean isNext = false; 401 for (ZoneItem zi : zone.getZoneItems()) 402 { 403 if (isNext) 404 { 405 ((ModifiableZoneItem) zoneItem).orderBefore(zi); 406 break; 407 } 408 else if (beforeOrAfterItemId.equals(zi.getId())) 409 { 410 isNext = true; 411 } 412 } 413 } 414 } 415 416 ModifiableSitemapElement zoneItemPage = (ModifiableSitemapElement) zoneItem.getZone().getSitemapElement(); 417 if (zoneItemPage.needsSave()) 418 { 419 zoneItemPage.saveChanges(); 420 } 421 422 Map<String, Object> eventParams = new HashMap<>(); 423 eventParams.put(ObservationConstants.ARGS_SITEMAP_ELEMENT, sitemapElement); 424 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItemId); 425 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, zoneItem.getType()); 426 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_MOVED, user, eventParams)); 427 428 return true; 429 } 430 431 /** 432 * Check if the zone item can be moved to the given zone of the given page. 433 * @param page the page. 434 * @param zoneName the zone name. 435 * @param zoneItem the zone item to move. 436 * @param user the user 437 */ 438 protected void _checkZoneItem(ModifiableSitemapElement page, String zoneName, ZoneItem zoneItem, UserIdentity user) 439 { 440 if (ZoneType.CONTENT.equals(zoneItem.getType())) 441 { 442 String[] contentTypes = zoneItem.getContent().getTypes(); 443 444 Set<String> availableCTypes = _cTypesAssignmentHandler.getAvailableContentTypes(page, zoneName, true); 445 446 for (String contentType : contentTypes) 447 { 448 if (!availableCTypes.contains(contentType)) 449 { 450 throw new IllegalArgumentException("The user '" + user + " illegally tried to move a content of type '" + contentType + "' to the zone '" + zoneName + "' of page '" + page.getId() + "'."); 451 } 452 } 453 } 454 else if (ZoneType.SERVICE.equals(zoneItem.getType())) 455 { 456 String serviceId = zoneItem.getServiceId(); 457 458 Set<String> services = _serviceAssignmentHandler.getAvailableServices(page, zoneName); 459 if (!services.contains(serviceId)) 460 { 461 throw new IllegalArgumentException("The user '" + user + " illegally tried to move a service of id '" + serviceId + "' to the zone '" + zoneName + "' of page '" + page.getId() + "'."); 462 } 463 } 464 } 465 466 private void _checkArguments(String zoneItemId, String zoneName, String pageId, UserIdentity user, ModifiableSitemapElement page, Zone zone, ZoneItem zoneItem) 467 { 468 if (_rightManager.hasRight(user, "Web_Rights_Page_OrganizeZoneItem", page) != RightResult.RIGHT_ALLOW) 469 { 470 throw new IllegalArgumentException("User '" + user + "' tryed without convinient privileges to move zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' !"); 471 } 472 else if (zoneItem == null) 473 { 474 throw new IllegalArgumentException("User '" + user + "' tryed to move unexisting zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' !"); 475 } 476 else if (zone == null) 477 { 478 throw new IllegalArgumentException("User '" + user + "' tryed to move zone item '" + zoneItemId + "' to an unexisting zone '" + zoneName + "' of the page '" + pageId + "' !"); 479 } 480 else if (!(zone instanceof ModifiableZone)) 481 { 482 throw new IllegalArgumentException("User '" + user + "' tryed to move zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' but this zone is not modifiable !"); 483 } 484 else if (!(zoneItem instanceof ModifiableZoneItem)) 485 { 486 throw new IllegalArgumentException("User '" + user + "' tryed to move zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' but this zone item is not modifiable !"); 487 } 488 } 489}