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