001/* 002 * Copyright 2010 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.workflow; 017 018import java.util.ArrayList; 019import java.util.HashSet; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import javax.mail.MessagingException; 028 029import org.apache.avalon.framework.activity.Initializable; 030import org.apache.avalon.framework.context.Context; 031import org.apache.avalon.framework.context.ContextException; 032import org.apache.avalon.framework.context.Contextualizable; 033import org.apache.cocoon.components.ContextHelper; 034import org.apache.cocoon.environment.Request; 035import org.apache.commons.lang.StringUtils; 036import org.apache.excalibur.source.SourceResolver; 037 038import org.ametys.cms.repository.WorkflowAwareContent; 039import org.ametys.core.right.RightManager; 040import org.ametys.core.user.CurrentUserProvider; 041import org.ametys.core.user.User; 042import org.ametys.core.user.UserIdentity; 043import org.ametys.core.user.UserManager; 044import org.ametys.core.util.I18nUtils; 045import org.ametys.core.util.mail.SendMailHelper; 046import org.ametys.plugins.workflow.support.WorkflowProvider; 047import org.ametys.runtime.config.Config; 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.runtime.plugin.component.PluginAware; 050 051import com.opensymphony.module.propertyset.PropertySet; 052import com.opensymphony.workflow.FunctionProvider; 053import com.opensymphony.workflow.WorkflowException; 054 055/** 056 * OS workflow function to send mail after an action is triggered. 057 */ 058public class SendMailFunction extends AbstractContentWorkflowComponent implements FunctionProvider, Initializable, PluginAware, Contextualizable 059{ 060 /** 061 * Provide "false" to prevent the function sending the mail. 062 * Useful when making large automatic workflow operations (for instance, when bulk importing and proposing in one action). 063 */ 064 public static final String SEND_MAIL = "send-mail"; 065 066 /** The rights key. */ 067 protected static final String RIGHTS_KEY = "rights"; 068 /** The mail subject key. */ 069 protected static final String SUBJECT_KEY = "subjectKey"; 070 /** The mail body key. */ 071 protected static final String BODY_KEY = "bodyKey"; 072 073 /** The current user provider. */ 074 protected CurrentUserProvider _currentUserProvider; 075 076 /** The rights manager. */ 077 protected RightManager _rightManager; 078 079 /** The users manager. */ 080 protected UserManager _userManager; 081 082 /** The source resolver. */ 083 protected SourceResolver _sourceResolver; 084 085 /** The workflow. */ 086 protected WorkflowProvider _workflowProvider; 087 088 /** The Avalon context. */ 089 protected Context _context; 090 091 /** The plugin name. */ 092 protected String _pluginName; 093 094 /** I18nUtils */ 095 protected I18nUtils _i18nUtils; 096 097 @Override 098 public void initialize() throws Exception 099 { 100 _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE); 101 _rightManager = (RightManager) _manager.lookup(RightManager.ROLE); 102 _userManager = (UserManager) _manager.lookup(UserManager.ROLE); 103 _sourceResolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE); 104 _workflowProvider = (WorkflowProvider) _manager.lookup(WorkflowProvider.ROLE); 105 _i18nUtils = (I18nUtils) _manager.lookup(I18nUtils.ROLE); 106 } 107 108 public void contextualize(Context context) throws ContextException 109 { 110 _context = context; 111 } 112 113 @Override 114 public void setPluginInfo(String pluginName, String featureName, String id) 115 { 116 _pluginName = pluginName; 117 } 118 119 @Override 120 public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException 121 { 122 String rightsParam = StringUtils.defaultString((String) args.get(RIGHTS_KEY)); 123 String subjectI18nKey = StringUtils.defaultString((String) args.get(SUBJECT_KEY)); 124 String bodyI18nKey = StringUtils.defaultString((String) args.get(BODY_KEY)); 125 126 Set<String> rights = new HashSet<>(); 127 for (String right : rightsParam.split(",")) 128 { 129 if (StringUtils.isNotBlank(right)) 130 { 131 rights.add(right.trim()); 132 } 133 } 134 135 // If "send-mail" is set to true or is not present in the vars, send the mail. 136 boolean dontSendMail = "false".equals(transientVars.get(SEND_MAIL)); 137 138 if (dontSendMail) 139 { 140 return; 141 } 142 143 try 144 { 145 WorkflowAwareContent content = getContent(transientVars); 146 147 Set<String> recipients = getRecipients(transientVars, content, rights); 148 149 if (recipients.size() > 0) 150 { 151 User caller = getCaller(transientVars, content); 152 String sender = getSender(transientVars, content); 153 String mailSubject = getMailSubject(subjectI18nKey, caller, content); 154 String mailBody = getMailBody(bodyI18nKey, caller, content, transientVars); 155 156 _sendMails(mailSubject, mailBody, recipients, sender); 157 } 158 } 159 catch (Exception e) 160 { 161 _logger.error("An error occurred: unable to send mail to notify workflow change.", e); 162 } 163 } 164 165 /** 166 * Get the subject of mail 167 * @param subjectI18nKey the i18n key to use for subject 168 * @param user the caller 169 * @param content the content 170 * @return the subject 171 */ 172 protected String getMailSubject (String subjectI18nKey, User user, WorkflowAwareContent content) 173 { 174 I18nizableText subjectKey = new I18nizableText(null, subjectI18nKey, getSubjectI18nParams(user, content)); 175 return _i18nUtils.translate(subjectKey, null); // FIXME Use user preference language 176 } 177 178 /** 179 * Get the text body of mail 180 * @param bodyI18nKey the i18n key to use for body 181 * @param user the caller 182 * @param content the content 183 * @param transientVars the transient variables 184 * @return the text body 185 */ 186 protected String getMailBody (String bodyI18nKey, User user, WorkflowAwareContent content, Map transientVars) 187 { 188 I18nizableText bodyKey = new I18nizableText(null, bodyI18nKey, getBodyI18nParams(user, content)); 189 String mailBody = _i18nUtils.translate(bodyKey, null); // FIXME Use user preference language 190 191 // Get the workflow comment 192 String comment = (String) transientVars.get("comment"); 193 if (StringUtils.isNotEmpty(comment)) 194 { 195 List<String> params = new ArrayList<>(); 196 params.add(comment); 197 198 I18nizableText commentKey = new I18nizableText("plugin.cms", "WORKFLOW_MAIL_BODY_USER_COMMENT", params); 199 String commentTxt = _i18nUtils.translate(commentKey, null); // FIXME Use user preference language 200 201 mailBody += "\n\n" + commentTxt; 202 } 203 204 return mailBody; 205 } 206 207 /** 208 * Send the notification emails. 209 * @param subject the e-mail subject. 210 * @param body the e-mail body. 211 * @param recipients the recipients emails address. 212 * @param from the address sending the e-mail. 213 */ 214 protected void _sendMails(String subject, String body, Set<String> recipients, String from) 215 { 216 for (String recipient : recipients) 217 { 218 try 219 { 220 SendMailHelper.sendMail(subject, null, body, recipient, from, true); 221 } 222 catch (MessagingException e) 223 { 224 _logger.warn("Could not send a workflow notification mail to " + recipient, e); 225 } 226 } 227 } 228 229 /** 230 * Get the i18n parameters of mail subject 231 * @param user the caller 232 * @param content the content 233 * @return the i18n parameters 234 */ 235 protected List<String> getSubjectI18nParams (User user, WorkflowAwareContent content) 236 { 237 List<String> params = new ArrayList<>(); 238 params.add(_contentHelper.getTitle(content)); 239 return params; 240 } 241 242 /** 243 * Get the i18n parameters of mail body text 244 * @param user the caller 245 * @param content the content 246 * @return the i18n parameters 247 */ 248 protected List<String> getBodyI18nParams (User user, WorkflowAwareContent content) 249 { 250 List<String> params = new ArrayList<>(); 251 252 params.add(user.getFullName()); // {0} 253 params.add(content.getTitle()); // {1} 254 params.add(_getContentUri(content)); // {2} 255 256 return params; 257 } 258 259 /** 260 * Get the content uri 261 * @param content the content 262 * @return the content uri 263 */ 264 protected String _getContentUri(WorkflowAwareContent content) 265 { 266 String requestUri = _getRequestUri(); 267 return requestUri + "/index.html?uitool=uitool-content,id:%27" + content.getId() + "%27"; 268 } 269 270 /** 271 * Get the request URI. 272 * @return the full request URI. 273 */ 274 protected String _getRequestUri() 275 { 276 return StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"), "/"); 277 } 278 279 /** 280 * Retrieve the request from which this component is called. 281 * @return the request or <code>null</code>. 282 */ 283 public Request _getRequest() 284 { 285 try 286 { 287 return (Request) _context.get(ContextHelper.CONTEXT_REQUEST_OBJECT); 288 } 289 catch (ContextException ce) 290 { 291 _logger.info("Unable to get the request", ce); 292 return null; 293 } 294 } 295 296 /** 297 * Get the caller of the workflow action 298 * @param transientVars the transient variables 299 * @param content content the content 300 * @return caller the caller if the workflow function 301 * @throws WorkflowException if failed to get caller 302 */ 303 public User getCaller(Map transientVars, WorkflowAwareContent content) throws WorkflowException 304 { 305 UserIdentity userIdentity = getUser(transientVars); 306 return userIdentity != null ? _userManager.getUser(userIdentity) : null; 307 } 308 309 /** 310 * Get the sender for mail 311 * @param transientVars the transient variables 312 * @param content the content 313 * @return the sender email address 314 * @throws WorkflowException if failed to get email for sender 315 */ 316 protected String getSender(Map transientVars, WorkflowAwareContent content) throws WorkflowException 317 { 318 User user = getCaller(transientVars, content); 319 return user != null ? user.getEmail() : null; 320 } 321 322 /** 323 * Get the recipients 324 * @param transientVars the transient variables 325 * @param content the content. 326 * @param rights the set of rights to check. 327 * @return the recipients. 328 * @throws WorkflowException If failed to get recipients 329 */ 330 protected Set<String> getRecipients(Map transientVars, WorkflowAwareContent content, Set<String> rights) throws WorkflowException 331 { 332 Set<UserIdentity> users = _getUsers(content, rights); 333 334 return users.stream() 335 .map(_userManager::getUser) 336 .filter(Objects::nonNull) 337 .map(User::getEmail) 338 .filter(StringUtils::isNotBlank) 339 .collect(Collectors.toSet()); 340 } 341 342 /** 343 * Get the user logins. 344 * @param content the content. 345 * @param rights the set of rights to check. 346 * @return the users. 347 * @throws WorkflowException If an error occurred 348 */ 349 protected Set<UserIdentity> _getUsers(WorkflowAwareContent content, Set<String> rights) throws WorkflowException 350 { 351 Set<UserIdentity> users = new HashSet<>(); 352 353 Iterator<String> rightIt = rights.iterator(); 354 355 // First right : add all the granted users. 356 if (rightIt.hasNext()) 357 { 358 users.addAll(_rightManager.getAllowedUsers(rightIt.next(), content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 359 } 360 361 // Next rights : retain all the granted users. 362 while (rightIt.hasNext()) 363 { 364 users.retainAll(_rightManager.getAllowedUsers(rightIt.next(), content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 365 } 366 367 return users; 368 } 369}