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}