001/* 002 * Copyright 2014 Anyware Services 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 017package org.ametys.cms.repository.comment.ui; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.cocoon.ProcessingException; 028import org.apache.commons.lang.StringUtils; 029 030import org.ametys.cms.ObservationConstants; 031import org.ametys.cms.content.ContentHelper; 032import org.ametys.cms.repository.Content; 033import org.ametys.cms.repository.comment.Comment; 034import org.ametys.cms.repository.comment.CommentableContent; 035import org.ametys.core.observation.Event; 036import org.ametys.core.observation.ObservationManager; 037import org.ametys.core.right.RightManager.RightResult; 038import org.ametys.core.ui.Callable; 039import org.ametys.core.ui.StaticClientSideElement; 040import org.ametys.core.user.CurrentUserProvider; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.core.util.DateUtils; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.plugins.repository.UnknownAmetysObjectException; 045 046/** 047 * This client site elements creates a button representing the validation state of a content's comment 048 */ 049public class CommentClientSideElement extends StaticClientSideElement 050{ 051 /** The constant for the name of the author from parameters */ 052 public static final String PARAMETER_AUTHOR_NAME = "author-name"; 053 /** The constant for the email of the author from parameters */ 054 public static final String PARAMETER_AUTHOR_EMAIL = "author-email"; 055 /** The constant for the hidden status of the email of the author from parameters */ 056 public static final String PARAMETER_AUTHOR_EMAILHIDDEN = "author-emailhidden"; 057 /** The constant for the url of the author from parameters */ 058 public static final String PARAMETER_AUTHOR_URL = "author-url"; 059 /** The constant for the text from parameters */ 060 public static final String PARAMETER_TEXT = "text"; 061 /** The constant for the content id from parameters */ 062 public static final String PARAMETER_CONTENT_ID = "contentId"; 063 /** The constant for the comment id from parameters */ 064 public static final String PARAMETER_COMMENT_ID = "commentId"; 065 /** The Ametys object resolver */ 066 protected AmetysObjectResolver _resolver; 067 /** The current user provider */ 068 protected CurrentUserProvider _userProvider; 069 /** The observation manager */ 070 protected ObservationManager _observationManager; 071 /** The content helper */ 072 private ContentHelper _contentHelper; 073 074 @Override 075 public void service(ServiceManager smanager) throws ServiceException 076 { 077 super.service(smanager); 078 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 079 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 080 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 081 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 082 } 083 084 /** 085 * Return the list of comments with its validation state 086 * @param parameters The contents and their comments 087 * @return The comments and their validated status 088 */ 089 @Callable 090 public Map<String, Object> getComments(Map<String, Object> parameters) 091 { 092 Map<String, Object> results = new HashMap<>(); 093 results.put("comments", new ArrayList<Map<String, Object>>()); 094 095 for (String contentId : parameters.keySet()) 096 { 097 try 098 { 099 Content content = _resolver.resolveById(contentId); 100 if (content instanceof CommentableContent) 101 { 102 CommentableContent cContent = (CommentableContent) content; 103 104 @SuppressWarnings("unchecked") 105 List<String> commentIds = (List<String>) parameters.get(contentId); 106 107 for (String commentId : commentIds) 108 { 109 Comment comment = cContent.getComment(commentId); 110 @SuppressWarnings("unchecked") 111 List<Map<String, Object>> comments = (List<Map<String, Object>>) results.get("comments"); 112 comments.add(getCommentParameters(cContent, comment)); 113 } 114 } 115 else 116 { 117 getLogger().warn("Ignoring request to get comments status on non commentable content " + contentId); 118 } 119 } 120 catch (UnknownAmetysObjectException e) 121 { 122 getLogger().warn("Ignoring request to get comments status on inexisting content " + contentId, e); 123 } 124 } 125 126 return results; 127 } 128 129 /** 130 * Get a comment properties 131 * @param contentId the id of the content 132 * @param commentId the id of the comment. Can be null 133 * @return results the server's response in JSON. 134 * @throws IOException If an error occurred 135 * @throws ProcessingException If an error occurred 136 */ 137 @Callable 138 public Map<String, Object> getComment(String contentId, String commentId) throws IOException, ProcessingException 139 { 140 Map<String, Object> results = new HashMap<> (); 141 142 UserIdentity user = _userProvider.getUser(); 143 144 Map<String, Object> comments = new HashMap<> (); 145 146 Content content = _resolver.resolveById(contentId); 147 if (StringUtils.isNotEmpty(commentId)) 148 { 149 if (content instanceof CommentableContent) 150 { 151 if (_rightManager.hasRight(user, "CMS_Rights_CommentModerate", content) == RightResult.RIGHT_ALLOW) 152 { 153 CommentableContent commentableContent = (CommentableContent) content; 154 Comment comment = commentableContent.getComment(commentId); 155 comments = _jsonifyComment(comment, content); 156 } 157 } 158 } 159 160 results.put("comments", comments); 161 return results; 162 } 163 164 /** 165 * Edit a comment if connected user has sufficient rights 166 * @param parameters the JS parameters. Necessarily contains the content and comment id, and the values to edit 167 * @return An empty map 168 */ 169 @Callable 170 public Map<String, Object> editComment(Map<String, Object> parameters) 171 { 172 String contentId = (String) parameters.get(PARAMETER_CONTENT_ID); 173 String commentId = (String) parameters.get(PARAMETER_COMMENT_ID); 174 175 try 176 { 177 Content content = _resolver.resolveById(contentId); 178 179 if (!hasRight("CMS_Rights_CommentModerate", content)) 180 { 181 String errorMessage = "User " + getCurrentUser() + " try to edit a comment on content of id '" + contentId + "' with no sufficient rights"; 182 getLogger().error(errorMessage); 183 throw new IllegalStateException(errorMessage); 184 } 185 186 if (!(content instanceof CommentableContent)) 187 { 188 String errorMessage = "Can not edit comment for non-commentable content of id '" + contentId + "'"; 189 getLogger().error(errorMessage); 190 throw new IllegalStateException(errorMessage); 191 } 192 193 CommentableContent cContent = (CommentableContent) content; 194 195 Comment comment = cContent.getComment(commentId); 196 197 String oldAuthorName = comment.getAuthorName(); 198 String oldAuthorEmail = comment.getAuthorEmail(); 199 boolean oldAuthorEmailHidden = comment.isEmailHidden(); 200 String oldAuthorURL = comment.getAuthorURL(); 201 String oldContent = comment.getContent(); 202 203 boolean needSave = false; 204 205 String authorName = (String) parameters.get(PARAMETER_AUTHOR_NAME); 206 if (!authorName.equals(oldAuthorName)) 207 { 208 comment.setAuthorName(authorName); 209 needSave = true; 210 } 211 212 String authorEmail = (String) parameters.get(PARAMETER_AUTHOR_EMAIL); 213 if (!authorEmail.equals(oldAuthorEmail)) 214 { 215 comment.setAuthorEmail(authorEmail); 216 needSave = true; 217 } 218 219 boolean authorEmailHidden = (Boolean) parameters.get(PARAMETER_AUTHOR_EMAILHIDDEN); 220 if (authorEmailHidden != oldAuthorEmailHidden) 221 { 222 comment.setEmailHiddenStatus(authorEmailHidden); 223 needSave = true; 224 } 225 226 String authorUrl = (String) parameters.get(PARAMETER_AUTHOR_URL); 227 if (!authorUrl.equals(oldAuthorURL)) 228 { 229 comment.setAuthorURL(authorUrl); 230 needSave = true; 231 } 232 233 String text = (String) parameters.get(PARAMETER_TEXT); 234 if (!text.equals(oldContent)) 235 { 236 comment.setContent(text); 237 needSave = true; 238 } 239 240 if (needSave) 241 { 242 cContent.saveChanges(); 243 244 Map<String, Object> eventParams = new HashMap<>(); 245 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 246 eventParams.put(ObservationConstants.ARGS_COMMENT, comment); 247 eventParams.put("content.comment.old.author", oldAuthorName); 248 eventParams.put("content.comment.old.author.email", oldAuthorEmail); 249 eventParams.put("content.comment.old.author.email.hidden", oldAuthorEmailHidden); 250 eventParams.put("content.comment.old.author.url", oldAuthorURL); 251 eventParams.put("content.comment.old.content", oldContent); 252 253 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_COMMENT_MODIFYING, getCurrentUser(), eventParams)); 254 } 255 256 return getCommentParameters(content, comment); 257 } 258 catch (UnknownAmetysObjectException e) 259 { 260 getLogger().error("Unknown content of id '" + contentId + "'", e); 261 throw new IllegalArgumentException("Unknown content of id '" + contentId + "'", e); 262 } 263 } 264 265 /** 266 * Removes comments 267 * @param contents the contents with comments to remove 268 * @return the JSON result with deleted comments or not 269 */ 270 @SuppressWarnings("unchecked") 271 @Callable 272 public Map<String, Object> deleteComments(Map<String, List<String>> contents) 273 { 274 Map<String, Object> results = new HashMap<> (); 275 276 results.put("deleted-comments", new ArrayList<Map<String, Object>>()); 277 results.put("undeleted-comments", new ArrayList<Map<String, Object>>()); 278 results.put("uncommentable-contents", new ArrayList<Map<String, Object>>()); 279 results.put("noright-contents", new ArrayList<Map<String, Object>>()); 280 results.put("unknown-contents", new ArrayList<Map<String, Object>>()); 281 282 for (String contentId : contents.keySet()) 283 { 284 try 285 { 286 Content content = _resolver.resolveById(contentId); 287 288 if (hasRight("CMS_Rights_CommentModerate", content)) 289 { 290 List<String> deleteCommentIds = new ArrayList<>(); 291 // For each associated comment 292 for (String commentId : contents.get(contentId)) 293 { 294 if (!_isParentCommentAlreadyDelete(deleteCommentIds, commentId)) 295 { 296 Map<String, Object> commentParams = new HashMap<>(); 297 commentParams.put("id", commentId); 298 commentParams.put("contentId", contentId); 299 commentParams.put("contentTitle", _contentHelper.getTitle(content)); 300 301 if (content instanceof CommentableContent) 302 { 303 CommentableContent cContent = (CommentableContent) content; 304 Comment comment = cContent.getComment(commentId); 305 306 Map<String, Object> eventParams = new HashMap<>(); 307 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 308 eventParams.put(ObservationConstants.ARGS_COMMENT_ID, comment.getId()); 309 eventParams.put(ObservationConstants.ARGS_COMMENT_AUTHOR, comment.getAuthorName()); 310 eventParams.put(ObservationConstants.ARGS_COMMENT_AUTHOR_EMAIL, comment.getAuthorEmail()); 311 eventParams.put(ObservationConstants.ARGS_COMMENT_VALIDATED, comment.isValidated()); 312 eventParams.put("comment.content", comment.getContent()); 313 314 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_COMMENT_DELETING, getCurrentUser(), eventParams)); 315 316 comment.remove(); 317 cContent.saveChanges(); 318 319 List<Map<String, Object>> deletedComments = (List<Map<String, Object>>) results.get("deleted-comments"); 320 deletedComments.add(commentParams); 321 deleteCommentIds.add(commentId); 322 323 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_COMMENT_DELETED, getCurrentUser(), eventParams)); 324 } 325 else 326 { 327 getLogger().error("Can not remove a comment on a non commentable content"); 328 329 List<Map<String, Object>> uncommentableContents = (List<Map<String, Object>>) results.get("uncommentable-contents"); 330 uncommentableContents.add(commentParams); 331 } 332 } 333 } 334 } 335 else 336 { 337 // No right 338 getLogger().error("User '" + getCurrentUser() + "' does not have right to moderate comments on content '" + content.getId() + "'"); 339 340 Map<String, Object> contentParams = new HashMap<>(); 341 contentParams.put("contentId", contentId); 342 contentParams.put("contentTitle", _contentHelper.getTitle(content)); 343 344 List<Map<String, Object>> norightContents = (List<Map<String, Object>>) results.get("noright-contents"); 345 norightContents.add(contentParams); 346 } 347 } 348 catch (UnknownAmetysObjectException e) 349 { 350 getLogger().error("Can not remove a comment on a non existing content", e); 351 352 Map<String, Object> contentParams = new HashMap<>(); 353 contentParams.put("contentId", contentId); 354 355 List<Map<String, Object>> unknownContents = (List<Map<String, Object>>) results.get("unknown-contents"); 356 unknownContents.add(contentParams); 357 } 358 } 359 360 return results; 361 } 362 363 /** 364 * True if the a parent comment of the comment id is already deleted 365 * @param deleteCommentIds the delete comment ids 366 * @param commentId the comment id 367 * @return true if the a parent comment of the comment id is already deleted 368 */ 369 protected boolean _isParentCommentAlreadyDelete(List<String> deleteCommentIds, String commentId) 370 { 371 for (String deleteCommentId : deleteCommentIds) 372 { 373 if (commentId.startsWith(deleteCommentId)) 374 { 375 return true; 376 } 377 } 378 379 return false; 380 } 381 382 /** 383 * Validates comments when it is possible. 384 * @param contents the contents with comments to validate 385 * @return the JSON result with validated comments or not 386 */ 387 @SuppressWarnings("unchecked") 388 @Callable 389 public Map<String, Object> validateComments(Map<String, List<String>> contents) 390 { 391 Map<String, Object> results = new HashMap<>(); 392 393 results.put("validated-comments", new ArrayList<Map<String, Object>>()); 394 results.put("error-comments", new ArrayList<Map<String, Object>>()); 395 results.put("uncommentable-contents", new ArrayList<Map<String, Object>>()); 396 results.put("noright-contents", new ArrayList<Map<String, Object>>()); 397 results.put("unknown-contents", new ArrayList<Map<String, Object>>()); 398 399 for (String contentId : contents.keySet()) 400 { 401 try 402 { 403 Content content = _resolver.resolveById(contentId); 404 405 if (hasRight("CMS_Rights_CommentModerate", content)) 406 { 407 // For each associated comment 408 for (String commentId : contents.get(contentId)) 409 { 410 Map<String, Object> commentParams = new HashMap<>(); 411 commentParams.put("id", commentId); 412 commentParams.put("contentId", contentId); 413 commentParams.put("contentTitle", _contentHelper.getTitle(content)); 414 415 if (content instanceof CommentableContent) 416 { 417 CommentableContent cContent = (CommentableContent) content; 418 419 Comment comment = cContent.getComment(commentId); 420 commentParams.put("reportsCount", comment.getReportsCount()); 421 422 if (!comment.isValidated()) 423 { 424 comment.setValidated(true); 425 cContent.saveChanges(); 426 427 List<Map<String, Object>> validatedComments = (List<Map<String, Object>>) results.get("validated-comments"); 428 validatedComments.add(commentParams); 429 430 Map<String, Object> eventParams = new HashMap<>(); 431 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 432 eventParams.put(ObservationConstants.ARGS_COMMENT, comment); 433 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_COMMENT_VALIDATED, getCurrentUser(), eventParams)); 434 } 435 } 436 else 437 { 438 getLogger().error("Can not validate a comment on a non commentable content"); 439 440 List<Map<String, Object>> errorComments = (List<Map<String, Object>>) results.get("error-comments"); 441 errorComments.add(commentParams); 442 } 443 } 444 } 445 else 446 { 447 // No right 448 getLogger().error("User '" + getCurrentUser() + "' does not have right to validate comments on content '" + content.getId() + "'"); 449 450 Map<String, Object> contentParams = new HashMap<>(); 451 contentParams.put("contentId", contentId); 452 contentParams.put("contentTitle", _contentHelper.getTitle(content)); 453 } 454 } 455 catch (UnknownAmetysObjectException e) 456 { 457 getLogger().error("Can not validate a comment on a non existing content", e); 458 459 Map<String, Object> contentParams = new HashMap<>(); 460 contentParams.put("contentId", contentId); 461 462 List<Map<String, Object>> unknownContents = (List<Map<String, Object>>) results.get("unknown-contents"); 463 unknownContents.add(contentParams); 464 } 465 } 466 467 return results; 468 } 469 470 /** 471 * Invalidates comments when it is possible. 472 * @param contents the contents with comments to invalidate 473 * @return the JSON result with invalidated comments or not 474 */ 475 @SuppressWarnings("unchecked") 476 @Callable 477 public Map<String, Object> invalidateComments(Map<String, List<String>> contents) 478 { 479 Map<String, Object> results = new HashMap<>(); 480 481 results.put("unvalidated-comments", new ArrayList<Map<String, Object>>()); 482 results.put("error-comments", new ArrayList<Map<String, Object>>()); 483 results.put("uncommentable-contents", new ArrayList<Map<String, Object>>()); 484 results.put("noright-contents", new ArrayList<Map<String, Object>>()); 485 results.put("unknown-contents", new ArrayList<Map<String, Object>>()); 486 487 for (String contentId : contents.keySet()) 488 { 489 try 490 { 491 Content content = _resolver.resolveById(contentId); 492 493 if (hasRight("CMS_Rights_CommentModerate", content)) 494 { 495 // For each associated comment 496 for (String commentId : contents.get(contentId)) 497 { 498 Map<String, Object> commentParams = new HashMap<>(); 499 commentParams.put("id", commentId); 500 commentParams.put("contentId", contentId); 501 commentParams.put("contentTitle", _contentHelper.getTitle(content)); 502 503 if (content instanceof CommentableContent) 504 { 505 CommentableContent cContent = (CommentableContent) content; 506 507 Comment comment = cContent.getComment(commentId); 508 commentParams.put("reportsCount", comment.getReportsCount()); 509 if (comment.isValidated()) 510 { 511 comment.setValidated(false); 512 cContent.saveChanges(); 513 514 List<Map<String, Object>> validatedComments = (List<Map<String, Object>>) results.get("unvalidated-comments"); 515 validatedComments.add(commentParams); 516 517 Map<String, Object> eventParams = new HashMap<>(); 518 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 519 eventParams.put(ObservationConstants.ARGS_COMMENT, comment); 520 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_COMMENT_UNVALIDATED, getCurrentUser(), eventParams)); 521 } 522 } 523 else 524 { 525 getLogger().error("Can not validate a comment on a non commentable content"); 526 527 List<Map<String, Object>> errorComments = (List<Map<String, Object>>) results.get("error-comments"); 528 errorComments.add(commentParams); 529 } 530 } 531 } 532 else 533 { 534 // No right 535 getLogger().error("User '" + getCurrentUser() + "' does not have right to validate comments on content '" + content.getId() + "'"); 536 537 Map<String, Object> contentParams = new HashMap<>(); 538 contentParams.put("contentId", contentId); 539 contentParams.put("contentTitle", _contentHelper.getTitle(content)); 540 } 541 } 542 catch (UnknownAmetysObjectException e) 543 { 544 getLogger().error("Can not validate a comment on a non existing content", e); 545 546 Map<String, Object> contentParams = new HashMap<>(); 547 contentParams.put("contentId", contentId); 548 549 List<Map<String, Object>> unknownContents = (List<Map<String, Object>>) results.get("unknown-contents"); 550 unknownContents.add(contentParams); 551 } 552 } 553 554 return results; 555 } 556 557 /** 558 * Jsonify the comment. 559 * @param comment The comment 560 * @param content The content 561 * @return commentMap the comment map 562 */ 563 protected Map<String, Object> _jsonifyComment (Comment comment, Content content) 564 { 565 Map<String, Object> result = new HashMap<> (); 566 567 Map<String, Object> commentMap = new HashMap<> (); 568 569 570 commentMap.put("validated", Boolean.toString(comment.isValidated())); 571 commentMap.put("id", comment.getId()); 572 commentMap.put("creationDate", DateUtils.zonedDateTimeToString(comment.getCreationDate())); 573 574 String authorName = comment.getAuthorName(); 575 if (authorName != null) 576 { 577 commentMap.put("author-name", authorName); 578 } 579 580 String authorEmail = comment.getAuthorEmail(); 581 if (authorEmail != null) 582 { 583 Map<String, Object> authorEmailMap = new HashMap<> (); 584 authorEmailMap.put("hidden", Boolean.toString(comment.isEmailHidden())); 585 authorEmailMap.put("value", authorEmail); 586 commentMap.put("author-email", authorEmailMap); 587 } 588 589 String authorUrl = comment.getAuthorURL(); 590 if (authorUrl != null) 591 { 592 commentMap.put("author-url", authorUrl); 593 } 594 595 String text = comment.getContent(); 596 if (text != null) 597 { 598 commentMap.put("text", comment.getContent()); 599 } 600 commentMap.put("content", _jsonifyContent(content)); 601 602 result.put("comment", commentMap); 603 604 return result; 605 } 606 607 /** 608 * Jsonify the content. 609 * @param content The content 610 * @return contentMap the content map 611 */ 612 protected Map<String, Object> _jsonifyContent (Content content) 613 { 614 Map<String, Object> contentMap = new HashMap<> (); 615 616 contentMap.put("id", content.getId()); 617 contentMap.put("title", _contentHelper.getTitle(content)); 618 contentMap.put("name", content.getName()); 619 620 return contentMap; 621 } 622 623 /** 624 * Get the parameters for a comment 625 * @param content The content 626 * @param comment The comment 627 * @return The parameters 628 */ 629 protected Map<String, Object> getCommentParameters (Content content, Comment comment) 630 { 631 Map<String, Object> params = new HashMap<>(); 632 633 params.put("contentId", content.getId()); 634 params.put("contentTitle", _contentHelper.getTitle(content)); 635 params.put("id", comment.getId()); 636 params.put("validated", comment.isValidated()); 637 params.put("reportsCount", comment.getReportsCount()); 638 639 return params; 640 } 641 642 /** 643 * Get the current user 644 * @return The current user 645 */ 646 protected UserIdentity getCurrentUser () 647 { 648 return _userProvider.getUser(); 649 } 650 651 /** 652 * Determines if connected user has right on content 653 * @param rightId The right id 654 * @param content The content 655 * @return true if user has right 656 */ 657 protected boolean hasRight (String rightId, Content content) 658 { 659 UserIdentity user = _userProvider.getUser(); 660 661 return _rightManager.hasRight(user, "CMS_Rights_CommentModerate", content) == RightResult.RIGHT_ALLOW; 662 } 663}