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.cms.repository; 017 018import java.util.ArrayList; 019import java.util.Date; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import javax.jcr.Node; 027import javax.jcr.RepositoryException; 028import javax.jcr.Session; 029 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.context.Context; 032import org.apache.avalon.framework.context.ContextException; 033import org.apache.avalon.framework.context.Contextualizable; 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.cocoon.components.ContextHelper; 039import org.apache.cocoon.environment.Request; 040import org.apache.commons.lang3.StringUtils; 041 042import org.ametys.cms.ObservationConstants; 043import org.ametys.cms.content.ContentHelper; 044import org.ametys.cms.contenttype.ContentType; 045import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 046import org.ametys.cms.contenttype.ContentTypesHelper; 047import org.ametys.cms.contenttype.MetadataSet; 048import org.ametys.cms.lock.LockContentManager; 049import org.ametys.cms.tag.Tag; 050import org.ametys.cms.tag.TagProviderExtensionPoint; 051import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 052import org.ametys.cms.workflow.ContentWorkflowHelper; 053import org.ametys.core.observation.Event; 054import org.ametys.core.observation.ObservationManager; 055import org.ametys.core.right.RightManager; 056import org.ametys.core.right.RightManager.RightResult; 057import org.ametys.core.ui.Callable; 058import org.ametys.core.user.CurrentUserProvider; 059import org.ametys.core.user.UserIdentity; 060import org.ametys.core.user.UserManager; 061import org.ametys.plugins.core.user.UserHelper; 062import org.ametys.plugins.explorer.ExplorerNode; 063import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 064import org.ametys.plugins.explorer.resources.Resource; 065import org.ametys.plugins.repository.AmetysObject; 066import org.ametys.plugins.repository.AmetysObjectResolver; 067import org.ametys.plugins.repository.AmetysRepositoryException; 068import org.ametys.plugins.repository.ModifiableAmetysObject; 069import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 070import org.ametys.plugins.repository.RemovableAmetysObject; 071import org.ametys.plugins.repository.TraversableAmetysObject; 072import org.ametys.plugins.repository.UnknownAmetysObjectException; 073import org.ametys.plugins.repository.lock.LockAwareAmetysObject; 074import org.ametys.plugins.repository.lock.LockHelper; 075import org.ametys.plugins.repository.lock.LockableAmetysObject; 076import org.ametys.plugins.repository.version.VersionableAmetysObject; 077import org.ametys.plugins.workflow.store.AbstractJackrabbitWorkflowStore; 078import org.ametys.plugins.workflow.store.AmetysObjectWorkflowStore; 079import org.ametys.plugins.workflow.support.WorkflowProvider; 080import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 081import org.ametys.runtime.parameter.ParameterHelper; 082 083import com.opensymphony.workflow.WorkflowException; 084import com.opensymphony.workflow.spi.Step; 085 086/** 087 * DAO for manipulating contents 088 * 089 */ 090public class ContentDAO extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 091{ 092 /** Avalon Role */ 093 public static final String ROLE = ContentDAO.class.getName(); 094 095 /** Ametys resolver */ 096 protected AmetysObjectResolver _resolver; 097 /** Ametys observation manger */ 098 protected ObservationManager _observationManager; 099 /** Component to get current user */ 100 protected CurrentUserProvider _currentUserProvider; 101 /** Component to get tags */ 102 protected TagProviderExtensionPoint _tagProvider; 103 104 /** Workflow component */ 105 protected WorkflowProvider _workflowProvider; 106 /** Workflow helper component */ 107 protected ContentWorkflowHelper _contentWorkflowHelper; 108 /** Component to manager lock */ 109 protected LockContentManager _lockManager; 110 /** Content-type extension point */ 111 protected ContentTypeExtensionPoint _contentTypeEP; 112 /** Content helper */ 113 protected ContentHelper _contentHelper; 114 /** Content types helper */ 115 protected ContentTypesHelper _cTypesHelper; 116 /** Rights manager */ 117 protected RightManager _rightManager; 118 /** Cocoon context */ 119 protected Context _context; 120 /** The user manager */ 121 protected UserManager _usersManager; 122 /** Helper for users */ 123 protected UserHelper _userHelper; 124 125 /** The mode for tag edition */ 126 public enum TagMode 127 { 128 /** Value will replace existing one */ 129 REPLACE, 130 /** Value will be added to existing one */ 131 INSERT, 132 /** Value will be removed from existing one */ 133 REMOVE 134 } 135 136 public void contextualize(Context context) throws ContextException 137 { 138 _context = context; 139 } 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 _usersManager = (UserManager) smanager.lookup(UserManager.ROLE); 148 _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE); 149 _tagProvider = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE); 150 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 151 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 152 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 153 _lockManager = (LockContentManager) smanager.lookup(LockContentManager.ROLE); 154 _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); 155 _cTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 156 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 157 } 158 159 /** 160 * Delete contents 161 * @param contentsId The ids of contents to delete 162 * @return the deleted and undeleted contents 163 */ 164 @Callable 165 public Map<String, Object> deleteContents(List<String> contentsId) 166 { 167 return deleteContents(contentsId, false); 168 } 169 170 /** 171 * Delete contents 172 * @param contentsId The ids of contents to delete 173 * @param ignoreRights true to ignore user rights 174 * @return the deleted and undeleted contents 175 */ 176 public Map<String, Object> deleteContents(List<String> contentsId, boolean ignoreRights) 177 { 178 Map<String, Object> results = new HashMap<>(); 179 180 results.put("deleted-contents", new ArrayList<Map<String, Object>>()); 181 results.put("undeleted-contents", new ArrayList<Map<String, Object>>()); 182 results.put("referenced-contents", new ArrayList<Map<String, Object>>()); 183 results.put("unauthorized-contents", new ArrayList<Map<String, Object>>()); 184 results.put("locked-contents", new ArrayList<Map<String, Object>>()); 185 186 for (String contentId : contentsId) 187 { 188 Content content = _resolver.resolveById(contentId); 189 String contentName = content.getName(); 190 String contentTitle = StringUtils.defaultString(content.getTitle(), contentName); 191 192 Map<String, Object> contentParams = new HashMap<>(); 193 contentParams.put("id", content.getId()); 194 contentParams.put("title", contentTitle); 195 contentParams.put("name", contentName); 196 197 if (!(content instanceof RemovableAmetysObject)) 198 { 199 throw new IllegalArgumentException("The content [" + content.getId() + "] is not a RemovableAmetysObject, it can't be deleted."); 200 } 201 202 try 203 { 204 if (!ignoreRights && !canDelete(content)) 205 { 206 // User has no sufficient right 207 @SuppressWarnings("unchecked") 208 List<Map<String, Object>> unauthorizedContents = (List<Map<String, Object>>) results.get("unauthorized-contents"); 209 unauthorizedContents.add(contentParams); 210 continue; 211 } 212 213 if (content instanceof LockableAmetysObject) 214 { 215 // If the content is locked, try to unlock it. 216 LockableAmetysObject lockableContent = (LockableAmetysObject) content; 217 if (lockableContent.isLocked()) 218 { 219 boolean canUnlockAll = _rightManager.hasRight(_currentUserProvider.getUser(), "CMS_Rights_UnlockAll", "/cms") == RightResult.RIGHT_ALLOW; 220 if (LockHelper.isLockOwner(lockableContent, _currentUserProvider.getUser()) || canUnlockAll) 221 { 222 lockableContent.unlock(); 223 } 224 else 225 { 226 @SuppressWarnings("unchecked") 227 List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents"); 228 lockedContents.add(contentParams); 229 continue; 230 } 231 } 232 } 233 234 if (_isContentReferenced(content)) 235 { 236 // Indicate that the content is referenced. 237 @SuppressWarnings("unchecked") 238 List<Map<String, Object>> referencedContents = (List<Map<String, Object>>) results.get("referenced-contents"); 239 referencedContents.add(contentParams); 240 } 241 else 242 { 243 // All checks have been done, the content can be deleted 244 Map<String, Object> eventParams = _getEventParametersForDeletion(content); 245 246 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_DELETING, _currentUserProvider.getUser(), eventParams)); 247 248 RemovableAmetysObject removableContent = (RemovableAmetysObject) content; 249 ModifiableAmetysObject parent = removableContent.getParent(); 250 251 // Remove the content. 252 removableContent.remove(); 253 254 parent.saveChanges(); 255 256 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_DELETED, _currentUserProvider.getUser(), eventParams)); 257 258 @SuppressWarnings("unchecked") 259 List<Map<String, Object>> deletedContents = (List<Map<String, Object>>) results.get("deleted-contents"); 260 deletedContents.add(contentParams); 261 } 262 } 263 catch (AmetysRepositoryException e) 264 { 265 getLogger().error("Unable to delete content '" + contentId + "'", e); 266 267 @SuppressWarnings("unchecked") 268 List<Map<String, Object>> undeletedContents = (List<Map<String, Object>>) results.get("undeleted-contents"); 269 undeletedContents.add(contentParams); 270 } 271 } 272 273 return results; 274 } 275 276 /** 277 * Test if content is still referenced before removing it 278 * @param content The content to remove 279 * @return true if content is still referenced 280 */ 281 protected boolean _isContentReferenced (Content content) 282 { 283 return !content.getReferencingContents().isEmpty(); 284 } 285 286 /** 287 * Get parameters for content deleted {@link Event} 288 * @param content the removed content 289 * @return the event's parameters 290 */ 291 protected Map<String, Object> _getEventParametersForDeletion (Content content) 292 { 293 Map<String, Object> eventParams = new HashMap<>(); 294 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 295 eventParams.put(ObservationConstants.ARGS_CONTENT_NAME, content.getName()); 296 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 297 return eventParams; 298 } 299 300 /** 301 * Get the contents properties 302 * @param contentIds The ids of contents 303 * @param workspaceName The workspace name. Can be null to get contents in current workspace. 304 * @return The contents' properties 305 */ 306 @Callable 307 public Map<String, Object> getContentsProperties (List<String> contentIds, String workspaceName) 308 { 309 Map<String, Object> result = new HashMap<>(); 310 311 List<Map<String, Object>> contents = new ArrayList<>(); 312 List<String> contentsNotFound = new ArrayList<>(); 313 314 Request request = ContextHelper.getRequest(_context); 315 String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 316 try 317 { 318 if (StringUtils.isNotEmpty(workspaceName)) 319 { 320 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 321 } 322 323 for (String contentId : contentIds) 324 { 325 try 326 { 327 Content content = _resolver.resolveById(contentId); 328 contents.add(getContentProperties(content)); 329 } 330 catch (UnknownAmetysObjectException e) 331 { 332 contentsNotFound.add(contentId); 333 } 334 } 335 336 result.put("contents", contents); 337 result.put("contentsNotFound", contentsNotFound); 338 } 339 finally 340 { 341 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace); 342 } 343 344 return result; 345 } 346 347 /** 348 * Get the content properties 349 * @param contentId The id of content 350 * @param workspaceName The workspace name. Can be null to get content in current workspace. 351 * @return The content's properties 352 */ 353 @Callable 354 public Map<String, Object> getContentProperties (String contentId, String workspaceName) 355 { 356 Request request = ContextHelper.getRequest(_context); 357 String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 358 try 359 { 360 if (StringUtils.isNotEmpty(workspaceName)) 361 { 362 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 363 } 364 365 Content content = _resolver.resolveById(contentId); 366 return getContentProperties(content); 367 } 368 finally 369 { 370 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace); 371 } 372 } 373 374 /** 375 * Get the content properties 376 * @param content The content 377 * @return The content properties 378 */ 379 public Map<String, Object> getContentProperties (Content content) 380 { 381 Map<String, Object> infos = new HashMap<>(); 382 383 infos.put("id", content.getId()); 384 infos.put("name", content.getName()); 385 infos.put("title", content.getTitle()); 386 infos.put("path", content.getPath()); 387 infos.put("types", content.getTypes()); 388 infos.put("mixins", content.getMixinTypes()); 389 infos.put("lang", content.getLanguage()); 390 infos.put("creator", _userHelper.user2json(content.getCreator())); 391 infos.put("lastContributor", _userHelper.user2json(content.getLastContributor())); 392 infos.put("creationDate", ParameterHelper.valueToString(content.getCreationDate())); 393 infos.put("lastModified", ParameterHelper.valueToString(content.getLastModified())); 394 infos.put("isSimple", _contentHelper.isSimple(content)); 395 396 if (content instanceof WorkflowAwareContent) 397 { 398 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 399 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 400 401 infos.put("workflowName", workflow.getWorkflowName(waContent.getWorkflowId())); 402 403 List<Long> workflowSteps = new ArrayList<>(); 404 405 List<Step> currentSteps = workflow.getCurrentSteps(waContent.getWorkflowId()); 406 for (Step step : currentSteps) 407 { 408 workflowSteps.add(step.getId()); 409 } 410 infos.put("workflowStep", workflowSteps); 411 412 int[] availableActions = _contentWorkflowHelper.getAvailableActions(waContent); 413 infos.put("availableActions", availableActions); 414 } 415 416 if (content instanceof ModifiableContent) 417 { 418 infos.put("isModifiable", true); 419 } 420 421 if (content instanceof LockAwareAmetysObject) 422 { 423 LockAwareAmetysObject lockableContent = (LockAwareAmetysObject) content; 424 if (lockableContent.isLocked()) 425 { 426 infos.put("locked", true); 427 infos.put("lockOwner", lockableContent.getLockOwner()); 428 infos.put("canUnlock", _lockManager.canUnlock(lockableContent)); 429 } 430 } 431 432 infos.put("rights", getUserRights(content)); 433 434 Map<String, Object> additionalData = new HashMap<>(); 435 436 String[] contenttypes = content.getTypes(); 437 for (String cTypeId : contenttypes) 438 { 439 ContentType cType = _contentTypeEP.getExtension(cTypeId); 440 if (cType != null) 441 { 442 additionalData.putAll(cType.getAdditionalData(content)); 443 } 444 } 445 446 if (!additionalData.isEmpty()) 447 { 448 infos.put("additionalData", additionalData); 449 } 450 451 return infos; 452 } 453 454 /** 455 * Get the content's properties for description 456 * @param contentId The id of content 457 * @param workspaceName The workspace name. Can be null to get content in current workspace. 458 * @return The content's properties for description 459 */ 460 @Callable 461 public Map<String, Object> getContentDescription (String contentId, String workspaceName) 462 { 463 Request request = ContextHelper.getRequest(_context); 464 String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 465 try 466 { 467 if (StringUtils.isNotEmpty(workspaceName)) 468 { 469 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 470 } 471 472 Content content = _resolver.resolveById(contentId); 473 return getContentDescription(content); 474 } 475 finally 476 { 477 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace); 478 } 479 } 480 481 /** 482 *Get the content's properties for description 483 * @param content The content 484 * @return The content's properties for description 485 */ 486 public Map<String, Object> getContentDescription (Content content) 487 { 488 Map<String, Object> infos = new HashMap<>(); 489 490 infos.put("id", content.getId()); 491 infos.put("name", content.getName()); 492 infos.put("title", content.getTitle()); 493 infos.put("types", content.getTypes()); 494 infos.put("mixins", content.getMixinTypes()); 495 infos.put("lang", content.getLanguage()); 496 infos.put("creator", _userHelper.user2json(content.getCreator())); 497 infos.put("lastContributor", _userHelper.user2json(content.getLastContributor())); 498 infos.put("lastModified", ParameterHelper.valueToString(content.getLastModified())); 499 infos.put("iconGlyph", _cTypesHelper.getIconGlyph(content)); 500 infos.put("iconDecorator", _cTypesHelper.getIconDecorator(content)); 501 infos.put("smallIcon", _cTypesHelper.getSmallIcon(content)); 502 infos.put("mediumIcon", _cTypesHelper.getMediumIcon(content)); 503 infos.put("largeIcon", _cTypesHelper.getLargeIcon(content)); 504 505 return infos; 506 } 507 508 /** 509 * Get the metadata sets of a content 510 * @param contentId the content's id 511 * @param edition Set to true to get edition metadata set. False otherwise. 512 * @param includeInternal Set to true to include internal metadata sets. 513 * @return the metadata sets 514 */ 515 @Callable 516 public List<Map<String, Object>> getContentMetadataSets (String contentId, boolean edition, boolean includeInternal) 517 { 518 List<Map<String, Object>> metadataSets = new ArrayList<>(); 519 520 Content content = _resolver.resolveById(contentId); 521 String contentTypeId = _cTypesHelper.getContentTypeIdForRendering(content); 522 523 ContentType cType = _contentTypeEP.getExtension(contentTypeId); 524 525 Set<String> metadataSetNames = edition ? cType.getEditionMetadataSetNames(includeInternal) : cType.getViewMetadataSetNames(includeInternal); 526 for (String metadataSetName : metadataSetNames) 527 { 528 MetadataSet metadataSet = edition ? cType.getMetadataSetForEdition(metadataSetName) : cType.getMetadataSetForView(metadataSetName); 529 530 Map<String, Object> viewInfos = new HashMap<>(); 531 viewInfos.put("name", metadataSetName); 532 viewInfos.put("label", metadataSet.getLabel()); 533 viewInfos.put("description", metadataSet.getDescription()); 534 metadataSets.add(viewInfos); 535 } 536 537 return metadataSets; 538 } 539 540 /** 541 * Get the user rights on content 542 * @param content The content 543 * @return The user's rights 544 */ 545 protected Set<String> getUserRights (Content content) 546 { 547 UserIdentity user = _currentUserProvider.getUser(); 548 return _rightManager.getUserRights(user, content); 549 } 550 551 /** 552 * Get the tags of contents 553 * @param contentIds The content's ids 554 * @return the tags 555 */ 556 @Callable 557 public Set<String> getTags (List<String> contentIds) 558 { 559 Set<String> tags = new HashSet<>(); 560 561 for (String contentId : contentIds) 562 { 563 Content content = _resolver.resolveById(contentId); 564 tags.addAll(content.getTags()); 565 } 566 567 return tags; 568 } 569 570 /** 571 * Tag a list of contents with the given tags 572 * @param contentIds The ids of contents to tag 573 * @param tagNames The tags 574 * @param contextualParameters The contextual parameters 575 * @return the result 576 */ 577 @Callable 578 public Map<String, Object> tag (List<String> contentIds, List<String> tagNames, Map<String, Object> contextualParameters) 579 { 580 return tag(contentIds, tagNames, TagMode.REPLACE.toString(), contextualParameters); 581 } 582 583 /** 584 * Tag a list of contents 585 * @param contentIds The ids of contents to tag 586 * @param tagNames The tags 587 * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 588 * @param contextualParameters The contextual parameters 589 * @return the result 590 */ 591 @Callable 592 public Map<String, Object> tag (List<String> contentIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters) 593 { 594 Map<String, Object> result = new HashMap<>(); 595 596 result.put("notaggable-contents", new ArrayList<Map<String, Object>>()); 597 result.put("invalid-tags", new ArrayList<String>()); 598 result.put("allright-contents", new ArrayList<Map<String, Object>>()); 599 result.put("locked-contents", new ArrayList<Map<String, Object>>()); 600 601 for (String contentId : contentIds) 602 { 603 Content content = _resolver.resolveById(contentId); 604 605 Map<String, Object> content2json = new HashMap<>(); 606 content2json.put("id", content.getId()); 607 content2json.put("title", content.getTitle()); 608 609 if (content instanceof TaggableAmetysObject) 610 { 611 TaggableAmetysObject mContent = (TaggableAmetysObject) content; 612 613 boolean wasLocked = false; 614 615 if (content instanceof LockableAmetysObject) 616 { 617 LockableAmetysObject lockableContent = (LockableAmetysObject) content; 618 UserIdentity user = _currentUserProvider.getUser(); 619 if (lockableContent.isLocked() && !LockHelper.isLockOwner(lockableContent, user)) 620 { 621 @SuppressWarnings("unchecked") 622 List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) result.get("locked-contents"); 623 content2json.put("lockOwner", lockableContent.getLockOwner()); 624 lockedContents.add(content2json); 625 626 // Stop process 627 continue; 628 } 629 630 if (lockableContent.isLocked()) 631 { 632 wasLocked = true; 633 lockableContent.unlock(); 634 } 635 } 636 637 TagMode tagMode = TagMode.valueOf(mode); 638 639 Set<String> oldTags = mContent.getTags(); 640 _removeAllTagsInReplaceMode(mContent, tagMode, oldTags); 641 642 // Then set new tags 643 for (String tagName : tagNames) 644 { 645 if (_isTagValid(tagName, contextualParameters)) 646 { 647 if (TagMode.REMOVE.equals(tagMode)) 648 { 649 mContent.untag(tagName); 650 } 651 else if (TagMode.REPLACE.equals(tagMode) || !oldTags.contains(tagName)) 652 { 653 mContent.tag(tagName); 654 } 655 656 } 657 else 658 { 659 @SuppressWarnings("unchecked") 660 List<String> invalidTags = (List<String>) result.get("invalid-tags"); 661 invalidTags.add(tagName); 662 } 663 } 664 665 ((ModifiableAmetysObject) content).saveChanges(); 666 667 if (wasLocked) 668 { 669 // Relock content if it was locked before tagging 670 ((LockableAmetysObject) content).lock(); 671 } 672 673 content2json.put("tags", content.getTags()); 674 @SuppressWarnings("unchecked") 675 List<Map<String, Object>> allRightPages = (List<Map<String, Object>>) result.get("allright-contents"); 676 allRightPages.add(content2json); 677 678 if (!oldTags.equals(content.getTags())) 679 { 680 // Notify observers that the content has been tagged 681 Map<String, Object> eventParams = new HashMap<>(); 682 eventParams.put(org.ametys.cms.ObservationConstants.ARGS_CONTENT, content); 683 eventParams.put(org.ametys.cms.ObservationConstants.ARGS_CONTENT_ID, content.getId()); 684 eventParams.put("content.tags", content.getTags()); 685 eventParams.put("content.old.tags", oldTags); 686 _observationManager.notify(new Event(org.ametys.cms.ObservationConstants.EVENT_CONTENT_TAGGED, _currentUserProvider.getUser(), eventParams)); 687 } 688 } 689 else 690 { 691 @SuppressWarnings("unchecked") 692 List<Map<String, Object>> notaggableContents = (List<Map<String, Object>>) result.get("notaggable-contents"); 693 notaggableContents.add(content2json); 694 } 695 } 696 697 return result; 698 } 699 700 private void _removeAllTagsInReplaceMode(TaggableAmetysObject mContent, TagMode tagMode, Set<String> oldTags) 701 { 702 if (TagMode.REPLACE.equals(tagMode)) 703 { 704 // First delete old tags 705 for (String tagName : oldTags) 706 { 707 mContent.untag(tagName); 708 } 709 } 710 } 711 712 /** 713 * Is the tag a content tag 714 * @param tagName The tag name 715 * @param contextualParameters The contextual parameters 716 * @return true if the tag is a valid content tag 717 */ 718 public boolean _isTagValid (String tagName, Map<String, Object> contextualParameters) 719 { 720 Tag tag = _tagProvider.getTag(tagName, contextualParameters); 721 return tag.getTarget().getName().equals("CONTENT"); 722 } 723 724 /** 725 * Copy a content. 726 * @param originalContent the original content. 727 * @param parent the object in which to create a content. 728 * @param name the content name. 729 * @param initWorkflowActionId The initial workflow action id 730 * @return the copied content. 731 * @throws AmetysRepositoryException If an error occured 732 */ 733 public ModifiableContent copy(DefaultContent originalContent, ModifiableTraversableAmetysObject parent, String name, int initWorkflowActionId) throws AmetysRepositoryException 734 { 735 return copy(originalContent, parent, name, null, initWorkflowActionId); 736 } 737 738 /** 739 * Copy a content. 740 * @param originalContent the original content. 741 * @param parent the object in which to create a content. 742 * @param name the content name. 743 * @param lang the content language. If null, the content language will be the same of the original content 744 * @param initWorkflowActionId The initial workflow action id 745 * @return the copied content. 746 * @throws AmetysRepositoryException If an error occured 747 */ 748 public ModifiableContent copy(DefaultContent originalContent, ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId) throws AmetysRepositoryException 749 { 750 return copy(originalContent, parent, name, lang, initWorkflowActionId, true); 751 } 752 753 /** 754 * Copy a content. 755 * @param originalContent the original content. 756 * @param parent the object in which to create a content. 757 * @param name the content name. 758 * @param lang the content language. If null, the content language will be the same of the original content 759 * @param initWorkflowActionId The initial workflow action id 760 * @param notifyObservers Set to false to do not fire observer events 761 * @return the copied content. 762 * @throws AmetysRepositoryException If an error occured 763 */ 764 public ModifiableContent copy(DefaultContent originalContent, ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId, boolean notifyObservers) throws AmetysRepositoryException 765 { 766 try 767 { 768 String originalName = name == null ? originalContent.getName() : name; 769 String contentName = originalName; 770 int index = 2; 771 while (parent.hasChild(contentName)) 772 { 773 contentName = originalName + "-" + (index++); 774 } 775 776 String originalContentType = originalContent.getNode().getPrimaryNodeType().getName(); 777 778 ModifiableContent content = parent.createChild(contentName, originalContentType); 779 content.setLanguage(lang == null ? originalContent.getLanguage() : lang); 780 content.setTypes(originalContent.getTypes()); 781 content.setTitle(originalContent.getTitle()); 782 783 if (originalContent instanceof WorkflowAwareContent) 784 { 785 WorkflowAwareContent waOriginalContent = (WorkflowAwareContent) originalContent; 786 AmetysObjectWorkflow originalContentWorkflow = _workflowProvider.getAmetysObjectWorkflow(waOriginalContent); 787 String workflowName = originalContentWorkflow.getWorkflowName(waOriginalContent.getWorkflowId()); 788 789 // Initialize new content workflow 790 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 791 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 792 793 HashMap<String, Object> inputs = new HashMap<>(); 794 // Provide the content key 795 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, waContent); 796 797 long workflowId = workflow.initialize(workflowName, initWorkflowActionId, inputs); 798 waContent.setWorkflowId(workflowId); 799 800 // Set the current step ID property 801 Step currentStep = (Step) workflow.getCurrentSteps(workflowId).iterator().next(); 802 waContent.setCurrentStepId(currentStep.getStepId()); 803 804 Node workflowEntryNode = null; 805 Node node = waContent.getNode(); 806 Session session = node.getSession(); 807 808 809 try 810 { 811 AbstractJackrabbitWorkflowStore workflowStore = (AbstractJackrabbitWorkflowStore) workflow.getConfiguration().getWorkflowStore(); 812 813 if (workflowStore instanceof AmetysObjectWorkflowStore) 814 { 815 AmetysObjectWorkflowStore ametysObjectWorkflowStore = (AmetysObjectWorkflowStore) workflowStore; 816 ametysObjectWorkflowStore.bindAmetysObject(waContent); 817 } 818 819 workflowEntryNode = workflowStore.getEntryNode(session, workflowId); 820 workflowEntryNode.setProperty("ametys-internal:initialActionId", initWorkflowActionId); 821 } 822 catch (RepositoryException e) 823 { 824 throw new AmetysRepositoryException("Unable to link the workflow to the content", e); 825 } 826 } 827 828 // Copy metadata 829 originalContent.getMetadataHolder().copyTo(content.getMetadataHolder()); 830 831 if (_currentUserProvider.getUser() != null) 832 { 833 content.setCreator(_currentUserProvider.getUser()); 834 content.setLastModified(new Date()); 835 content.setCreationDate(new Date()); 836 } 837 838 parent.saveChanges(); 839 840 // Create a new version 841 if (content instanceof VersionableAmetysObject) 842 { 843 ((VersionableAmetysObject) content).checkpoint(); 844 } 845 846 if (notifyObservers) 847 { 848 _notifyContentCopied(content); 849 } 850 851 return content; 852 } 853 catch (WorkflowException e) 854 { 855 throw new AmetysRepositoryException(e); 856 } 857 catch (RepositoryException e) 858 { 859 throw new AmetysRepositoryException(e); 860 } 861 } 862 863 /** 864 * Notify observers that the content has been created 865 * @param content The content added 866 * @throws WorkflowException If an error occurred 867 */ 868 protected void _notifyContentCopied(Content content) throws WorkflowException 869 { 870 Map<String, Object> eventParams = new HashMap<>(); 871 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 872 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 873 874 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_ADDED, _currentUserProvider.getUser(), eventParams)); 875 876 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_WORKFLOW_CHANGED, _currentUserProvider.getUser(), eventParams)); 877 } 878 879 /** 880 * Returns the content's attachments root node 881 * @param id the content's id 882 * @return The attachments' root node informations 883 */ 884 @Callable 885 public Map<String, Object> getAttachmentsRootNode (String id) 886 { 887 Map<String, Object> result = new HashMap<>(); 888 889 Content content = _resolver.resolveById(id); 890 891 result.put("title", content.getTitle()); 892 result.put("contentId", content.getId()); 893 894 TraversableAmetysObject attachments = content.getRootAttachments(); 895 896 if (attachments != null) 897 { 898 result.put("id", attachments.getId()); 899 if (attachments instanceof ModifiableAmetysObject) 900 { 901 result.put("isModifiable", true); 902 } 903 if (attachments instanceof ModifiableResourceCollection) 904 { 905 result.put("canCreateChild", true); 906 } 907 908 boolean hasChildNodes = false; 909 boolean hasResources = false; 910 911 for (AmetysObject child : attachments.getChildren()) 912 { 913 if (child instanceof Resource) 914 { 915 hasResources = true; 916 } 917 else if (child instanceof ExplorerNode) 918 { 919 hasChildNodes = true; 920 } 921 } 922 923 if (hasChildNodes) 924 { 925 result.put("hasChildNodes", true); 926 } 927 928 if (hasResources) 929 { 930 result.put("hasResources", true); 931 } 932 933 return result; 934 } 935 936 throw new IllegalArgumentException("Content with id '" + id + "' does not support attachments."); 937 } 938 939 /** 940 * Dertermines if the current user has right to delete the content 941 * @param content The content 942 * @return true if current user is authorized to delete the content 943 */ 944 public boolean canDelete(Content content) 945 { 946 UserIdentity user = _currentUserProvider.getUser(); 947 if (_rightManager.hasRight(user, "CMS_Rights_DeleteContent", content) == RightResult.RIGHT_ALLOW) 948 { 949 return true; 950 } 951 952 return false; 953 } 954}