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, arguments); 097 } 098 099 /** 100 * Send mail to all mentioned users in the mentionable object 101 * @param mentionableObject the mentionable object 102 * @param arguments the event arguments 103 */ 104 @SuppressWarnings("unchecked") 105 protected void _sendMailToMentionedUsers(MentionableObject mentionableObject, Map<String, Object> arguments) 106 { 107 T ametysObject = (T) mentionableObject.ametysObject(); 108 I18nizableText i18nSubject = _getMailSubject(mentionableObject); 109 110 I18nizableText i18nMessage = _getMailMessage(mentionableObject); 111 112 for (UserIdentity mentionedUserIdentity : mentionableObject.mentionedUsers()) 113 { 114 if (_canSendMailToMentionedUser(ametysObject, mentionableObject.author(), mentionedUserIdentity, arguments)) 115 { 116 // Get mentioned user 117 User mentionedUser = _userManager.getUser(mentionedUserIdentity); 118 if (mentionedUser == null) 119 { 120 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()); 121 continue; 122 } 123 124 if (StringUtils.isBlank(mentionedUser.getEmail())) 125 { 126 getLogger().warn("Could not send a notification e-mail to user {}: this user has no email address", mentionedUser); 127 continue; 128 } 129 130 String language = StringUtils.defaultIfBlank(mentionedUser.getLanguage(), StringUtils.defaultIfBlank(mentionableObject.language(), _userLanguagesManager.getDefaultLanguage())); 131 String subject = _i18nUtils.translate(i18nSubject, language); 132 133 String body = _createBody(mentionedUser, i18nMessage, mentionableObject, language); 134 _sendMail(mentionedUser, subject, body); 135 } 136 } 137 } 138 139 /** 140 * create a mail body with arguments 141 * @param user the user 142 * @param message the message 143 * @param mentionableObject the object 144 * @param language the language to use 145 * @return the mail body 146 */ 147 protected String _createBody(User user, I18nizableText message, MentionableObject mentionableObject, String language) 148 { 149 try 150 { 151 String contentWithReplacedMentions = _transformSyntaxTextToReadableTextWithColors(mentionableObject.content(), user.getIdentity()); 152 MailBodyBuilder mailBuilder = getStandardMailBodyHelper() 153 .withLanguage(mentionableObject.language()) 154 .withTitle(_getMailTitle(mentionableObject)) 155 .addMessage(message) 156 .withLink(mentionableObject.linkToAmetysObject().linkUrl(), mentionableObject.linkToAmetysObject().linkText()); 157 User author = mentionableObject.author(); 158 if (author != null) 159 { 160 UserInput userInput = new UserInput(author, mentionableObject.creationDate(), contentWithReplacedMentions); 161 mailBuilder.withUserInputs(List.of(userInput), _getMailMessageType()); 162 } 163 else 164 { 165 String unknownAuthorName = _i18nUtils.translate(new I18nizableText("plugin.core", "PLUGINS_CORE_USERS_UNKNOWN_USER")); 166 UnauthenticatedUserInput userInput = new UnauthenticatedUserInput(unknownAuthorName, mentionableObject.creationDate(), contentWithReplacedMentions); 167 mailBuilder.withUnauthenticatedUserInputs(List.of(userInput), _getMailMessageType()); 168 } 169 170 return mailBuilder.withLanguage(language).build(); 171 } 172 catch (IOException e) 173 { 174 getLogger().warn("Could not send a notification e-mail to " + user + ": an error occured while sending the email", e); 175 } 176 return null; 177 } 178 179 /** 180 * Get a standard mail body helper 181 * @return the standard mail body helper 182 */ 183 protected MailBodyBuilder getStandardMailBodyHelper() 184 { 185 return StandardMailBodyHelper.newHTMLBody(); 186 } 187 188 /** 189 * Get the type of message to display before the message 190 * @return the message type 191 */ 192 protected abstract I18nizableText _getMailMessageType(); 193 194 /** 195 * Get the title of the mail body 196 * @param mentionableObject the mentionable object 197 * @return the mail title 198 */ 199 protected abstract I18nizableText _getMailTitle(MentionableObject mentionableObject); 200 201 /** 202 * Transform syntax text to readable text with colors according to whether the recipient is the one being tagged or not 203 * @param syntaxText the syntax text 204 * @param recipient the recipient 205 * @return the readable text 206 */ 207 protected abstract String _transformSyntaxTextToReadableTextWithColors(String syntaxText, UserIdentity recipient); 208 209 /** 210 * <code>true</code> if we can send a mail to the mentioned user 211 * @param ametysObject the ametys object 212 * @param authorIdentity the author 213 * @param mentionedUserIdentity the mentioned user 214 * @param arguments the event arguments 215 * @return <code>true</code> if we can send a mail to the mentioned user 216 */ 217 protected boolean _canSendMailToMentionedUser(T ametysObject, User authorIdentity, UserIdentity mentionedUserIdentity, Map<String, Object> arguments) 218 { 219 return authorIdentity != null && !mentionedUserIdentity.equals(authorIdentity.getIdentity()); 220 } 221 222 /** 223 * Retrieves the notification mail's subject 224 * @param mentionableObject the mentionable object 225 * @return the notification mail's subject 226 */ 227 protected abstract I18nizableText _getMailSubject(MentionableObject mentionableObject); 228 229 /** 230 * Retrieves the notification mail's subject 231 * @param mentionableObject the mentionable object 232 * @return the notification mail's subject 233 */ 234 protected abstract I18nizableText _getMailMessage(MentionableObject mentionableObject); 235 236 /** 237 * Get all information of the mentionable object from the arguments 238 * @param arguments the arguments map 239 * @return the mentionable object 240 * @throws Exception if an error occurs. 241 */ 242 protected abstract MentionableObject _getMentionableObjectFromArguments(Map<String, Object> arguments) throws Exception; 243 244 /** 245 * Send mail to the given user 246 * @param user the user 247 * @param subject the mail subject 248 * @param body the mail body 249 */ 250 protected void _sendMail(User user, String subject, String body) 251 { 252 try 253 { 254 255 if (StringUtils.isBlank(body)) 256 { 257 getLogger().warn("Could not send a notification e-mail to {}: the email body is empty", user); 258 } 259 else 260 { 261 SendMailHelper.newMail() 262 .withSubject(subject) 263 .withHTMLBody(body) 264 .withRecipient(user.getEmail()) 265 .sendMail(); 266 } 267 } 268 catch (MessagingException | IOException e) 269 { 270 getLogger().warn("Could not send a notification e-mail to " + user + ": an error occured while sending the email", e); 271 } 272 } 273 274 /** 275 * Link to the ametys object 276 * @param linkUrl the link to the ametys object 277 * @param linkText the text of the link to the ametys object 278 */ 279 public record LinkToAmetysObject(String linkUrl, I18nizableText linkText) { /* empty */ } 280 281 282 /** 283 * A record to get all information of the mentionable object 284 * @param content the content 285 * @param author the author 286 * @param mentionedUsers the mentioned users in the mentionable object 287 * @param creationDate the creation date 288 * @param ametysObject the ametys object holding the mentionable object 289 * @param linkToAmetysObject the link of the ametys object 290 * @param language the language 291 */ 292 public record MentionableObject(User author, String content, Collection<UserIdentity> mentionedUsers, ZonedDateTime creationDate, AmetysObject ametysObject, LinkToAmetysObject linkToAmetysObject, String language) { /* empty */ } 293}