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.web.alerts; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Calendar; 021import java.util.GregorianCalendar; 022import java.util.HashSet; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Set; 026 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.cocoon.components.ContextHelper; 030import org.apache.cocoon.environment.Request; 031import org.quartz.JobDataMap; 032import org.quartz.JobExecutionContext; 033 034import org.ametys.cms.repository.Content; 035import org.ametys.core.schedule.progression.ContainerProgressionTracker; 036import org.ametys.core.ui.mail.StandardMailBodyHelper; 037import org.ametys.core.user.UserIdentity; 038import org.ametys.core.user.population.PopulationContextHelper; 039import org.ametys.core.util.URIUtils; 040import org.ametys.plugins.core.schedule.Scheduler; 041import org.ametys.plugins.repository.AmetysObjectIterable; 042import org.ametys.plugins.repository.AmetysRepositoryException; 043import org.ametys.plugins.repository.query.expression.DateExpression; 044import org.ametys.plugins.repository.query.expression.Expression; 045import org.ametys.plugins.repository.query.expression.Expression.Operator; 046import org.ametys.runtime.config.Config; 047import org.ametys.runtime.i18n.I18nizableText; 048import org.ametys.web.repository.content.WebContent; 049import org.ametys.web.repository.page.Page; 050import org.ametys.web.repository.page.PageQueryHelper; 051import org.ametys.web.repository.page.jcr.DefaultPage; 052import org.ametys.web.repository.site.Site; 053 054/** 055 * Alerts engine: sends alerts mail. 056 * This is the web version of the engine: it sets the currently processed content's 057 * site name in the request object, and sends additional site and page information 058 * in the alerts e-mails. 059 */ 060public class AlertSchedulable extends org.ametys.cms.alerts.AlertSchedulable 061{ 062 /** True if the alert of page publication ending is enabled */ 063 protected boolean _pagePublicationEnabled; 064 065 /** The "page nearing end of publication" e-mail will be sent to users that have this right. */ 066 protected Set<String> _pagePublicationEndRights; 067 068 /** The "page nearing end of publication" e-mail subject i18n key. */ 069 protected String _pagePublicationEndSubject; 070 071 /** The "page nearing end of publication" e-mail body i18n key. */ 072 protected String _pagePublicationEndBody; 073 074 @Override 075 public void configure(Configuration configuration) throws ConfigurationException 076 { 077 super.configure(configuration); 078 079 Configuration pagePublicationEndConf = configuration.getChild("pagePublicationEnd", false); 080 081 if (pagePublicationEndConf != null) 082 { 083 _pagePublicationEnabled = true; 084 _pagePublicationEndRights = _getRightsFromConf(pagePublicationEndConf); 085 _pagePublicationEndSubject = pagePublicationEndConf.getChild("subjectKey").getValue(); 086 _pagePublicationEndBody = pagePublicationEndConf.getChild("bodyKey").getValue(); 087 } 088 } 089 090 @Override 091 public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception 092 { 093 // Send the content alerts. 094 super.execute(context, progressionTracker); 095 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 096 boolean instantMode = jobDataMap.getBooleanValue(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_INSTANT_MODE_KEY); 097 if (!instantMode && _pagePublicationEnabled) 098 { 099 // Send the page alerts. 100 _sendPagePublicationEndAlerts(); 101 } 102 } 103 104 @Override 105 protected void _setRequestAttributes (Content content) 106 { 107 Request request = ContextHelper.getRequest(_context); 108 109 Site site = _getSite(content); 110 if (site != null) 111 { 112 String siteName = site.getName(); 113 // Set the site name into the request for the other components to be able to retrieve it. 114 request.setAttribute("siteName", siteName); 115 116 List<String> populationContexts = new ArrayList<>(); 117 118 // Set the population contexts to be able to get allowed users 119 populationContexts.add("/sites/" + siteName); 120 populationContexts.add("/sites-fo/" + siteName); 121 122 request.setAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR, populationContexts); 123 } 124 } 125 126 /** 127 * Set the necessary request attributes 128 * @param page The concerned page 129 */ 130 protected void _setRequestAttributes (Page page) 131 { 132 Request request = ContextHelper.getRequest(_context); 133 134 String siteName = page.getSiteName(); 135 136 // Set the site name into the request for the other components to be able to retrieve it. 137 request.setAttribute("siteName", siteName); 138 139 List<String> populationContexts = new ArrayList<>(); 140 141 // Set the population contexts to be able to get allowed users 142 populationContexts.add("/sites/" + siteName); 143 populationContexts.add("/sites-fo/" + siteName); 144 145 request.setAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR, populationContexts); 146 } 147 148 /** 149 * Send the "page nearing end of publication" alerts. 150 * @throws AmetysRepositoryException if an error occurs. 151 */ 152 protected void _sendPagePublicationEndAlerts() throws AmetysRepositoryException 153 { 154 Long delay = Config.getInstance().getValue("remind.before.publication.end"); 155 if (delay != null && delay > 0) 156 { 157 Calendar calendar = new GregorianCalendar(); 158 _removeTimeParts(calendar); 159 calendar.add(Calendar.DAY_OF_MONTH, delay.intValue()); 160 161 // Get all the pages which publication end is in X days. 162 Expression nearPublicationEndExpr = new DateExpression(DefaultPage.METADATA_PUBLICATION_END_DATE, Operator.EQ, calendar.getTime()); 163 164 String query = PageQueryHelper.getPageXPathQuery(null, null, null, nearPublicationEndExpr, null); 165 166 try (AmetysObjectIterable<Page> pages = _resolver.query(query)) 167 { 168 if (getLogger().isInfoEnabled()) 169 { 170 getLogger().info("Pages within " + Long.toString(delay) + " days of publication end: " + pages.getSize()); 171 } 172 173 for (Page page : pages) 174 { 175 // Send the alert e-mails. 176 _sendPagePublicationEndEmail(page); 177 } 178 } 179 } 180 } 181 182 /** 183 * Send the "page publication end" alert e-mail. 184 * @param page the page nearing publication end. 185 * @throws AmetysRepositoryException if an error occurs. 186 */ 187 protected void _sendPagePublicationEndEmail(Page page) throws AmetysRepositoryException 188 { 189 _setRequestAttributes(page); 190 191 Set<UserIdentity> users = new HashSet<>(); 192 for (String right : _pagePublicationEndRights) 193 { 194 users.addAll(_rightManager.getAllowedUsers(right, page).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 195 } 196 197 List<String> params = _getPagePublicationEndParams(page); 198 199 I18nizableText i18nSubject = new I18nizableText(null, _pagePublicationEndSubject, params); 200 I18nizableText i18nBody = new I18nizableText(null, _pagePublicationEndBody, params); 201 202 String subject = _i18nUtils.translate(i18nSubject); 203 String body = _i18nUtils.translate(i18nBody); 204 205 try 206 { 207 body = StandardMailBodyHelper.newHTMLBody() 208 .withTitle(i18nSubject) 209 .withMessage(i18nBody) 210 .withLink(_getPageUrl(page), new I18nizableText("plugin.web", "CONTENT_ALERTS_MAIL_PAGE_LINK_TITLE")) 211 .build(); 212 213 _sendMails(subject, body, users, _mailFrom); 214 } 215 catch (IOException e) 216 { 217 getLogger().error("Fail to build HTML mail body for page publication end", e); 218 } 219 } 220 221 @Override 222 protected String getI18nKeyBody(String bodyI18nKey, Content content) 223 { 224 String i18nKey = bodyI18nKey; 225 226 Site site = _getSite(content); 227 if (site == null) 228 { 229 i18nKey += "_NOSITE"; 230 } 231 else if (_getPage(content, site) == null) 232 { 233 // Orphan content 234 i18nKey += "_ORPHAN"; 235 } 236 237 return i18nKey; 238 } 239 240 @Override 241 protected List<String> _getAdditionalParams(Content content) 242 { 243 List<String> params = new ArrayList<>(); 244 245 Site site = _getSite(content); 246 if (site != null) 247 { 248 params.add(site.getTitle()); // {4} 249 } 250 251 Page page = _getPage(content, site); 252 if (page != null) 253 { 254 params.add(page.getTitle()); // {5} 255 } 256 257 return params; 258 } 259 260 /** 261 * Get the "page nearing end of publication" mail parameters. 262 * @param page the page. 263 * @return the mail parameters. 264 */ 265 protected List<String> _getPagePublicationEndParams(Page page) 266 { 267 List<String> params = new ArrayList<>(); 268 269 String siteTitle = page.getSite().getTitle(); 270 Long delay = Config.getInstance().getValue("remind.before.publication.end"); 271 272 params.add(page.getTitle()); 273 params.add(_getPageUrl(page)); 274 params.add(String.valueOf(delay)); 275 params.add(siteTitle); 276 277 return params; 278 } 279 280 @Override 281 protected String _getContentUrl(Content content) 282 { 283 Site site = _getSite(content); 284 if (site != null) 285 { 286 StringBuilder url = new StringBuilder(_baseUrl); 287 if (!_baseUrl.endsWith("/")) 288 { 289 url.append('/'); 290 } 291 292 Page page = _getPage(content, site); 293 if (page != null) 294 { 295 url.append(site.getName()).append("/index.html?uitool=uitool-page,id:%27").append(URIUtils.encodeParameter(page.getId())).append("%27"); 296 } 297 else 298 { 299 url.append(site.getName()).append("/index.html?uitool=uitool-content,id:%27").append(URIUtils.encodeParameter(content.getId())).append("%27"); 300 } 301 302 return url.toString(); 303 } 304 else 305 { 306 return super._getContentUrl(content); 307 } 308 } 309 310 /** 311 * Get the URL of the back-office, opening on the page tool. 312 * @param page the page to link to. 313 * @return the page URL. 314 */ 315 protected String _getPageUrl(Page page) 316 { 317 StringBuilder url = new StringBuilder(_baseUrl); 318 if (!_baseUrl.endsWith("/")) 319 { 320 url.append('/'); 321 } 322 url.append(page.getSite().getName()).append("/index.html?uitool=uitool-page,id:%27").append(page.getId()).append("%27"); 323 return url.toString(); 324 } 325 326 /** 327 * Get the site name 328 * @param content The content 329 * @return the site name 330 */ 331 protected Site _getSite(Content content) 332 { 333 if (content instanceof WebContent) 334 { 335 return ((WebContent) content).getSite(); 336 } 337 338 return null; 339 } 340 341 /** 342 * Get the page referenced by this content 343 * @param content The content 344 * @param site the site 345 * @return the page or null. 346 */ 347 protected Page _getPage(Content content, Site site) 348 { 349 if (content instanceof WebContent) 350 { 351 Iterator<Page> pages = ((WebContent) content).getReferencingPages().iterator(); 352 if (pages.hasNext()) 353 { 354 return pages.next(); 355 } 356 } 357 return null; 358 } 359 360}