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