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