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