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}