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