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