001/* 002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 * $Id$ 020 */ 021package org.nuxeo.ecm.webapp.clipboard; 022 023import static org.jboss.seam.ScopeType.EVENT; 024import static org.jboss.seam.ScopeType.SESSION; 025 026import java.io.IOException; 027import java.io.Serializable; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.LinkedList; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036 037import javax.faces.context.ExternalContext; 038import javax.faces.context.FacesContext; 039 040import org.apache.commons.logging.Log; 041import org.apache.commons.logging.LogFactory; 042import org.jboss.seam.annotations.Factory; 043import org.jboss.seam.annotations.In; 044import org.jboss.seam.annotations.Name; 045import org.jboss.seam.annotations.Scope; 046import org.jboss.seam.annotations.remoting.WebRemote; 047import org.jboss.seam.annotations.web.RequestParameter; 048import org.jboss.seam.core.Events; 049import org.jboss.seam.faces.FacesMessages; 050import org.jboss.seam.international.LocaleSelector; 051import org.jboss.seam.international.StatusMessage; 052import org.nuxeo.ecm.core.api.Blob; 053import org.nuxeo.ecm.core.api.CoreSession; 054import org.nuxeo.ecm.core.api.CoreSession.CopyOption; 055import org.nuxeo.ecm.core.api.DocumentModel; 056import org.nuxeo.ecm.core.api.DocumentModelList; 057import org.nuxeo.ecm.core.api.DocumentRef; 058import org.nuxeo.ecm.core.api.IdRef; 059import org.nuxeo.ecm.core.api.LifeCycleConstants; 060import org.nuxeo.ecm.core.api.NuxeoException; 061import org.nuxeo.ecm.core.api.security.SecurityConstants; 062import org.nuxeo.ecm.core.io.download.DownloadService; 063import org.nuxeo.ecm.core.schema.FacetNames; 064import org.nuxeo.ecm.core.schema.SchemaManager; 065import org.nuxeo.ecm.platform.actions.Action; 066import org.nuxeo.ecm.platform.types.TypeManager; 067import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 068import org.nuxeo.ecm.platform.ui.web.api.WebActions; 069import org.nuxeo.ecm.platform.ui.web.cache.SeamCacheHelper; 070import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 071import org.nuxeo.ecm.webapp.documentsLists.DocumentsListDescriptor; 072import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager; 073import org.nuxeo.ecm.webapp.helpers.EventManager; 074import org.nuxeo.ecm.webapp.helpers.EventNames; 075import org.nuxeo.runtime.api.Framework; 076 077/** 078 * This is the action listener behind the copy/paste template that knows how to copy/paste the selected user data to the 079 * target action listener, and also create/remove the corresponding objects into the backend. 080 * 081 * @author <a href="mailto:rcaraghin@nuxeo.com">Razvan Caraghin</a> 082 */ 083@Name("clipboardActions") 084@Scope(SESSION) 085public class ClipboardActionsBean implements ClipboardActions, Serializable { 086 087 private static final long serialVersionUID = -2407222456116573225L; 088 089 private static final Log log = LogFactory.getLog(ClipboardActionsBean.class); 090 091 @In(create = true, required = false) 092 protected FacesMessages facesMessages; 093 094 @In(create = true) 095 protected Map<String, String> messages; 096 097 @In(create = true, required = false) 098 protected transient CoreSession documentManager; 099 100 @In(create = true) 101 protected transient DocumentsListsManager documentsListsManager; 102 103 @In(create = true) 104 protected TypeManager typeManager; 105 106 @In(create = true) 107 protected NavigationContext navigationContext; 108 109 @In(create = true) 110 protected transient WebActions webActions; // it is serializable 111 112 @In(create = true) 113 protected transient LocaleSelector localeSelector; 114 115 @RequestParameter() 116 protected String workListDocId; 117 118 private String currentSelectedList; 119 120 private String previouslySelectedList; 121 122 private transient List<String> availableLists; 123 124 private transient List<DocumentsListDescriptor> descriptorsForAvailableLists; 125 126 private Boolean canEditSelectedDocs; 127 128 private transient Map<String, List<Action>> actionCache; 129 130 @Override 131 public void releaseClipboardableDocuments() { 132 } 133 134 @Override 135 public boolean isInitialized() { 136 return documentManager != null; 137 } 138 139 @Override 140 public void putSelectionInWorkList(Boolean forceAppend) { 141 canEditSelectedDocs = null; 142 if (!documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION)) { 143 putSelectionInWorkList( 144 documentsListsManager.getWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION), forceAppend); 145 autoSelectCurrentList(DocumentsListsManager.DEFAULT_WORKING_LIST); 146 } else { 147 log.debug("No selectable Documents in context to process copy on..."); 148 } 149 log.debug("add to worklist processed..."); 150 } 151 152 @Override 153 public void putSelectionInWorkList() { 154 putSelectionInWorkList(false); 155 } 156 157 @Override 158 public void putSelectionInDefaultWorkList() { 159 canEditSelectedDocs = null; 160 if (!documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION)) { 161 List<DocumentModel> docsList = documentsListsManager.getWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 162 Object[] params = { docsList.size() }; 163 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 164 documentsListsManager.addToWorkingList(DocumentsListsManager.DEFAULT_WORKING_LIST, docsList); 165 166 // auto select clipboard 167 autoSelectCurrentList(DocumentsListsManager.DEFAULT_WORKING_LIST); 168 169 } else { 170 log.debug("No selectable Documents in context to process copy on..."); 171 } 172 log.debug("add to worklist processed..."); 173 } 174 175 @Override 176 @WebRemote 177 public void putInClipboard(String docId) { 178 DocumentModel doc = documentManager.getDocument(new IdRef(docId)); 179 documentsListsManager.addToWorkingList(DocumentsListsManager.CLIPBOARD, doc); 180 Object[] params = { 1 }; 181 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 182 183 autoSelectCurrentList(DocumentsListsManager.CLIPBOARD); 184 } 185 186 @Override 187 public void putSelectionInClipboard() { 188 canEditSelectedDocs = null; 189 if (!documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION)) { 190 List<DocumentModel> docsList = documentsListsManager.getWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 191 Object[] params = { docsList.size() }; 192 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 193 194 documentsListsManager.addToWorkingList(DocumentsListsManager.CLIPBOARD, docsList); 195 196 // auto select clipboard 197 autoSelectCurrentList(DocumentsListsManager.CLIPBOARD); 198 199 } else { 200 log.debug("No selectable Documents in context to process copy on..."); 201 } 202 log.debug("add to worklist processed..."); 203 } 204 205 @Override 206 public void putSelectionInWorkList(List<DocumentModel> docsList) { 207 putSelectionInWorkList(docsList, false); 208 } 209 210 @Override 211 public void putSelectionInWorkList(List<DocumentModel> docsList, Boolean forceAppend) { 212 canEditSelectedDocs = null; 213 if (null != docsList) { 214 Object[] params = { docsList.size() }; 215 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_added_to_worklist_docs"), params); 216 217 // Add to the default working list 218 documentsListsManager.addToWorkingList(getCurrentSelectedListName(), docsList, forceAppend); 219 log.debug("Elements copied to clipboard..."); 220 221 } else { 222 log.debug("No copiedDocs to process copy on..."); 223 } 224 225 log.debug("add to worklist processed..."); 226 } 227 228 @Override 229 @Deprecated 230 public void copySelection(List<DocumentModel> copiedDocs) { 231 if (null != copiedDocs) { 232 Object[] params = { copiedDocs.size() }; 233 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 234 235 // clipboard.copy(copiedDocs); 236 237 // Reset + Add to clipboard list 238 documentsListsManager.resetWorkingList(DocumentsListsManager.CLIPBOARD); 239 documentsListsManager.addToWorkingList(DocumentsListsManager.CLIPBOARD, copiedDocs); 240 241 // Add to the default working list 242 documentsListsManager.addToWorkingList(copiedDocs); 243 log.debug("Elements copied to clipboard..."); 244 245 } else { 246 log.debug("No copiedDocs to process copy on..."); 247 } 248 249 log.debug("Copy processed..."); 250 } 251 252 public boolean exists(DocumentRef ref) { 253 return ref != null && documentManager.exists(ref); 254 } 255 256 @Override 257 public String removeWorkListItem(DocumentRef ref) { 258 DocumentModel doc = null; 259 if (exists(ref)) { 260 doc = documentManager.getDocument(ref); 261 } else { // document was permanently deleted so let's use the one in the work list 262 List<DocumentModel> workingListDocs = documentsListsManager.getWorkingList(getCurrentSelectedListName()); 263 for (DocumentModel wDoc : workingListDocs) { 264 if (wDoc.getRef().equals(ref)) { 265 doc = wDoc; 266 } 267 } 268 } 269 documentsListsManager.removeFromWorkingList(getCurrentSelectedListName(), doc); 270 return null; 271 } 272 273 @Override 274 public String clearWorkingList() { 275 documentsListsManager.resetWorkingList(getCurrentSelectedListName()); 276 return null; 277 } 278 279 @Override 280 public String pasteDocumentList(String listName) { 281 return pasteDocumentList(documentsListsManager.getWorkingList(listName)); 282 } 283 284 @Override 285 public String pasteDocumentListInside(String listName, String docId) { 286 return pasteDocumentListInside(documentsListsManager.getWorkingList(listName), docId); 287 } 288 289 @Override 290 public String pasteDocumentList(List<DocumentModel> docPaste) { 291 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 292 if (null != docPaste) { 293 List<DocumentModel> newDocs = recreateDocumentsWithNewParent(getParent(currentDocument), docPaste); 294 295 Object[] params = { newDocs.size() }; 296 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_pasted_docs"), params); 297 298 EventManager.raiseEventsOnDocumentSelected(currentDocument); 299 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, currentDocument); 300 301 log.debug("Elements pasted and created into the backend..."); 302 } else { 303 log.debug("No docPaste to process paste on..."); 304 } 305 306 return null; 307 } 308 309 @Override 310 public String pasteDocumentListInside(List<DocumentModel> docPaste, String docId) { 311 DocumentModel targetDoc = documentManager.getDocument(new IdRef(docId)); 312 if (null != docPaste) { 313 List<DocumentModel> newDocs = recreateDocumentsWithNewParent(targetDoc, docPaste); 314 315 Object[] params = { newDocs.size() }; 316 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_pasted_docs"), params); 317 318 EventManager.raiseEventsOnDocumentSelected(targetDoc); 319 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, targetDoc); 320 321 log.debug("Elements pasted and created into the backend..."); 322 } else { 323 log.debug("No docPaste to process paste on..."); 324 } 325 326 return null; 327 } 328 329 public List<DocumentModel> moveDocumentsToNewParent(DocumentModel destFolder, List<DocumentModel> docs) { 330 DocumentRef destFolderRef = destFolder.getRef(); 331 boolean destinationIsDeleted = destFolder.isTrashed(); 332 List<DocumentModel> newDocs = new ArrayList<>(); 333 StringBuilder sb = new StringBuilder(); 334 for (DocumentModel docModel : docs) { 335 DocumentRef sourceFolderRef = docModel.getParentRef(); 336 337 String sourceType = docModel.getType(); 338 boolean canRemoveDoc = documentManager.hasPermission(sourceFolderRef, SecurityConstants.REMOVE_CHILDREN); 339 boolean canPasteInCurrentFolder = typeManager.isAllowedSubType(sourceType, destFolder.getType(), 340 navigationContext.getCurrentDocument()); 341 boolean sameFolder = sourceFolderRef.equals(destFolderRef); 342 if (canRemoveDoc && canPasteInCurrentFolder && !sameFolder) { 343 if (destinationIsDeleted) { 344 if (checkDeletedState(docModel)) { 345 DocumentModel newDoc = documentManager.move(docModel.getRef(), destFolderRef, null); 346 setDeleteState(newDoc); 347 newDocs.add(newDoc); 348 } else { 349 addWarnMessage(sb, docModel); 350 } 351 } else { 352 DocumentModel newDoc = documentManager.move(docModel.getRef(), destFolderRef, null); 353 newDocs.add(newDoc); 354 } 355 } 356 } 357 documentManager.save(); 358 359 if (sb.length() > 0) { 360 facesMessages.add(StatusMessage.Severity.WARN, sb.toString()); 361 } 362 return newDocs; 363 } 364 365 public String moveDocumentList(String listName, String docId) { 366 List<DocumentModel> docs = documentsListsManager.getWorkingList(listName); 367 DocumentModel targetDoc = documentManager.getDocument(new IdRef(docId)); 368 // Get all parent folders 369 Set<DocumentRef> parentRefs = new HashSet<>(); 370 for (DocumentModel doc : docs) { 371 parentRefs.add(doc.getParentRef()); 372 } 373 374 List<DocumentModel> newDocs = moveDocumentsToNewParent(targetDoc, docs); 375 376 documentsListsManager.resetWorkingList(listName); 377 378 Object[] params = { newDocs.size() }; 379 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_moved_docs"), params); 380 381 EventManager.raiseEventsOnDocumentSelected(targetDoc); 382 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, targetDoc); 383 384 // Send event to all initial parents 385 for (DocumentRef docRef : parentRefs) { 386 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, documentManager.getDocument(docRef)); 387 } 388 389 log.debug("Elements moved and created into the backend..."); 390 391 return null; 392 } 393 394 public String moveDocumentList(String listName) { 395 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 396 return moveDocumentList(listName, currentDocument.getId()); 397 } 398 399 @Override 400 public String moveWorkingList() { 401 try { 402 moveDocumentList(getCurrentSelectedListName()); 403 } catch (NuxeoException e) { 404 log.error("moveWorkingList failed" + e.getMessage(), e); 405 facesMessages.add(StatusMessage.Severity.WARN, messages.get("invalid_operation")); 406 } 407 return null; 408 } 409 410 @Override 411 public String pasteWorkingList() { 412 try { 413 pasteDocumentList(getCurrentSelectedList()); 414 } catch (NuxeoException e) { 415 log.error("pasteWorkingList failed" + e.getMessage(), e); 416 facesMessages.add(StatusMessage.Severity.WARN, messages.get("invalid_operation")); 417 } 418 return null; 419 } 420 421 @Override 422 public String pasteClipboard() { 423 try { 424 pasteDocumentList(DocumentsListsManager.CLIPBOARD); 425 returnToPreviouslySelectedList(); 426 } catch (NuxeoException e) { 427 log.error("pasteClipboard failed" + e.getMessage(), e); 428 facesMessages.add(StatusMessage.Severity.WARN, messages.get("invalid_operation")); 429 430 } 431 return null; 432 } 433 434 @Override 435 @WebRemote 436 public String pasteClipboardInside(String docId) { 437 pasteDocumentListInside(DocumentsListsManager.CLIPBOARD, docId); 438 return null; 439 } 440 441 @Override 442 @WebRemote 443 public String moveClipboardInside(String docId) { 444 moveDocumentList(DocumentsListsManager.CLIPBOARD, docId); 445 return null; 446 } 447 448 /** 449 * Creates the documents in the backend under the target parent. 450 */ 451 protected List<DocumentModel> recreateDocumentsWithNewParent(DocumentModel parent, List<DocumentModel> documents) { 452 453 List<DocumentModel> newDocuments = new ArrayList<>(); 454 455 if (null == parent || null == documents) { 456 log.error("Null params received, returning..."); 457 return newDocuments; 458 } 459 460 List<DocumentModel> documentsToPast = new LinkedList<>(); 461 462 // filter list on content type 463 for (DocumentModel doc : documents) { 464 if (typeManager.isAllowedSubType(doc.getType(), parent.getType(), navigationContext.getCurrentDocument())) { 465 documentsToPast.add(doc); 466 } 467 } 468 469 // copying proxy or document 470 boolean isPublishSpace = isPublishSpace(parent); 471 boolean destinationIsDeleted = parent.isTrashed(); 472 List<DocumentRef> docRefs = new ArrayList<>(); 473 List<DocumentRef> proxyRefs = new ArrayList<>(); 474 StringBuilder sb = new StringBuilder(); 475 for (DocumentModel doc : documentsToPast) { 476 if (destinationIsDeleted && !checkDeletedState(doc)) { 477 addWarnMessage(sb, doc); 478 } else if (doc.isProxy() && !isPublishSpace) { 479 // in a non-publish space, we want to expand proxies into 480 // normal docs 481 proxyRefs.add(doc.getRef()); 482 } else { 483 // copy as is 484 docRefs.add(doc.getRef()); 485 } 486 } 487 if (!proxyRefs.isEmpty()) { 488 newDocuments.addAll(documentManager.copyProxyAsDocument(proxyRefs, parent.getRef(), 489 CopyOption.RESET_LIFE_CYCLE)); 490 } 491 if (!docRefs.isEmpty()) { 492 newDocuments.addAll(documentManager.copy(docRefs, parent.getRef(), CopyOption.RESET_LIFE_CYCLE)); 493 } 494 if (destinationIsDeleted) { 495 for (DocumentModel d : newDocuments) { 496 setDeleteState(d); 497 } 498 } 499 documentManager.save(); 500 if (sb.length() > 0) { 501 facesMessages.add(StatusMessage.Severity.WARN, sb.toString()); 502 } 503 return newDocuments; 504 } 505 506 protected boolean checkDeletedState(DocumentModel doc) { 507 return doc.isTrashed() || doc.getAllowedStateTransitions().contains(LifeCycleConstants.DELETE_TRANSITION); 508 } 509 510 protected void setDeleteState(DocumentModel doc) { 511 if (doc.getAllowedStateTransitions().contains(LifeCycleConstants.DELETE_TRANSITION)) { 512 doc.followTransition(LifeCycleConstants.DELETE_TRANSITION); 513 } 514 } 515 516 protected void addWarnMessage(StringBuilder sb, DocumentModel doc) { 517 if (sb.length() == 0) { 518 sb.append(messages.get("document_no_deleted_state")); 519 sb.append("'").append(doc.getTitle()).append("'"); 520 } else { 521 sb.append(", '").append(doc.getTitle()).append("'"); 522 } 523 } 524 525 /** 526 * Check if the container is a publish space. If this is not the case, a proxy copied to it will be recreated as a 527 * new document. 528 */ 529 protected boolean isPublishSpace(DocumentModel container) { 530 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 531 Set<String> publishSpaces = schemaManager.getDocumentTypeNamesForFacet(FacetNames.PUBLISH_SPACE); 532 if (publishSpaces == null || publishSpaces.isEmpty()) { 533 publishSpaces = new HashSet<>(); 534 } 535 return publishSpaces.contains(container.getType()); 536 } 537 538 /** 539 * Gets the parent document under the paste should be performed. 540 * <p> 541 * Rules: 542 * <p> 543 * In general the currentDocument is the parent. Exceptions to this rule: when the currentDocument is a domain or 544 * null. If Domain then content root is the parent. If null is passed, then the JCR root is taken as parent. 545 */ 546 protected DocumentModel getParent(DocumentModel currentDocument) { 547 548 if (currentDocument.isFolder()) { 549 return currentDocument; 550 } 551 552 DocumentModelList parents = navigationContext.getCurrentPath(); 553 for (int i = parents.size() - 1; i >= 0; i--) { 554 DocumentModel parent = parents.get(i); 555 if (parent.isFolder()) { 556 return parent; 557 } 558 } 559 560 return null; 561 } 562 563 @Override 564 @Factory(value = "isCurrentWorkListEmpty", scope = EVENT) 565 public boolean factoryForIsCurrentWorkListEmpty() { 566 return isWorkListEmpty(); 567 } 568 569 @Override 570 public boolean isWorkListEmpty() { 571 return documentsListsManager.isWorkingListEmpty(getCurrentSelectedListName()); 572 } 573 574 @Override 575 public String exportWorklistAsZip() { 576 return exportWorklistAsZip(documentsListsManager.getWorkingList(getCurrentSelectedListName())); 577 } 578 579 @Override 580 public String exportAllBlobsFromWorkingListAsZip() { 581 return exportWorklistAsZip(); 582 } 583 584 @Override 585 public String exportMainBlobFromWorkingListAsZip() { 586 return exportWorklistAsZip(); 587 } 588 589 @Override 590 public String exportWorklistAsZip(List<DocumentModel> documents) { 591 return exportWorklistAsZip(documents, true); 592 } 593 594 public String exportWorklistAsZip(DocumentModel document) { 595 return exportWorklistAsZip(Collections.singletonList(document), true); 596 } 597 598 /** 599 * Checks if copy action is available in the context of the current Document. 600 * <p> 601 * Condition: the list of selected documents is not empty. 602 */ 603 @Override 604 public boolean getCanCopy() { 605 if (navigationContext.getCurrentDocument() == null) { 606 return false; 607 } 608 return !documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 609 } 610 611 /** 612 * Checks if the Paste action is available in the context of the current Document. Conditions: 613 * <p> 614 * <ul> 615 * <li>list is not empty 616 * <li>user has the needed permissions on the current document 617 * <li>the content of the list can be added as children of the current document 618 * </ul> 619 */ 620 @Override 621 public boolean getCanPaste(String listName) { 622 623 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 624 625 if (documentsListsManager.isWorkingListEmpty(listName) || currentDocument == null) { 626 return false; 627 } 628 629 DocumentModel pasteTarget = getParent(navigationContext.getCurrentDocument()); 630 if (pasteTarget == null) { 631 // parent may be unreachable (right inheritance blocked) 632 return false; 633 } 634 if (!documentManager.hasPermission(pasteTarget.getRef(), SecurityConstants.ADD_CHILDREN)) { 635 return false; 636 } else { 637 // filter on allowed content types 638 // see if at least one doc can be pasted 639 // String pasteTypeName = clipboard.getClipboardDocumentType(); 640 List<String> pasteTypesName = documentsListsManager.getWorkingListTypes(listName); 641 for (String pasteTypeName : pasteTypesName) { 642 if (typeManager.isAllowedSubType(pasteTypeName, pasteTarget.getType(), 643 navigationContext.getCurrentDocument())) { 644 return true; 645 } 646 } 647 return false; 648 } 649 } 650 651 @Override 652 public boolean getCanPasteInside(String listName, DocumentModel document) { 653 if (documentsListsManager.isWorkingListEmpty(listName) || document == null) { 654 return false; 655 } 656 657 if (!documentManager.hasPermission(document.getRef(), SecurityConstants.ADD_CHILDREN)) { 658 return false; 659 } else { 660 // filter on allowed content types 661 // see if at least one doc can be pasted 662 // String pasteTypeName = clipboard.getClipboardDocumentType(); 663 List<String> pasteTypesName = documentsListsManager.getWorkingListTypes(listName); 664 for (String pasteTypeName : pasteTypesName) { 665 if (typeManager.isAllowedSubType(pasteTypeName, document.getType(), 666 navigationContext.getCurrentDocument())) { 667 return true; 668 } 669 } 670 return false; 671 } 672 } 673 674 /** 675 * Checks if the Move action is available in the context of the document document. Conditions: 676 * <p> 677 * <ul> 678 * <li>list is not empty 679 * <li>user has the needed permissions on the document 680 * <li>an element in the list can be removed from its folder and added as child of the current document 681 * </ul> 682 */ 683 @Override 684 public boolean getCanMoveInside(String listName, DocumentModel document) { 685 if (documentsListsManager.isWorkingListEmpty(listName) || document == null) { 686 return false; 687 } 688 DocumentRef destFolderRef = document.getRef(); 689 DocumentModel destFolder = document; 690 if (!documentManager.hasPermission(destFolderRef, SecurityConstants.ADD_CHILDREN)) { 691 return false; 692 } else { 693 // filter on allowed content types 694 // see if at least one doc can be removed and pasted 695 for (DocumentModel docModel : documentsListsManager.getWorkingList(listName)) { 696 // skip deleted documents 697 if (!exists(docModel.getRef())) { 698 continue; 699 } 700 DocumentRef sourceFolderRef = docModel.getParentRef(); 701 String sourceType = docModel.getType(); 702 boolean canRemoveDoc = documentManager.hasPermission(sourceFolderRef, SecurityConstants.REMOVE_CHILDREN); 703 boolean canPasteInCurrentFolder = typeManager.isAllowedSubType(sourceType, destFolder.getType(), 704 navigationContext.getCurrentDocument()); 705 boolean sameFolder = sourceFolderRef.equals(destFolderRef); 706 if (canRemoveDoc && canPasteInCurrentFolder && !sameFolder) { 707 return true; 708 } 709 } 710 return false; 711 } 712 } 713 714 /** 715 * Checks if the Move action is available in the context of the current Document. Conditions: 716 * <p> 717 * <ul> 718 * <li>list is not empty 719 * <li>user has the needed permissions on the current document 720 * <li>an element in the list can be removed from its folder and added as child of the current document 721 * </ul> 722 */ 723 public boolean getCanMove(String listName) { 724 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 725 return getCanMoveInside(listName, currentDocument); 726 } 727 728 @Override 729 public boolean getCanPasteWorkList() { 730 return getCanPaste(getCurrentSelectedListName()); 731 } 732 733 @Override 734 public boolean getCanMoveWorkingList() { 735 return getCanMove(getCurrentSelectedListName()); 736 } 737 738 @Override 739 public boolean getCanPasteFromClipboard() { 740 return getCanPaste(DocumentsListsManager.CLIPBOARD); 741 } 742 743 @Override 744 public boolean getCanPasteFromClipboardInside(DocumentModel document) { 745 return getCanPasteInside(DocumentsListsManager.CLIPBOARD, document); 746 } 747 748 @Override 749 public boolean getCanMoveFromClipboardInside(DocumentModel document) { 750 return getCanMoveInside(DocumentsListsManager.CLIPBOARD, document); 751 } 752 753 @Override 754 public void setCurrentSelectedList(String listId) { 755 if (listId != null && !listId.equals(currentSelectedList)) { 756 currentSelectedList = listId; 757 canEditSelectedDocs = null; 758 } 759 } 760 761 @RequestParameter() 762 String listIdToSelect; 763 764 @Override 765 public void selectList() { 766 if (listIdToSelect != null) { 767 setCurrentSelectedList(listIdToSelect); 768 } 769 } 770 771 @Override 772 public List<DocumentModel> getCurrentSelectedList() { 773 return documentsListsManager.getWorkingList(getCurrentSelectedListName()); 774 } 775 776 @Override 777 public String getCurrentSelectedListName() { 778 if (currentSelectedList == null) { 779 if (!getAvailableLists().isEmpty()) { 780 setCurrentSelectedList(availableLists.get(0)); 781 } 782 } 783 return currentSelectedList; 784 } 785 786 @Override 787 public String getCurrentSelectedListTitle() { 788 String title = null; 789 String listName = getCurrentSelectedListName(); 790 if (listName != null) { 791 DocumentsListDescriptor desc = documentsListsManager.getWorkingListDescriptor(listName); 792 if (desc != null) { 793 title = desc.getTitle(); 794 } 795 } 796 return title; 797 } 798 799 @Override 800 public List<String> getAvailableLists() { 801 if (availableLists == null) { 802 availableLists = documentsListsManager.getWorkingListNamesForCategory("CLIPBOARD"); 803 } 804 return availableLists; 805 } 806 807 @Override 808 public List<DocumentsListDescriptor> getDescriptorsForAvailableLists() { 809 if (descriptorsForAvailableLists == null) { 810 List<String> availableLists = getAvailableLists(); 811 descriptorsForAvailableLists = new ArrayList<>(); 812 for (String lName : availableLists) { 813 descriptorsForAvailableLists.add(documentsListsManager.getWorkingListDescriptor(lName)); 814 } 815 } 816 return descriptorsForAvailableLists; 817 } 818 819 @Override 820 public List<Action> getActionsForCurrentList() { 821 String lstName = getCurrentSelectedListName(); 822 if (isWorkListEmpty()) { 823 // we use cache here since this is a very common case ... 824 if (actionCache == null) { 825 actionCache = new HashMap<>(); 826 } 827 if (!actionCache.containsKey(lstName)) { 828 actionCache.put(lstName, webActions.getActionsList(lstName + "_LIST")); 829 } 830 return actionCache.get(lstName); 831 } else { 832 return webActions.getActionsList(lstName + "_LIST"); 833 } 834 } 835 836 @Override 837 public List<Action> getActionsForSelection() { 838 return webActions.getActionsList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION + "_LIST", false); 839 } 840 841 private void autoSelectCurrentList(String listName) { 842 previouslySelectedList = getCurrentSelectedListName(); 843 setCurrentSelectedList(listName); 844 } 845 846 private void returnToPreviouslySelectedList() { 847 setCurrentSelectedList(previouslySelectedList); 848 } 849 850 @Override 851 public boolean getCanEditSelectedDocs() { 852 if (canEditSelectedDocs == null) { 853 if (getCurrentSelectedList().isEmpty()) { 854 canEditSelectedDocs = false; 855 } else { 856 final List<DocumentModel> selectedDocs = getCurrentSelectedList(); 857 858 // check selected docs 859 canEditSelectedDocs = checkWritePerm(selectedDocs); 860 } 861 } 862 return canEditSelectedDocs; 863 } 864 865 @Override 866 @Deprecated 867 // no longer used by the user_clipboard.xhtml template 868 public boolean getCanEditListDocs(String listName) { 869 final List<DocumentModel> docs = documentsListsManager.getWorkingList(listName); 870 871 final boolean canEdit; 872 if (docs.isEmpty()) { 873 canEdit = false; 874 } else { 875 // check selected docs 876 canEdit = checkWritePerm(docs); 877 } 878 return canEdit; 879 } 880 881 private boolean checkWritePerm(List<DocumentModel> selectedDocs) { 882 for (DocumentModel documentModel : selectedDocs) { 883 boolean canWrite = documentManager.hasPermission(documentModel.getRef(), SecurityConstants.WRITE_PROPERTIES); 884 if (!canWrite) { 885 return false; 886 } 887 } 888 return true; 889 } 890 891 @Override 892 public boolean isCacheEnabled() { 893 if (!SeamCacheHelper.canUseSeamCache()) { 894 return false; 895 } 896 return isWorkListEmpty(); 897 } 898 899 @Override 900 public String getCacheKey() { 901 return getCurrentSelectedListName() + "::" + localeSelector.getLocaleString(); 902 } 903 904 @Override 905 public boolean isCacheEnabledForSelection() { 906 if (!SeamCacheHelper.canUseSeamCache()) { 907 return false; 908 } 909 return documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 910 } 911 912 @Override 913 public String exportWorklistAsZip(List<DocumentModel> documents, boolean exportAllBlobs) { 914 Blob blob = null; 915 try { 916 DownloadService downloadService = Framework.getService(DownloadService.class); 917 DocumentListZipExporter zipExporter = new DocumentListZipExporter(); 918 blob = zipExporter.exportWorklistAsZip(documents, documentManager, exportAllBlobs); 919 if (blob == null) { 920 // empty zip file, do nothing 921 facesMessages.add(StatusMessage.Severity.INFO, messages.get("label.clipboard.emptyDocuments")); 922 return null; 923 } 924 blob.setMimeType("application/zip"); 925 blob.setFilename("clipboard.zip"); 926 927 String key = downloadService.storeBlobs(Collections.singletonList(blob)); 928 String url = BaseURL.getBaseURL() + downloadService.getDownloadUrl(key); 929 ExternalContext context = FacesContext.getCurrentInstance().getExternalContext(); 930 context.redirect(url); 931 return ""; 932 } catch (IOException io) { 933 if (blob != null) { 934 blob.getFile().delete(); 935 } 936 throw new NuxeoException("Error while redirecting for clipboard content", io); 937 } 938 } 939}