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