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 = LifeCycleConstants.DELETED_STATE.equals(destFolder.getCurrentLifeCycleState()); 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 = LifeCycleConstants.DELETED_STATE.equals(parent.getCurrentLifeCycleState()); 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 if (LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) { 508 return true; 509 } 510 if (doc.getAllowedStateTransitions().contains(LifeCycleConstants.DELETE_TRANSITION)) { 511 return true; 512 } 513 return false; 514 } 515 516 protected void setDeleteState(DocumentModel doc) { 517 if (doc.getAllowedStateTransitions().contains(LifeCycleConstants.DELETE_TRANSITION)) { 518 doc.followTransition(LifeCycleConstants.DELETE_TRANSITION); 519 } 520 } 521 522 protected void addWarnMessage(StringBuilder sb, DocumentModel doc) { 523 if (sb.length() == 0) { 524 sb.append(messages.get("document_no_deleted_state")); 525 sb.append("'").append(doc.getTitle()).append("'"); 526 } else { 527 sb.append(", '").append(doc.getTitle()).append("'"); 528 } 529 } 530 531 /** 532 * Check if the container is a publish space. If this is not the case, a proxy copied to it will be recreated as a 533 * new document. 534 */ 535 protected boolean isPublishSpace(DocumentModel container) { 536 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 537 Set<String> publishSpaces = schemaManager.getDocumentTypeNamesForFacet(FacetNames.PUBLISH_SPACE); 538 if (publishSpaces == null || publishSpaces.isEmpty()) { 539 publishSpaces = new HashSet<>(); 540 } 541 return publishSpaces.contains(container.getType()); 542 } 543 544 /** 545 * Gets the parent document under the paste should be performed. 546 * <p> 547 * Rules: 548 * <p> 549 * In general the currentDocument is the parent. Exceptions to this rule: when the currentDocument is a domain or 550 * null. If Domain then content root is the parent. If null is passed, then the JCR root is taken as parent. 551 */ 552 protected DocumentModel getParent(DocumentModel currentDocument) { 553 554 if (currentDocument.isFolder()) { 555 return currentDocument; 556 } 557 558 DocumentModelList parents = navigationContext.getCurrentPath(); 559 for (int i = parents.size() - 1; i >= 0; i--) { 560 DocumentModel parent = parents.get(i); 561 if (parent.isFolder()) { 562 return parent; 563 } 564 } 565 566 return null; 567 } 568 569 @Override 570 @Factory(value = "isCurrentWorkListEmpty", scope = EVENT) 571 public boolean factoryForIsCurrentWorkListEmpty() { 572 return isWorkListEmpty(); 573 } 574 575 @Override 576 public boolean isWorkListEmpty() { 577 return documentsListsManager.isWorkingListEmpty(getCurrentSelectedListName()); 578 } 579 580 @Override 581 public String exportWorklistAsZip() { 582 return exportWorklistAsZip(documentsListsManager.getWorkingList(getCurrentSelectedListName())); 583 } 584 585 @Override 586 public String exportAllBlobsFromWorkingListAsZip() { 587 return exportWorklistAsZip(); 588 } 589 590 @Override 591 public String exportMainBlobFromWorkingListAsZip() { 592 return exportWorklistAsZip(); 593 } 594 595 @Override 596 public String exportWorklistAsZip(List<DocumentModel> documents) { 597 return exportWorklistAsZip(documents, true); 598 } 599 600 public String exportWorklistAsZip(DocumentModel document) { 601 return exportWorklistAsZip(Collections.singletonList(document), true); 602 } 603 604 /** 605 * Checks if copy action is available in the context of the current Document. 606 * <p> 607 * Condition: the list of selected documents is not empty. 608 */ 609 @Override 610 public boolean getCanCopy() { 611 if (navigationContext.getCurrentDocument() == null) { 612 return false; 613 } 614 return !documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 615 } 616 617 /** 618 * Checks if the Paste action is available in the context of the current Document. Conditions: 619 * <p> 620 * <ul> 621 * <li>list is not empty 622 * <li>user has the needed permissions on the current document 623 * <li>the content of the list can be added as children of the current document 624 * </ul> 625 */ 626 @Override 627 public boolean getCanPaste(String listName) { 628 629 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 630 631 if (documentsListsManager.isWorkingListEmpty(listName) || currentDocument == null) { 632 return false; 633 } 634 635 DocumentModel pasteTarget = getParent(navigationContext.getCurrentDocument()); 636 if (pasteTarget == null) { 637 // parent may be unreachable (right inheritance blocked) 638 return false; 639 } 640 if (!documentManager.hasPermission(pasteTarget.getRef(), SecurityConstants.ADD_CHILDREN)) { 641 return false; 642 } else { 643 // filter on allowed content types 644 // see if at least one doc can be pasted 645 // String pasteTypeName = clipboard.getClipboardDocumentType(); 646 List<String> pasteTypesName = documentsListsManager.getWorkingListTypes(listName); 647 for (String pasteTypeName : pasteTypesName) { 648 if (typeManager.isAllowedSubType(pasteTypeName, pasteTarget.getType(), 649 navigationContext.getCurrentDocument())) { 650 return true; 651 } 652 } 653 return false; 654 } 655 } 656 657 @Override 658 public boolean getCanPasteInside(String listName, DocumentModel document) { 659 if (documentsListsManager.isWorkingListEmpty(listName) || document == null) { 660 return false; 661 } 662 663 if (!documentManager.hasPermission(document.getRef(), SecurityConstants.ADD_CHILDREN)) { 664 return false; 665 } else { 666 // filter on allowed content types 667 // see if at least one doc can be pasted 668 // String pasteTypeName = clipboard.getClipboardDocumentType(); 669 List<String> pasteTypesName = documentsListsManager.getWorkingListTypes(listName); 670 for (String pasteTypeName : pasteTypesName) { 671 if (typeManager.isAllowedSubType(pasteTypeName, document.getType(), 672 navigationContext.getCurrentDocument())) { 673 return true; 674 } 675 } 676 return false; 677 } 678 } 679 680 /** 681 * Checks if the Move action is available in the context of the document document. Conditions: 682 * <p> 683 * <ul> 684 * <li>list is not empty 685 * <li>user has the needed permissions on the document 686 * <li>an element in the list can be removed from its folder and added as child of the current document 687 * </ul> 688 */ 689 @Override 690 public boolean getCanMoveInside(String listName, DocumentModel document) { 691 if (documentsListsManager.isWorkingListEmpty(listName) || document == null) { 692 return false; 693 } 694 DocumentRef destFolderRef = document.getRef(); 695 DocumentModel destFolder = document; 696 if (!documentManager.hasPermission(destFolderRef, SecurityConstants.ADD_CHILDREN)) { 697 return false; 698 } else { 699 // filter on allowed content types 700 // see if at least one doc can be removed and pasted 701 for (DocumentModel docModel : documentsListsManager.getWorkingList(listName)) { 702 // skip deleted documents 703 if (!exists(docModel.getRef())) { 704 continue; 705 } 706 DocumentRef sourceFolderRef = docModel.getParentRef(); 707 String sourceType = docModel.getType(); 708 boolean canRemoveDoc = documentManager.hasPermission(sourceFolderRef, SecurityConstants.REMOVE_CHILDREN); 709 boolean canPasteInCurrentFolder = typeManager.isAllowedSubType(sourceType, destFolder.getType(), 710 navigationContext.getCurrentDocument()); 711 boolean sameFolder = sourceFolderRef.equals(destFolderRef); 712 if (canRemoveDoc && canPasteInCurrentFolder && !sameFolder) { 713 return true; 714 } 715 } 716 return false; 717 } 718 } 719 720 /** 721 * Checks if the Move action is available in the context of the current Document. Conditions: 722 * <p> 723 * <ul> 724 * <li>list is not empty 725 * <li>user has the needed permissions on the current document 726 * <li>an element in the list can be removed from its folder and added as child of the current document 727 * </ul> 728 */ 729 public boolean getCanMove(String listName) { 730 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 731 return getCanMoveInside(listName, currentDocument); 732 } 733 734 @Override 735 public boolean getCanPasteWorkList() { 736 return getCanPaste(getCurrentSelectedListName()); 737 } 738 739 @Override 740 public boolean getCanMoveWorkingList() { 741 return getCanMove(getCurrentSelectedListName()); 742 } 743 744 @Override 745 public boolean getCanPasteFromClipboard() { 746 return getCanPaste(DocumentsListsManager.CLIPBOARD); 747 } 748 749 @Override 750 public boolean getCanPasteFromClipboardInside(DocumentModel document) { 751 return getCanPasteInside(DocumentsListsManager.CLIPBOARD, document); 752 } 753 754 @Override 755 public boolean getCanMoveFromClipboardInside(DocumentModel document) { 756 return getCanMoveInside(DocumentsListsManager.CLIPBOARD, document); 757 } 758 759 @Override 760 public void setCurrentSelectedList(String listId) { 761 if (listId != null && !listId.equals(currentSelectedList)) { 762 currentSelectedList = listId; 763 canEditSelectedDocs = null; 764 } 765 } 766 767 @RequestParameter() 768 String listIdToSelect; 769 770 @Override 771 public void selectList() { 772 if (listIdToSelect != null) { 773 setCurrentSelectedList(listIdToSelect); 774 } 775 } 776 777 @Override 778 public List<DocumentModel> getCurrentSelectedList() { 779 return documentsListsManager.getWorkingList(getCurrentSelectedListName()); 780 } 781 782 @Override 783 public String getCurrentSelectedListName() { 784 if (currentSelectedList == null) { 785 if (!getAvailableLists().isEmpty()) { 786 setCurrentSelectedList(availableLists.get(0)); 787 } 788 } 789 return currentSelectedList; 790 } 791 792 @Override 793 public String getCurrentSelectedListTitle() { 794 String title = null; 795 String listName = getCurrentSelectedListName(); 796 if (listName != null) { 797 DocumentsListDescriptor desc = documentsListsManager.getWorkingListDescriptor(listName); 798 if (desc != null) { 799 title = desc.getTitle(); 800 } 801 } 802 return title; 803 } 804 805 @Override 806 public List<String> getAvailableLists() { 807 if (availableLists == null) { 808 availableLists = documentsListsManager.getWorkingListNamesForCategory("CLIPBOARD"); 809 } 810 return availableLists; 811 } 812 813 @Override 814 public List<DocumentsListDescriptor> getDescriptorsForAvailableLists() { 815 if (descriptorsForAvailableLists == null) { 816 List<String> availableLists = getAvailableLists(); 817 descriptorsForAvailableLists = new ArrayList<>(); 818 for (String lName : availableLists) { 819 descriptorsForAvailableLists.add(documentsListsManager.getWorkingListDescriptor(lName)); 820 } 821 } 822 return descriptorsForAvailableLists; 823 } 824 825 @Override 826 public List<Action> getActionsForCurrentList() { 827 String lstName = getCurrentSelectedListName(); 828 if (isWorkListEmpty()) { 829 // we use cache here since this is a very common case ... 830 if (actionCache == null) { 831 actionCache = new HashMap<>(); 832 } 833 if (!actionCache.containsKey(lstName)) { 834 actionCache.put(lstName, webActions.getActionsList(lstName + "_LIST")); 835 } 836 return actionCache.get(lstName); 837 } else { 838 return webActions.getActionsList(lstName + "_LIST"); 839 } 840 } 841 842 @Override 843 public List<Action> getActionsForSelection() { 844 return webActions.getActionsList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION + "_LIST", false); 845 } 846 847 private void autoSelectCurrentList(String listName) { 848 previouslySelectedList = getCurrentSelectedListName(); 849 setCurrentSelectedList(listName); 850 } 851 852 private void returnToPreviouslySelectedList() { 853 setCurrentSelectedList(previouslySelectedList); 854 } 855 856 @Override 857 public boolean getCanEditSelectedDocs() { 858 if (canEditSelectedDocs == null) { 859 if (getCurrentSelectedList().isEmpty()) { 860 canEditSelectedDocs = false; 861 } else { 862 final List<DocumentModel> selectedDocs = getCurrentSelectedList(); 863 864 // check selected docs 865 canEditSelectedDocs = checkWritePerm(selectedDocs); 866 } 867 } 868 return canEditSelectedDocs; 869 } 870 871 @Override 872 @Deprecated 873 // no longer used by the user_clipboard.xhtml template 874 public boolean getCanEditListDocs(String listName) { 875 final List<DocumentModel> docs = documentsListsManager.getWorkingList(listName); 876 877 final boolean canEdit; 878 if (docs.isEmpty()) { 879 canEdit = false; 880 } else { 881 // check selected docs 882 canEdit = checkWritePerm(docs); 883 } 884 return canEdit; 885 } 886 887 private boolean checkWritePerm(List<DocumentModel> selectedDocs) { 888 for (DocumentModel documentModel : selectedDocs) { 889 boolean canWrite = documentManager.hasPermission(documentModel.getRef(), SecurityConstants.WRITE_PROPERTIES); 890 if (!canWrite) { 891 return false; 892 } 893 } 894 return true; 895 } 896 897 @Override 898 public boolean isCacheEnabled() { 899 if (!SeamCacheHelper.canUseSeamCache()) { 900 return false; 901 } 902 return isWorkListEmpty(); 903 } 904 905 @Override 906 public String getCacheKey() { 907 return getCurrentSelectedListName() + "::" + localeSelector.getLocaleString(); 908 } 909 910 @Override 911 public boolean isCacheEnabledForSelection() { 912 if (!SeamCacheHelper.canUseSeamCache()) { 913 return false; 914 } 915 return documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 916 } 917 918 @Override 919 public String exportWorklistAsZip(List<DocumentModel> documents, boolean exportAllBlobs) { 920 Blob blob = null; 921 try { 922 DownloadService downloadService = Framework.getService(DownloadService.class); 923 DocumentListZipExporter zipExporter = new DocumentListZipExporter(); 924 blob = zipExporter.exportWorklistAsZip(documents, documentManager, exportAllBlobs); 925 if (blob == null) { 926 // empty zip file, do nothing 927 facesMessages.add(StatusMessage.Severity.INFO, messages.get("label.clipboard.emptyDocuments")); 928 return null; 929 } 930 blob.setMimeType("application/zip"); 931 blob.setFilename("clipboard.zip"); 932 933 String key = downloadService.storeBlobs(Collections.singletonList(blob)); 934 String url = BaseURL.getBaseURL() + downloadService.getDownloadUrl(key); 935 ExternalContext context = FacesContext.getCurrentInstance().getExternalContext(); 936 context.redirect(url); 937 return ""; 938 } catch (IOException io) { 939 if (blob != null) { 940 blob.getFile().delete(); 941 } 942 throw new NuxeoException("Error while redirecting for clipboard content", io); 943 } 944 } 945}