001/* 002 * Copyright 2023 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.web.usermanagement; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.cocoon.components.ContextHelper; 027import org.apache.cocoon.environment.Request; 028import org.apache.commons.lang3.StringUtils; 029import org.quartz.JobDataMap; 030import org.quartz.JobExecutionContext; 031 032import org.ametys.cms.schedule.AbstractSendingMailSchedulable; 033import org.ametys.core.user.population.UserPopulation; 034import org.ametys.core.user.population.UserPopulationDAO; 035import org.ametys.plugins.core.schedule.Scheduler; 036import org.ametys.runtime.config.Config; 037import org.ametys.runtime.i18n.I18nizableText; 038import org.ametys.runtime.i18n.I18nizableTextParameter; 039import org.ametys.web.WebConstants; 040import org.ametys.web.repository.site.Site; 041import org.ametys.web.repository.site.SiteManager; 042import org.ametys.web.usermanagement.UserManagementException.StatusError; 043 044/** 045 * Job for sending invitations email 046 */ 047public class SendInvitationsSchedulable extends AbstractSendingMailSchedulable 048{ 049 /** The key for the id of the population */ 050 public static final String USER_POPULATION_ID_KEY = "populationId"; 051 052 /** The key for the id of the population */ 053 public static final String USER_DIRECTORY_ID_KEY = "userDirectoryId"; 054 055 /** The key for the site name */ 056 public static final String SITE_NAME_KEY = "siteName"; 057 058 /** The key for the guests */ 059 public static final String GUESTS_KEY = "guests"; 060 061 /** The key for the guests */ 062 public static final String RESEND_INVITATIONS_KEY = "resendInvitations"; 063 064 private static final String __JOBDATAMAP_USER_POPULATION_ID_KEY = Scheduler.PARAM_VALUES_PREFIX + USER_POPULATION_ID_KEY; 065 066 private static final String __JOBDATAMAP_USER_DIRECTORY_ID_KEY = Scheduler.PARAM_VALUES_PREFIX + USER_DIRECTORY_ID_KEY; 067 068 private static final String __JOBDATAMAP_SITE_NAME_KEY = Scheduler.PARAM_VALUES_PREFIX + SITE_NAME_KEY; 069 070 private static final String __JOBDATAMAP_GUESTS_KEY = Scheduler.PARAM_VALUES_PREFIX + GUESTS_KEY; 071 072 private static final String __JOBDATAMAP_RESEND_INVITATIONS_KEY = Scheduler.PARAM_VALUES_PREFIX + RESEND_INVITATIONS_KEY; 073 074 private static final String __GLOBAL_ERROR_CAUSE_KEY = SendInvitationsSchedulable.class.getName() + "$globalError"; 075 076 private static final String __INVALID_EMAILS_KEY = SendInvitationsSchedulable.class.getName() + "$invalidEmails"; 077 078 private static final String __ERROR_MAILS_KEY = SendInvitationsSchedulable.class.getName() + "$errorMails"; 079 080 private static final String __SUCCESS_EMAILS_KEY = SendInvitationsSchedulable.class.getName() + "$successEmails"; 081 082 private static final String __EXISTING_USERS_KEY = SendInvitationsSchedulable.class.getName() + "$existingUsers"; 083 084 private static final String __EXISTING_TEMP_USERS_KEY = SendInvitationsSchedulable.class.getName() + "$existingTempUsers"; 085 086 /** The signup manager */ 087 protected UserSignupManager _signupManager; 088 /** The site manager */ 089 protected SiteManager _siteManager; 090 /** DAO for user population */ 091 protected UserPopulationDAO _userPopulationDAO; 092 /** The CMS base url */ 093 private String _cmsUrl; 094 095 @Override 096 public void service(ServiceManager smanager) throws ServiceException 097 { 098 super.service(smanager); 099 _signupManager = (UserSignupManager) smanager.lookup(UserSignupManager.ROLE); 100 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 101 _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE); 102 } 103 104 @Override 105 public void initialize() throws Exception 106 { 107 super.initialize(); 108 _cmsUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"), "/"); 109 } 110 111 @Override 112 protected void _doExecute(JobExecutionContext context) throws Exception 113 { 114 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 115 116 String populationId = jobDataMap.getString(__JOBDATAMAP_USER_POPULATION_ID_KEY); 117 String userDirectoryId = jobDataMap.getString(__JOBDATAMAP_USER_DIRECTORY_ID_KEY); 118 String siteName = jobDataMap.getString(__JOBDATAMAP_SITE_NAME_KEY); 119 boolean resendInvitations = jobDataMap.getBooleanValue(__JOBDATAMAP_RESEND_INVITATIONS_KEY); 120 121 @SuppressWarnings("unchecked") 122 List<String> guestUserLines = (List<String>) jobDataMap.get(__JOBDATAMAP_GUESTS_KEY); 123 124 List<String> successEmails = new ArrayList<>(); 125 List<String> invalidEmails = new ArrayList<>(); 126 List<String> errorMails = new ArrayList<>(); 127 List<String> existingUsers = new ArrayList<>(); 128 List<String> existingTempUsers = new ArrayList<>(); 129 130 Request request = ContextHelper.getRequest(_context); 131 132 // Set the site name to be able to check rights 133 request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, siteName); 134 135 for (String line : guestUserLines) 136 { 137 String[] columns = Arrays.stream(line.split(";")).map(StringUtils::normalizeSpace).toArray(String[]::new); 138 139 String email = columns[0]; 140 String lastname = columns.length > 1 ? columns[1] : null; 141 String firstname = columns.length > 2 ? columns[2] : null; 142 143 try 144 { 145 _signupManager.inviteToSignup(siteName, null, email, populationId, userDirectoryId, lastname, firstname, true, resendInvitations, true); 146 successEmails.add(email); 147 } 148 catch (UserManagementException e) 149 { 150 StatusError errorCause = e.getStatusError(); 151 switch (errorCause) 152 { 153 case NO_SIGNUP_PAGE: 154 case SIGNUP_NOT_ALLOWED: 155 case USER_NOT_ALLOWED: 156 context.put(__GLOBAL_ERROR_CAUSE_KEY, errorCause); 157 throw new IllegalArgumentException("Invalid configuration to send invitation", e); 158 case USER_ALREADY_EXISTS: 159 existingUsers.add(email); 160 break; 161 case INVALID_EMAIL: 162 invalidEmails.add(email); 163 break; 164 case MAIL_ERROR: 165 errorMails.add(email); 166 break; 167 case TEMP_USER_ALREADY_EXISTS: 168 existingTempUsers.add(email); 169 break; 170 default: 171 throw new IllegalArgumentException("Unexpected error: " + errorCause, e); 172 } 173 } 174 } 175 176 context.put(__SUCCESS_EMAILS_KEY, successEmails); 177 context.put(__ERROR_MAILS_KEY, errorMails); 178 context.put(__INVALID_EMAILS_KEY, invalidEmails); 179 context.put(__EXISTING_TEMP_USERS_KEY, existingTempUsers); 180 context.put(__EXISTING_USERS_KEY, existingUsers); 181 } 182 183 @Override 184 protected I18nizableText _getSuccessMailSubject(JobExecutionContext context) throws Exception 185 { 186 Site site = _getSite(context); 187 String siteTitle = site.getTitle() != null ? site.getTitle() : site.getName(); 188 189 return new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_SUCCESS_MAIL_SUBJECT", List.of(siteTitle)); 190 } 191 192 @SuppressWarnings("unchecked") 193 @Override 194 protected I18nizableText _getSuccessMailBody(JobExecutionContext context) throws Exception 195 { 196 List<String> successEmails = (List<String>) context.get(__SUCCESS_EMAILS_KEY); 197 List<String> invalidEmails = (List<String>) context.get(__INVALID_EMAILS_KEY); 198 List<String> errorMails = (List<String>) context.get(__ERROR_MAILS_KEY); 199 List<String> existingTempUsers = (List<String>) context.get(__EXISTING_TEMP_USERS_KEY); 200 List<String> existingUsers = (List<String>) context.get(__EXISTING_USERS_KEY); 201 202 String htmlBody = _buildMailBody(context, successEmails, invalidEmails, errorMails, existingTempUsers, existingUsers); 203 204 return new I18nizableText(htmlBody); 205 } 206 207 @Override 208 protected boolean _isMailBodyInHTML(JobExecutionContext context) throws Exception 209 { 210 return true; 211 } 212 213 /** 214 * Build the HTML mail body 215 * 216 * @param context the job context data 217 * @param successEmails the mails in success 218 * @param invalidEmails the invalid emails 219 * @param errorMails the mails for which the send has failed 220 * @param existingTempUsers the existing temporary users 221 * @param existingUsers the existing users 222 * @return the HTML mail body 223 */ 224 protected String _buildMailBody(JobExecutionContext context, List<String> successEmails, List<String> invalidEmails, List<String> errorMails, List<String> existingTempUsers, List<String> existingUsers) 225 { 226 Site site = _getSite(context); 227 String siteTitle = site.getTitle() != null ? site.getTitle() : site.getName(); 228 229 Map<String, I18nizableTextParameter> i18nParams = new HashMap<>(); 230 231 i18nParams.put("siteTitle", new I18nizableText(siteTitle)); 232 i18nParams.put("siteUrl", new I18nizableText(site.getUrl())); 233 i18nParams.put("count", new I18nizableText(String.valueOf(successEmails.size()))); 234 235 StringBuilder sb = new StringBuilder(); 236 237 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_SUCCESS_MAIL_BODY", i18nParams))); 238 239 if (!invalidEmails.isEmpty()) 240 { 241 i18nParams.put("count", new I18nizableText(String.valueOf(invalidEmails.size()))); 242 i18nParams.put("mails", new I18nizableText(StringUtils.join(invalidEmails, "<br/>"))); 243 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_SUCCESS_MAIL_BODY_INVALID_EMAILS_ERROR", i18nParams))); 244 } 245 246 if (!errorMails.isEmpty()) 247 { 248 i18nParams.put("count", new I18nizableText(String.valueOf(errorMails.size()))); 249 i18nParams.put("mails", new I18nizableText(StringUtils.join(errorMails, "<br/>"))); 250 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_SUCCESS_MAIL_BODY_MAILS_ERROR", i18nParams))); 251 } 252 253 if (!existingTempUsers.isEmpty()) 254 { 255 i18nParams.put("count", new I18nizableText(String.valueOf(existingTempUsers.size()))); 256 i18nParams.put("mails", new I18nizableText(StringUtils.join(existingTempUsers, "<br/>"))); 257 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_SUCCESS_MAIL_BODY_TEMP_USERS_EXIST_ERROR", i18nParams))); 258 } 259 260 if (!existingUsers.isEmpty()) 261 { 262 i18nParams.put("count", new I18nizableText(String.valueOf(existingUsers.size()))); 263 i18nParams.put("mails", new I18nizableText(StringUtils.join(existingUsers, "<br/>"))); 264 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_SUCCESS_MAIL_BODY_USERS_EXIST_ERROR", i18nParams))); 265 } 266 267 // TODO check user rights 268 i18nParams.put("cmsToolUri", new I18nizableText(getToolUri(site))); 269 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_SUCCESS_MAIL_BODY_END", i18nParams))); 270 271 return sb.toString(); 272 } 273 274 /** 275 * Get the back-office url to access user temp tool 276 * @param site the site 277 * @return the tool uri 278 */ 279 protected String getToolUri(Site site) 280 { 281 StringBuilder url = new StringBuilder(_cmsUrl); 282 283 url.append("/" + site.getName()); 284 url.append("/index.html?uitool=uitool-temp-users"); 285 286 return url.toString(); 287 } 288 289 @Override 290 protected I18nizableText _getErrorMailSubject(JobExecutionContext context) throws Exception 291 { 292 Site site = _getSite(context); 293 String siteTitle = site.getTitle() != null ? site.getTitle() : site.getName(); 294 return new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_ERROR_MAIL_SUBJECT", List.of(siteTitle)); 295 } 296 297 @Override 298 protected I18nizableText _getErrorMailBody(JobExecutionContext context, Throwable throwable) throws Exception 299 { 300 StatusError errorCause = (StatusError) context.get(__GLOBAL_ERROR_CAUSE_KEY); 301 302 String htmlBody = _buildErrorMailBody(context, errorCause); 303 304 return new I18nizableText(htmlBody); 305 } 306 307 /** 308 * Build the HTML mail body in case of error 309 * 310 * @param context the job context data 311 * @param globalErrorCause the global error cause 312 * @return the HTML mail body in case of error 313 */ 314 protected String _buildErrorMailBody(JobExecutionContext context, StatusError globalErrorCause) 315 { 316 Site site = _getSite(context); 317 String siteTitle = site.getTitle() != null ? site.getTitle() : site.getName(); 318 319 Map<String, I18nizableTextParameter> i18nParams = new HashMap<>(); 320 321 i18nParams.put("siteTitle", new I18nizableText(siteTitle)); 322 i18nParams.put("siteUrl", new I18nizableText(site.getUrl())); 323 324 StringBuilder sb = new StringBuilder(); 325 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_ERROR_MAIL_BODY", i18nParams))); 326 327 if (globalErrorCause != null) 328 { 329 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 330 331 UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(jobDataMap.getString(__JOBDATAMAP_USER_POPULATION_ID_KEY)); 332 333 switch (globalErrorCause) 334 { 335 case NO_SIGNUP_PAGE: 336 i18nParams.put("population", userPopulation.getLabel()); 337 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_ERROR_MAIL_NO_SIGNUP_PAGE_ERROR", i18nParams))); 338 break; 339 case SIGNUP_NOT_ALLOWED: 340 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_ERROR_MAIL_NO_SIGNUP_ALLOWED_ERROR"))); 341 break; 342 case USER_NOT_ALLOWED: 343 i18nParams.put("population", userPopulation.getLabel()); 344 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_ERROR_MAIL_USER_NOT_ALLOWED_ERROR", i18nParams))); 345 break; 346 default: 347 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_ERROR_MAIL_BODY_SEE_LOGS"))); 348 break; 349 } 350 } 351 else 352 { 353 sb.append(_i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATIONS_ERROR_MAIL_BODY_SEE_LOGS"))); 354 } 355 356 return sb.toString(); 357 } 358 359 /** 360 * Get the site title from job execution context 361 * 362 * @param context the job context 363 * @return the site title 364 */ 365 protected Site _getSite(JobExecutionContext context) 366 { 367 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 368 String siteName = (String) jobDataMap.get(__JOBDATAMAP_SITE_NAME_KEY); 369 370 return _siteManager.getSite(siteName); 371 } 372 373}