001/*
002 *  Copyright 2023 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.activities;
017
018import java.io.ByteArrayOutputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Map;
024import java.util.Objects;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034import org.apache.cocoon.components.ContextHelper;
035import org.apache.cocoon.environment.Request;
036import org.apache.commons.lang3.StringUtils;
037import org.apache.commons.lang3.tuple.Pair;
038import org.apache.excalibur.source.Source;
039import org.apache.excalibur.source.SourceResolver;
040import org.apache.excalibur.source.SourceUtil;
041
042import org.ametys.core.right.RightManager;
043import org.ametys.core.user.UserIdentity;
044import org.ametys.core.user.UserManager;
045import org.ametys.core.util.I18nUtils;
046import org.ametys.core.util.language.UserLanguagesManager;
047import org.ametys.plugins.repository.AmetysObject;
048import org.ametys.plugins.repository.AmetysObjectResolver;
049import org.ametys.plugins.repository.activities.Activity;
050import org.ametys.plugins.workspaces.WorkspacesHelper;
051import org.ametys.plugins.workspaces.project.ProjectManager;
052import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper;
053import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper.Frequency;
054import org.ametys.plugins.workspaces.project.objects.Project;
055import org.ametys.runtime.config.Config;
056import org.ametys.runtime.i18n.I18nizableText;
057import org.ametys.runtime.plugin.component.PluginAware;
058import org.ametys.web.WebConstants;
059import org.ametys.web.activities.notify.ActivityNotifier;
060import org.ametys.web.renderingcontext.RenderingContext;
061import org.ametys.web.renderingcontext.RenderingContextHandler;
062import org.ametys.web.repository.site.Site;
063
064/**
065 * Abstract class representing a activity notifier for workspaces
066 */
067public abstract class AbstractWorkspacesActivityNotifier implements ActivityNotifier, Serviceable, PluginAware, Contextualizable
068{
069    /** The right manager */
070    protected RightManager _rightManager;
071    
072    /** The notification preference helper */
073    protected NotificationPreferencesHelper _notificationPreferenceHelper;
074    
075    /** The user manager */
076    protected UserManager _userManager;
077    
078    /** The project manager */
079    protected ProjectManager _projectManager;
080    
081    /** The i18n utils */
082    protected I18nUtils _i18nUtils;
083    
084    /** The rendering context handler */
085    protected RenderingContextHandler _renderingContextHandler;
086    
087    /** The source resolver */
088    protected SourceResolver _srcResolver;
089    
090    /** The Ametys Object resolver */
091    protected AmetysObjectResolver _resolver;
092    
093    /** The user languages manager */
094    protected UserLanguagesManager _userLanguagesManager;
095    
096    /** The workspaces helper */
097    protected WorkspacesHelper _workspacesHelper;
098    
099    /** The plugin name */
100    protected String _pluginName;
101
102    /** The context */
103    protected Context _context;
104    
105    public void service(ServiceManager manager) throws ServiceException
106    {
107        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
108        _notificationPreferenceHelper = (NotificationPreferencesHelper) manager.lookup(NotificationPreferencesHelper.ROLE);
109        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
110        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
111        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
112        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
113        _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
114        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
115        _userLanguagesManager = (UserLanguagesManager) manager.lookup(UserLanguagesManager.ROLE);
116        _workspacesHelper = (WorkspacesHelper) manager.lookup(WorkspacesHelper.ROLE);
117    }
118    
119    public void contextualize(Context context) throws ContextException
120    {
121        _context = context;
122    }
123    
124    public void setPluginInfo(String pluginName, String featureName, String id)
125    {
126        _pluginName = pluginName;
127    }
128    
129    @Override
130    public Map<String, List<String>> getUsersToNotifyByLanguage(Activity activity)
131    {
132        boolean returnAll = Config.getInstance().getValue("runtime.mail.massive.sending");
133        
134        String projectName = activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME);
135        Project project = _projectManager.getProject(projectName);
136        String defaultLanguage = StringUtils.defaultIfBlank(_workspacesHelper.getLang(project), _userLanguagesManager.getDefaultLanguage());
137        
138        // FIXME GG right should be provided directly by the activity
139        AmetysObject aObject = getTargetAmetysObject(activity);
140        Set<UserIdentity> readAccessUsers = _rightManager.getReadAccessAllowedUsers(aObject).resolveAllowedUsers(returnAll);
141        
142        Map<String, List<String>> usersByLanguage =  readAccessUsers.stream()
143            .filter(userId -> _notificationPreferenceHelper.askedToBeNotified(userId, activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME), Frequency.EACH))
144            .map(_userManager::getUser)
145            .filter(Objects::nonNull)
146            .map(user -> Pair.of(user.getLanguage(), user.getEmail()))
147            .filter(p -> StringUtils.isNotEmpty(p.getRight()))
148            .collect(Collectors.groupingBy(
149                    p -> {
150                        return StringUtils.defaultIfBlank(p.getLeft(), defaultLanguage);
151                    },
152                    Collectors.mapping(
153                            Pair::getRight,
154                            Collectors.toList()
155                    )
156                )
157            );
158        
159        return usersByLanguage;
160    }
161    
162    /**
163     * Retrieve the ametys object targeted by this activity.
164     * This method is intended for right computation purposes.
165     * @param activity the activity
166     * @return the target
167     */
168    // FIXME this should not exist and the activity itself should provide the right. (probably by a right convertor)
169    public abstract AmetysObject getTargetAmetysObject(Activity activity);
170
171    @Override
172    public String getMailSubject(Activity activity, String language)
173    {
174        return _i18nUtils.translate(new I18nizableText("plugin." + _pluginName, _getSubjectI18nKey(activity), getSubjectI18nParams(activity)), language);
175    }
176    
177    /**
178     * Get the subject i18n key
179     * @param activity the activity
180     * @return the subject i18n key
181     */
182    protected String _getSubjectI18nKey(Activity activity)
183    {
184        return "PROJECT_MAIL_NOTIFICATION_SUBJECT_" + StringUtils.replaceChars(activity.getEventType().toUpperCase(), '.', '_');
185    }
186
187    /**
188     * Get the subject i18n parameters
189     * @param activity the activity
190     * @return the subject i18n parameters
191     */
192    public List<String> getSubjectI18nParams(Activity activity)
193    {
194        List<String> i18nparams = new ArrayList<>();
195        i18nparams.add(activity.getValue(AbstractWorkspacesActivityType.PROJECT_TITLE)); // {0}
196        return i18nparams;
197    }
198    
199    @Override
200    public String getMailTextBody(Activity activity, String language)
201    {
202        // No text body
203        return null;
204    }
205    
206    @Override
207    public String getMailHtmlBody(Activity activity, String language)
208    {
209        String projectName = activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME);
210        Project project = _projectManager.getProject(projectName);
211        Site site = project.getSite();
212        
213        String mailBody;
214        Source source = null;
215        RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
216        
217        try
218        {
219            // Force rendering context.FRONT to resolve URI
220            _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
221            
222            Request request = ContextHelper.getRequest(_context);
223            
224            request.setAttribute("lang", language);
225            request.setAttribute("sitemapLanguage", language);
226            request.setAttribute("forceAbsoluteUrl", true);
227            request.setAttribute(WebConstants.REQUEST_ATTR_SITE, site);
228            request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, site.getName());
229            request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, site.getSkinId());
230            
231            source = _srcResolver.resolveURI(getMailBodyURI(activity), null, Map.of(AbstractWorkspacesActivityType.ACTIVITY_CONTEXT_PARAM, activity));
232            
233            try (InputStream is = source.getInputStream())
234            {
235                ByteArrayOutputStream bos = new ByteArrayOutputStream();
236                SourceUtil.copy(is, bos);
237                
238                mailBody = bos.toString("UTF-8");
239            }
240        }
241        catch (IOException e)
242        {
243            throw new RuntimeException("Failed to create mail body", e);
244        }
245        finally
246        {
247            _renderingContextHandler.setRenderingContext(currentContext);
248            
249            if (source != null)
250            {
251                _srcResolver.release(source);
252            }
253        }
254        return mailBody;
255    }
256    
257    /**
258     * Get the URI to resolve to get the mail body
259     * @param activity the activity
260     * @return the uri
261     */
262    public abstract String getMailBodyURI(Activity activity);
263    
264}