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.web.mail; 017 018import java.io.IOException; 019import java.io.InputStreamReader; 020import java.io.Reader; 021import java.time.ZonedDateTime; 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import org.apache.avalon.framework.activity.Initializable; 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.cocoon.components.ContextHelper; 034import org.apache.cocoon.environment.Request; 035import org.apache.commons.io.IOUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.excalibur.source.Source; 038import org.apache.excalibur.source.SourceResolver; 039 040import org.ametys.core.cocoon.ActionResultGenerator; 041import org.ametys.core.user.User; 042import org.ametys.core.util.DateUtils; 043import org.ametys.plugins.core.user.UserHelper; 044import org.ametys.runtime.i18n.I18nizableText; 045import org.ametys.web.WebHelper; 046 047/** 048 * Component to build wrapped HTML mail body for report activities 049 */ 050public class ReportActivitiesMailBodyHelper implements Contextualizable, Serviceable, Initializable 051{ 052 /** The avalon context */ 053 protected static Context _context; 054 055 private static SourceResolver _srcResolver; 056 private static UserHelper _userHelper; 057 058 private static ReportActivitiesMailBodyHelper __instance; 059 060 @Override 061 public void contextualize(Context context) 062 { 063 _context = context; 064 } 065 066 public void initialize() throws Exception 067 { 068 __instance = this; 069 } 070 071 @Override 072 public void service(ServiceManager manager) throws ServiceException 073 { 074 _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 075 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 076 } 077 078 /** 079 * Get the URI to resolve wrapped HTML email body 080 * @return the uri 081 */ 082 public String getMailBodyUri() 083 { 084 Request request = ContextHelper.getRequest(_context); 085 086 String siteName = WebHelper.getSiteName(request); 087 088 return "cocoon://_plugins/web/" + siteName + "/wrapped-mail/activities/body.html"; 089 } 090 091 /** 092 * Creates a new mail body builder. 093 * @return the newly created {@link MailBodyBuilder}. 094 */ 095 public static MailBodyBuilder newHTMLBody() 096 { 097 return new MailBodyBuilder(__instance.getMailBodyUri()); 098 } 099 100 /** 101 * Implements the builder pattern for creating and sending emails. 102 */ 103 public static class MailBodyBuilder 104 { 105 private Object _title; 106 private List<Object> _messages; 107 private List<Activity> _activities; 108 private I18nizableText _linkText; 109 private String _linkUrl; 110 private I18nizableText _footerLinkText; 111 private String _footerLinkUrl; 112 private String _footerImgPluginName; 113 private String _footerImgPath; 114 private String _lang; 115 private String _uri; 116 117 MailBodyBuilder(String uri) 118 { 119 _uri = uri; 120 } 121 122 /** 123 * Set the language 124 * @param lang the language 125 * @return this builder 126 */ 127 public MailBodyBuilder withLanguage(String lang) 128 { 129 _lang = lang; 130 return this; 131 } 132 /** 133 * Set the title 134 * @param title the title as String or {@link I18nizableText} 135 * @return this builder 136 */ 137 public MailBodyBuilder withTitle(Object title) 138 { 139 _title = title; 140 return this; 141 } 142 143 /** 144 * Set the main message 145 * @param message the message as String or {@link I18nizableText} 146 * @return this builder 147 */ 148 public MailBodyBuilder withMessage(Object message) 149 { 150 _messages = List.of(message); 151 return this; 152 } 153 154 /** 155 * Add a new message 156 * @param message the message as String or {@link I18nizableText} 157 * @return this builder 158 */ 159 public MailBodyBuilder addMessage(Object message) 160 { 161 if (_messages == null) 162 { 163 _messages = new ArrayList<>(); 164 } 165 166 _messages.add(message); 167 return this; 168 } 169 170 /** 171 * Set the activities 172 * @param activities the activities 173 * @return this builder 174 */ 175 public MailBodyBuilder withActivities(List<Activity> activities) 176 { 177 _activities = activities; 178 return this; 179 } 180 181 /** 182 * Add an activity 183 * @param activity the activity 184 * @return this builder 185 */ 186 public MailBodyBuilder addActivity(Activity activity) 187 { 188 if (_activities == null) 189 { 190 _activities = new ArrayList<>(); 191 } 192 193 _activities.add(activity); 194 return this; 195 } 196 197 /** 198 * Add a main link to be displayed as a button 199 * @param linkUrl the link url 200 * @param linkText the link text 201 * @return this builder 202 */ 203 public MailBodyBuilder withLink(String linkUrl, I18nizableText linkText) 204 { 205 _linkText = linkText; 206 _linkUrl = linkUrl; 207 return this; 208 } 209 210 /** 211 * Add a link to be displayed on footer 212 * @param linkUrl the link url 213 * @param linkText the link text 214 * @return this builder 215 */ 216 public MailBodyBuilder withFooterLink(String linkUrl, I18nizableText linkText) 217 { 218 return withFooterLink(linkUrl, linkText, null, null); 219 } 220 221 /** 222 * Add a link to be displayed on footer 223 * @param linkUrl the link url 224 * @param linkText the link text 225 * @param imgPluginName the plugin name that constains the image 226 * @param imgPath the path of the image in the plugin 227 * @return this builder 228 */ 229 public MailBodyBuilder withFooterLink(String linkUrl, I18nizableText linkText, String imgPluginName, String imgPath) 230 { 231 _footerLinkText = linkText; 232 _footerLinkUrl = linkUrl; 233 _footerImgPluginName = imgPluginName; 234 _footerImgPath = imgPath; 235 return this; 236 } 237 238 /** 239 * Build the body of the email as HTML 240 * @return the HTML body 241 * @throws IOException if an error occurred when building the email 242 */ 243 public String build() throws IOException 244 { 245 Source src = null; 246 Request request = ContextHelper.getRequest(_context); 247 248 try 249 { 250 request.setAttribute(ActionResultGenerator.MAP_REQUEST_ATTR, toJson()); 251 252 Map<String, Object> parameters = new HashMap<>(); 253 parameters.put("lang", _lang); 254 255 src = _srcResolver.resolveURI(_uri, null, parameters); 256 Reader reader = new InputStreamReader(src.getInputStream(), "UTF-8"); 257 return IOUtils.toString(reader); 258 } 259 finally 260 { 261 _srcResolver.release(src); 262 } 263 } 264 265 /** 266 * Get the JSON representation of the email ingredients 267 * @return the JSON ingredients 268 */ 269 public Map<String, Object> toJson() 270 { 271 Map<String, Object> json = new HashMap<>(); 272 273 json.put("title", _title); 274 275 if (_messages != null && !_messages.isEmpty()) 276 { 277 json.put("message", _messages); 278 } 279 280 if (StringUtils.isNotEmpty(_linkUrl)) 281 { 282 json.put("link", Map.of("url", _linkUrl, "text", _linkText)); 283 } 284 285 if (_activities != null && !_activities.isEmpty()) 286 { 287 json.put("activity", _activities.stream() 288 .map(this::_activity2json) 289 .toList()); 290 } 291 292 json.put("footer", _footer2json()); 293 294 return json; 295 } 296 297 private Map<String, Object> _footer2json() 298 { 299 Map<String, Object> footer2json = new HashMap<>(); 300 301 if (_footerLinkUrl != null && _footerLinkText != null) 302 { 303 Map<String, Object> link2json = new HashMap<>(); 304 305 link2json.put("text", _footerLinkText); 306 link2json.put("url", _footerLinkUrl); 307 if (_footerImgPath != null && _footerImgPluginName != null) 308 { 309 link2json.put("imgPluginName", _footerImgPluginName); 310 link2json.put("imgPath", _footerImgPath); 311 } 312 footer2json.put("link", link2json); 313 } 314 315 return footer2json; 316 } 317 318 private Map<String, Object> _activity2json(Activity activity) 319 { 320 Map<String, Object> activity2json = new HashMap<>(); 321 activity2json.put("author", _userHelper.user2json(activity.author())); 322 activity2json.put("date", DateUtils.zonedDateTimeToString(activity.date())); 323 activity2json.put("description", activity.description()); 324 return activity2json; 325 } 326 327 /** 328 * Record for an activity 329 * @param author The author. 330 * @param date the date of the action 331 * @param description the description if the activity as String or {@link I18nizableText} 332 */ 333 public record Activity(User author, ZonedDateTime date, Object description) { /* empty */ } 334 } 335 336}