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}