001/*
002 *  Copyright 2018 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.workspaces.members;
017
018import java.io.IOException;
019import java.io.UnsupportedEncodingException;
020import java.net.URLEncoder;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import javax.jcr.RepositoryException;
030
031import org.apache.avalon.framework.component.Component;
032import org.apache.avalon.framework.configuration.Configurable;
033import org.apache.avalon.framework.configuration.Configuration;
034import org.apache.avalon.framework.configuration.ConfigurationException;
035import org.apache.avalon.framework.context.Context;
036import org.apache.avalon.framework.context.ContextException;
037import org.apache.avalon.framework.context.Contextualizable;
038import org.apache.avalon.framework.service.ServiceException;
039import org.apache.avalon.framework.service.ServiceManager;
040import org.apache.avalon.framework.service.Serviceable;
041import org.apache.cocoon.components.ContextHelper;
042import org.apache.cocoon.environment.Request;
043import org.apache.commons.lang3.StringUtils;
044
045import org.ametys.cms.languages.Language;
046import org.ametys.cms.languages.LanguagesManager;
047import org.ametys.cms.transformation.xslt.ResolveURIComponent;
048import org.ametys.core.ui.Callable;
049import org.ametys.core.user.CurrentUserProvider;
050import org.ametys.core.user.User;
051import org.ametys.core.user.UserIdentity;
052import org.ametys.core.user.UserManager;
053import org.ametys.core.user.directory.NotUniqueUserException;
054import org.ametys.core.user.directory.UserDirectory;
055import org.ametys.core.user.population.PopulationContextHelper;
056import org.ametys.core.util.I18nUtils;
057import org.ametys.core.util.mail.SendMailHelper;
058import org.ametys.plugins.core.user.UserHelper;
059import org.ametys.plugins.repository.AmetysObjectIterable;
060import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
061import org.ametys.plugins.workspaces.members.MembersWorkspaceModule.Invitation;
062import org.ametys.plugins.workspaces.project.ProjectManager;
063import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
064import org.ametys.plugins.workspaces.project.objects.Project;
065import org.ametys.plugins.workspaces.project.rights.ProjectRightHelper;
066import org.ametys.runtime.authentication.AccessDeniedException;
067import org.ametys.runtime.i18n.I18nizableText;
068import org.ametys.runtime.i18n.I18nizableTextParameter;
069import org.ametys.runtime.plugin.component.AbstractLogEnabled;
070import org.ametys.runtime.plugin.component.PluginAware;
071import org.ametys.web.WebConstants;
072import org.ametys.web.WebHelper;
073import org.ametys.web.repository.page.Page;
074import org.ametys.web.repository.page.Zone;
075import org.ametys.web.repository.page.ZoneItem;
076import org.ametys.web.repository.site.Site;
077import org.ametys.web.repository.site.SiteManager;
078import org.ametys.web.usermanagement.UserManagementException;
079import org.ametys.web.usermanagement.UserManagementException.StatusError;
080import org.ametys.web.usermanagement.UserSignUpConfiguration;
081import org.ametys.web.usermanagement.UserSignupManager;
082
083import jakarta.mail.MessagingException;
084
085/**
086 * Helper for invitations by email
087 *
088 */
089public class ProjectInvitationHelper extends AbstractLogEnabled implements Serviceable, Component, Configurable, PluginAware, Contextualizable
090{
091    /** The role */
092    public static final String ROLE = ProjectInvitationHelper.class.getName();
093    
094    private static final String __MAIL_PROJECT_EMAIL_PATTERN = "${email}";
095    private static final String __MAIL_PROJECT_TOKEN_PATTERN = "${token}";
096    
097    private ProjectManager _projectManager;
098    private UserSignUpConfiguration _signupConfig;
099    private UserSignupManager _signupManager;
100    private SiteManager _siteManager;
101    private WorkspaceModuleExtensionPoint _moduleEP;
102    private CurrentUserProvider _currentUserProvider;
103    private UserManager _userManager;
104    private UserHelper _userHelper;
105    private ProjectRightHelper _projectRightsHelper;
106    private ProjectMemberManager _projectMemberManager;
107    private PopulationContextHelper _populationContextHelper;
108    private LanguagesManager _languagesManager;
109    
110    private String _subjectKeyForInvitation;
111    private String _textBodyKeyForInvitation;
112    private String _htmlBodyKeyForInvitation;
113    private String _subjectKeyForInvitationAccepted;
114    private String _textBodyKeyForInvitationAccepted;
115    private String _htmlBodyKeyForInvitationAccepted;
116
117    private I18nUtils _i18nUtils;
118
119    private String _pluginName;
120
121    private Context _context;
122
123
124    public void setPluginInfo(String pluginName, String featureName, String id)
125    {
126        _pluginName = pluginName;
127    }
128    
129    public void contextualize(Context context) throws ContextException
130    {
131        _context = context;
132    }
133    
134    @Override
135    public void service(ServiceManager serviceManager) throws ServiceException
136    {
137        _projectManager = (ProjectManager) serviceManager.lookup(ProjectManager.ROLE);
138        _projectRightsHelper = (ProjectRightHelper) serviceManager.lookup(ProjectRightHelper.ROLE);
139        _projectMemberManager = (ProjectMemberManager) serviceManager.lookup(ProjectMemberManager.ROLE);
140        _signupConfig = (UserSignUpConfiguration) serviceManager.lookup(UserSignUpConfiguration.ROLE);
141        _signupManager = (UserSignupManager) serviceManager.lookup(UserSignupManager.ROLE);
142        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
143        _moduleEP = (WorkspaceModuleExtensionPoint) serviceManager.lookup(WorkspaceModuleExtensionPoint.ROLE);
144        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
145        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
146        _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE);
147        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
148        _populationContextHelper = (PopulationContextHelper) serviceManager.lookup(PopulationContextHelper.ROLE);
149        _languagesManager = (LanguagesManager) serviceManager.lookup(LanguagesManager.ROLE);
150    }
151    
152    @Override
153    public void configure(Configuration configuration) throws ConfigurationException
154    {
155        _subjectKeyForInvitation = configuration.getChild("invitation-email-subject").getValue(null);
156        _textBodyKeyForInvitation = configuration.getChild("invitation-email-text-body").getValue(null);
157        _htmlBodyKeyForInvitation = configuration.getChild("invitation-email-html-body").getValue(null);
158        
159        _subjectKeyForInvitationAccepted = configuration.getChild("invitation-accepted-email-subject").getValue(null);
160        _textBodyKeyForInvitationAccepted = configuration.getChild("invitation-accepted-email-text-body").getValue(null);
161        _htmlBodyKeyForInvitationAccepted = configuration.getChild("invitation-accepted-email-html-body").getValue(null);
162    }
163    
164    /**
165     * Invite emails to be member of a project
166     * @param projectName The project name
167     * @param emails The emails
168     * @param allowedProfileByModule the allowed profiles by module
169     * @return The result
170     * @throws UserManagementException if failed to invit user
171     * @throws NotUniqueUserException if many users match the given email
172     */
173    public Map<String, Object> inviteEmails(String projectName, List<String> emails, Map<String, String> allowedProfileByModule) throws UserManagementException, NotUniqueUserException
174    {
175        Request request = ContextHelper.getRequest(_context);
176        String siteName = WebHelper.getSiteName(request);
177        String sitemapLanguage = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME);
178        
179        Project project = _projectManager.getProject(projectName);
180        Map<String, String> emailConfiguration = _getInvitationEmailConfiguration(project, sitemapLanguage);
181        String mailSubject = emailConfiguration.get("subject");
182        String mailBody = emailConfiguration.get("bodyText");
183        
184        return inviteEmails(projectName, siteName, sitemapLanguage, emails, allowedProfileByModule, mailSubject, mailBody);
185    }
186    /**
187     * Invite emails to be member of a project
188     * @param projectName The project name
189     * @param siteName The site name
190     * @param lang the current language
191     * @param emails The emails
192     * @param allowedProfileByModule the allowed profiles by module
193     * @param mailSubject The subject of mail
194     * @param mailBody The body of mail
195     * @return The result
196     * @throws UserManagementException if failed to invit user
197     * @throws NotUniqueUserException if many users match the given email
198     */
199    @Callable
200    public Map<String, Object> inviteEmails(String projectName, String siteName, String lang, List<String> emails, Map<String, String> allowedProfileByModule, String mailSubject, String mailBody) throws UserManagementException, NotUniqueUserException
201    {
202        Map<String, Object> result = new HashMap<>();
203        
204        result.put("existing-users", new ArrayList<Map<String, Object>>());
205        result.put("email-success", new ArrayList<String>());
206        result.put("email-error", new ArrayList<String>());
207        
208        Project project = _projectManager.getProject(projectName);
209        if (project != null)
210        {
211            MembersWorkspaceModule module = _moduleEP.getModule(MembersWorkspaceModule.MEMBERS_MODULE_ID);
212            if (module != null && _projectManager.isModuleActivated(project, module.getId()))
213            {
214                if (!_projectRightsHelper.canAddMember(project))
215                {
216                    throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to send invitations without sufficient right."); 
217                }
218                
219                String catalogSiteName = _projectManager.getCatalogSiteName();
220                
221                UserDirectory userDirectory = _getUserDirectoryForSignup(result, catalogSiteName, lang);
222                if (userDirectory != null)
223                {
224                    String populationId = userDirectory.getPopulationId();
225                    String userDirectoryId = userDirectory.getId();
226                    
227                    for (String email : emails)
228                    {
229                        try
230                        {
231                            _signupManager.inviteToSignup(catalogSiteName, lang, email, populationId, userDirectoryId, null, null, false, false, false);
232                            
233                            if (_addOrUpdateInvitation(project, catalogSiteName, module, email, allowedProfileByModule, populationId, userDirectoryId, mailSubject, mailBody))
234                            {
235                                @SuppressWarnings("unchecked")
236                                List<String> emailSuccess = (List<String>) result.get("email-success");
237                                emailSuccess.add(email);
238                                
239                            }
240                        }
241                        catch (UserManagementException e)
242                        {
243                            switch (e.getStatusError())
244                            {
245                                case USER_ALREADY_EXISTS:
246                                    User user = _getUser(catalogSiteName, email);
247                                    
248                                    Map<String, Object> user2json = _userHelper.user2json(user, true);
249                                    
250                                    @SuppressWarnings("unchecked")
251                                    List<Map<String, Object>> existingUsers = (List<Map<String, Object>>) result.get("existing-users");
252                                    existingUsers.add(user2json);
253                                    break;
254                                    
255                                case TEMP_USER_ALREADY_EXISTS:
256                                    // if mail has already been invited, re-created the invitation
257                                    if (_addOrUpdateInvitation(project, catalogSiteName, module, email, allowedProfileByModule, populationId, userDirectoryId, mailSubject, mailBody))
258                                    {
259                                        @SuppressWarnings("unchecked")
260                                        List<String> emailSuccess = (List<String>) result.get("email-success");
261                                        emailSuccess.add(email);
262                                        
263                                    }
264                                    break;
265                                    
266                                default:
267                                    getLogger().error("Cannot invite " + email, e);
268                                    @SuppressWarnings("unchecked")
269                                    List<String> emailErrors = (List<String>) result.get("email-error");
270                                    emailErrors.add(email);
271                                    break;
272                            }
273                        }
274                    }
275                    
276                    @SuppressWarnings("unchecked")
277                    List<String> emailSuccess = (List<String>) result.get("email-success");
278                    if (emailSuccess.size() == emails.size())
279                    {
280                        result.put("success", true);
281                    }
282                }
283            }
284        }
285        else
286        {
287            result.put("success", false);
288            result.put("unknown-project", projectName);
289        }
290        
291        return result;
292    }
293    
294    private User _getUser(String siteName, String email) throws NotUniqueUserException
295    {
296        Set<String> populations = _populationContextHelper.getUserPopulationsOnContexts(Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName), false);
297        for (String population : populations)
298        {
299            User user = _userManager.getUser(population, email);
300            if (user == null)
301            {
302                user = _userManager.getUserByEmail(population, email);
303            }
304            
305            if (user != null)
306            {
307                return user;
308            }
309        }
310        
311        return null;
312    }
313    
314    /**
315     * Get the configuration to invite users by emails
316     * @param projectName The current project
317     * @param lang the current language
318     * @return the configuration for email invitations
319     */
320    @Callable
321    public Map<String, Object> getInvitationConfiguration(String projectName, String lang)
322    {
323        Project project = _projectManager.getProject(projectName);
324        
325        Map<String, Object> config = new HashMap<>();
326        
327        // Check the configuration is valid for invitations
328        String catalogSiteName = _projectManager.getCatalogSiteName();
329        if (_getUserDirectoryForSignup(config, catalogSiteName, lang) == null)
330        {
331            config.remove("success");
332            config.put("allowed", false); 
333            return config;
334        }
335        
336        // Check the current user has right to invite users
337        if (!_projectRightsHelper.canAddMember(project))
338        {
339            config.put("allowed", false); 
340            config.put("error", "no-right"); 
341            return config;
342        }
343        
344        config.put("allowed", true);
345        config.put("email", _getInvitationEmailConfiguration(project, lang));
346        config.put("rights", _projectRightsHelper.getProjectRightsData(projectName)); 
347        
348        return config;
349    }
350    
351    private Map<String, String> _getInvitationEmailConfiguration(Project project, String lang)
352    {
353        Map<String, String> emailConfig = new HashMap<>();
354        
355        Map<String, I18nizableTextParameter> i18nparams = new HashMap<>();
356        i18nparams.put("projectTitle", new I18nizableText(project.getTitle()));
357        i18nparams.put("projectUrl", new I18nizableText(_projectManager.getProjectUrl(project, StringUtils.EMPTY)));
358        i18nparams.put("nbDays", new I18nizableText(String.valueOf(_signupConfig.getTokenValidity())));
359        i18nparams.put("nbDays", new I18nizableText(String.valueOf(_signupConfig.getTokenValidity())));
360        
361        String catalogSiteName = _projectManager.getCatalogSiteName();
362        Site catalogSite = _siteManager.getSite(catalogSiteName);
363        i18nparams.put("catalogSiteTitle", new I18nizableText(catalogSite.getTitle()));
364        i18nparams.put("catalogSiteUrl", new I18nizableText(catalogSite.getUrl()));
365        
366        Page signupPage = _getSignupPage(_projectManager.getCatalogSiteName(), lang);
367        if (signupPage != null)
368        {
369            String signupUri = ResolveURIComponent.resolve("page", signupPage.getId(), false, true) + "?email=${email}&token=${token}";
370            i18nparams.put("signupUri", new I18nizableText(signupUri));
371        }
372        
373        String subject = getSubjectForInvitationEmail(i18nparams, lang);
374        if (subject != null)
375        {
376            emailConfig.put("subject", subject);
377        }
378        String bodyTxt = getTextBodyForInvitationEmail(i18nparams, lang);
379        if (bodyTxt != null)
380        {
381            emailConfig.put("bodyText", bodyTxt);
382        }
383        String bodyHtml = getHtmlBodyForInvitationEmail(i18nparams, lang);
384        if (bodyHtml != null)
385        {
386            emailConfig.put("bodyHtml", bodyHtml);
387        }
388        
389        return emailConfig;
390    }
391
392    private Page _getSignupPage(String catalogSiteName, String lang)
393    {
394        Page signupPage = _signupManager.getSignupPage(catalogSiteName, lang);
395
396        if (signupPage == null)
397        {
398            signupPage = _signupManager.getSignupPage(catalogSiteName, "en");
399        }
400        
401        if (signupPage == null)
402        {
403            Map<String, Language> availableLanguages = _languagesManager.getAvailableLanguages();
404            for (Language availableLanguage : availableLanguages.values())
405            {
406                if (signupPage == null)
407                {
408                    signupPage = _signupManager.getSignupPage(catalogSiteName, availableLanguage.getCode());
409                }
410            }
411        }
412        return signupPage;
413    }
414    
415    private void _sendInvitationMail (String catalogSiteName, String email, String populationId, String userDirectoryId, String mailSubject, String mailBodyTpl) throws UserManagementException
416    {
417        String bodyTxt = mailBodyTpl;
418        
419        String encodedEmail;
420        try
421        {
422            encodedEmail = URLEncoder.encode(email, "UTF-8");
423        }
424        catch (UnsupportedEncodingException e)
425        {
426            // Should never happen.
427            throw new UserManagementException("Encoding error while sending a sign-up confirmation e-mail.", StatusError.MAIL_ERROR, e);
428        }
429
430        String token = _signupManager.getToken(catalogSiteName, email, populationId, userDirectoryId);
431        
432        bodyTxt = StringUtils.replace(bodyTxt, __MAIL_PROJECT_TOKEN_PATTERN, token);
433        bodyTxt = StringUtils.replace(bodyTxt, __MAIL_PROJECT_EMAIL_PATTERN, encodedEmail);
434        
435        getLogger().debug("Sending signup invitation e-mail to {}", email);
436        
437        Site site = _siteManager.getSite(catalogSiteName);
438        String from = site.getValue("site-mail-from");
439
440        try
441        {
442            List<String> errorReport = new ArrayList<>();
443            
444            // Send the e-mail.
445            SendMailHelper.newMail()
446                          .withSubject(mailSubject)
447                          .withTextBody(bodyTxt)
448                          .withSender(from)
449                          .withRecipient(email)
450                          .withErrorReport(errorReport)
451                          .sendMail();
452            
453            if (errorReport.contains(email))
454            {
455                throw new UserManagementException("Error sending the sign-up confirmation mail.", StatusError.MAIL_ERROR);
456            }
457        }
458        catch (MessagingException | IOException e)
459        {
460            throw new UserManagementException("Error sending the sign-up confirmation mail.", StatusError.MAIL_ERROR, e);
461        }
462    }
463    
464    private boolean _addOrUpdateInvitation(Project project, String catalogSiteName, MembersWorkspaceModule module, String email, Map<String, String> allowedProfileByModules, String populationId, String userDirectoryId, String mailSubject, String mailBody) throws UserManagementException
465    {
466        try
467        {
468            // First remove invitation if exists
469            module.removeInvitation(project, email);
470            
471            // Add invitations with temporary rights
472            module.addInvitation(project, new Date(), email, _currentUserProvider.getUser(), allowedProfileByModules);
473            
474            project.saveChanges();
475
476            _sendInvitationMail(catalogSiteName, email, populationId, userDirectoryId, mailSubject, mailBody);
477            
478            return true;
479        }
480        catch (RepositoryException e)
481        {
482            getLogger().error("Fail to store invitation for email " + email, e);
483            return false;
484        }
485    }
486    
487    private UserDirectory _getUserDirectoryForSignup(Map<String, Object> result, String catalogSiteName, String lang)
488    {
489        if (catalogSiteName == null)
490        {
491            getLogger().error("The catalog's site name is not configured. User invitations can not be activated.");
492            result.put("success", false);
493            result.put("error", "invalid-configuration");
494            return null;
495        }
496        
497        if (!_signupManager.isSignupAllowed(catalogSiteName))
498        {
499            getLogger().warn("Signup is disabled for the catalog's site.");
500            result.put("success", false);
501            return null;
502        }
503        
504        Page signupPage = _getSignupPage(catalogSiteName, lang);
505        if (signupPage == null)
506        {
507            getLogger().error("The catalog's site does not contain the signup service for language " + lang + ". User invitations can not be activated.");
508            result.put("success", false);
509            result.put("error", "invalid-configuration");
510            return null;
511        }
512        
513        UserDirectory userDirectory = _getUserDirectoryForSignup(signupPage);
514        if (userDirectory == null)
515        {
516            getLogger().error("There is no user directory configured for users signup. Please check the sign up service of catalog's site.");
517            result.put("success", false);
518            result.put("error", "invalid-configuration");
519            return null;
520        }
521        
522        return userDirectory;
523            
524    }
525    
526    private UserDirectory _getUserDirectoryForSignup(Page signupPage)
527    {
528        for (Zone zone : signupPage.getZones())
529        {
530            try (AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems())
531            {
532                for (ZoneItem zoneItem : zoneItems)
533                {
534                    UserDirectory userDirectory = _signupManager.getUserDirectory(zoneItem);
535                    if (userDirectory != null)
536                    {
537                        return userDirectory;
538                    }
539                }
540            }
541        }
542        
543        return null;
544        
545    }
546    
547    /**
548     * Add user as member of all project where it has been invited
549     * @param user the new user
550     */
551    public void createMemberFromInvitations(User user)
552    {
553        Request request = ContextHelper.getRequest(_context);
554        String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
555        
556        try
557        {
558            // Force default workspace
559            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, "default");
560            
561            MembersWorkspaceModule memberModule = (MembersWorkspaceModule) _moduleEP.getExtension(MembersWorkspaceModule.MEMBERS_MODULE_ID);
562            
563            List<Invitation> invitations = memberModule.getInvitations(user.getEmail());
564            
565            for (Invitation invitation : invitations)
566            {
567                Map<String, String> allowedProfileByModules = invitation.getAllowedProfileByModules();
568                String projectName = invitation.getProjectName();
569                
570                Project project = _projectManager.getProject(projectName);
571                
572                _projectMemberManager.addOrUpdateProjectMember(project, user.getIdentity(), allowedProfileByModules, invitation.getAuthor());
573                
574                // Notify author that invitation was accepted
575                UserIdentity author = invitation.getAuthor();
576                sendInvitationAcceptedMail(project, _userManager.getUser(author), user);
577                
578                try
579                {
580                    // Remove invitation
581                    memberModule.removeInvitation(project, invitation.getEmail());
582                }
583                catch (RepositoryException e)
584                {
585                    getLogger().error("Failed to remove invitation " + invitation, e);
586                }
587            }
588        }
589        finally 
590        {
591            // Restore current workspace
592            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace);
593        }
594    
595    }
596    
597    /**
598     * Send email to the user who initiated the invitation
599     * @param project The project
600     * @param invitAuthor The author of invitation
601     * @param newMember The new member
602     */
603    protected void sendInvitationAcceptedMail(Project project, User invitAuthor, User newMember)
604    {
605        if (invitAuthor != null)
606        {
607            Site site = project.getSite();
608            String from = site.getValue("site-mail-from");
609            String email = invitAuthor.getEmail();
610            
611            if (StringUtils.isNotEmpty(email))
612            {
613                // Prepare mail.
614                Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
615                i18nParams.put("projectTitle", new I18nizableText(project.getTitle()));
616                i18nParams.put("projectUrl", new I18nizableText(_projectManager.getProjectUrl(project, StringUtils.EMPTY)));
617                i18nParams.put("newMember", new I18nizableText(newMember.getFullName()));
618                i18nParams.put("newMemberMail", new I18nizableText(newMember.getEmail()));
619                
620                Set<Page> memberPages = _projectManager.getModulePages(project, MembersWorkspaceModule.MEMBERS_MODULE_ID);
621                if (!memberPages.isEmpty())
622                {
623                    Page page = memberPages.iterator().next();
624                    i18nParams.put("membersPageUri", new I18nizableText(ResolveURIComponent.resolve("page", page.getId(), false, true)));
625                }
626
627                String subject = getSubjectForInvitationAcceptedEmail(i18nParams, null);
628                String textBody = getTextBodyForInvitationAcceptedEmail(i18nParams, null);
629                String htmlBody = getHtmlBodyForInvitationAcceptedEmail(i18nParams, null);
630
631                try
632                {
633                    // Send the e-mail.
634                    SendMailHelper.newMail()
635                                  .withSubject(subject)
636                                  .withHTMLBody(htmlBody)
637                                  .withTextBody(textBody)
638                                  .withSender(from)
639                                  .withRecipient(email)
640                                  .sendMail();
641                }
642                catch (MessagingException | IOException e)
643                {
644                    getLogger().error("Error sending the invitation accepted email.", e);
645                }
646            }
647        }
648    }
649    
650    /**
651     * The email subject for invitation by email
652     * @param defaultI18nParams The default i18n parameters
653     * @param language the language
654     * @return the email subject for invitation by email
655     */
656    public String getSubjectForInvitationEmail (Map<String, I18nizableTextParameter> defaultI18nParams , String language)
657    {
658        if (StringUtils.isNotBlank(_subjectKeyForInvitation))
659        {
660            return _i18nUtils.translate(_getI18nizableText(_subjectKeyForInvitation, defaultI18nParams), language);
661        }
662        
663        return null;
664    }
665
666    /**
667     * The email text body for invitation by email
668     * @param defaultI18nParams The default i18n parameters with :
669     * siteName the site name
670     * email the mail
671     * token the token
672     * confirmUri the confirmation uri
673     * siteTitle the site title
674     * siteUrl the site url
675     * @param language the language
676     * @return the email text for invitation by email
677     */
678    public String getTextBodyForInvitationEmail(Map<String, I18nizableTextParameter> defaultI18nParams, String language)
679    {
680        if (StringUtils.isNotBlank(_textBodyKeyForInvitation))
681        {
682            return _i18nUtils.translate(_getI18nizableText(_textBodyKeyForInvitation, defaultI18nParams), language);
683        }
684        
685        return null;
686    }
687
688    /**
689     * The email html body for invitation by email
690     * @param defaultI18nParams The default i18n parameters with :
691     * siteName the site name
692     * email the mail
693     * token the token
694     * confirmUri the confirmation uri
695     * siteTitle the site title
696     * siteUrl the site url
697     * @param language the language
698     * @return the email html for invitation by email
699     */
700    public String getHtmlBodyForInvitationEmail(Map<String, I18nizableTextParameter> defaultI18nParams, String language)
701    {
702        if (StringUtils.isNotBlank(_htmlBodyKeyForInvitation))
703        {
704            return _i18nUtils.translate(_getI18nizableText(_htmlBodyKeyForInvitation, defaultI18nParams), language);
705        }
706        
707        return null;
708    }
709    
710    /**
711     * The email subject for invitation by email
712     * @param defaultI18nParams The default i18n parameters
713     * @param language the language
714     * @return the email subject for invitation by email
715     */
716    public String getSubjectForInvitationAcceptedEmail (Map<String, I18nizableTextParameter> defaultI18nParams , String language)
717    {
718        if (StringUtils.isNotBlank(_subjectKeyForInvitationAccepted))
719        {
720            return _i18nUtils.translate(_getI18nizableText(_subjectKeyForInvitationAccepted, defaultI18nParams), language);
721        }
722        
723        return null;
724    }
725    
726    /**
727     * The email text body for invitation by email
728     * @param defaultI18nParams The default i18n parameters with :
729     * @param language the language
730     * @return the email text for invitation by email
731     */
732    public String getTextBodyForInvitationAcceptedEmail(Map<String, I18nizableTextParameter> defaultI18nParams, String language)
733    {
734        if (StringUtils.isNotBlank(_textBodyKeyForInvitationAccepted))
735        {
736            return _i18nUtils.translate(_getI18nizableText(_textBodyKeyForInvitationAccepted, defaultI18nParams), language);
737        }
738        
739        return null;
740    }
741    
742    /**
743     * The email html body for invitation by email
744     * @param defaultI18nParams The default i18n parameters with :
745     * @param language the language
746     * @return the email html for invitation by email
747     */
748    public String getHtmlBodyForInvitationAcceptedEmail(Map<String, I18nizableTextParameter> defaultI18nParams, String language)
749    {
750        if (StringUtils.isNotBlank(_htmlBodyKeyForInvitationAccepted))
751        {
752            return _i18nUtils.translate(_getI18nizableText(_htmlBodyKeyForInvitationAccepted, defaultI18nParams), language);
753        }
754        
755        return null;
756    }
757    
758    /**
759     * Get the {@link I18nizableText} from the configured key and i18n parameters
760     * @param fullI18nKey the configured i18n key
761     * @param i18nParams the i18n parameters
762     * @return the i18nizable text
763     */
764    protected I18nizableText _getI18nizableText(String fullI18nKey, Map<String, I18nizableTextParameter> i18nParams)
765    {
766        String catalogue = StringUtils.contains(fullI18nKey, ":") ? StringUtils.substringBefore(fullI18nKey, ":") : "plugin." + _pluginName;
767        String i18nKey = StringUtils.contains(fullI18nKey, ":") ? StringUtils.substringAfter(fullI18nKey, ":") : fullI18nKey;
768        
769        return new I18nizableText(catalogue, i18nKey, i18nParams);
770    }
771    
772}