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