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.plugins.workspaces.activities; 017 018import java.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.cocoon.components.ContextHelper; 035import org.apache.cocoon.environment.Request; 036import org.apache.commons.lang3.StringUtils; 037import org.apache.commons.lang3.tuple.Pair; 038import org.apache.excalibur.source.Source; 039import org.apache.excalibur.source.SourceResolver; 040import org.apache.excalibur.source.SourceUtil; 041 042import org.ametys.core.right.RightManager; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.core.user.UserManager; 045import org.ametys.core.util.I18nUtils; 046import org.ametys.core.util.language.UserLanguagesManager; 047import org.ametys.plugins.repository.AmetysObject; 048import org.ametys.plugins.repository.AmetysObjectResolver; 049import org.ametys.plugins.repository.activities.Activity; 050import org.ametys.plugins.workspaces.project.ProjectManager; 051import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper; 052import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper.Frequency; 053import org.ametys.plugins.workspaces.project.objects.Project; 054import org.ametys.runtime.config.Config; 055import org.ametys.runtime.i18n.I18nizableText; 056import org.ametys.runtime.plugin.component.PluginAware; 057import org.ametys.web.WebConstants; 058import org.ametys.web.activities.notify.ActivityNotifier; 059import org.ametys.web.renderingcontext.RenderingContext; 060import org.ametys.web.renderingcontext.RenderingContextHandler; 061import org.ametys.web.repository.site.Site; 062 063/** 064 * Abstract class representing a activity notifier for workspaces 065 */ 066public abstract class AbstractWorkspacesActivityNotifier implements ActivityNotifier, Serviceable, PluginAware, Contextualizable 067{ 068 /** The right manager */ 069 protected RightManager _rightManager; 070 071 /** The notification preference helper */ 072 protected NotificationPreferencesHelper _notificationPreferenceHelper; 073 074 /** The user manager */ 075 protected UserManager _userManager; 076 077 /** The project manager */ 078 protected ProjectManager _projectManager; 079 080 /** The i18n utils */ 081 protected I18nUtils _i18nUtils; 082 083 /** The rendering context handler */ 084 protected RenderingContextHandler _renderingContextHandler; 085 086 /** The source resolver */ 087 protected SourceResolver _srcResolver; 088 089 /** The Ametys Object resolver */ 090 protected AmetysObjectResolver _resolver; 091 092 /** The user languages manager */ 093 protected UserLanguagesManager _userLanguagesManager; 094 095 /** The plugin name */ 096 protected String _pluginName; 097 098 /** The context */ 099 protected Context _context; 100 101 public void service(ServiceManager manager) throws ServiceException 102 { 103 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 104 _notificationPreferenceHelper = (NotificationPreferencesHelper) manager.lookup(NotificationPreferencesHelper.ROLE); 105 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 106 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 107 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 108 _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE); 109 _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 110 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 111 _userLanguagesManager = (UserLanguagesManager) manager.lookup(UserLanguagesManager.ROLE); 112 } 113 114 public void contextualize(Context context) throws ContextException 115 { 116 _context = context; 117 } 118 119 public void setPluginInfo(String pluginName, String featureName, String id) 120 { 121 _pluginName = pluginName; 122 } 123 124 @Override 125 public Map<String, List<String>> getUsersToNotifyByLanguage(Activity activity) 126 { 127 boolean returnAll = Config.getInstance().getValue("runtime.mail.massive.sending"); 128 129 // FIXME GG right should be provided directly by the activity 130 AmetysObject aObject = getTargetAmetysObject(activity); 131 Set<UserIdentity> readAccessUsers = _rightManager.getReadAccessAllowedUsers(aObject).resolveAllowedUsers(returnAll); 132 133 String defaultLanguage = _userLanguagesManager.getDefaultLanguage(); 134 135 Map<String, List<String>> usersByLanguage = readAccessUsers.stream() 136 .filter(userId -> _notificationPreferenceHelper.askedToBeNotified(userId, activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME), Frequency.EACH)) 137 .map(_userManager::getUser) 138 .filter(Objects::nonNull) 139 .map(user -> Pair.of(user.getLanguage(), user.getEmail())) 140 .filter(p -> StringUtils.isNotEmpty(p.getRight())) 141 .collect(Collectors.groupingBy( 142 p -> { 143 return StringUtils.defaultIfBlank(p.getLeft(), defaultLanguage); 144 }, 145 Collectors.mapping( 146 Pair::getRight, 147 Collectors.toList() 148 ) 149 ) 150 ); 151 152 return usersByLanguage; 153 } 154 155 /** 156 * Retrieve the ametys object targeted by this activity. 157 * This method is intended for right computation purposes. 158 * @param activity the activity 159 * @return the target 160 */ 161 // FIXME this should not exist and the activity itself should provide the right. (probably by a right convertor) 162 public abstract AmetysObject getTargetAmetysObject(Activity activity); 163 164 @Override 165 public String getMailSubject(Activity activity, String language) 166 { 167 return _i18nUtils.translate(new I18nizableText("plugin." + _pluginName, _getSubjectI18nKey(activity), getSubjectI18nParams(activity)), language); 168 } 169 170 /** 171 * Get the subject i18n key 172 * @param activity the activity 173 * @return the subject i18n key 174 */ 175 protected String _getSubjectI18nKey(Activity activity) 176 { 177 return "PROJECT_MAIL_NOTIFICATION_SUBJECT_" + StringUtils.replaceChars(activity.getEventType().toUpperCase(), '.', '_'); 178 } 179 180 /** 181 * Get the subject i18n parameters 182 * @param activity the activity 183 * @return the subject i18n parameters 184 */ 185 public List<String> getSubjectI18nParams(Activity activity) 186 { 187 List<String> i18nparams = new ArrayList<>(); 188 i18nparams.add(activity.getValue(AbstractWorkspacesActivityType.PROJECT_TITLE)); // {0} 189 return i18nparams; 190 } 191 192 @Override 193 public String getMailTextBody(Activity activity, String language) 194 { 195 // No text body 196 return null; 197 } 198 199 @Override 200 public String getMailHtmlBody(Activity activity, String language) 201 { 202 String projectName = activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME); 203 Project project = _projectManager.getProject(projectName); 204 Site site = project.getSite(); 205 206 String mailBody; 207 Source source = null; 208 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 209 210 try 211 { 212 // Force rendering context.FRONT to resolve URI 213 _renderingContextHandler.setRenderingContext(RenderingContext.FRONT); 214 215 Request request = ContextHelper.getRequest(_context); 216 217 request.setAttribute("lang", language); 218 request.setAttribute("sitemapLanguage", language); 219 request.setAttribute("forceAbsoluteUrl", true); 220 request.setAttribute(WebConstants.REQUEST_ATTR_SITE, site); 221 request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, site.getName()); 222 request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, site.getSkinId()); 223 224 source = _srcResolver.resolveURI(getMailBodyURI(activity), null, Map.of(AbstractWorkspacesActivityType.ACTIVITY_CONTEXT_PARAM, activity)); 225 226 try (InputStream is = source.getInputStream()) 227 { 228 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 229 SourceUtil.copy(is, bos); 230 231 mailBody = bos.toString("UTF-8"); 232 } 233 } 234 catch (IOException e) 235 { 236 throw new RuntimeException("Failed to create mail body", e); 237 } 238 finally 239 { 240 _renderingContextHandler.setRenderingContext(currentContext); 241 242 if (source != null) 243 { 244 _srcResolver.release(source); 245 } 246 } 247 return mailBody; 248 } 249 250 /** 251 * Get the URI to resolve to get the mail body 252 * @param activity the activity 253 * @return the uri 254 */ 255 public abstract String getMailBodyURI(Activity activity); 256 257}