001/*
002 *  Copyright 2017 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.project.notification;
017
018import java.util.Collections;
019import java.util.List;
020import java.util.Map;
021import java.util.Objects;
022import java.util.Set;
023import java.util.stream.Collectors;
024
025import org.apache.commons.lang3.StringUtils;
026import org.apache.commons.lang3.tuple.Pair;
027
028import org.ametys.core.group.Group;
029import org.ametys.core.group.GroupIdentity;
030import org.ametys.core.observation.Event;
031import org.ametys.core.user.User;
032import org.ametys.core.user.UserIdentity;
033import org.ametys.plugins.workspaces.ObservationConstants;
034import org.ametys.plugins.workspaces.members.JCRProjectMember;
035import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType;
036import org.ametys.plugins.workspaces.members.ProjectMemberManager.ProjectMember;
037import org.ametys.plugins.workspaces.project.objects.Project;
038import org.ametys.runtime.config.Config;
039import org.ametys.runtime.i18n.I18nizableText;
040
041/**
042 * Notifier to send mail to a newly added member of a workspace.
043 */
044public class WelcomeMemberMailNotifierObserver extends AbstractMemberMailNotifierObserver
045{
046    @Override
047    public boolean supports(Event event)
048    {
049        return event.getId().equals(ObservationConstants.EVENT_MEMBER_ADDED)
050                && Config.getInstance().getValue("workspaces.member.added.send.notification", true, false); // Check feature enabled
051    }
052
053    @Override
054    protected Map<String, List<String>> getUserToNotifyByLanguage(Event event, Project project)
055    {
056        // async observer, we need to resolve the member
057        String memberId = (String) event.getArguments().get(ObservationConstants.ARGS_MEMBER_ID);
058        JCRProjectMember newMember = _resolver.resolveById(memberId);
059        
060        String defaultLanguage = _userLanguagesManager.getDefaultLanguage();
061        
062        // get user to notify
063        if (MemberType.USER == newMember.getType())
064        {
065            User user = _userManager.getUser(newMember.getUser());
066            String email = user.getEmail();
067            if (StringUtils.isNotEmpty(email) && _isUserMemberNewToProject(user.getIdentity(), project))
068            {
069                return Collections.singletonMap(StringUtils.defaultIfBlank(user.getLanguage(), defaultLanguage), List.of(email));
070            }
071        }
072        else if (MemberType.GROUP == newMember.getType())
073        {
074            GroupIdentity groupIdentity = newMember.getGroup();
075            Group group = _groupManager.getGroup(groupIdentity);
076            if (group != null && project != null)
077            {
078                List<User> newUsersInProject = _projectMemberManager.getGroupUsersFromProject(group, project, (currentProject, identity) -> _isGroupMemberNewToTheProject(currentProject, identity, groupIdentity));
079                
080                return newUsersInProject.stream()
081                        .map(user -> Pair.of(user, user.getEmail()))
082                        .filter(p -> StringUtils.isNotEmpty(p.getRight()))
083                        .collect(Collectors.groupingBy(
084                                p -> {
085                                    return StringUtils.defaultIfBlank(p.getLeft().getLanguage(), defaultLanguage);
086                                },
087                                Collectors.mapping(
088                                        Pair::getRight,
089                                        Collectors.toList()
090                                )
091                            )
092                        );
093            }
094        }
095        
096        return Map.of();
097    }
098    
099    @Override
100    protected I18nizableText getI18nSubject(Event event, Project project)
101    {
102        return new I18nizableText("plugin." + _pluginName, "PROJECT_MAIL_NOTIFICATION_EVENT_SUBJECT_MEMBER_ADDED", List.of(project.getTitle()));
103    }
104    
105    @Override
106    protected String getMailBodyURI(Event event, Project project)
107    {
108        return "cocoon://_plugins/workspaces/notification-mail-member-event";
109    }
110
111    private boolean _isGroupMemberNewToTheProject(Project project, UserIdentity userIdentity, GroupIdentity newGroupToIgnore)
112    {
113        Set<ProjectMember> projectMembers = _projectMemberManager.getProjectMembers(project, false);
114        boolean isUserOfProject = projectMembers.stream()
115                .filter(member -> MemberType.USER == member.getType())
116                .map(ProjectMember::getUser)
117                .map(User::getIdentity)
118                .filter(userIdentity::equals)
119                .findAny()
120                .isPresent();
121        
122        if (isUserOfProject)
123        {
124            // User from group was already part of the project, as a member of type "user"
125            return false;
126        }
127        
128        return _isUserMemberInAGroupMember(userIdentity, newGroupToIgnore, projectMembers);
129    }
130    
131    private boolean _isUserMemberNewToProject(UserIdentity userIdentity, Project project)
132    {
133        Set<ProjectMember> projectMembers = _projectMemberManager.getProjectMembers(project, false);
134        return _isUserMemberInAGroupMember(userIdentity, null, projectMembers);
135    }
136
137    private boolean _isUserMemberInAGroupMember(UserIdentity userIdentity, GroupIdentity newGroupToIgnore, Set<ProjectMember> projectMembers)
138    {
139        return projectMembers.stream()
140                .filter(member -> MemberType.GROUP == member.getType())
141                .map(ProjectMember::getGroup)
142                .filter(group -> newGroupToIgnore == null || !newGroupToIgnore.equals(group.getIdentity()))
143                .filter(Objects::nonNull)
144                .map(Group::getUsers)
145                .flatMap(Set::stream)
146                .filter(userIdentity::equals)
147                .findAny()
148                .isEmpty(); // User was not found in any group of the project, apart from the ignored group
149    }
150}