001/* 002 * Copyright 2022 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.forms.helper; 017 018import java.io.IOException; 019import java.io.InputStreamReader; 020import java.io.Reader; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.stream.Collectors; 029 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.context.Context; 032import org.apache.avalon.framework.context.ContextException; 033import org.apache.avalon.framework.context.Contextualizable; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037import org.apache.cocoon.components.ContextHelper; 038import org.apache.cocoon.environment.Request; 039import org.apache.commons.io.IOUtils; 040import org.apache.commons.lang.StringUtils; 041import org.apache.commons.lang3.ArrayUtils; 042import org.apache.excalibur.source.Source; 043import org.apache.excalibur.source.SourceResolver; 044 045import org.ametys.cms.data.File; 046import org.ametys.core.ui.Callable; 047import org.ametys.core.ui.mail.StandardMailBodyHelper; 048import org.ametys.core.user.User; 049import org.ametys.core.user.UserIdentity; 050import org.ametys.core.user.UserManager; 051import org.ametys.core.util.I18nUtils; 052import org.ametys.core.util.mail.SendMailHelper; 053import org.ametys.core.util.mail.SendMailHelper.MailBuilder; 054import org.ametys.core.util.mail.SendMailHelper.NamedStream; 055import org.ametys.plugins.forms.dao.FormDAO; 056import org.ametys.plugins.forms.question.FormQuestionType; 057import org.ametys.plugins.forms.question.sources.AbstractSourceType; 058import org.ametys.plugins.forms.question.sources.ManualWithEmailSourceType; 059import org.ametys.plugins.forms.question.sources.UsersSourceType; 060import org.ametys.plugins.forms.question.types.ChoicesListQuestionType; 061import org.ametys.plugins.forms.question.types.FileQuestionType; 062import org.ametys.plugins.forms.question.types.SimpleTextQuestionType; 063import org.ametys.plugins.forms.repository.Form; 064import org.ametys.plugins.forms.repository.FormEntry; 065import org.ametys.plugins.forms.repository.FormQuestion; 066import org.ametys.plugins.forms.rights.FormsDirectoryRightAssignmentContext; 067import org.ametys.plugins.repository.AmetysObjectResolver; 068import org.ametys.runtime.config.Config; 069import org.ametys.runtime.i18n.I18nizableText; 070import org.ametys.runtime.plugin.component.AbstractLogEnabled; 071import org.ametys.web.repository.page.Page; 072import org.ametys.web.repository.site.Site; 073 074import jakarta.mail.MessagingException; 075 076/** 077 * The helper for form mail dialog 078 */ 079public class FormMailHelper extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 080{ 081 082 /** Avalon ROLE. */ 083 public static final String ROLE = FormMailHelper.class.getName(); 084 085 /** The param key for read restriction enable */ 086 public static final String READ_RESTRICTION_ENABLE = "read-restriction-enable"; 087 088 /** The value for entry user in the receiver combobox */ 089 public static final String RECEIVER_COMBOBOX_ENTRY_USER_VALUE = "entry-user"; 090 091 /** The empty value in the receiver combobox */ 092 public static final String RECEIVER_COMBOBOX_INPUT_ONLY = "input-only"; 093 094 /** The request key to ignore right */ 095 public static final String IGNORE_RIGHT_KEY = "ignore-right"; 096 097 /** Pattern for adding entry in acknowledgement of receipt if present in body */ 098 protected static final String _FORM_ENTRY_PATTERN = "{form}"; 099 100 /** Ametys object resolver. */ 101 protected AmetysObjectResolver _resolver; 102 103 /** I18n Utils */ 104 protected I18nUtils _i18nUtils; 105 106 /** The user manager */ 107 protected UserManager _userManager; 108 109 /** The source resolver. */ 110 protected SourceResolver _sourceResolver; 111 112 /** The context */ 113 protected Context _context; 114 115 /** The analyse of files for virus helper */ 116 protected FormDAO _formDAO; 117 118 /** The form admin mail helper */ 119 protected FormAdminMailsHelper _formAdminMailsHelper; 120 121 /** 122 * The type of limitation for the mail 123 */ 124 public enum LimitationMailType 125 { 126 /** The mail for queue */ 127 QUEUE, 128 /** The mail for the limit */ 129 LIMIT; 130 } 131 132 public void service(ServiceManager manager) throws ServiceException 133 { 134 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 135 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 136 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 137 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 138 _formDAO = (FormDAO) manager.lookup(FormDAO.ROLE); 139 _formAdminMailsHelper = (FormAdminMailsHelper) manager.lookup(FormAdminMailsHelper.ROLE); 140 } 141 142 public void contextualize(Context context) throws ContextException 143 { 144 _context = context; 145 } 146 147 /** 148 * Get the fields with email regex constraint so they can be used as receivers address for form admin mail 149 * @param formId Id of the form 150 * @return a map of the form question, key is the question id,value is the question title 151 */ 152 @Callable (rights = FormDAO.HANDLE_FORMS_RIGHT_ID, rightContext = FormsDirectoryRightAssignmentContext.ID, paramIndex = 0) 153 public Map<String, Object> getAvailableAdminReceiverFields(String formId) 154 { 155 List<Object> receiverfields = new ArrayList<>(); 156 157 for (FormQuestion question : getQuestionWithMail(formId)) 158 { 159 Map<String, String> properties = new HashMap<>(); 160 properties.put("id", question.getNameForForm()); 161 properties.put("title", question.getTitle()); 162 receiverfields.add(properties); 163 } 164 165 return Map.of("data", receiverfields); 166 } 167 168 /** 169 * Get the fields with email regex constraint so they can be used as receivers address for form receipt mail 170 * @param formId Id of the form 171 * @return a map of the form question, key is the question id,value is the question title 172 */ 173 @SuppressWarnings("unchecked") 174 @Callable (rights = FormDAO.HANDLE_FORMS_RIGHT_ID, rightContext = FormsDirectoryRightAssignmentContext.ID, paramIndex = 0) 175 public Map<String, Object> getAvailableReceiverFields(String formId) 176 { 177 Map<String, Object> results = getAvailableAdminReceiverFields(formId); 178 List<Object> receiverfields = (List<Object>) results.get("data"); 179 180 Map<String, String> properties = new HashMap<>(); 181 properties.put("id", RECEIVER_COMBOBOX_ENTRY_USER_VALUE); 182 properties.put("title", _i18nUtils.translate(new I18nizableText("plugin.forms", "PLUGINS_FORMS_ACKNOWLEDGEMENT_RECEIPT_ENTRY_USER"))); 183 receiverfields.add(properties); 184 185 return Map.of("data", receiverfields); 186 } 187 188 /** 189 * Get the list of questions which return a mail 190 * @param formId the form id 191 * @return the list of questions 192 */ 193 public List<FormQuestion> getQuestionWithMail(String formId) 194 { 195 List<FormQuestion> questions = new ArrayList<>(); 196 Form form = _resolver.resolveById(formId); 197 198 for (FormQuestion question : form.getQuestions()) 199 { 200 FormQuestionType type = question.getType(); 201 if (type instanceof SimpleTextQuestionType) 202 { 203 if (question.hasValue(SimpleTextQuestionType.ATTRIBUTE_REGEXP) && question.getValue(SimpleTextQuestionType.ATTRIBUTE_REGEXP).equals(SimpleTextQuestionType.EMAIL_REGEX_VALUE)) 204 { 205 questions.add(question); 206 } 207 } 208 else if (type instanceof ChoicesListQuestionType cLType && (cLType.getSourceType(question) instanceof UsersSourceType || cLType.getSourceType(question) instanceof ManualWithEmailSourceType)) 209 { 210 questions.add(question); 211 } 212 } 213 214 return questions; 215 } 216 217 /** 218 * Get all the email addresses from manual entry and/or a form field 219 * @param form the form 220 * @param entry the last entry 221 * @return an array of all the admin emails 222 */ 223 public String[] getAdminEmails(Form form, FormEntry entry) 224 { 225 Optional<String[]> adminEmails = form.getAdminEmails(); 226 Optional<String> otherAdminEmails = form.getOtherAdminEmails(); 227 String[] emailsAsArray = adminEmails.isPresent() ? adminEmails.get() : ArrayUtils.EMPTY_STRING_ARRAY; 228 Optional<String> otherEmailSource = getReceiver(entry, otherAdminEmails); 229 String[] mergedEmails = emailsAsArray; 230 if (otherEmailSource.isPresent()) 231 { 232 String otherEmails = otherEmailSource.get(); 233 String[] emails = otherEmails.split("[ ,;\r]"); 234 List<String> validEmails = _formAdminMailsHelper.getValidAdminEmails(emails); 235 if (!validEmails.isEmpty()) 236 { 237 mergedEmails = ArrayUtils.addAll(emailsAsArray, validEmails.toArray(new String[validEmails.size()])); 238 } 239 else 240 { 241 getLogger().error("Mails addresses " + otherEmails + " did not match regex"); 242 } 243 } 244 return mergedEmails; 245 } 246 247 /** 248 * Get the receiver from the entry 249 * @param entry the entry 250 * @param receiverPath the path to get receiver 251 * @return the receiver from the entry 252 */ 253 public Optional<String> getReceiver(FormEntry entry, Optional<String> receiverPath) 254 { 255 if (receiverPath.isEmpty()) 256 { 257 return Optional.empty(); 258 } 259 260 if (receiverPath.get().equals(RECEIVER_COMBOBOX_ENTRY_USER_VALUE)) 261 { 262 UserIdentity userIdentity = entry.getUser(); 263 User user = _userManager.getUser(userIdentity); 264 return Optional.ofNullable(user != null ? user.getEmail() : null); 265 } 266 else 267 { 268 FormQuestion question = entry.getForm().getQuestion(receiverPath.get()); 269 FormQuestionType type = question.getType(); 270 if (type instanceof SimpleTextQuestionType) 271 { 272 return Optional.ofNullable(entry.getValue(receiverPath.get())); 273 } 274 else if (type instanceof ChoicesListQuestionType cLType) 275 { 276 if (cLType.getSourceType(question) instanceof UsersSourceType && !entry.isMultiple(receiverPath.get())) 277 { 278 UserIdentity userIdentity = entry.getValue(receiverPath.get()); 279 if (userIdentity != null) 280 { 281 User user = _userManager.getUser(userIdentity); 282 return user != null ? Optional.ofNullable(user.getEmail()) : Optional.empty(); 283 } 284 } 285 else if (cLType.getSourceType(question) instanceof ManualWithEmailSourceType manualWithEmail) 286 { 287 String value = entry.getValue(receiverPath.get()); 288 Map<String, Object> enumParam = new HashMap<>(); 289 enumParam.put(AbstractSourceType.QUESTION_PARAM_KEY, question); 290 try 291 { 292 return Optional.ofNullable(manualWithEmail.getEntryEmail(value, enumParam)); 293 } 294 catch (Exception e) 295 { 296 getLogger().error("Could not get email from question '" + question.getNameForForm() + "' with value '" + value + "'"); 297 } 298 } 299 } 300 301 return Optional.empty(); 302 } 303 } 304 305 /** 306 * Send the notification emails. 307 * @param form the form. 308 * @param entry the user input. 309 * @param adminEmails list of email address where to send the notification 310 */ 311 public void sendEmailsForAdmin(Form form, FormEntry entry, String[] adminEmails) 312 { 313 try 314 { 315 String lang = _formDAO.getFormLocale(form); 316 String sender = Config.getInstance().getValue("smtp.mail.from"); 317 UserIdentity userIdentity = entry.getUser(); 318 User user = userIdentity != null ? _userManager.getUser(userIdentity) : null; 319 320 // Get subject 321 Optional<String> adminEmailSubject = form.getAdminEmailSubject(); 322 I18nizableText subject = adminEmailSubject.isPresent() 323 ? _replaceVariablesAndBreaks(form, user, adminEmailSubject.get(), lang) 324 : null; 325 326 // Get body with details 327 Optional<String> adminEmailBody = form.getAdminEmailBody(); 328 I18nizableText message = adminEmailBody.isPresent() 329 ? _replaceVariablesAndBreaks(form, user, adminEmailBody.get(), lang) 330 : null; 331 332 String entryDetails = getMail("entry.html", entry, Map.of(), false); 333 334 String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody() 335 .withLanguage(lang) 336 .withTitle(subject) 337 .withMessage(message) 338 .withDetails(new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_RESULTS_DETAILS_TITLE"), entryDetails, false) 339 .build(); 340 341 // Get mail as text 342 String params = "?type=results&form-name=" + form.getTitle() + "&locale=" + lang; 343 String text = getMail("results.txt" + params, entry, Map.of(), false); 344 345 // Send mails 346 for (String email : adminEmails) 347 { 348 if (StringUtils.isNotEmpty(email)) 349 { 350 _sendMail(form, entry, _i18nUtils.translate(subject, lang), prettyHtmlBody, text, sender, email, false, false); 351 } 352 } 353 } 354 catch (IOException e) 355 { 356 getLogger().error("Error creating the notification message.", e); 357 } 358 } 359 360 private I18nizableText _replaceVariablesAndBreaks(Form form, User user, String mailText, String language) 361 { 362 String text = mailText; 363 text = StringUtils.replace(text, "{site}", form.getSite().getTitle()); 364 text = StringUtils.replace(text, "{user}", user != null ? user.getFullName() : _i18nUtils.translate(new I18nizableText("plugin.forms", "PLUGINS_FORMS_ADMIN_EMAILS_USER_ANONYMOUS"), language)); 365 text = StringUtils.replace(text, "{title}", form.getTitle()); 366 text = text.replaceAll("\r?\n", "<br/>"); 367 return new I18nizableText(text); 368 } 369 370 /** 371 * Send limitation mail when the limit is reached 372 * @param entry the form entry 373 * @param adminEmails list of email address where to send the notification 374 * @param limitationType the type of limitation 375 */ 376 public void sendLimitationReachedMailForAdmin(FormEntry entry, String[] adminEmails, LimitationMailType limitationType) 377 { 378 try 379 { 380 Form form = entry.getForm(); 381 382 String lang = _formDAO.getFormLocale(form); 383 String sender = Config.getInstance().getValue("smtp.mail.from"); 384 385 // Get subject 386 I18nizableText subject = limitationType == LimitationMailType.LIMIT 387 ? new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_LIMIT_SUBJECT", List.of(form.getTitle(), form.getSite().getTitle())) 388 : new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_QUEUE_SUBJECT", List.of(form.getTitle(), form.getSite().getTitle())); 389 390 // Get body 391 I18nizableText message = limitationType == LimitationMailType.LIMIT 392 ? new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_LIMIT_TEXT", List.of(form.getTitle())) 393 : new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_QUEUE_TEXT", List.of(form.getTitle())); 394 395 String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody() 396 .withLanguage(lang) 397 .withTitle(subject) 398 .withMessage(message) 399 .build(); 400 401 // Get mail as text 402 I18nizableText textI18n = limitationType == LimitationMailType.LIMIT 403 ? new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_LIMIT_TEXT_NO_HTML", List.of(form.getTitle())) 404 : new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_QUEUE_TEXT_NO_HTML", List.of(form.getTitle())); 405 String text = _i18nUtils.translate(textI18n, lang); 406 407 for (String email : adminEmails) 408 { 409 if (StringUtils.isNotEmpty(email)) 410 { 411 _sendMail(form, entry, _i18nUtils.translate(subject, lang), prettyHtmlBody, text, sender, email, false, false); 412 } 413 } 414 } 415 catch (IOException e) 416 { 417 getLogger().error("Error creating the limit message.", e); 418 } 419 } 420 421 /** 422 * Send the receipt email. 423 * @param form the form. 424 * @param entry the current entry 425 */ 426 public void sendReceiptEmail(Form form, FormEntry entry) 427 { 428 if (form.getReceiptSender().isPresent()) 429 { 430 Optional<String> receiver = getReceiver(entry, form.getReceiptReceiver()); 431 if (receiver.isPresent()) 432 { 433 String lang = _formDAO.getFormLocale(form); 434 435 String sender = form.getReceiptSender().get(); 436 String subject = form.getReceiptSubject().get(); 437 String bodyTxt = form.getReceiptBody().get(); 438 String bodyHTML = bodyTxt.replaceAll("\r?\n", "<br/>"); 439 440 try 441 { 442 String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody() 443 .withLanguage(lang) 444 .withTitle(subject) 445 .withMessage(bodyHTML) 446 .build(); 447 448 // Always check reading restriction for the receipt email 449 _sendMail(form, entry, subject, prettyHtmlBody, bodyTxt, sender, receiver.get(), true, true); 450 } 451 catch (IOException e) 452 { 453 getLogger().error("An error occurred sending receipt mail to '{}'", receiver.get(), e); 454 } 455 } 456 } 457 } 458 459 /** 460 * Send mail when the form entry if out of the queue 461 * @param form the form 462 * @param entry the form entry 463 */ 464 public void sendOutOfQueueMail(Form form, FormEntry entry) 465 { 466 Optional<String> receiver = getReceiver(entry, form.getQueueMailReceiver()); 467 if (receiver.isPresent()) 468 { 469 String lang = _formDAO.getFormLocale(form); 470 471 String sender = form.getQueueMailSender().get(); 472 String subject = form.getQueueMailSubject().get(); 473 String bodyTxt = form.getQueueMailBody().get(); 474 String bodyHTML = bodyTxt.replaceAll("\r?\n", "<br/>"); 475 476 try 477 { 478 String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody() 479 .withLanguage(lang) 480 .withTitle(subject) 481 .withMessage(bodyHTML) 482 .build(); 483 484 // Always check reading restriction for the receipt email 485 _sendMail(form, entry, subject, prettyHtmlBody, bodyTxt, sender, receiver.get(), true, true); 486 } 487 catch (IOException e) 488 { 489 getLogger().error("An error occurred sending mail to get out of the queue to '{}'", receiver.get(), e); 490 } 491 } 492 } 493 494 /** 495 * Send invitation mails to the users 496 * @param form the form 497 * @param users the users to receive the mail 498 * @param message the invitation message 499 */ 500 public void sendInvitationMails(Form form, List<User> users, String message) 501 { 502 String lang = _formDAO.getFormLocale(form); 503 String sender = Config.getInstance().getValue("smtp.mail.from"); 504 505 // Get subject 506 I18nizableText subject = new I18nizableText("plugin.forms", "PLUGINS_FORMS_SEND_INVITATIONS_BOX_SUBJECT"); 507 508 // Get body 509 Site site = form.getSite(); 510 String formURI = _getFormURI(form, lang); 511 512 String replacedMessage = StringUtils.replace(message, "{site}", site.getTitle()); 513 String textMessage = StringUtils.replace(replacedMessage, "{link}", formURI); 514 String htmlMessage = StringUtils.replace(replacedMessage, "{link}", "<a href='" + formURI + "'>" + formURI + "</a>"); 515 516 for (User user : users) 517 { 518 try 519 { 520 String finalTextMessage = StringUtils.replace(textMessage, "{name}", user.getFullName()); 521 String finalHtmlMessage = StringUtils.replace(htmlMessage, "{name}", user.getFullName()); 522 String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody() 523 .withLanguage(lang) 524 .withTitle(subject) 525 .withMessage(finalHtmlMessage) 526 .withLink(formURI, new I18nizableText("plugin.forms", "PLUGINS_FORMS_SEND_INVITATIONS_LINK_LABEL")) 527 .build(); 528 529 _sendMail(form, null, _i18nUtils.translate(subject, lang), prettyHtmlBody, finalTextMessage, sender, user.getEmail(), false, true); 530 } 531 catch (IOException e) 532 { 533 getLogger().error("Unable to send invitation mail to user {}", user.getEmail(), e); 534 } 535 } 536 } 537 538 /** 539 * Get the form page URI 540 * @param form the form 541 * @param language the language 542 * @return the form page URI 543 */ 544 protected String _getFormURI (Form form, String language) 545 { 546 Site site = form.getSite(); 547 Optional<Page> page = _formDAO.getFormPage(form.getId(), site.getName()) 548 .stream() 549 .filter(Page.class::isInstance) 550 .map(Page.class::cast) 551 .filter(p -> p.getSitemapName().equals(language)) 552 .findAny(); 553 554 return page.map(p -> site.getUrl() + "/" + p.getSitemap().getName() + "/" + p.getPathInSitemap() + ".html") 555 .orElse(null); 556 } 557 558 /** 559 * Get a mail pipeline's content. 560 * @param resource the mail resource pipeline 561 * @param entry the user input. 562 * @param additionalParameters the additional parameters 563 * @param withReadingRestriction <code>true</code> to enable reading restriction for form data in the mail 564 * @return the mail content. 565 * @throws IOException if an error occurs. 566 */ 567 public String getMail(String resource, FormEntry entry, Map<String, Object> additionalParameters, boolean withReadingRestriction) throws IOException 568 { 569 Source src = null; 570 Request request = ContextHelper.getRequest(_context); 571 Form form = entry.getForm(); 572 573 try 574 { 575 request.setAttribute(IGNORE_RIGHT_KEY, true); 576 577 String uri = "cocoon:/mail/entry/" + resource; 578 Map<String, Object> parameters = new HashMap<>(); 579 parameters.put("formId", form.getId()); 580 parameters.put("entryId", entry.getId()); 581 parameters.put(READ_RESTRICTION_ENABLE, withReadingRestriction); 582 583 parameters.putAll(additionalParameters); 584 585 src = _sourceResolver.resolveURI(uri, null, parameters); 586 Reader reader = new InputStreamReader(src.getInputStream(), "UTF-8"); 587 return IOUtils.toString(reader); 588 } 589 finally 590 { 591 request.setAttribute(IGNORE_RIGHT_KEY, false); 592 _sourceResolver.release(src); 593 } 594 } 595 596 /** 597 * Get the files of a user input. 598 * @param form The current form 599 * @param entry The current entry 600 * @return the files submitted by the user. 601 */ 602 protected Collection<NamedStream> _getFiles(Form form, FormEntry entry) 603 { 604 return form.getQuestions() 605 .stream() 606 .filter(q -> q.getType() instanceof FileQuestionType) 607 .map(q -> entry.<File>getValue(q.getNameForForm())) 608 .filter(Objects::nonNull) 609 .map(f -> new NamedStream(f.getInputStream(), f.getName(), f.getMimeType())) 610 .collect(Collectors.toList()); 611 } 612 613 private void _sendMail(Form form, FormEntry entry, String subject, String html, String text, String sender, String recipient, boolean addFormInformation, boolean withReadingRestriction) 614 { 615 try 616 { 617 String htmlBody = html; 618 String textBody = text; 619 620 Collection<NamedStream> records = entry != null ? _getFiles(form, entry) : new ArrayList<>(); 621 try 622 { 623 if (addFormInformation) 624 { 625 if (textBody.contains(_FORM_ENTRY_PATTERN)) 626 { 627 String entry2text = getMail("entry.txt", entry, Map.of(), withReadingRestriction); 628 textBody = StringUtils.replace(textBody, _FORM_ENTRY_PATTERN, entry2text); 629 } 630 631 if (htmlBody.contains(_FORM_ENTRY_PATTERN)) 632 { 633 String entry2html = getMail("entry.html", entry, Map.of(), withReadingRestriction); 634 htmlBody = StringUtils.replace(htmlBody, _FORM_ENTRY_PATTERN, entry2html); 635 } 636 } 637 638 MailBuilder mailBuilder = SendMailHelper.newMail() 639 .withAsync(true) 640 .withSubject(subject) 641 .withHTMLBody(htmlBody) 642 .withTextBody(textBody) 643 .withSender(sender) 644 .withRecipient(recipient); 645 646 if (!records.isEmpty()) 647 { 648 mailBuilder = mailBuilder.withAttachmentsAsStream(records); 649 } 650 mailBuilder.sendMail(); 651 } 652 finally 653 { 654 for (NamedStream record : records) 655 { 656 IOUtils.close(record.inputStream()); 657 } 658 } 659 } 660 catch (MessagingException | IOException e) 661 { 662 getLogger().error("Error sending the mail to " + recipient, e); 663 } 664 } 665}