001/* 002 * Copyright 2024 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.mentions; 017 018import java.io.IOException; 019import java.time.ZonedDateTime; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.commons.lang3.StringUtils; 030 031import org.ametys.core.observation.AsyncObserver; 032import org.ametys.core.observation.Event; 033import org.ametys.core.ui.mail.StandardMailBodyHelper; 034import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder; 035import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder.UnauthenticatedUserInput; 036import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder.UserInput; 037import org.ametys.core.user.CurrentUserProvider; 038import org.ametys.core.user.User; 039import org.ametys.core.user.UserIdentity; 040import org.ametys.core.user.UserManager; 041import org.ametys.core.util.I18nUtils; 042import org.ametys.core.util.language.UserLanguagesManager; 043import org.ametys.core.util.mail.SendMailHelper; 044import org.ametys.plugins.repository.AmetysObject; 045import org.ametys.plugins.repository.AmetysObjectResolver; 046import org.ametys.runtime.i18n.I18nizableText; 047import org.ametys.runtime.plugin.component.AbstractLogEnabled; 048 049import jakarta.mail.MessagingException; 050 051/** 052 * Abstract observer to send mails to mentioned users in {@link AmetysObject} 053 * @param <T> type of the {@link AmetysObject} 054 */ 055public abstract class AbstractNotifyMentionsObserver<T extends AmetysObject> extends AbstractLogEnabled implements Component, Serviceable, AsyncObserver 056{ 057 /** The ametys object resolver */ 058 protected AmetysObjectResolver _resolver; 059 /** The user manager */ 060 protected UserManager _userManager; 061 /** The current user provider */ 062 protected CurrentUserProvider _currentUserProvider; 063 /** The i18n utils. */ 064 protected I18nUtils _i18nUtils; 065 066 /** Cache for resolved users */ 067 protected Map<UserIdentity, User> _resolvedUsers = new HashMap<>(); 068 069 /** The mention utils */ 070 protected MentionUtils _mentionUtils; 071 /** The user languages manager */ 072 protected UserLanguagesManager _userLanguagesManager; 073 074 public void service(ServiceManager manager) throws ServiceException 075 { 076 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 077 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 078 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 079 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 080 _mentionUtils = (MentionUtils) manager.lookup(MentionUtils.ROLE); 081 _userLanguagesManager = (UserLanguagesManager) manager.lookup(UserLanguagesManager.ROLE); 082 } 083 084 public int getPriority() 085 { 086 return MIN_PRIORITY; 087 } 088 089 public void observe(Event event, Map<String, Object> transientVars) throws Exception 090 { 091 Map<String, Object> arguments = event.getArguments(); 092 093 MentionableObject mentionableObject = _getMentionableObjectFromArguments(arguments); 094 095 // Send mail to all mentioned users in the added mentionable object 096 _sendMailToMentionedUsers(mentionableObject); 097 } 098 099 /** 100 * Send mail to all mentioned users in the mentionable object 101 * @param mentionableObject the mentionable object 102 */ 103 @SuppressWarnings("unchecked") 104 protected void _sendMailToMentionedUsers(MentionableObject mentionableObject) 105 { 106 T ametysObject = (T) mentionableObject.ametysObject(); 107 I18nizableText i18nSubject = _getMailSubject(mentionableObject); 108 109 I18nizableText i18nMessage = _getMailMessage(mentionableObject); 110 111 for (UserIdentity mentionedUserIdentity : mentionableObject.mentionedUsers()) 112 { 113 if (_canSendMailToMentionedUser(ametysObject, mentionableObject.author(), mentionedUserIdentity)) 114 { 115 // Get mentioned user 116 User mentionedUser = _userManager.getUser(mentionedUserIdentity); 117 if (mentionedUser == null) 118 { 119 getLogger().warn("Could not send a notification e-mail to user with login {}: there is no user with this login in population {}", mentionedUserIdentity.getLogin(), mentionedUserIdentity.getPopulationId()); 120 continue; 121 } 122 123 if (StringUtils.isBlank(mentionedUser.getEmail())) 124 { 125 getLogger().warn("Could not send a notification e-mail to user {}: this user has no email address", mentionedUser); 126 continue; 127 } 128 129 String language = StringUtils.defaultIfBlank(mentionedUser.getLanguage(), _userLanguagesManager.getDefaultLanguage()); 130 String subject = _i18nUtils.translate(i18nSubject, language); 131 132 String body = _createBody(mentionedUser, i18nMessage, mentionableObject, language); 133 _sendMail(mentionedUser, subject, body); 134 } 135 } 136 } 137 138 /** 139 * create a mail body with arguments 140 * @param user the user 141 * @param message the message 142 * @param mentionableObject the object 143 * @param language the language to use 144 * @return the mail body 145 */ 146 protected String _createBody(User user, I18nizableText message, MentionableObject mentionableObject, String language) 147 { 148 try 149 { 150 String contentWithReplacedMentions = _transformSyntaxTextToReadableTextWithColors(mentionableObject.content(), user.getIdentity()); 151 MailBodyBuilder mailBuilder = getStandardMailBodyHelper() 152 .withLanguage(mentionableObject.language()) 153 .withTitle(_getMailTitle(mentionableObject)) 154 .addMessage(message) 155 .withLink(mentionableObject.linkToAmetysObject().linkUrl(), mentionableObject.linkToAmetysObject().linkText()); 156 User author = mentionableObject.author(); 157 if (author != null) 158 { 159 UserInput userInput = new UserInput(author, mentionableObject.creationDate(), contentWithReplacedMentions); 160 mailBuilder.withUserInputs(List.of(userInput), _getMailMessageType()); 161 } 162 else 163 { 164 String unknownAuthorName = _i18nUtils.translate(new I18nizableText("plugin.core", "PLUGINS_CORE_USERS_UNKNOWN_USER")); 165 UnauthenticatedUserInput userInput = new UnauthenticatedUserInput(unknownAuthorName, mentionableObject.creationDate(), contentWithReplacedMentions); 166 mailBuilder.withUnauthenticatedUserInputs(List.of(userInput), _getMailMessageType()); 167 } 168 169 return mailBuilder.withLanguage(language).build(); 170 } 171 catch (IOException e) 172 { 173 getLogger().warn("Could not send a notification e-mail to " + user + ": an error occured while sending the email", e); 174 } 175 return null; 176 } 177 178 /** 179 * Get a standard mail body helper 180 * @return the standard mail body helper 181 */ 182 protected MailBodyBuilder getStandardMailBodyHelper() 183 { 184 return StandardMailBodyHelper.newHTMLBody(); 185 } 186 187 /** 188 * Get the type of message to display before the message 189 * @return the message type 190 */ 191 protected abstract I18nizableText _getMailMessageType(); 192 193 /** 194 * Get the title of the mail body 195 * @param mentionableObject the mentionable object 196 * @return the mail title 197 */ 198 protected abstract I18nizableText _getMailTitle(MentionableObject mentionableObject); 199 200 /** 201 * Transform syntax text to readable text with colors according to whether the recipient is the one being tagged or not 202 * @param syntaxText the syntax text 203 * @param recipient the recipient 204 * @return the readable text 205 */ 206 protected abstract String _transformSyntaxTextToReadableTextWithColors(String syntaxText, UserIdentity recipient); 207 208 /** 209 * <code>true</code> if we can send a mail to the mentioned user 210 * @param ametysObject the ametys object 211 * @param authorIdentity the author 212 * @param mentionedUserIdentity the mentioned user 213 * @return <code>true</code> if we can send a mail to the mentioned user 214 */ 215 protected boolean _canSendMailToMentionedUser(T ametysObject, User authorIdentity, UserIdentity mentionedUserIdentity) 216 { 217 return authorIdentity != null && !mentionedUserIdentity.equals(authorIdentity.getIdentity()); 218 } 219 220 /** 221 * Retrieves the notification mail's subject 222 * @param mentionableObject the mentionable object 223 * @return the notification mail's subject 224 */ 225 protected abstract I18nizableText _getMailSubject(MentionableObject mentionableObject); 226 227 /** 228 * Retrieves the notification mail's subject 229 * @param mentionableObject the mentionable object 230 * @return the notification mail's subject 231 */ 232 protected abstract I18nizableText _getMailMessage(MentionableObject mentionableObject); 233 234 /** 235 * Get all information of the mentionable object from the arguments 236 * @param arguments the arguments map 237 * @return the mentionable object 238 * @throws Exception if an error occurs. 239 */ 240 protected abstract MentionableObject _getMentionableObjectFromArguments(Map<String, Object> arguments) throws Exception; 241 242 /** 243 * Send mail to the given user 244 * @param user the user 245 * @param subject the mail subject 246 * @param body the mail body 247 */ 248 protected void _sendMail(User user, String subject, String body) 249 { 250 try 251 { 252 253 if (StringUtils.isBlank(body)) 254 { 255 getLogger().warn("Could not send a notification e-mail to {}: the email body is empty", user); 256 } 257 else 258 { 259 SendMailHelper.newMail() 260 .withSubject(subject) 261 .withHTMLBody(body) 262 .withRecipient(user.getEmail()) 263 .sendMail(); 264 } 265 } 266 catch (MessagingException | IOException e) 267 { 268 getLogger().warn("Could not send a notification e-mail to " + user + ": an error occured while sending the email", e); 269 } 270 } 271 272 /** 273 * Link to the ametys object 274 * @param linkUrl the link to the ametys object 275 * @param linkText the text of the link to the ametys object 276 */ 277 public record LinkToAmetysObject(String linkUrl, I18nizableText linkText) { /* empty */ } 278 279 280 /** 281 * A record to get all information of the mentionable object 282 * @param content the content 283 * @param author the author 284 * @param mentionedUsers the mentioned users in the mentionable object 285 * @param creationDate the creation date 286 * @param ametysObject the ametys object holding the mentionable object 287 * @param linkToAmetysObject the link of the ametys object 288 * @param language the language 289 */ 290 public record MentionableObject(User author, String content, Collection<UserIdentity> mentionedUsers, ZonedDateTime creationDate, AmetysObject ametysObject, LinkToAmetysObject linkToAmetysObject, String language) { /* empty */ } 291}