001/*
002 *  Copyright 2019 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 */
016package org.ametys.cms.repository.comment.ui;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import javax.jcr.Node;
026import javax.jcr.NodeIterator;
027import javax.jcr.Repository;
028import javax.jcr.RepositoryException;
029import javax.jcr.Session;
030import javax.jcr.query.Query;
031
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.context.Context;
034import org.apache.avalon.framework.context.ContextException;
035import org.apache.avalon.framework.context.Contextualizable;
036import org.apache.avalon.framework.service.ServiceException;
037import org.apache.avalon.framework.service.ServiceManager;
038import org.apache.avalon.framework.service.Serviceable;
039import org.apache.cocoon.components.ContextHelper;
040import org.apache.cocoon.environment.Request;
041import org.apache.commons.lang.StringUtils;
042
043import org.ametys.cms.content.ContentHelper;
044import org.ametys.cms.repository.Content;
045import org.ametys.cms.repository.ReportableObject;
046import org.ametys.cms.repository.ReportableObjectHelper;
047import org.ametys.cms.repository.comment.Comment;
048import org.ametys.cms.repository.comment.CommentableContent;
049import org.ametys.core.right.RightManager;
050import org.ametys.core.right.RightManager.RightResult;
051import org.ametys.core.ui.Callable;
052import org.ametys.core.user.CurrentUserProvider;
053import org.ametys.core.user.User;
054import org.ametys.core.user.UserIdentity;
055import org.ametys.core.user.UserManager;
056import org.ametys.core.util.DateUtils;
057import org.ametys.plugins.repository.AmetysObjectResolver;
058import org.ametys.plugins.repository.provider.AbstractRepository;
059
060/**
061 * Action for getting the list of comments.
062 */
063public class CommentsAndReportsTreeComponent implements Component, Serviceable, Contextualizable
064{
065    /** The ametys object resolver */
066    protected AmetysObjectResolver _resolver;
067   
068    /** The right manager */
069    protected RightManager _rightManager;
070    
071    /** The current user provider */
072    protected CurrentUserProvider _currentUserProvider;
073    
074    /** The content helper */
075    protected ContentHelper _contentHelper;
076    
077    /** The user manager */
078    protected UserManager _userManager;
079    
080    /** The context */
081    protected Context _context;
082    
083    /** The JCR repository */
084    protected Repository _repository;
085
086    
087    @Override
088    public void service(ServiceManager smanager) throws ServiceException
089    {
090        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
091        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
092        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
093        _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE);
094        _userManager = (UserManager) smanager.lookup(UserManager.ROLE);
095        _repository = (Repository) smanager.lookup(AbstractRepository.ROLE);
096    }
097    
098    public void contextualize(Context context) throws ContextException
099    {
100        _context = context;
101    }
102    
103    /**
104     * Retrieves the comments and reports of the given selected contents
105     * @param contentIds identifiers of the contents
106     * @return the comments and reports of the contents
107     */
108    @Callable
109    public Map getCommentsAndReportsFromSelectedContents(List<String> contentIds)
110    {
111        // Get current user to check rights
112        UserIdentity user = _currentUserProvider.getUser();
113        
114        // Get comments and reports for each content
115        List<Map<String, Object>> contentComments = new ArrayList<>();
116        for (String contentId : contentIds)
117        {
118            Content content = _resolver.resolveById(contentId);
119            contentComments.add(_content2json(content, user));
120        }
121        
122        return Map.of("comments", contentComments);
123    }
124    
125    /**
126     * Retrieves the comments and reports of the contents of the given type
127     * Manages only contents that have at least one report (on itself or on a comment)
128     * @param contentTypeId the content type identifier
129     * @return the comments and reports of the contents
130     * @throws RepositoryException if an error occurs while retrieving contents from the repository
131     */
132    @Callable
133    public Map getCommentsAndReportsFromContentTypeId(String contentTypeId) throws RepositoryException
134    {
135        // Get current user to check rights
136        UserIdentity user = _currentUserProvider.getUser();
137        
138        // Get contents from the given content type
139        Iterable<Content> contents = _getReportedContentsFromContentType(contentTypeId);
140     
141        // Get comments and reports for each content
142        List<Map<String, Object>> contentComments = new ArrayList<>();
143        for (Content content : contents)
144        {
145            contentComments.add(_content2json(content, user, false));
146        }
147        
148        return Map.of("comments", contentComments);
149    }
150    
151    /**
152     * Retrieves the contents from the current site with the given content type that have at least a report or a reported comment 
153     * @param contentTypeId the content type identifier
154     * @return the contents
155     * @throws RepositoryException if an error occurs while retrieving the contents from the repository
156     */
157    protected Iterable<Content> _getReportedContentsFromContentType(String contentTypeId) throws RepositoryException
158    {
159        Request request = ContextHelper.getRequest(_context);
160        String currentSiteName = (String) request.getAttribute("siteName");
161        
162        String xPathQuery = "//element(*, ametys:content)[@ametys:site = '" + currentSiteName + "' and @ametys-internal:contentType = '" + contentTypeId + "']"
163                + "//*[@ametys:" + ReportableObjectHelper.REPORTS_COUNT_ATTRIBUTE_NAME + " > 0]";
164        
165        Session session = _repository.login();
166        @SuppressWarnings("deprecation")
167        Query query = session.getWorkspace().getQueryManager().createQuery(xPathQuery, Query.XPATH);
168        NodeIterator reportedNodesIterator = query.execute().getNodes();
169        
170        Set<Content> contents = new HashSet<>();
171        
172        while (reportedNodesIterator.hasNext())
173        {
174            Node node = reportedNodesIterator.nextNode();
175            while (!node.isNodeType("ametys:content"))
176            {
177                node = node.getParent();
178            }
179            contents.add(_resolver.resolve(node, false));
180        }
181        
182        return contents;
183    }
184    
185    /**
186     * Get the JSON representation of a content
187     * @param content The content
188     * @param user the current user
189     * @return The content as JSON object
190     */
191    protected Map<String, Object> _content2json (Content content, UserIdentity user)
192    {
193        return _content2json(content, user, true);
194    }
195    
196    /**
197     * Get the JSON representation of a content
198     * @param content The content
199     * @param user the current user
200     * @param includeNoReportedComments <code>true</code> to include the comment that have no report, <code>false</code> otherwise
201     * @return The content as JSON object
202     */
203    protected Map<String, Object> _content2json (Content content, UserIdentity user, boolean includeNoReportedComments)
204    {
205        Map<String, Object> object = new HashMap<>();
206        
207        object.put("contentId", content.getId());
208        object.put("commentId", "");
209        object.put("contentTitle", StringUtils.defaultString(content.getTitle()));
210        object.put("text", StringUtils.defaultString(content.getTitle()));
211        object.put("creationDate", DateUtils.dateToString(content.getLastModified()));
212        
213        UserIdentity lastContributorIdentity = content.getLastContributor();
214        User lastContributor = _userManager.getUser(lastContributorIdentity);
215        if (lastContributor != null)
216        {
217            object.put("authorName", lastContributor.getFullName());
218            object.put("authorEmail", lastContributor.getEmail());
219        }
220        
221        if (content instanceof ReportableObject)
222        {
223            object.put("reportsCount", ((ReportableObject) content).getReportsCount());
224        }
225        
226        List<Map<String, Object>> commentsList = new ArrayList<>();
227        if (content instanceof CommentableContent)
228        {
229            if (_rightManager.hasRight(user, "CMS_Rights_CommentModerate", content) == RightResult.RIGHT_ALLOW)
230            {
231                CommentableContent commentableContent = (CommentableContent) content;
232                List<Comment> comments = commentableContent.getComments(true, true);
233                
234                for (Comment comment : comments)
235                {
236                    Map<String, Object> commentAsJSON = _comment2json(comment, content, includeNoReportedComments);
237                    if (!commentAsJSON.isEmpty())
238                    {
239                        commentsList.add(commentAsJSON);
240                    }
241                }
242            }
243        }
244        
245        object.put("comments", commentsList);
246        
247        return object;
248    }
249    
250    /**
251     * Get the JSON representation of a comment
252     * @param comment the comment
253     * @param content the content
254     * @param includeNoReportedComments <code>true</code> to include the comment that have no report, <code>false</code> otherwise
255     * @return The comment as JSON object
256     */
257    protected Map<String, Object> _comment2json (Comment comment, Content content, boolean includeNoReportedComments)
258    {
259        List<Map<String, Object>> commentsList = new ArrayList<>();
260        for (Comment subcomment : comment.getSubComment(true, true))
261        {
262            Map<String, Object> subcommentAsJSON = _comment2json(subcomment, content, includeNoReportedComments);
263            if (!subcommentAsJSON.isEmpty())
264            {
265                commentsList.add(_comment2json(subcomment, content, includeNoReportedComments));
266            }
267        }
268        
269        if (includeNoReportedComments || comment.getReportsCount() > 0 || !commentsList.isEmpty())
270        {
271            Map<String, Object> object = new HashMap<>();
272            object.put("commentId", comment.getId());
273            object.put("contentId", content.getId());
274            object.put("contentTitle", StringUtils.defaultString(content.getTitle()));
275            object.put("text", comment.getContent());
276            object.put("authorName", comment.getAuthorName());
277            object.put("authorEmail", comment.getAuthorEmail());
278            object.put("authorUrl", comment.getAuthorURL());
279            object.put("reportsCount", comment.getReportsCount());
280            object.put("creationDate", DateUtils.zonedDateTimeToString(comment.getCreationDate()));
281            object.put("validated", Boolean.toString(comment.isValidated()));
282            object.put("authorIsEmailHidden", Boolean.toString(comment.isEmailHidden()));
283            object.put("comments", commentsList);
284            return object;
285        }
286        
287        return Map.of();
288    }
289}