001/*
002 *  Copyright 2010 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.cms.workflow;
017
018import java.util.ArrayList;
019import java.util.HashSet;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import javax.mail.MessagingException;
026
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.cocoon.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.commons.lang.StringUtils;
034import org.apache.excalibur.source.SourceResolver;
035
036import org.ametys.cms.repository.WorkflowAwareContent;
037import org.ametys.core.right.RightManager;
038import org.ametys.core.user.CurrentUserProvider;
039import org.ametys.core.user.User;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.core.user.UserManager;
042import org.ametys.core.util.I18nUtils;
043import org.ametys.core.util.mail.SendMailHelper;
044import org.ametys.plugins.workflow.support.WorkflowProvider;
045import org.ametys.runtime.config.Config;
046import org.ametys.runtime.i18n.I18nizableText;
047import org.ametys.runtime.plugin.component.PluginAware;
048
049import com.opensymphony.module.propertyset.PropertySet;
050import com.opensymphony.workflow.FunctionProvider;
051import com.opensymphony.workflow.WorkflowException;
052
053/**
054 * OS workflow function to send mail after an action is triggered.
055 */
056public class SendMailFunction extends AbstractContentWorkflowComponent implements FunctionProvider, Initializable, PluginAware, Contextualizable
057{
058    /**
059     * Provide "false" to prevent the function sending the mail.
060     * Useful when making large automatic workflow operations (for instance, when bulk importing and proposing in one action). 
061     */
062    public static final String SEND_MAIL = "send-mail";
063    
064    /** The rights key. */
065    protected static final String RIGHTS_KEY = "rights";
066    /** The mail subject key. */
067    protected static final String SUBJECT_KEY = "subjectKey";
068    /** The mail body key. */
069    protected static final String BODY_KEY = "bodyKey";
070    
071    /** The current user provider. */
072    protected CurrentUserProvider _currentUserProvider;
073    
074    /** The rights manager. */
075    protected RightManager _rightManager;
076    
077    /** The users manager. */
078    protected UserManager _userManager;
079    
080    /** The source resolver. */
081    protected SourceResolver _sourceResolver;
082    
083    /** The workflow. */
084    protected WorkflowProvider _workflowProvider;
085    
086    /** The Avalon context. */
087    protected Context _context;
088    
089    /** The plugin name. */
090    protected String _pluginName;
091    
092    /** I18nUtils */
093    protected I18nUtils _i18nUtils;
094    
095    @Override
096    public void initialize() throws Exception
097    {
098        _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE);
099        _rightManager = (RightManager) _manager.lookup(RightManager.ROLE);
100        _userManager = (UserManager) _manager.lookup(UserManager.ROLE);
101        _sourceResolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE);
102        _workflowProvider = (WorkflowProvider) _manager.lookup(WorkflowProvider.ROLE);
103        _i18nUtils = (I18nUtils) _manager.lookup(I18nUtils.ROLE);
104    }
105    
106    public void contextualize(Context context) throws ContextException
107    {
108        _context = context;
109    }
110    
111    @Override
112    public void setPluginInfo(String pluginName, String featureName, String id)
113    {
114        _pluginName = pluginName;
115    }
116    
117    @Override
118    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
119    {
120        String rightsParam = StringUtils.defaultString((String) args.get(RIGHTS_KEY));
121        String subjectI18nKey = StringUtils.defaultString((String) args.get(SUBJECT_KEY));
122        String bodyI18nKey = StringUtils.defaultString((String) args.get(BODY_KEY));
123        
124        Set<String> rights = new HashSet<>();
125        for (String right : rightsParam.split(","))
126        {
127            if (StringUtils.isNotBlank(right))
128            {
129                rights.add(right.trim());
130            }
131        }
132        
133        // If "send-mail" is set to true or is not present in the vars, send the mail.
134        boolean dontSendMail = "false".equals(transientVars.get(SEND_MAIL));
135        
136        if (dontSendMail)
137        {
138            return;
139        }
140        
141        try
142        {
143            WorkflowAwareContent content = getContent(transientVars);
144            UserIdentity userIdentity = getUser(transientVars);
145            User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
146            
147            Set<UserIdentity> users = _getUsers(content, rights);
148            
149            if (users.size() > 0)
150            {
151                String mailSubject = getMailSubject(subjectI18nKey, user, content);
152                String mailBody = getMailBody(bodyI18nKey, user, content, transientVars);
153                
154                _sendMails(mailSubject, mailBody, users, user.getEmail());
155            }
156        }
157        catch (Exception e)
158        {
159            _logger.error("An error occurred: unable to send mail to notify workflow change.", e);
160        }
161    }
162    
163    /**
164     * Get the subject of mail
165     * @param subjectI18nKey  the i18n key to use for subject
166     * @param user the caller
167     * @param content the content
168     * @return the subject
169     */
170    protected String getMailSubject (String subjectI18nKey, User user, WorkflowAwareContent content)
171    {
172        I18nizableText subjectKey = new I18nizableText(null, subjectI18nKey, getSubjectI18nParams(user, content));
173        return _i18nUtils.translate(subjectKey, null); // FIXME Use user preference language
174    }
175    
176    /**
177     * Get the text body of mail
178     * @param bodyI18nKey the i18n key to use for body
179     * @param user the caller
180     * @param content the content
181     * @param transientVars the transient variables
182     * @return the text body
183     */
184    protected String getMailBody (String bodyI18nKey, User user, WorkflowAwareContent content, Map transientVars)
185    {
186        I18nizableText bodyKey = new I18nizableText(null, bodyI18nKey, getBodyI18nParams(user, content));
187        String mailBody = _i18nUtils.translate(bodyKey, null); // FIXME Use user preference language
188        
189        // Get the workflow comment
190        String comment = (String) transientVars.get("comment");
191        if (StringUtils.isNotEmpty(comment))
192        {
193            List<String> params = new ArrayList<>();
194            params.add(comment);
195            
196            I18nizableText commentKey = new I18nizableText("plugin.cms", "WORKFLOW_MAIL_BODY_USER_COMMENT", params);
197            String commentTxt = _i18nUtils.translate(commentKey, null); // FIXME Use user preference language
198            
199            mailBody += "\n\n" + commentTxt; 
200        }
201        
202        return mailBody;
203    }
204    
205    /**
206     * Send the notification emails.
207     * @param subject the e-mail subject.
208     * @param body the e-mail body.
209     * @param users users to send the mail to.
210     * @param from the address sending the e-mail.
211     */
212    protected void _sendMails(String subject, String body, Set<UserIdentity> users, String from)
213    {
214        for (UserIdentity userIdentity : users)
215        {
216            User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
217            if (user != null && StringUtils.isNotBlank(user.getEmail()))
218            {
219                String mail = user.getEmail();
220                
221                try
222                {
223                    SendMailHelper.sendMail(subject, null, body, mail, from, true);
224                }
225                catch (MessagingException e)
226                {
227                    _logger.warn("Could not send a workflow notification mail to " + mail, e);
228                }
229            }
230        }
231    }
232    
233    /**
234     * Get the i18n parameters of mail subject
235     * @param user the caller
236     * @param content the content
237     * @return the i18n parameters
238     */
239    protected List<String> getSubjectI18nParams (User user, WorkflowAwareContent content)
240    {
241        List<String> params = new ArrayList<>();
242        params.add(_contentHelper.getTitle(content));
243        return params;
244    }
245    
246    /**
247     * Get the i18n parameters of mail body text
248     * @param user the caller
249     * @param content the content
250     * @return the i18n parameters
251     */
252    protected List<String> getBodyI18nParams (User user, WorkflowAwareContent content)
253    {
254        List<String> params = new ArrayList<>();
255        
256        params.add(user.getFullName()); // {0}
257        params.add(content.getTitle()); // {1}
258        params.add(_getContentUri(content)); // {2}
259        
260        return params;
261    }
262    
263    /**
264     * Get the content uri
265     * @param content the content
266     * @return the content uri
267     */
268    protected String _getContentUri(WorkflowAwareContent content)
269    {
270        String requestUri = _getRequestUri();
271        return requestUri + "/index.html?uitool=uitool-content,id:%27" + content.getId() + "%27";
272    }
273    
274    /**
275     * Get the request URI.
276     * @return the full request URI.
277     */
278    protected String _getRequestUri()
279    {
280        return StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"), "/");
281    }
282    
283    /**
284     * Retrieve the request from which this component is called.
285     * @return the request or <code>null</code>.
286     */
287    public Request _getRequest()
288    {
289        try 
290        {
291            return (Request) _context.get(ContextHelper.CONTEXT_REQUEST_OBJECT);
292        } 
293        catch (ContextException ce)
294        {
295            _logger.info("Unable to get the request", ce);
296            return null;
297        }
298    }
299    
300    /**
301     * Get the user logins.
302     * @param content the content.
303     * @param rights the set of rights to check.
304     * @return the users.
305     * @throws WorkflowException If an error occurred
306     */
307    protected Set<UserIdentity> _getUsers(WorkflowAwareContent content, Set<String> rights) throws WorkflowException
308    {
309        Set<UserIdentity> users = new HashSet<>();
310        
311        Iterator<String> rightIt = rights.iterator();
312        
313        // First right : add all the granted users.
314        if (rightIt.hasNext())
315        {
316            users.addAll(_rightManager.getAllowedUsers(rightIt.next(), content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending")));
317        }
318        
319        // Next rights : retain all the granted users.
320        while (rightIt.hasNext())
321        {
322            users.retainAll(_rightManager.getAllowedUsers(rightIt.next(), content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending")));
323        }
324        
325        return users;
326    }
327}