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 protected void fetchInvalidationsIfNeeded() { 128 // fetch invalidations from unrestricted session if needed 129 if (!documentManager.isStateSharedByAllThreadSessions()) { 130 documentManager.save(); 131 } 132 } 133 134 /** 135 * Adds the post to the thread and starts the moderation WF on the post created. 136 */ 137 @Override 138 public String addPost() { 139 DocumentModel dm = documentManager.createDocumentModel("Post"); 140 141 dm.setProperty("post", "author", commentManagerActions.getPrincipalName()); 142 143 dm.setProperty("post", "title", title); 144 dm.setProperty("post", "text", text); 145 dm.setProperty("post", "creationDate", new Date()); 146 dm.setProperty("post", "filename", filename); 147 dm.setProperty("post", "fileContent", fileContent); 148 149 // save it to the repository 150 dm = commentManagerActions.addComment(dm); 151 152 if (threadAction.isCurrentThreadModerated() && !threadAction.isPrincipalModerator()) { 153 // start moderation workflow + warn user that post 154 // won't be displayed until moderation kicks in 155 startModeration(dm); 156 facesMessages.add(StatusMessage.Severity.INFO, 157 resourcesAccessor.getMessages().get("label.comment.waiting_approval")); 158 } else { 159 // publish post 160 DocumentRef postRef = dm.getRef(); 161 if (documentManager.hasPermission(postRef, SecurityConstants.WRITE_LIFE_CYCLE)) { 162 documentManager.followTransition(postRef, ForumConstants.TRANSITION_TO_PUBLISHED_STATE); 163 documentManager.save(); 164 } else { 165 // Here user only granted with read rights should be able to 166 // create a post => open a system session to put it in 167 // published 168 // state 169 try (CoreSession systemSession = CoreInstance.openCoreSessionSystem(currentServerLocation.getName())) { 170 // follow transition 171 systemSession.followTransition(dm.getRef(), ForumConstants.TRANSITION_TO_PUBLISHED_STATE); 172 systemSession.save(); 173 } 174 } 175 fetchInvalidationsIfNeeded(); 176 // NXP-1262 display the message only when about to publish 177 facesMessages.add(StatusMessage.Severity.INFO, 178 resourcesAccessor.getMessages().get("label.comment.added.sucess")); 179 } 180 181 // force comment manager to reload posts 182 commentManagerActions.documentChanged(); 183 cleanContextVariables(); 184 185 return navigationContext.navigateToDocument(getParentThread()); 186 } 187 188 @Override 189 public String cancelPost() { 190 cleanContextVariables(); 191 commentManagerActions.cancelComment(); 192 fetchInvalidationsIfNeeded(); 193 return navigationContext.navigateToDocument(getParentThread()); 194 } 195 196 @Override 197 public String deletePost() { 198 if (deletePostId == null) { 199 throw new NuxeoException("No id for post to delete"); 200 } 201 202 DocumentModel thread = getParentThread(); 203 DocumentModel post = documentManager.getDocument(new IdRef(deletePostId)); 204 205 if (threadAction.isThreadModerated(thread) 206 && ForumConstants.PENDING_STATE.equals(post.getCurrentLifeCycleState())) { 207 Task task = getModerationTask(thread, deletePostId); 208 if (task != null) { 209 taskService.deleteTask(documentManager, task.getId()); 210 } 211 } 212 commentManagerActions.deleteComment(deletePostId); 213 214 fetchInvalidationsIfNeeded(); 215 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_ENDED); 216 217 return navigationContext.navigateToDocument(getParentThread()); 218 } 219 220 @Override 221 public String rejectPost(DocumentModel post) { 222 DocumentModel thread = getParentThread(); 223 224 Task moderationTask = getModerationTask(thread, post.getId()); 225 if (moderationTask == null) { 226 throw new NuxeoException("No moderation task found"); 227 } 228 229 taskService.rejectTask(documentManager, (NuxeoPrincipal) currentUser, moderationTask, null); 230 231 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED); 232 233 // force comment manager to reload posts 234 commentManagerActions.documentChanged(); 235 236 fetchInvalidationsIfNeeded(); 237 238 return navigationContext.navigateToDocument(getParentThread()); 239 } 240 241 /** 242 * Ends the task on a post. 243 */ 244 @Override 245 public String approvePost(DocumentModel post) { 246 DocumentModel thread = getParentThread(); 247 248 Task moderationTask = getModerationTask(thread, post.getId()); 249 if (moderationTask == null) { 250 throw new NuxeoException("No moderation task found"); 251 } 252 taskService.acceptTask(documentManager, (NuxeoPrincipal) currentUser, moderationTask, null); 253 254 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED); 255 256 // force comment manager to reload posts 257 commentManagerActions.documentChanged(); 258 259 fetchInvalidationsIfNeeded(); 260 261 return navigationContext.navigateToDocument(getParentThread()); 262 } 263 264 @Override 265 public DocumentModel getParentThread() { 266 return navigationContext.getCurrentDocument(); 267 } 268 269 @Override 270 public boolean isPostPublished(DocumentModel post) { 271 boolean published = false; 272 if (post != null && ForumConstants.PUBLISHED_STATE.equals(post.getCurrentLifeCycleState())) { 273 published = true; 274 } 275 return published; 276 } 277 278 /** 279 * Starts the moderation on given Post. 280 */ 281 @SuppressWarnings("unchecked") 282 protected void startModeration(DocumentModel post) { 283 284 DocumentModel thread = getParentThread(); 285 List<String> moderators = (ArrayList<String>) thread.getProperty("thread", "moderators"); 286 287 if (moderators == null || moderators.isEmpty()) { 288 throw new NuxeoException("No moderators defined"); 289 } 290 291 Map<String, String> vars = new HashMap<String, String>(); 292 vars.put(ForumConstants.COMMENT_ID, post.getId()); 293 vars.put(CreateTask.OperationTaskVariableName.createdFromCreateTaskOperation.name(), "false"); 294 vars.put(Task.TaskVariableName.needi18n.name(), "true"); 295 vars.put(Task.TaskVariableName.taskType.name(), ForumConstants.FORUM_TASK_TYPE); 296 297 vars.put(CreateTask.OperationTaskVariableName.acceptOperationChain.name(), CommentsConstants.ACCEPT_CHAIN_NAME); 298 vars.put(CreateTask.OperationTaskVariableName.rejectOperationChain.name(), CommentsConstants.REJECT_CHAIN_NAME); 299 300 taskService.createTask(documentManager, (NuxeoPrincipal) currentUser, thread, 301 ForumConstants.MODERATION_TASK_NAME, moderators, false, ForumConstants.MODERATION_TASK_NAME, null, 302 null, vars, null); 303 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_NEW_STARTED); 304 305 } 306 307 protected Task getModerationTask(DocumentModel thread, String postId) { 308 List<Task> tasks = DocumentTaskProvider.getTasks("GET_FORUM_MODERATION_TASKS", documentManager, false, null, 309 thread.getId(), postId); 310 if (tasks != null && !tasks.isEmpty()) { 311 if (tasks.size() > 1) { 312 log.error("There are several moderation workflows running, " + "taking only first found"); 313 } 314 Task task = tasks.get(0); 315 return task; 316 } 317 return null; 318 } 319 320 protected void cleanContextVariables() { 321 fileContent = null; 322 filename = null; 323 text = null; 324 title = null; 325 } 326 327 // getters/setters 328 329 @Override 330 public String getText() { 331 return text; 332 } 333 334 @Override 335 public void setText(String text) { 336 this.text = text; 337 } 338 339 @Override 340 public String getFilename() { 341 return filename; 342 } 343 344 @Override 345 public void setFilename(String filename) { 346 this.filename = filename; 347 } 348 349 @Override 350 public Blob getFileContent() { 351 return fileContent; 352 } 353 354 @Override 355 public void setFileContent(Blob fileContent) { 356 this.fileContent = fileContent; 357 } 358 359 /** 360 * Gets the title of the post for creation purpose. If the post to be created reply to a previous post, the title of 361 * the new post comes with the previous title, and a prefix (i.e : Re : Previous Title). 362 */ 363 @Override 364 public String getTitle() { 365 366 String previousId = commentManagerActions.getSavedReplyCommentId(); 367 if (previousId != null && !"".equals(previousId)) { 368 DocumentModel previousPost = documentManager.getDocument(new IdRef(previousId)); 369 370 // Test to ensure that previous comment got the "post" schema 371 if (previousPost.getDataModel("post") != null) { 372 String previousTitle = (String) previousPost.getProperty("post", "title"); 373 String prefix = resourcesAccessor.getMessages().get("label.forum.post.title.prefix"); 374 title = prefix + previousTitle; 375 } 376 } 377 return title; 378 } 379 380 @Override 381 public void setTitle(String title) { 382 this.title = title; 383 } 384 385}