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