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.excalibur.source.Source;
038import org.apache.excalibur.source.SourceResolver;
039import org.apache.excalibur.source.SourceUtil;
040
041import org.ametys.core.right.RightManager;
042import org.ametys.core.user.User;
043import org.ametys.core.user.UserIdentity;
044import org.ametys.core.user.UserManager;
045import org.ametys.core.util.I18nUtils;
046import org.ametys.plugins.repository.AmetysObject;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.plugins.repository.activities.Activity;
049import org.ametys.plugins.workspaces.project.ProjectManager;
050import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper;
051import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper.Frequency;
052import org.ametys.plugins.workspaces.project.objects.Project;
053import org.ametys.runtime.config.Config;
054import org.ametys.runtime.i18n.I18nizableText;
055import org.ametys.runtime.plugin.component.PluginAware;
056import org.ametys.web.WebConstants;
057import org.ametys.web.activities.notify.ActivityNotifier;
058import org.ametys.web.renderingcontext.RenderingContext;
059import org.ametys.web.renderingcontext.RenderingContextHandler;
060import org.ametys.web.repository.site.Site;
061import org.ametys.web.repository.sitemap.Sitemap;
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 plugin name */
093    protected String _pluginName;
094
095    /** The context */
096    protected Context _context;
097    
098    public void service(ServiceManager manager) throws ServiceException
099    {
100        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
101        _notificationPreferenceHelper = (NotificationPreferencesHelper) manager.lookup(NotificationPreferencesHelper.ROLE);
102        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
103        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
104        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
105        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
106        _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
107        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
108    }
109    
110    public void contextualize(Context context) throws ContextException
111    {
112        _context = context;
113    }
114    
115    public void setPluginInfo(String pluginName, String featureName, String id)
116    {
117        _pluginName = pluginName;
118    }
119    
120    public List<String> getUsersEmailToNotify(Activity activity)
121    {
122        boolean returnAll = Config.getInstance().getValue("runtime.mail.massive.sending");
123        
124        // FIXME GG right should be provided directly by the activity
125        AmetysObject aObject = getTargetAmetysObject(activity);
126        Set<UserIdentity> readAccessUsers = _rightManager.getReadAccessAllowedUsers(aObject).resolveAllowedUsers(returnAll); 
127        
128        return readAccessUsers.stream()
129                .filter(userId -> _notificationPreferenceHelper.askedToBeNotified(userId, activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME), Frequency.EACH))
130                .map(_userManager::getUser)
131                .filter(Objects::nonNull)
132                .map(User::getEmail)
133                .filter(StringUtils::isNotEmpty)
134                .collect(Collectors.toList());
135    }
136    
137    /**
138     * Retrieve the ametys object targeted by this activity.
139     * This method is intended for right computation purposes.
140     * @param activity the activity
141     * @return the target
142     */
143    // FIXME this should not exist and the activity itself should provide the right. (probably by a right convertor)
144    public abstract AmetysObject getTargetAmetysObject(Activity activity);
145
146    public String getMailSubject(Activity activity)
147    {
148        String projectName = activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME);
149        Project project = _projectManager.getProject(projectName);
150        Site site = project.getSite();
151        
152        // Retrieve the lang through the activity to match summary implementation
153        String lang = site.getSitemaps().stream().findFirst().map(Sitemap::getName).orElse(null);
154        
155        return _i18nUtils.translate(new I18nizableText("plugin." + _pluginName, _getSubjectI18nKey(activity), getSubjectI18nParams(activity)), lang);
156    }
157    
158    /**
159     * Get the subject i18n key
160     * @param activity the activity
161     * @return the subject i18n key
162     */
163    protected String _getSubjectI18nKey(Activity activity)
164    {
165        return "PROJECT_MAIL_NOTIFICATION_SUBJECT_" + StringUtils.replaceChars(activity.getEventType().toUpperCase(), '.', '_');
166    }
167
168    /**
169     * Get the subject i18n parameters
170     * @param activity the activity
171     * @return the subject i18n parameters
172     */
173    public List<String> getSubjectI18nParams(Activity activity)
174    {
175        List<String> i18nparams = new ArrayList<>();
176        i18nparams.add(activity.getValue(AbstractWorkspacesActivityType.PROJECT_TITLE)); // {0}
177        return i18nparams;
178    }
179    
180    public String getMailTextBody(Activity activity)
181    {
182        // No text body
183        return null;
184    }
185    
186    public String getMailHtmlBody(Activity activity)
187    {
188        String projectName = activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME);
189        Project project = _projectManager.getProject(projectName);
190        Site site = project.getSite();
191        
192        // Retrieve the lang through the activity to match summary implementation
193        String lang = site.getSitemaps().stream().findFirst().map(Sitemap::getName).orElse(null);
194        
195        String mailBody;
196        Source source = null;
197        RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
198        
199        try
200        {
201            // Force rendering context.FRONT to resolve URI
202            _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
203            
204            Request request = ContextHelper.getRequest(_context);
205            
206            request.setAttribute("lang", lang);
207            request.setAttribute("forceAbsoluteUrl", true);
208            request.setAttribute(WebConstants.REQUEST_ATTR_SITE, site);
209            request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, site.getName());
210            request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, site.getSkinId());
211            
212            source = _srcResolver.resolveURI(getMailBodyURI(activity), null, Map.of(AbstractWorkspacesActivityType.ACTIVITY_CONTEXT_PARAM, activity));
213            
214            try (InputStream is = source.getInputStream())
215            {
216                ByteArrayOutputStream bos = new ByteArrayOutputStream();
217                SourceUtil.copy(is, bos);
218                
219                mailBody = bos.toString("UTF-8");
220            }
221        }
222        catch (IOException e)
223        {
224            throw new RuntimeException("Failed to create mail body", e);
225        }
226        finally
227        {
228            _renderingContextHandler.setRenderingContext(currentContext);
229            
230            if (source != null)
231            {
232                _srcResolver.release(source);
233            }
234        }
235        return mailBody;
236    }
237    
238    /**
239     * Get the URI to resolve to get the mail body
240     * @param activity the activity
241     * @return the uri
242     */
243    public abstract String getMailBodyURI(Activity activity);
244    
245}