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.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 = _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            I18nizableText subject = adminEmailSubject.isPresent() 
334                    ? _replaceVariablesAndBreaks(form, user, adminEmailSubject.get(), lang) 
335                    : null;
336            
337            // Get body with details
338            Optional<String> adminEmailBody = form.getAdminEmailBody();
339            I18nizableText message = adminEmailBody.isPresent()
340                    ? _replaceVariablesAndBreaks(form, user, adminEmailBody.get(), lang) 
341                    : null;
342
343            String entryDetails = getMail("entry.html", entry, Map.of(), false);
344            
345            String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody()
346                    .withLanguage(lang)
347                    .withTitle(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, _i18nUtils.translate(subject, lang), 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 I18nizableText _replaceVariablesAndBreaks(Form form, User user, String mailText, String language)
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        text = text.replaceAll("\r?\n", "<br/>");
378        return new I18nizableText(text);
379    }
380    
381    /**
382     * Send limitation mail when the limit is reached
383     * @param entry the form entry
384     * @param adminEmails list of email address where to send the notification
385     * @param limitationType the type of limitation
386     */
387    public void sendLimitationReachedMailForAdmin(FormEntry entry, String[] adminEmails, LimitationMailType limitationType)
388    {
389        try
390        {
391            Form form = entry.getForm();
392            
393            String lang = _formDAO.getFormLocale(form);
394            String sender = Config.getInstance().getValue("smtp.mail.from");
395            
396            // Get subject
397            I18nizableText subject = limitationType == LimitationMailType.LIMIT
398                    ? new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_LIMIT_SUBJECT", List.of(form.getTitle(), form.getSite().getTitle()))
399                    : new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_QUEUE_SUBJECT", List.of(form.getTitle(), form.getSite().getTitle()));
400            
401            // Get body
402            I18nizableText message = limitationType == LimitationMailType.LIMIT
403                    ? new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_LIMIT_TEXT", List.of(form.getTitle()))
404                    : new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_QUEUE_TEXT", List.of(form.getTitle())); 
405            
406            String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody()
407                    .withLanguage(lang)
408                    .withTitle(subject)
409                    .withMessage(message)
410                    .build();
411            
412            // Get mail as text
413            I18nizableText textI18n = limitationType == LimitationMailType.LIMIT
414                    ? new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_LIMIT_TEXT_NO_HTML", List.of(form.getTitle()))
415                    : new I18nizableText("plugin.forms", "PLUGINS_FORMS_MAIL_QUEUE_TEXT_NO_HTML", List.of(form.getTitle()));
416            String text = _i18nUtils.translate(textI18n, lang);
417            
418            for (String email : adminEmails)
419            {
420                if (StringUtils.isNotEmpty(email))
421                {
422                    _sendMail(form, entry, _i18nUtils.translate(subject, lang), prettyHtmlBody, text, sender, email, false, false);
423                }
424            }
425        }
426        catch (IOException e)
427        {
428            getLogger().error("Error creating the limit message.", e);
429        }
430    }
431    
432    /**
433     * Send the receipt email.
434     * @param form the form.
435     * @param entry the current entry
436     */
437    public void sendReceiptEmail(Form form, FormEntry entry)
438    {
439        if (form.getReceiptSender().isPresent())
440        {
441            Optional<String> receiver = getReceiver(entry, form.getReceiptReceiver());
442            if (receiver.isPresent())
443            {
444                String lang = _formDAO.getFormLocale(form);
445                
446                String sender = form.getReceiptSender().get();
447                String subject = form.getReceiptSubject().get();
448                String bodyTxt = form.getReceiptBody().get();
449                String bodyHTML = bodyTxt.replaceAll("\r?\n", "<br/>");
450                
451                try
452                {
453                    String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody()
454                            .withLanguage(lang)
455                            .withTitle(subject)
456                            .withMessage(bodyHTML)
457                            .build();
458
459                    // Always check reading restriction for the receipt email
460                    _sendMail(form, entry, subject, prettyHtmlBody, bodyTxt, sender, receiver.get(), true, true);
461                }
462                catch (IOException e)
463                {
464                    getLogger().error("An error occurred sending receipt mail to '{}'", receiver.get(), e);
465                }
466            }
467        }
468    }
469    
470    /**
471     * Send mail when the form entry if out of the queue
472     * @param form the form
473     * @param entry the form entry
474     */
475    public void sendOutOfQueueMail(Form form, FormEntry entry)
476    {
477        Optional<String> receiver = getReceiver(entry, form.getQueueMailReceiver());
478        if (receiver.isPresent())
479        {
480            String lang = _formDAO.getFormLocale(form);
481            
482            String sender = form.getQueueMailSender().get();
483            String subject = form.getQueueMailSubject().get();
484            String bodyTxt = form.getQueueMailBody().get();
485            String bodyHTML = bodyTxt.replaceAll("\r?\n", "<br/>");
486            
487            try
488            {
489                String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody()
490                        .withLanguage(lang)
491                        .withTitle(subject)
492                        .withMessage(bodyHTML)
493                        .build();
494
495                // Always check reading restriction for the receipt email
496                _sendMail(form, entry, subject, prettyHtmlBody, bodyTxt, sender, receiver.get(), true, true);
497            }
498            catch (IOException e)
499            {
500                getLogger().error("An error occurred sending mail to get out of the queue to '{}'", receiver.get(), e);
501            }
502        }
503    }
504    
505    /**
506     * Send invitation mails to the users
507     * @param form the form
508     * @param users the users to receive the mail
509     * @param message the invitation message
510     */
511    public void sendInvitationMails(Form form, List<User> users, String message)
512    {
513        String lang = _formDAO.getFormLocale(form);
514        String sender = Config.getInstance().getValue("smtp.mail.from");
515        
516        // Get subject
517        I18nizableText subject = new I18nizableText("plugin.forms", "PLUGINS_FORMS_SEND_INVITATIONS_BOX_SUBJECT");
518        
519        // Get body
520        Site site = form.getSite();
521        String formURI = _getFormURI(form, lang);
522        
523        String replacedMessage = StringUtils.replace(message, "{site}", site.getTitle());
524        String textMessage = StringUtils.replace(replacedMessage, "{link}", formURI);
525        String htmlMessage = StringUtils.replace(replacedMessage, "{link}", "<a href='" + formURI + "'>" + formURI + "</a>");
526        
527        for (User user : users)
528        {
529            try
530            {
531                String finalTextMessage = StringUtils.replace(textMessage, "{name}", user.getFullName());
532                String finalHtmlMessage = StringUtils.replace(htmlMessage, "{name}", user.getFullName());
533                String prettyHtmlBody = StandardMailBodyHelper.newHTMLBody()
534                        .withLanguage(lang)
535                        .withTitle(subject)
536                        .withMessage(finalHtmlMessage)
537                        .withLink(formURI, new I18nizableText("plugin.forms", "PLUGINS_FORMS_SEND_INVITATIONS_LINK_LABEL"))
538                        .build();
539                
540                _sendMail(form, null, _i18nUtils.translate(subject, lang), prettyHtmlBody, finalTextMessage, sender, user.getEmail(), false, true);
541            }
542            catch (IOException e) 
543            {
544                getLogger().error("Unable to send invitation mail to user {}", user.getEmail(), e);
545            }
546        }
547    }
548    
549    /**
550     * Get the form page URI
551     * @param form the form
552     * @param language the language
553     * @return the form page URI
554     */
555    protected String _getFormURI (Form form, String language)
556    {
557        Site site = form.getSite();
558        Optional<Page> page = _formDAO.getFormPage(form.getId(), site.getName())
559            .stream()
560            .filter(Page.class::isInstance)
561            .map(Page.class::cast)
562            .filter(p -> p.getSitemapName().equals(language))
563            .findAny();
564        
565        return page.map(p -> site.getUrl() + "/" + p.getSitemap().getName() + "/" + p.getPathInSitemap() + ".html")
566                   .orElse(null);
567    }
568    
569    /**
570     * Get a mail pipeline's content.
571     * @param resource the mail resource pipeline 
572     * @param entry the user input.
573     * @param additionalParameters the additional parameters
574     * @param withReadingRestriction <code>true</code> to enable reading restriction for form data in the mail
575     * @return the mail content.
576     * @throws IOException if an error occurs.
577     */
578    public String getMail(String resource, FormEntry entry, Map<String, Object> additionalParameters, boolean withReadingRestriction) throws IOException
579    {
580        Source src = null;
581        Request request = ContextHelper.getRequest(_context);
582        Form form = entry.getForm();
583        
584        try
585        {
586            request.setAttribute(IGNORE_RIGHT_KEY, true);
587            
588            String uri = "cocoon:/mail/entry/" + resource;
589            Map<String, Object> parameters = new HashMap<>();
590            parameters.put("formId", form.getId());
591            parameters.put("entryId", entry.getId());
592            parameters.put(READ_RESTRICTION_ENABLE, withReadingRestriction);
593
594            parameters.putAll(additionalParameters);
595            
596            src = _sourceResolver.resolveURI(uri, null, parameters);
597            Reader reader = new InputStreamReader(src.getInputStream(), "UTF-8");
598            return IOUtils.toString(reader);
599        }
600        finally
601        {
602            request.setAttribute(IGNORE_RIGHT_KEY, false);
603            _sourceResolver.release(src);
604        }
605    }
606    
607    /**
608     * Get the files of a user input.
609     * @param form The current form
610     * @param entry The current entry
611     * @param onlyWritableQuestion <code>true</code> to have only writable question
612     * @param onlyReadableQuestion <code>true</code> to have only readable question
613     * @return the files submitted by the user.
614     */
615    public Collection<NamedStream> getEntryFiles(Form form, FormEntry entry, boolean onlyWritableQuestion, boolean onlyReadableQuestion)
616    {
617        Optional<Long> currentStepId = entry.getForm().hasWorkflow() ? Optional.of(_formEntryDAO.getCurrentStepId(entry)) : Optional.empty();
618        return _formQuestionDAO.getRuleFilteredQuestions(form, new FormEntryValues(null, entry), currentStepId, onlyWritableQuestion, onlyReadableQuestion).stream()
619            .filter(q -> q.getType() instanceof FileQuestionType)
620            .map(q -> entry.<File>getValue(q.getNameForForm()))
621            .filter(Objects::nonNull)
622            .map(f -> new NamedStream(f.getInputStream(), f.getName(), f.getMimeType()))
623            .collect(Collectors.toList());
624    }
625    
626    private void _sendMail(Form form, FormEntry entry, String subject, String html, String text, String sender, String recipient, boolean addFormInformation, boolean withReadingRestriction)
627    {
628        try
629        {
630            String htmlBody = html;
631            String textBody = text;
632        
633            Collection<NamedStream> records = entry != null ? getEntryFiles(form, entry, false, false) : new ArrayList<>();
634            try
635            {
636                if (addFormInformation)
637                {
638                    if (textBody.contains(_FORM_ENTRY_PATTERN))
639                    {
640                        String entry2text = getMail("entry.txt", entry, Map.of(), withReadingRestriction);
641                        textBody = StringUtils.replace(textBody, _FORM_ENTRY_PATTERN, entry2text);
642                    }
643                    
644                    if (htmlBody.contains(_FORM_ENTRY_PATTERN))
645                    {
646                        String entry2html = getMail("entry.html", entry, Map.of(), withReadingRestriction);
647                        htmlBody = StringUtils.replace(htmlBody, _FORM_ENTRY_PATTERN, entry2html);
648                    }
649                }
650                
651                MailBuilder mailBuilder = SendMailHelper.newMail()
652                    .withAsync(true)
653                    .withSubject(subject)
654                    .withHTMLBody(htmlBody)
655                    .withTextBody(textBody)
656                    .withSender(sender)
657                    .withRecipient(recipient);
658                
659                if (!records.isEmpty())
660                {
661                    mailBuilder = mailBuilder.withAttachmentsAsStream(records);
662                }
663                mailBuilder.sendMail();
664            }
665            finally 
666            {
667                for (NamedStream record : records)
668                {
669                    IOUtils.close(record.inputStream());
670                }
671            }
672        }
673        catch (MessagingException | IOException e)
674        {
675            getLogger().error("Error sending the mail to " + recipient, e);
676        }
677    }
678}