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