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