001/* 002 * Copyright 2026 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.plugins.workspaces.project.notification; 017 018import java.io.IOException; 019import java.util.Arrays; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.commons.lang3.StringUtils; 031import org.apache.commons.lang3.tuple.Pair; 032 033import org.ametys.core.observation.AsyncObserver; 034import org.ametys.core.observation.Event; 035import org.ametys.core.ui.mail.StandardMailBodyHelper; 036import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder; 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.AmetysObjectResolver; 045import org.ametys.plugins.workspaces.ObservationConstants; 046import org.ametys.plugins.workspaces.WorkspacesHelper; 047import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper; 048import org.ametys.plugins.workspaces.project.objects.Project; 049import org.ametys.runtime.i18n.I18nizableText; 050import org.ametys.runtime.i18n.I18nizableTextParameter; 051import org.ametys.runtime.plugin.component.AbstractLogEnabled; 052 053import jakarta.mail.MessagingException; 054 055/** 056 * Observer to send mail notifications to manager of project when a new member join the project 057 * @implNote the class doesn't extends AbstractMemberMailNotifierObserver because it relies 058 * directly on the {@link StandardMailBodyHelper} instead of the early custom implementation 059 */ 060public class NewMemberMailManagersNotifierObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable 061{ 062 /** The current user provider */ 063 protected CurrentUserProvider _currentUserProvider; 064 /** The I18n utils */ 065 protected I18nUtils _i18nUtils; 066 /** The notification preference helper */ 067 protected NotificationPreferencesHelper _notificationPrefHelper; 068 /** The ametys object resolver */ 069 protected AmetysObjectResolver _resolver; 070 /** The user languages manager */ 071 protected UserLanguagesManager _userLanguagesManager; 072 /** The user manager */ 073 protected UserManager _userManager; 074 /** The workspaces helper */ 075 protected WorkspacesHelper _workspacesHelper; 076 077 public void service(ServiceManager manager) throws ServiceException 078 { 079 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 080 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 081 _notificationPrefHelper = (NotificationPreferencesHelper) manager.lookup(NotificationPreferencesHelper.ROLE); 082 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 083 _userLanguagesManager = (UserLanguagesManager) manager.lookup(UserLanguagesManager.ROLE); 084 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 085 _workspacesHelper = (WorkspacesHelper) manager.lookup(WorkspacesHelper.ROLE); 086 } 087 088 @Override 089 public int getPriority() 090 { 091 return MIN_PRIORITY; 092 } 093 094 public boolean supports(Event event) 095 { 096 return event.getId().equals(ObservationConstants.EVENT_MEMBER_JOINED); 097 } 098 099 @Override 100 public void observe(Event event, Map<String, Object> transientVars) throws Exception 101 { 102 Map<String, Object> args = event.getArguments(); 103 UserIdentity currentUser = _currentUserProvider.getUser(); 104 105 String projectId = (String) args.get(ObservationConstants.ARGS_PROJECT_ID); 106 Project project = _resolver.resolveById(projectId); 107 String url = project.getSite().getUrl(); 108 109 // Compute subject and body 110 Map<String, I18nizableTextParameter> params = new HashMap<>(); 111 User current = _userManager.getUser(currentUser); 112 params.put("user", new I18nizableText(current != null ? current.getFullName() : currentUser.getLogin())); 113 params.put("project", new I18nizableText(project.getTitle())); 114 params.put("url", new I18nizableText(url != null ? url : "")); 115 116 I18nizableText i18nSubject = new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CATALOGUE_JOINPROJECT_MAIL_TITLE", params); 117 I18nizableText i18nTextBody = new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CATALOGUE_JOINPROJECT_MAIL_BODY_TEXT", params); 118 119 MailBodyBuilder htmlBodyBuilder = StandardMailBodyHelper.newHTMLBody() 120 .withTitle(i18nSubject) 121 .withMessage(new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CATALOGUE_JOINPROJECT_MAIL_BODY_HTML", params)) 122 .withLink(url, new I18nizableText("plugin.workspaces", "PROJECT_MAIL_NOTIFICATION_BODY_DEFAULT_BUTTON_TEXT")); 123 124 Map<String, List<String>> recipientsByLanguage = getUserToNotifyByLanguage(event, project); 125 126 for (String language : recipientsByLanguage.keySet()) 127 { 128 String subject = _i18nUtils.translate(i18nSubject, language); 129 String txtBody = _i18nUtils.translate(i18nTextBody, language); 130 131 String htmlBody = htmlBodyBuilder.withLanguage(language).build(); 132 133 try 134 { 135 SendMailHelper.newMail() 136 .withSubject(subject) 137 .withTextBody(txtBody) 138 .withHTMLBody(htmlBody) 139 .withRecipients(recipientsByLanguage.get(language)) 140 .withAsync(true) 141 .withInlineCSS(false) 142 .sendMail(); 143 } 144 catch (MessagingException | IOException e) 145 { 146 getLogger().warn("Could not send a notification e-mail to " + recipientsByLanguage + " following his removal from the project " + project.getTitle(), e); 147 } 148 149 } 150 } 151 152 /** 153 * Get the user to notify 154 * @param event the event to notify 155 * @param project the project related to the event 156 * @return the list of email to notify by language of notification 157 */ 158 protected Map<String, List<String>> getUserToNotifyByLanguage(Event event, Project project) 159 { 160 String defaultLang = _workspacesHelper.getLang(project, _userLanguagesManager.getDefaultLanguage()); 161 162 // Recipients are project managers 163 return Arrays.stream(project.getManagers()) 164 .map(_userManager::getUser) 165 .filter(Objects::nonNull) 166 .map(user -> Pair.of(user, user.getEmail())) 167 .filter(p -> StringUtils.isNotEmpty(p.getRight())) 168 .filter(p -> { 169 Set<String> pausedProjects = _notificationPrefHelper.getPausedProjects(p.getLeft().getIdentity()); 170 return pausedProjects != null // all notification paused 171 && !pausedProjects.contains(project.getName()); 172 }) 173 .collect(Collectors.groupingBy( 174 p -> { 175 return StringUtils.defaultIfEmpty(p.getLeft().getLanguage(), defaultLang); 176 }, 177 Collectors.mapping( 178 Pair::getRight, 179 Collectors.toList() 180 ) 181 ) 182 ); 183 } 184}