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        // get user to notify
061        if (MemberType.USER == newMember.getType())
062        {
063            User user = _userManager.getUser(newMember.getUser());
064            String email = user.getEmail();
065            if (StringUtils.isNotEmpty(email) && _isUserMemberNewToProject(user.getIdentity(), project))
066            {
067                return Collections.singletonMap(user.getLanguage(), List.of(email));
068            }
069        }
070        else if (MemberType.GROUP == newMember.getType())
071        {
072            GroupIdentity groupIdentity = newMember.getGroup();
073            Group group = _groupManager.getGroup(groupIdentity);
074            if (group != null && project != null)
075            {
076                List<User> newUsersInProject = _projectMemberManager.getGroupUsersFromProject(group, project, (currentProject, identity) -> _isGroupMemberNewToTheProject(currentProject, identity, groupIdentity));
077                
078                return newUsersInProject.stream()
079                        .map(user -> Pair.of(user, user.getEmail()))
080                        .filter(p -> StringUtils.isNotEmpty(p.getRight()))
081                        .collect(Collectors.groupingBy(
082                                p -> {
083                                    return StringUtils.defaultString(p.getLeft().getLanguage());
084                                },
085                                Collectors.mapping(
086                                        Pair::getRight,
087                                        Collectors.toList()
088                                )
089                            )
090                        );
091            }
092        }
093        
094        return Map.of();
095    }
096    
097    @Override
098    protected I18nizableText getI18nSubject(Event event, Project project)
099    {
100        return new I18nizableText("plugin." + _pluginName, "PROJECT_MAIL_NOTIFICATION_EVENT_SUBJECT_MEMBER_ADDED", List.of(project.getTitle()));
101    }
102    
103    @Override
104    protected String getMailBodyURI(Event event, Project project)
105    {
106        return "cocoon://_plugins/workspaces/notification-mail-member-event";
107    }
108
109    private boolean _isGroupMemberNewToTheProject(Project project, UserIdentity userIdentity, GroupIdentity newGroupToIgnore)
110    {
111        Set<ProjectMember> projectMembers = _projectMemberManager.getProjectMembers(project, false);
112        boolean isUserOfProject = projectMembers.stream()
113                .filter(member -> MemberType.USER == member.getType())
114                .map(ProjectMember::getUser)
115                .map(User::getIdentity)
116                .filter(userIdentity::equals)
117                .findAny()
118                .isPresent();
119        
120        if (isUserOfProject)
121        {
122            // User from group was already part of the project, as a member of type "user"
123            return false;
124        }
125        
126        return _isUserMemberInAGroupMember(userIdentity, newGroupToIgnore, projectMembers);
127    }
128    
129    private boolean _isUserMemberNewToProject(UserIdentity userIdentity, Project project)
130    {
131        Set<ProjectMember> projectMembers = _projectMemberManager.getProjectMembers(project, false);
132        return _isUserMemberInAGroupMember(userIdentity, null, projectMembers);
133    }
134
135    private boolean _isUserMemberInAGroupMember(UserIdentity userIdentity, GroupIdentity newGroupToIgnore, Set<ProjectMember> projectMembers)
136    {
137        return projectMembers.stream()
138                .filter(member -> MemberType.GROUP == member.getType())
139                .map(ProjectMember::getGroup)
140                .filter(group -> newGroupToIgnore == null || !newGroupToIgnore.equals(group.getIdentity()))
141                .filter(Objects::nonNull)
142                .map(Group::getUsers)
143                .flatMap(Set::stream)
144                .filter(userIdentity::equals)
145                .findAny()
146                .isEmpty(); // User was not found in any group of the project, apart from the ignored group
147    }
148}