001/*
002 *  Copyright 2015 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.newsletter.workflow;
017
018import java.io.IOException;
019import java.io.InputStreamReader;
020import java.io.Reader;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.cocoon.components.ContextHelper;
033import org.apache.cocoon.components.source.impl.SitemapSource;
034import org.apache.cocoon.environment.Request;
035import org.apache.commons.io.IOUtils;
036import org.apache.excalibur.source.SourceResolver;
037
038import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
039import org.ametys.plugins.newsletter.category.Category;
040import org.ametys.plugins.newsletter.category.CategoryProvider;
041import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint;
042import org.ametys.plugins.newsletter.daos.Subscriber;
043import org.ametys.plugins.newsletter.daos.SubscribersDAO;
044import org.ametys.web.renderingcontext.RenderingContext;
045import org.ametys.web.renderingcontext.RenderingContextHandler;
046import org.ametys.web.repository.content.jcr.DefaultWebContent;
047import org.ametys.web.site.SiteConfigurationExtensionPoint;
048
049import com.opensymphony.module.propertyset.PropertySet;
050import com.opensymphony.workflow.FunctionProvider;
051import com.opensymphony.workflow.WorkflowException;
052
053/**
054 * OSWorkflow function for creating a content.
055 */
056public class SendNewsletterFunction extends AbstractContentWorkflowComponent implements FunctionProvider, Contextualizable
057{
058    private SourceResolver _sourceResolver;
059    private SubscribersDAO _subscribersDAO;
060    private CategoryProviderExtensionPoint _categoryProviderEP;
061    private SiteConfigurationExtensionPoint _siteConfiguration;
062    private RenderingContextHandler _renderingContextHandler;
063    
064    private Context _context;
065    
066    @Override
067    public void service(ServiceManager manager) throws ServiceException
068    {
069        super.service(manager);
070        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
071        _subscribersDAO = (SubscribersDAO) manager.lookup(SubscribersDAO.ROLE);
072        _categoryProviderEP = (CategoryProviderExtensionPoint) manager.lookup(CategoryProviderExtensionPoint.ROLE);
073        _siteConfiguration = (SiteConfigurationExtensionPoint) manager.lookup(SiteConfigurationExtensionPoint.ROLE);
074        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
075    }
076
077    public void contextualize(Context context) throws ContextException
078    {
079        _context = context;
080    }
081    
082    @Override
083    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
084    {
085        Request request = _getRequest();
086        if (request.getParameter("send") == null && request.getAttribute("send") == null)
087        {
088            // Do not send the newsletter
089            return;
090        }
091        
092        DefaultWebContent content = (DefaultWebContent) getContent(transientVars);
093        try
094        {
095            String subject = _getSubject (content);
096            String htmlBody = _getBodyAsHtml(content);
097            String textBody = _getBodyAsText(content);
098            
099            // Subscribers
100            String categoryID = content.getMetadataHolder().getString("category");
101            String siteName = content.getSiteName();
102            
103            boolean descending = _siteConfiguration.getValueAsBoolean(siteName, "newsletter-subscription-descending");
104            boolean ascending = _siteConfiguration.getValueAsBoolean(siteName, "newsletter-subscription-ascending");
105            
106            List<Subscriber> subscribers = _subscribersDAO.getSubscribers(content.getSiteName(), content.getMetadataHolder().getString("category"));
107            if (descending)
108            {
109                subscribers.addAll(_getSubscribersOfParentCategories(categoryID, siteName));
110            }
111            if (ascending)
112            {
113                subscribers.addAll(_getSubscribersOfChildCategories(categoryID, siteName));
114            }
115            
116            Map<String, String> recipients = new HashMap<>();
117            for (Subscriber subcriber : subscribers)
118            {
119                recipients.put(subcriber.getEmail(), subcriber.getToken());
120            }
121            
122            String sender = _siteConfiguration.getValueAsString(siteName, "newsletter-mail-sender");
123            
124            // Send the mail
125            SendMailEngine sendEngine = new SendMailEngine();
126            sendEngine.parameterize(subject, htmlBody, textBody, recipients, sender);
127
128            new Thread(sendEngine).start();
129            
130            content.getMetadataHolder().setMetadata("sent", true);
131            content.saveChanges();
132            
133            // Send a google analytics event for every newsletter e-mail sent.
134            SendGAEventsEngine sendGaEngine = new SendGAEventsEngine();
135            sendGaEngine.initialize(_manager, _context);
136            sendGaEngine.parametrize(siteName, content, _getCategory(categoryID), recipients.size());
137            
138            new Thread(sendGaEngine).start();
139        }
140        catch (IOException e)
141        {
142            throw new WorkflowException("Unable to send mails !", e);
143        }
144        catch (ContextException e)
145        {
146            _logger.warn("Context exception when initializing an engine.", e);
147        }
148        catch (ServiceException e)
149        {
150            _logger.warn("Service exception when initializing an engine.", e);
151        }
152        
153    }
154    
155    /**
156     * Get the subscribers of parent categories
157     * @param categoryID The category id
158     * @param siteName The site name
159     * @return the subscribers of parent categories
160     */
161    protected List<Subscriber> _getSubscribersOfParentCategories (String categoryID, String siteName)
162    {
163        List<Subscriber> subscribers = new ArrayList<>();
164        
165        Category category = _getCategory(categoryID);
166        Category parentCategory = _getCategory(category.getParentId());
167        while (parentCategory != null)
168        {
169            subscribers.addAll(_subscribersDAO.getSubscribers(siteName, parentCategory.getId()));
170            parentCategory = _getCategory(parentCategory.getParentId());
171        }
172        
173        return subscribers;
174    }
175    
176    /**
177     * Get the subscribers of child categories
178     * @param categoryID The category id
179     * @param siteName The site name
180     * @return The subscribers of child categories
181     */
182    protected List<Subscriber> _getSubscribersOfChildCategories (String categoryID, String siteName)
183    {
184        List<Subscriber> subscribers = new ArrayList<>();
185        
186        CategoryProvider provider = _getProvider(categoryID);
187        
188        List<Category> children = provider.getCategories(categoryID);
189        for (Category child : children)
190        {
191            subscribers.addAll(_subscribersDAO.getSubscribers(siteName, child.getId()));
192            subscribers.addAll(_getSubscribersOfChildCategories (child.getId(), siteName));
193        }
194        
195        return subscribers;
196    }
197    
198    /**
199     * Get the newsletter mail subject
200     * @param content The content
201     * @return The subject
202     */
203    protected String _getSubject (DefaultWebContent content)
204    {
205        String subject = "[" + content.getSite().getTitle() + "] ";
206        String categoryId = content.getMetadataHolder().getString("category");
207        Category category = _getCategory(categoryId);
208        if (category != null)
209        {
210            subject += category.getTitle().getLabel() + " - ";
211        }
212        subject += content.getTitle();
213        
214        return subject;
215    }
216    
217    /**
218     * Get the newsletter HTML body
219     * @param content The content
220     * @return The body 
221     * @throws IOException if an I/O error occurred
222     */
223    protected String _getBodyAsHtml (DefaultWebContent content) throws IOException
224    {
225        SitemapSource src = null;
226        Request request = _getRequest();
227        
228        boolean includeImages = _siteConfiguration.getValueAsBoolean(content.getSiteName(), "newsletter-mail-include-images");
229        
230        if (includeImages)
231        {
232            request.setAttribute("forceBase64Encoding", true);
233        }
234        
235        RenderingContext renderingContext = _renderingContextHandler.getRenderingContext();
236        _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
237        
238        try
239        {
240            String uri = "cocoon://_content.mail?contentId=" + content.getId() + "&site=" + content.getSiteName() + "&lang=" + content.getLanguage() + "&_contextPath=" + content.getSite().getUrl();
241            src = (SitemapSource) _sourceResolver.resolveURI(uri);
242            Reader reader = new InputStreamReader(src.getInputStream(), "UTF-8");
243            return IOUtils.toString(reader);
244        }
245        finally
246        {
247            _sourceResolver.release(src);
248            _renderingContextHandler.setRenderingContext(renderingContext);
249            request.removeAttribute("forceBase64Encoding");
250        }
251    }
252    
253    /**
254     * Retrieve the request from which this component is called.
255     * @return the request
256     */
257    protected Request _getRequest()
258    {
259        return ContextHelper.getRequest(_context);
260    }
261    
262    /**
263     * Get the newsletter text body
264     * @param content The content
265     * @return The body 
266     * @throws IOException if an I/O error occurred
267     */
268    protected String _getBodyAsText (DefaultWebContent content) throws IOException
269    {
270        SitemapSource src = null;
271        
272        RenderingContext renderingContext = _renderingContextHandler.getRenderingContext();
273        _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
274        
275        try
276        {
277            String uri = "cocoon://_content.text?contentId=" + content.getId() + "&site=" + content.getSiteName() + "&lang=" + content.getLanguage() + "&_contextPath=" + content.getSite().getUrl();
278            src = (SitemapSource) _sourceResolver.resolveURI(uri);
279            Reader reader = new InputStreamReader(src.getInputStream(), "UTF-8");
280            return IOUtils.toString(reader);
281        }
282        finally
283        {
284            _sourceResolver.release(src);
285            _renderingContextHandler.setRenderingContext(renderingContext);
286        }
287    }
288    
289    /**
290     * Get a category
291     * @param categoryID The category id
292     * @return The category
293     */
294    protected Category _getCategory (String categoryID)
295    {
296        Set<String> ids = _categoryProviderEP.getExtensionsIds();
297        for (String id : ids)
298        {
299            CategoryProvider provider = _categoryProviderEP.getExtension(id);
300            if (!categoryID.startsWith("provider_") && provider.hasCategory(categoryID))
301            {
302                return provider.getCategory(categoryID);
303            }
304        }
305        
306        return null;
307    }
308    
309    /**
310     * Get the category provider
311     * @param categoryID The category id
312     * @return The category
313     */
314    protected CategoryProvider _getProvider (String categoryID)
315    {
316        Set<String> ids = _categoryProviderEP.getExtensionsIds();
317        for (String id : ids)
318        {
319            CategoryProvider provider = _categoryProviderEP.getExtension(id);
320            if (provider.hasCategory(categoryID))
321            {
322                return provider;
323            }
324        }
325        
326        return null;
327    }
328
329}