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