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.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023import java.util.Optional;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.commons.lang.StringUtils;
030
031import org.ametys.core.right.RightManager;
032import org.ametys.core.right.RightManager.RightResult;
033import org.ametys.core.ui.Callable;
034import org.ametys.core.user.User;
035import org.ametys.core.user.UserManager;
036import org.ametys.core.util.I18nUtils;
037import org.ametys.core.util.mail.SendMailHelper;
038import org.ametys.plugins.forms.dao.FormDAO;
039import org.ametys.plugins.forms.repository.Form;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.runtime.config.Config;
042import org.ametys.runtime.i18n.I18nizableText;
043import org.ametys.runtime.plugin.component.AbstractLogEnabled;
044import org.ametys.web.repository.page.Page;
045import org.ametys.web.repository.page.SitemapElement;
046import org.ametys.web.repository.site.Site;
047import org.ametys.web.repository.site.SiteManager;
048
049import jakarta.mail.MessagingException;
050
051/**
052 * The helper to schedule opening of form
053 */
054public class FormInvitationsHelper extends AbstractLogEnabled implements Serviceable, Component
055{
056    /** Avalon ROLE. */
057    public static final String ROLE = FormInvitationsHelper.class.getName();
058    
059    /** Ametys object resolver. */
060    protected AmetysObjectResolver _resolver;
061    
062    /** The users manager */
063    protected UserManager _userManager;
064    
065    /** The site manager */
066    protected SiteManager _siteManager;
067    
068    /** The i18n utils */
069    protected I18nUtils _i18nUtils;
070    
071    /** The right manager */
072    protected RightManager _rightManager;
073    
074    /** The limited entries helper */
075    protected LimitedEntriesHelper _limitedEntriesHelper;
076    
077    /** The form DAO */
078    protected FormDAO _formDAO;
079    
080    public void service(ServiceManager manager) throws ServiceException
081    {
082        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
083        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
084        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
085        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
086        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
087        _limitedEntriesHelper = (LimitedEntriesHelper) manager.lookup(LimitedEntriesHelper.ROLE);
088        _formDAO = (FormDAO) manager.lookup(FormDAO.ROLE);
089    }
090    
091    /**
092     * Sends invitations emails.
093     * @param formId The id of the form.
094     * @param message The message content.
095     * @param siteName The site name.
096     * @param language the language
097     * @return An empty map
098     */
099    @Callable
100    public Map<String, Object> sendInvitations (String formId, String message, String siteName, String language)
101    {
102        Form form = _resolver.resolveById(formId);
103        if (_rightManager.currentUserHasRight(FormDAO.HANDLE_FORMS_RIGHT_ID, form) != RightResult.RIGHT_ALLOW)
104        {
105            throw new IllegalAccessError("Can't send invitations for form id '" + formId + "' without convenient right.");
106        }
107        
108        List<Page> pages = _getFormPages(formId, siteName);
109        if (pages.isEmpty())
110        {
111            throw new IllegalAccessError("Can't send invitations for form id '" + formId + "' because it is not published.");
112        }
113        
114        String subject = _getMailSubject(language);
115        String body = _getMailBody(formId, message, siteName, language);
116        
117        Site site = _siteManager.getSite(siteName);
118        String defaultFromValue = Config.getInstance().getValue("smtp.mail.from");
119        String from = site.getValue("site-mail-from", false, defaultFromValue);
120        for (User user : _getUsersToInvite(form))
121        {
122            try
123            {
124                String finalMessage = StringUtils.replace(body, "{name}", user.getFullName());
125                
126                SendMailHelper.newMail()
127                              .withSubject(subject)
128                              .withTextBody(finalMessage)
129                              .withSender(from)
130                              .withRecipient(user.getEmail())
131                              .sendMail();
132            }
133            catch (MessagingException | IOException e) 
134            {
135                getLogger().warn("Unable to send mail to user " + user.getEmail(), e);
136            }
137        }
138        
139        return new HashMap<>();
140    }
141
142    private List<Page> _getFormPages(String formId, String siteName)
143    {
144        List<Page> pages = _formDAO.getFormPage(formId, siteName)
145            .stream()
146            .filter(Page.class::isInstance)
147            .map(Page.class::cast)
148            .toList();
149        return pages;
150    }
151    
152    /**
153     * Get users to invite to the form (user with mail and who are not answered yet to the form)
154     * @param form the form
155     * @return the users to invite
156     */
157    protected List<User> _getUsersToInvite(Form form)
158    {
159        // Can invite users if the form is on anonymous read access
160        if (_rightManager.hasAnonymousReadAccess(form))
161        {
162            return List.of();
163        }
164        
165        return _rightManager.getReadAccessAllowedUsers(form).resolveAllowedUsers(true)
166                    .stream()
167                    .filter(id -> !_limitedEntriesHelper.hasUserAlreadyAnswer(form, id, null)) // User who are not answered yet
168                    .map(id -> _userManager.getUser(id)) // Get user object
169                    .filter(Objects::nonNull) // User not null
170                    .filter(u -> StringUtils.isNotEmpty(u.getEmail())) // User with a mail
171                    .toList();
172    }
173    
174    /**
175     * Get users to invite to the form (user with mail and who are not answered yet to the form)
176     * @param formId The id of the form.
177     * @return The number of users to invite
178     */
179    @Callable
180    public int getNbOfUsersToInvite(String formId)
181    {
182        Form form = _resolver.resolveById(formId);
183        return _getUsersToInvite(form).size();
184    }
185    
186    /**
187     * Get the email subject
188     * @param language the language
189     * @return The subject
190     */
191    protected String _getMailSubject (String language)
192    {
193        return _i18nUtils.translate(new I18nizableText("plugin.forms", "PLUGINS_FORMS_SEND_INVITATIONS_BOX_SUBJECT"), language);
194    }
195    
196    /**
197     * Get the email body
198     * @param formId The form id
199     * @param message The message
200     * @param siteName The site name
201     * @param language the language
202     * @return The text body
203     */
204    protected String _getMailBody (String formId, String message, String siteName, String language)
205    {
206        Site site = _siteManager.getSite(siteName);
207        String formURI = _getFormURI(formId, siteName, language);
208        
209        String replacedMessage = StringUtils.replace(message, "{link}", formURI);
210        replacedMessage = StringUtils.replace(replacedMessage, "{site}", site.getTitle());
211        
212        return replacedMessage;
213    }
214    
215    /**
216     * Get the form page uri
217     * @param formId The form id
218     * @param siteName The site name
219     * @param language the language
220     * @return The survey absolute uri
221     */
222    protected String _getFormURI (String formId, String siteName, String language)
223    {
224        List<Page> pages = _getFormPages(formId, siteName);
225        
226        Optional<Page> pageOptional = pages.stream()
227            .filter(p -> p.getSitemapName().equals(language))
228            .findAny();
229        
230        SitemapElement page = pageOptional.isPresent() ? pageOptional.get() : pages.get(0);
231        Site site = _siteManager.getSite(siteName);
232        return site.getUrl() + "/" + page.getSitemap().getName() + "/" + page.getPathInSitemap() + ".html";
233    }
234}