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 * nulrich 016 * 017 * $Id 018 */ 019package org.nuxeo.ecm.platform.forum.web; 020 021import static org.jboss.seam.ScopeType.EVENT; 022 023import java.security.Principal; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.GregorianCalendar; 028import java.util.List; 029 030import javax.faces.context.FacesContext; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.jboss.seam.ScopeType; 035import org.jboss.seam.annotations.Factory; 036import org.jboss.seam.annotations.In; 037import org.jboss.seam.annotations.Name; 038import org.jboss.seam.annotations.Scope; 039import org.jboss.seam.core.Events; 040import org.nuxeo.ecm.core.api.CoreSession; 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.DocumentNotFoundException; 043import org.nuxeo.ecm.core.api.DocumentRef; 044import org.nuxeo.ecm.core.api.NuxeoGroup; 045import org.nuxeo.ecm.core.api.NuxeoPrincipal; 046import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 047import org.nuxeo.ecm.core.api.security.SecurityConstants; 048import org.nuxeo.ecm.platform.comment.web.CommentManagerActions; 049import org.nuxeo.ecm.platform.comment.web.ThreadEntry; 050import org.nuxeo.ecm.platform.forum.web.api.PostAction; 051import org.nuxeo.ecm.platform.forum.web.api.ThreadAction; 052import org.nuxeo.ecm.platform.forum.web.api.ThreadAdapter; 053import org.nuxeo.ecm.platform.forum.workflow.ForumConstants; 054import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 055import org.nuxeo.ecm.webapp.helpers.EventNames; 056import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor; 057import org.nuxeo.runtime.api.Framework; 058 059/** 060 * This Action Listener represents a Thread inside a forum. 061 * 062 * @author <a href="bchaffangeon@nuxeo.com">Brice Chaffangeon</a> 063 */ 064@Name("threadAction") 065@Scope(ScopeType.CONVERSATION) 066public class ThreadActionBean implements ThreadAction { 067 068 private static final Log log = LogFactory.getLog(ThreadActionBean.class); 069 070 private static final long serialVersionUID = -2667460487440135732L; 071 072 protected static final String schema = "thread"; 073 074 protected static final String type = "Thread"; 075 076 protected boolean principalIsAdmin; 077 078 @In(create = true) 079 protected transient Principal currentUser; 080 081 @In(create = true, required = false) 082 protected transient CoreSession documentManager; 083 084 @In(create = true) 085 protected transient CommentManagerActions commentManagerActions; 086 087 @In(create = true) 088 protected NavigationContext navigationContext; 089 090 @In(create = true) 091 protected ResourcesAccessor resourcesAccessor; 092 093 @In(create = true) 094 protected PostAction postAction; 095 096 protected String title; 097 098 protected String description; 099 100 protected List<String> selectedModerators; 101 102 protected boolean moderated; 103 104 protected NuxeoPrincipal principal; 105 106 public String addThread() { 107 108 // The thread to be created 109 DocumentModel docThread = getThreadModel(); 110 111 docThread = documentManager.createDocument(docThread); 112 documentManager.save(); 113 114 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 115 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, currentDocument); 116 clean(); 117 return navigationContext.navigateToDocument(docThread, "after-create"); 118 } 119 120 /** 121 * Clean variables. 122 */ 123 protected void clean() { 124 title = null; 125 description = null; 126 moderated = false; 127 selectedModerators = null; 128 } 129 130 /** 131 * Gets the Thread to create as a DocumentModel. 132 */ 133 protected DocumentModel getThreadModel() { 134 135 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 136 String path = currentDocument.getPathAsString(); 137 138 final DocumentModel docThread = documentManager.createDocumentModel(type); 139 docThread.setProperty("dublincore", "title", title); 140 docThread.setProperty("dublincore", "description", description); 141 docThread.setProperty(schema, "moderated", moderated); 142 143 if (moderated) { 144 145 List<String> selectedModerators = getSelectedModerators(); 146 147 // The current user should have the right to moderate 148 if (!selectedModerators.contains(NuxeoPrincipal.PREFIX + currentUser.getName())) { 149 selectedModerators.add(NuxeoPrincipal.PREFIX + currentUser.getName()); 150 } 151 152 // XXX: hack, administrators should have the right to moderate 153 // without being in this list 154 // We automatically add administrators (with prefix) as moderators 155 if (!selectedModerators.contains(NuxeoGroup.PREFIX + SecurityConstants.ADMINISTRATORS)) { 156 selectedModerators.add(NuxeoGroup.PREFIX + SecurityConstants.ADMINISTRATORS); 157 } 158 // We can also remove Administrator since his group is added 159 if (selectedModerators.contains(NuxeoPrincipal.PREFIX + SecurityConstants.ADMINISTRATOR)) { 160 selectedModerators.remove(NuxeoPrincipal.PREFIX + SecurityConstants.ADMINISTRATOR); 161 } 162 163 docThread.setProperty(schema, "moderators", selectedModerators); 164 } 165 166 PathSegmentService pss = Framework.getService(PathSegmentService.class); 167 docThread.setPathInfo(path, pss.generatePathSegment(docThread)); 168 return docThread; 169 } 170 171 @SuppressWarnings({ "unchecked" }) 172 public List<String> getModerators() { 173 DocumentModel currentThread = navigationContext.getCurrentDocument(); 174 return (List<String>) currentThread.getProperty("thread", "moderators"); 175 } 176 177 public boolean isPrincipalModerator() { 178 principal = (NuxeoPrincipal) FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal(); 179 List<String> moderators = getModerators(); 180 181 boolean moderator = false; 182 if (isPrincipalGroupModerator() || moderators != null 183 && moderators.contains(NuxeoPrincipal.PREFIX + principal.getName())) { 184 moderator = true; 185 } 186 return moderator; 187 } 188 189 public boolean isPrincipalGroupModerator() { 190 191 List<String> moderators = getModerators(); 192 List<String> principalGroups = principal.getAllGroups(); 193 194 for (String principalGroup : principalGroups) { 195 if (moderators != null && moderators.contains(NuxeoGroup.PREFIX + principalGroup)) { 196 return true; 197 } 198 } 199 return false; 200 } 201 202 public boolean isCurrentThreadModerated() { 203 DocumentModel currentThread = navigationContext.getCurrentDocument(); 204 return isThreadModerated(currentThread); 205 } 206 207 @Factory(value = "currentThreadPosts", scope = EVENT) 208 public List<ThreadEntry> getPostsAsThread() { 209 List<ThreadEntry> basicCommentList = null; 210 211 // Thread is not moderated, we return all Posts 212 if (!isCurrentThreadModerated()) { 213 basicCommentList = commentManagerActions.getCommentsAsThread(); 214 } else { 215 // Here we clean the list according the rights of principal. 216 basicCommentList = new ArrayList<ThreadEntry>(); 217 List<ThreadEntry> allThreadEntry = commentManagerActions.getCommentsAsThread(); 218 219 for (ThreadEntry threadEntry : allThreadEntry) { 220 // if current user is not moderator and post is not published, 221 // we remove it 222 DocumentModel dm = threadEntry.getComment(); 223 224 String[] contributorsArray = (String[]) dm.getProperty("dublincore", "contributors"); 225 if (contributorsArray == null) { 226 contributorsArray = new String[0]; 227 } 228 List<String> cs = Arrays.asList(contributorsArray); 229 230 if (postAction.isPostPublished(threadEntry.getComment()) || isPrincipalModerator() 231 || cs.contains(currentUser.getName())) { 232 basicCommentList.add(threadEntry); 233 } 234 } 235 } 236 return basicCommentList; 237 } 238 239 protected ThreadAdapter adapter; 240 241 public ThreadAdapter getAdapter(DocumentModel thread) { 242 if (thread == null) { 243 return null; 244 } 245 if (adapter != null && adapter.getThreadDoc().getRef().equals(thread.getRef())) { 246 return adapter; 247 } 248 if (thread.getSessionId() == null) { 249 try { 250 thread = documentManager.getDocument(thread.getRef()); 251 } catch (DocumentNotFoundException e) { 252 log.error("Unable to reconnect doc !,", e); 253 } 254 } 255 adapter = thread.getAdapter(ThreadAdapter.class); 256 return adapter; 257 } 258 259 public List<DocumentModel> getAllPosts(DocumentModel thread, String state) { 260 261 thread = getDocumentThreadModel(thread.getRef()); 262 List<DocumentModel> allPosts = Collections.emptyList(); 263 List<ThreadEntry> allThreadEntry = Collections.emptyList(); 264 265 if (thread != null) { 266 allThreadEntry = commentManagerActions.getCommentsAsThreadOnDoc(thread); 267 } 268 if (allThreadEntry != null && !allThreadEntry.isEmpty()) { 269 allPosts = new ArrayList<DocumentModel>(); 270 for (ThreadEntry entry : allThreadEntry) { 271 if (!"".equals(state) && state.equals(entry.getComment().getCurrentLifeCycleState())) { 272 allPosts.add(entry.getComment()); 273 } else if ("".equals(state)) { 274 allPosts.add(entry.getComment()); 275 } 276 } 277 278 } 279 280 return allPosts; 281 } 282 283 public List<DocumentModel> getPostsPublished(DocumentModel thread) { 284 return getAllPosts(thread, ForumConstants.PUBLISHED_STATE); 285 } 286 287 public List<DocumentModel> getPostsPending(DocumentModel thread) { 288 return getAllPosts(thread, ForumConstants.PENDING_STATE); 289 } 290 291 public String getDescription() { 292 return description; 293 } 294 295 public String getTitle() { 296 return title; 297 } 298 299 public void setDescription(String description) { 300 this.description = description; 301 } 302 303 public void setTitle(String title) { 304 this.title = title; 305 } 306 307 public String getSchema() { 308 return schema; 309 } 310 311 public String getType() { 312 return type; 313 } 314 315 public void saveState() { 316 log.info("PrePassivate"); 317 } 318 319 public void readState() { 320 log.info("PostActivate"); 321 } 322 323 public boolean isModerated() { 324 return moderated; 325 } 326 327 public void setModerated(boolean moderated) { 328 this.moderated = moderated; 329 } 330 331 public DocumentModel getLastPostPublished(DocumentModel thread) { 332 333 thread = getDocumentThreadModel(thread.getRef()); 334 List<DocumentModel> posts = getPostsPublished(thread); 335 DocumentModel lastPost = null; 336 if (!posts.isEmpty()) { 337 lastPost = posts.get(0); 338 for (DocumentModel post : posts) { 339 GregorianCalendar lastPostDate = (GregorianCalendar) lastPost.getProperty("post", "creationDate"); 340 GregorianCalendar postDate = (GregorianCalendar) post.getProperty("post", "creationDate"); 341 if (postDate != null && postDate.after(lastPostDate)) { 342 lastPost = post; 343 } 344 } 345 346 } 347 return lastPost; 348 } 349 350 public String getModerationAsString(DocumentModel thread) { 351 if (isThreadModerated(thread)) { 352 return resourcesAccessor.getMessages().get("label.forum.thread.moderated.yes"); 353 } 354 return resourcesAccessor.getMessages().get("label.forum.thread.moderated.no"); 355 } 356 357 public boolean isThreadModerated(DocumentModel thread) { 358 if (thread != null) { 359 thread = getDocumentThreadModel(thread.getRef()); 360 if (thread != null) { 361 Boolean moderation = (Boolean) thread.getProperty("thread", "moderated"); 362 if (moderation != null) { 363 return moderation; 364 } 365 } 366 } 367 return false; 368 } 369 370 public DocumentModel getParentPost(int post) { 371 DocumentModel parentPost = null; 372 373 List<ThreadEntry> posts = getPostsAsThread(); 374 if (post > 0 && post <= posts.size()) { 375 ThreadEntry parent = posts.get(post - 1); 376 ThreadEntry currentPost = posts.get(post); 377 if (currentPost.getDepth() == parent.getDepth() + 1) { 378 parentPost = parent.getComment(); 379 } 380 } 381 return parentPost; 382 } 383 384 public boolean isParentPostPublished(int post) { 385 386 DocumentModel parent = getParentPost(post); 387 if (parent == null) { 388 return true; 389 } else if (ForumConstants.PUBLISHED_STATE.equals(parent.getCurrentLifeCycleState())) { 390 return true; 391 } 392 return false; 393 } 394 395 /** 396 * Gets the thread for a given document reference. 397 */ 398 protected DocumentModel getDocumentThreadModel(DocumentRef threadRef) { 399 DocumentModel thread = null; 400 if (threadRef != null) { 401 thread = documentManager.getDocument(threadRef); 402 } 403 return thread; 404 } 405 406 public List<String> getSelectedModerators() { 407 if (selectedModerators == null) { 408 selectedModerators = new ArrayList<String>(); 409 } 410 return selectedModerators; 411 } 412 413 public void setSelectedModerators(List<String> selectedModerators) { 414 this.selectedModerators = selectedModerators; 415 } 416 417}