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}