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