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}