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}