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}