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 *     ${user}
016 *
017 * $Id
018 */
019
020package org.nuxeo.ecm.platform.forum.web;
021
022import java.security.Principal;
023import java.util.ArrayList;
024import java.util.Date;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.jboss.seam.ScopeType;
032import org.jboss.seam.annotations.In;
033import org.jboss.seam.annotations.Name;
034import org.jboss.seam.annotations.Scope;
035import org.jboss.seam.annotations.web.RequestParameter;
036import org.jboss.seam.core.Events;
037import org.jboss.seam.faces.FacesMessages;
038import org.jboss.seam.international.StatusMessage;
039import org.nuxeo.ecm.automation.task.CreateTask;
040import org.nuxeo.ecm.core.api.Blob;
041import org.nuxeo.ecm.core.api.CoreInstance;
042import org.nuxeo.ecm.core.api.CoreSession;
043import org.nuxeo.ecm.core.api.DocumentModel;
044import org.nuxeo.ecm.core.api.DocumentRef;
045import org.nuxeo.ecm.core.api.IdRef;
046import org.nuxeo.ecm.core.api.NuxeoException;
047import org.nuxeo.ecm.core.api.NuxeoPrincipal;
048import org.nuxeo.ecm.core.api.security.SecurityConstants;
049import org.nuxeo.ecm.platform.comment.web.CommentManagerActions;
050import org.nuxeo.ecm.platform.comment.workflow.utils.CommentsConstants;
051import org.nuxeo.ecm.platform.forum.web.api.PostAction;
052import org.nuxeo.ecm.platform.forum.web.api.ThreadAction;
053import org.nuxeo.ecm.platform.forum.workflow.ForumConstants;
054import org.nuxeo.ecm.platform.task.Task;
055import org.nuxeo.ecm.platform.task.TaskEventNames;
056import org.nuxeo.ecm.platform.task.TaskService;
057import org.nuxeo.ecm.platform.task.core.service.DocumentTaskProvider;
058import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
059import org.nuxeo.ecm.platform.util.RepositoryLocation;
060import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
061
062/**
063 * This action listener is used to create a Post inside a Thread and also to handle the moderation cycle on Post.
064 *
065 * @author <a href="bchaffangeon@nuxeo.com">Brice Chaffangeon</a>
066 */
067@Name("postAction")
068@Scope(ScopeType.CONVERSATION)
069public class PostActionBean implements PostAction {
070
071    private static final long serialVersionUID = 1L;
072
073    private static final Log log = LogFactory.getLog(PostActionBean.class);
074
075    @In(create = true)
076    protected ThreadAction threadAction;
077
078    @In(create = true)
079    protected transient CommentManagerActions commentManagerActions;
080
081    @In(create = true, required = false)
082    protected transient CoreSession documentManager;
083
084    @In(create = true)
085    protected transient NavigationContext navigationContext;
086
087    @In(create = true)
088    protected transient TaskService taskService;
089
090    @In(required = false)
091    protected RepositoryLocation currentServerLocation;
092
093    @In(create = true)
094    protected transient Principal currentUser;
095
096    @In(create = true, required = false)
097    protected FacesMessages facesMessages;
098
099    @In(create = true)
100    protected ResourcesAccessor resourcesAccessor;
101
102    // the id of the comment to delete
103    @RequestParameter
104    protected String deletePostId;
105
106    protected String title;
107
108    protected String text;
109
110    protected String filename;
111
112    protected Blob fileContent;
113
114    @Override
115    public boolean checkWritePermissionOnThread() {
116        DocumentModel currentDocument = navigationContext.getCurrentDocument();
117        if (currentDocument != null) {
118            return documentManager.hasPermission(currentDocument.getRef(), SecurityConstants.READ_WRITE);
119        } else {
120            log.error("Cannot check write permission on thread: " + "no current document found");
121        }
122        return false;
123    }
124
125    protected void fetchInvalidationsIfNeeded() {
126        // fetch invalidations from unrestricted session if needed
127        if (!documentManager.isStateSharedByAllThreadSessions()) {
128            documentManager.save();
129        }
130    }
131
132    /**
133     * Adds the post to the thread and starts the moderation WF on the post created.
134     */
135    @Override
136    public String addPost() {
137        DocumentModel dm = documentManager.createDocumentModel("Post");
138
139        dm.setProperty("post", "author", commentManagerActions.getPrincipalName());
140
141        dm.setProperty("post", "title", title);
142        dm.setProperty("post", "text", text);
143        dm.setProperty("post", "creationDate", new Date());
144        dm.setProperty("post", "filename", filename);
145        dm.setProperty("post", "fileContent", fileContent);
146
147        // save it to the repository
148        dm = commentManagerActions.addComment(dm);
149
150        if (threadAction.isCurrentThreadModerated() && !threadAction.isPrincipalModerator()) {
151            // start moderation workflow + warn user that post
152            // won't be displayed until moderation kicks in
153            startModeration(dm);
154            facesMessages.add(StatusMessage.Severity.INFO,
155                    resourcesAccessor.getMessages().get("label.comment.waiting_approval"));
156        } else {
157            // publish post
158            DocumentRef postRef = dm.getRef();
159            if (documentManager.hasPermission(postRef, SecurityConstants.WRITE_LIFE_CYCLE)) {
160                documentManager.followTransition(postRef, ForumConstants.TRANSITION_TO_PUBLISHED_STATE);
161                documentManager.save();
162            } else {
163                // Here user only granted with read rights should be able to
164                // create a post => open a system session to put it in
165                // published
166                // state
167                try (CoreSession systemSession = CoreInstance.openCoreSessionSystem(currentServerLocation.getName())) {
168                    // follow transition
169                    systemSession.followTransition(dm.getRef(), ForumConstants.TRANSITION_TO_PUBLISHED_STATE);
170                    systemSession.save();
171                }
172            }
173            fetchInvalidationsIfNeeded();
174            // NXP-1262 display the message only when about to publish
175            facesMessages.add(StatusMessage.Severity.INFO,
176                    resourcesAccessor.getMessages().get("label.comment.added.sucess"));
177        }
178
179        // force comment manager to reload posts
180        commentManagerActions.documentChanged();
181        cleanContextVariables();
182
183        return navigationContext.navigateToDocument(getParentThread());
184    }
185
186    @Override
187    public String cancelPost() {
188        cleanContextVariables();
189        commentManagerActions.cancelComment();
190        fetchInvalidationsIfNeeded();
191        return navigationContext.navigateToDocument(getParentThread());
192    }
193
194    @Override
195    public String deletePost() {
196        if (deletePostId == null) {
197            throw new NuxeoException("No id for post to delete");
198        }
199
200        DocumentModel thread = getParentThread();
201        DocumentModel post = documentManager.getDocument(new IdRef(deletePostId));
202
203        if (threadAction.isThreadModerated(thread)
204                && ForumConstants.PENDING_STATE.equals(post.getCurrentLifeCycleState())) {
205            Task task = getModerationTask(thread, deletePostId);
206            if (task != null) {
207                taskService.deleteTask(documentManager, task.getId());
208            }
209        }
210        commentManagerActions.deleteComment(deletePostId);
211
212        fetchInvalidationsIfNeeded();
213        Events.instance().raiseEvent(TaskEventNames.WORKFLOW_ENDED);
214
215        return navigationContext.navigateToDocument(getParentThread());
216    }
217
218    @Override
219    public String rejectPost(DocumentModel post) {
220        DocumentModel thread = getParentThread();
221
222        Task moderationTask = getModerationTask(thread, post.getId());
223        if (moderationTask == null) {
224            throw new NuxeoException("No moderation task found");
225        }
226
227        taskService.rejectTask(documentManager, (NuxeoPrincipal) currentUser, moderationTask, null);
228
229        Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED);
230
231        // force comment manager to reload posts
232        commentManagerActions.documentChanged();
233
234        fetchInvalidationsIfNeeded();
235
236        return navigationContext.navigateToDocument(getParentThread());
237    }
238
239    /**
240     * Ends the task on a post.
241     */
242    @Override
243    public String approvePost(DocumentModel post) {
244        DocumentModel thread = getParentThread();
245
246        Task moderationTask = getModerationTask(thread, post.getId());
247        if (moderationTask == null) {
248            throw new NuxeoException("No moderation task found");
249        }
250        taskService.acceptTask(documentManager, (NuxeoPrincipal) currentUser, moderationTask, null);
251
252        Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED);
253
254        // force comment manager to reload posts
255        commentManagerActions.documentChanged();
256
257        fetchInvalidationsIfNeeded();
258
259        return navigationContext.navigateToDocument(getParentThread());
260    }
261
262    @Override
263    public DocumentModel getParentThread() {
264        return navigationContext.getCurrentDocument();
265    }
266
267    @Override
268    public boolean isPostPublished(DocumentModel post) {
269        boolean published = false;
270        if (post != null && ForumConstants.PUBLISHED_STATE.equals(post.getCurrentLifeCycleState())) {
271            published = true;
272        }
273        return published;
274    }
275
276    /**
277     * Starts the moderation on given Post.
278     */
279    @SuppressWarnings("unchecked")
280    protected void startModeration(DocumentModel post) {
281
282        DocumentModel thread = getParentThread();
283        List<String> moderators = (ArrayList<String>) thread.getProperty("thread", "moderators");
284
285        if (moderators == null || moderators.isEmpty()) {
286            throw new NuxeoException("No moderators defined");
287        }
288
289        Map<String, String> vars = new HashMap<String, String>();
290        vars.put(ForumConstants.COMMENT_ID, post.getId());
291        vars.put(CreateTask.OperationTaskVariableName.createdFromCreateTaskOperation.name(), "false");
292        vars.put(Task.TaskVariableName.needi18n.name(), "true");
293        vars.put(Task.TaskVariableName.taskType.name(), ForumConstants.FORUM_TASK_TYPE);
294
295        vars.put(CreateTask.OperationTaskVariableName.acceptOperationChain.name(), CommentsConstants.ACCEPT_CHAIN_NAME);
296        vars.put(CreateTask.OperationTaskVariableName.rejectOperationChain.name(), CommentsConstants.REJECT_CHAIN_NAME);
297
298        taskService.createTask(documentManager, (NuxeoPrincipal) currentUser, thread,
299                ForumConstants.MODERATION_TASK_NAME, moderators, false, ForumConstants.MODERATION_TASK_NAME, null,
300                null, vars, null);
301        Events.instance().raiseEvent(TaskEventNames.WORKFLOW_NEW_STARTED);
302
303    }
304
305    protected Task getModerationTask(DocumentModel thread, String postId) {
306        List<Task> tasks = DocumentTaskProvider.getTasks("GET_FORUM_MODERATION_TASKS", documentManager, false, null,
307                thread.getId(), postId);
308        if (tasks != null && !tasks.isEmpty()) {
309            if (tasks.size() > 1) {
310                log.error("There are several moderation workflows running, " + "taking only first found");
311            }
312            Task task = tasks.get(0);
313            return task;
314        }
315        return null;
316    }
317
318    protected void cleanContextVariables() {
319        fileContent = null;
320        filename = null;
321        text = null;
322        title = null;
323    }
324
325    // getters/setters
326
327    @Override
328    public String getText() {
329        return text;
330    }
331
332    @Override
333    public void setText(String text) {
334        this.text = text;
335    }
336
337    @Override
338    public String getFilename() {
339        return filename;
340    }
341
342    @Override
343    public void setFilename(String filename) {
344        this.filename = filename;
345    }
346
347    @Override
348    public Blob getFileContent() {
349        return fileContent;
350    }
351
352    @Override
353    public void setFileContent(Blob fileContent) {
354        this.fileContent = fileContent;
355    }
356
357    /**
358     * Gets the title of the post for creation purpose. If the post to be created reply to a previous post, the title of
359     * the new post comes with the previous title, and a prefix (i.e : Re : Previous Title).
360     */
361    @Override
362    public String getTitle() {
363
364        String previousId = commentManagerActions.getSavedReplyCommentId();
365        if (previousId != null && !"".equals(previousId)) {
366            DocumentModel previousPost = documentManager.getDocument(new IdRef(previousId));
367
368            // Test to ensure that previous comment got the "post" schema
369            if (previousPost.getDataModel("post") != null) {
370                String previousTitle = (String) previousPost.getProperty("post", "title");
371                String prefix = resourcesAccessor.getMessages().get("label.forum.post.title.prefix");
372                title = prefix + previousTitle;
373            }
374        }
375        return title;
376    }
377
378    @Override
379    public void setTitle(String title) {
380        this.title = title;
381    }
382
383}