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