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