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