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