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