001/*
002 *  Copyright 2018 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.plugins.workspaces.wall;
017
018import java.io.InputStream;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023import java.util.stream.Collectors;
024
025import javax.mail.MessagingException;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang.StringUtils;
032import org.apache.excalibur.xml.sax.SAXParser;
033import org.xml.sax.InputSource;
034
035import org.ametys.cms.ObservationConstants;
036import org.ametys.cms.content.RichTextHandler;
037import org.ametys.cms.contenttype.ContentType;
038import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
039import org.ametys.cms.data.RichText;
040import org.ametys.cms.repository.Content;
041import org.ametys.cms.repository.ContentDAO;
042import org.ametys.core.observation.Event;
043import org.ametys.core.observation.ObservationManager;
044import org.ametys.core.right.RightManager;
045import org.ametys.core.ui.Callable;
046import org.ametys.core.user.CurrentUserProvider;
047import org.ametys.core.user.User;
048import org.ametys.core.user.UserIdentity;
049import org.ametys.core.user.UserManager;
050import org.ametys.core.util.I18nUtils;
051import org.ametys.core.util.mail.SendMailHelper;
052import org.ametys.plugins.repository.AmetysObjectResolver;
053import org.ametys.plugins.repository.AmetysRepositoryException;
054import org.ametys.plugins.workspaces.project.ProjectManager;
055import org.ametys.plugins.workspaces.project.objects.Project;
056import org.ametys.runtime.config.Config;
057import org.ametys.runtime.i18n.I18nizableText;
058import org.ametys.runtime.plugin.component.AbstractLogEnabled;
059import org.ametys.web.content.FOContentCreationHelper;
060import org.ametys.web.repository.site.Site;
061import org.ametys.web.repository.site.SiteManager;
062
063import com.google.common.collect.ArrayListMultimap;
064import com.opensymphony.workflow.WorkflowException;
065
066/**
067 * Helper for wall contents
068 *
069 */
070public class WallContentManager extends AbstractLogEnabled implements Component, Serviceable
071{
072    /** The Avalon role */
073    public static final String ROLE = WallContentManager.class.getName();
074    /** The wall content type id */
075    public static final String WALL_CONTENT_CONTENT_TYPE_ID = "org.ametys.plugins.workspaces.Content.wallContent";
076    
077    private static final int __INITIAL_WORKFLOW_ACTION_ID = 1111;
078    private static final String __WORKFLOW_NAME = "wall-content";
079    
080    private FOContentCreationHelper _foContentHelper;
081    private ContentTypeExtensionPoint _cTypeEP;
082    private I18nUtils _i18nUtils;
083    private ContentDAO _contentDAO;
084    private ObservationManager _observationManager;
085    private RightManager _rightManager;
086    private AmetysObjectResolver _resolver;
087    private UserManager _userManager;
088    private CurrentUserProvider _currentUserProvider;
089    private ProjectManager _projectManager;
090    private SiteManager _siteManager;
091    private ServiceManager _smanager;
092    
093    public void service(ServiceManager manager) throws ServiceException
094    {
095        _smanager = manager;
096        _foContentHelper = (FOContentCreationHelper) manager.lookup(FOContentCreationHelper.ROLE);
097        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
098        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
099        _contentDAO = (ContentDAO) manager.lookup(ContentDAO.ROLE);
100        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
101        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
102        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
103        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
104        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
105        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
106        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
107    }
108    
109    /**
110     * Create and publish a new wall content
111     * @param siteName the site name
112     * @param lang the language
113     * @param rawValues the input values
114     * @return the results
115     */
116    @Callable
117    public Map<String, Object> publishContent(String siteName, String lang, Map<String, Object> rawValues)
118    {
119        Map<String, Object> results = new HashMap<>();
120
121        try
122        {
123            ContentType cType = _cTypeEP.getExtension(WALL_CONTENT_CONTENT_TYPE_ID);
124            
125            String contentTitle = _i18nUtils.translate(cType.getDefaultTitle(), lang);
126            
127            Map<String, Object> userValues = _foContentHelper.getAndValidateFormValues(rawValues, cType, "main", ArrayListMultimap.create());
128            
129            results = _foContentHelper.createAndEditContent(__INITIAL_WORKFLOW_ACTION_ID, WALL_CONTENT_CONTENT_TYPE_ID, siteName, contentTitle, contentTitle, lang, userValues, __WORKFLOW_NAME, null);
130            
131            Content content = (Content) results.get(Content.class.getName());
132            _notifyContentCreation(content);
133            
134            // remove Content from result
135            results.remove(Content.class.getName());
136            
137            results.put("success", true);
138        }
139        catch (AmetysRepositoryException | WorkflowException e)
140        {
141            results.put("success", false);
142            getLogger().error("Failed to create wall content for site {} and language {}", siteName, lang, e);
143        }
144        return results;
145    }
146    
147    private void _notifyContentCreation(Content content)
148    {
149        Map<String, Object> eventParams = new HashMap<>();
150        eventParams.put(ObservationConstants.ARGS_CONTENT, content);
151        eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId());
152        
153        _observationManager.notify(new Event(org.ametys.plugins.workspaces.ObservationConstants.EVENT_WALLCONTENT_ADDED, content.getCreator(), eventParams));
154    }
155    
156    /**
157     * Report content to webmasters (user with report notification right on wall contents)
158     * @param siteName the current site name
159     * @param contentId the id of content to report
160     * @return true if the content was successfully reported
161     */
162    @Callable
163    public boolean reportContent(String siteName, String contentId)
164    {
165        Content content = _resolver.resolveById(contentId);
166        User reporter = _userManager.getUser(_currentUserProvider.getUser());
167        Site site = _siteManager.getSite(siteName);
168        
169        // Add the report to the content
170        _contentDAO.report(content);
171        
172        // Send a mail to the allowed users
173        List<Project> projects = _projectManager.getProjectsForSite(site);
174        if (!projects.isEmpty())
175        {
176            Project project = projects.get(0);
177            
178            List<String> recipients = _getReportsRecipients(siteName);
179            if (!recipients.isEmpty())
180            {
181                Map<String, I18nizableText> i18nParams = new HashMap<>();
182                i18nParams.put("projectTitle", new I18nizableText(project.getTitle()));
183                i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
184                
185                I18nizableText i18nSubject = new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_WALL_CONTENT_REPORTED_SUBJECT", i18nParams);
186                String subject = _i18nUtils.translate(i18nSubject, content.getLanguage());
187                
188                i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
189                i18nParams.put("reporter", new I18nizableText(reporter.getFullName()));
190                i18nParams.put("content", new I18nizableText(getExcerpt(content, 200)));
191                
192                I18nizableText i18nBody = new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_WALL_CONTENT_REPORTED_BODY", i18nParams);
193                String body = _i18nUtils.translate(i18nBody, content.getLanguage());
194                
195                String from = site.getValue("site-mail-from");
196                
197                try
198                {
199                    SendMailHelper.sendMail(subject, null, body, recipients, from, true);
200                    return true;
201                }
202                catch (MessagingException e)
203                {
204                    getLogger().warn("Could not send a notification mail to {}", recipients, e);
205                }  
206            }
207        }
208        
209        return false;
210    }
211    
212    /**
213     * Get the excerpt of content
214     * @param content the content
215     * @param maxLength the max length for content excerpt
216     * @return the excerpt
217     */
218    public String getExcerpt(Content content, int maxLength)
219    {
220        if (content.hasValue("content"))
221        {
222            RichText richText = content.getValue("content");
223            SAXParser saxParser = null;
224            try (InputStream is = richText.getInputStream())
225            {
226                RichTextHandler txtHandler = new RichTextHandler(maxLength);
227                saxParser = (SAXParser) _smanager.lookup(SAXParser.ROLE);
228                saxParser.parse(new InputSource(is), txtHandler);
229                
230                return txtHandler.getValue();
231            }
232            catch (Exception e)
233            {
234                getLogger().error("Cannot extract excerpt from content {}", content.getId(), e);
235            }
236            finally
237            {
238                _smanager.release(saxParser);
239            }
240        }
241     
242        return "";
243    }
244    
245    /**
246     * Retrieves the list of recipients for reports notification sending
247     * @param siteName the current site name
248     * @return the list of recipients for reports notification sending
249     */
250    protected List<String> _getReportsRecipients(String siteName)
251    {
252        Set<UserIdentity> users = _rightManager.getAllowedUsers("Plugins_Workspaces_Right_ReportNotification_WallContent", "/cms/" + siteName).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"));
253        
254        return users.stream()
255                .map(_userManager::getUser)
256                .map(user -> user.getEmail())
257                .filter(StringUtils::isNotBlank)
258                .collect(Collectors.toList());
259    }
260    
261    /**
262     * Add or remove a reaction on a content
263     * @param contentId The content id
264     * @param reactionName the reaction name (ex: LIKE)
265     * @param remove true to remove the reaction, false to add reaction
266     * @return the result with the current actors of this reaction
267     */
268    @Callable
269    public Map<String, Object> react(String contentId, String reactionName, boolean remove)
270    {
271        return _contentDAO.react(contentId, reactionName, remove);
272    }
273    
274}