001/*
002 *  Copyright 2024 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.plugins.pagesubscription.schedulable.page;
017
018import java.io.IOException;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.Set;
025
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.cocoon.components.ContextHelper;
031import org.apache.cocoon.environment.Request;
032import org.apache.commons.lang3.StringUtils;
033import org.quartz.JobExecutionContext;
034
035import org.ametys.cms.transformation.xslt.ResolveURIComponent;
036import org.ametys.core.schedule.progression.ContainerProgressionTracker;
037import org.ametys.core.user.User;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.core.util.I18nUtils;
040import org.ametys.core.util.mail.SendMailHelper;
041import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable;
042import org.ametys.plugins.pagesubscription.BroadcastChannelHelper.BroadcastChannel;
043import org.ametys.plugins.pagesubscription.FrequencyHelper.Frequency;
044import org.ametys.plugins.pagesubscription.Subscription;
045import org.ametys.plugins.pagesubscription.notification.PageNotificationsHelper;
046import org.ametys.plugins.pagesubscription.type.PageSubscriptionType;
047import org.ametys.plugins.pagesubscription.type.SubscriptionTypeExtensionPoint;
048import org.ametys.plugins.repository.AmetysObjectResolver;
049import org.ametys.plugins.repository.activities.Activity;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.runtime.i18n.I18nizableTextParameter;
052import org.ametys.web.WebConstants;
053import org.ametys.web.activities.AbstractPageActivityType;
054import org.ametys.web.mail.ReportActivitiesMailBodyHelper;
055import org.ametys.web.mail.ReportActivitiesMailBodyHelper.MailBodyBuilder;
056import org.ametys.web.renderingcontext.RenderingContext;
057import org.ametys.web.renderingcontext.RenderingContextHandler;
058import org.ametys.web.repository.page.PageDAO;
059import org.ametys.web.repository.site.Site;
060import org.ametys.web.repository.site.SiteManager;
061
062/**
063 * Schedulable to send not forced page notification summary by mail
064 * The frequency is configurable. (DAILY, WEEKLY, MONTHLY)
065 */
066public class SendPageNotificationSummarySchedulable extends AbstractStaticSchedulable
067{
068    /** Tag for page that contains followed page configuration service */
069    public static final String FOLLOWED_PAGES_CONFIG_PAGE_TAG = "TAG_FOLLOWED_PAGES_CONFIG";
070    
071    /** The frequency */
072    protected Frequency _frequency;
073    
074    /** The mail subject */
075    protected I18nizableText _mailSubject;
076    
077    /** The mail title */
078    protected I18nizableText _mailTitle;
079    
080    /** The mail message */
081    protected I18nizableText _mailMessage;
082    
083    /** The subscription type EP */
084    protected SubscriptionTypeExtensionPoint _subscriptionTypeEP;
085    
086    /** The page subscription type */
087    protected PageSubscriptionType _pageSubscriptionType;
088    
089    /** The site manager */
090    protected SiteManager _siteManager;
091    
092    /** The i18n utils */
093    protected I18nUtils _i18nUtils;
094    
095    /** The page notification helper */
096    protected PageNotificationsHelper _pageNotificationHelper;
097    
098    /** The ametys object resolver */
099    protected AmetysObjectResolver _resolver;
100    
101    /** The page DAO */
102    protected PageDAO _pageDAO;
103    
104    /** The rendering context handler */
105    protected RenderingContextHandler _renderingContextHandler;
106    
107    @Override
108    public void service(ServiceManager manager) throws ServiceException
109    {
110        super.service(manager);
111        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
112        _subscriptionTypeEP = (SubscriptionTypeExtensionPoint) manager.lookup(SubscriptionTypeExtensionPoint.ROLE);
113        _pageSubscriptionType = (PageSubscriptionType) _subscriptionTypeEP.getExtension(PageSubscriptionType.ID);
114        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
115        _pageNotificationHelper = (PageNotificationsHelper) manager.lookup(PageNotificationsHelper.ROLE);
116        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
117        _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE);
118        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
119    }
120    
121    @Override
122    public void configure(Configuration configuration) throws ConfigurationException
123    {
124        super.configure(configuration);
125        
126        _frequency = Frequency.valueOf(configuration.getChild("frequency").getValue("DAILY"));
127        _mailSubject = I18nizableText.parseI18nizableText(configuration.getChild("mail-subject"), "plugin." + _pluginName);
128        _mailTitle = I18nizableText.parseI18nizableText(configuration.getChild("mail-title"), "plugin." + _pluginName);
129        _mailMessage = I18nizableText.parseI18nizableText(configuration.getChild("mail-message"), "plugin." + _pluginName);
130    }
131    
132    public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception
133    {
134        // For each site
135        for (Site site : _siteManager.getSites())
136        {
137            Request request = ContextHelper.getRequest(_context);
138            
139            // Retrieve the current context.
140            RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
141            try
142            {
143                // Force rendering context.FRONT to resolve URI
144                _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
145                request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, site.getName());
146                
147                Map<UserIdentity, Set<Activity>> pageActivitiesBySubscriber = _getPageActivitiesBySubscriber(site);
148                for (UserIdentity subscriber : pageActivitiesBySubscriber.keySet())
149                {
150                    Set<Activity> pageActivities = pageActivitiesBySubscriber.get(subscriber);
151                    if (!pageActivities.isEmpty())
152                    {
153                        User user = _userManager.getUser(subscriber);
154                        
155                        String lang = _getLanguage(user);
156                        
157                        String subject = _i18nUtils.translate(_getI18nSubject(site), lang);
158                        String body = _getMailBody(site, pageActivities, lang);
159                        
160                        if (user != null)
161                        {
162                            SendMailHelper.newMail()
163                                          .withRecipient(user.getEmail())
164                                          .withSubject(subject)
165                                          .withHTMLBody(body)
166                                          .withInlineCSS(false)
167                                          .withAsync(true)
168                                          .sendMail();
169                        }
170                    }
171                }
172            }
173            finally
174            {
175                // Restore current context
176                _renderingContextHandler.setRenderingContext(currentContext);
177            }
178        }
179    }
180    
181    private Map<UserIdentity, Set<Activity>> _getPageActivitiesBySubscriber(Site site)
182    {
183        Map<UserIdentity, Set<Activity>> pageActivitiesBySubscriber = new HashMap<>();
184        for (Subscription subscription : _pageSubscriptionType.getSubscriptions(site, _frequency, BroadcastChannel.MAIL, null))
185        {
186            UserIdentity subscriber = subscription.getSubscriber().get(); // For page subscription, there is only user subscription (not group) ... so subscriber always exists
187            Set<Activity> pages = pageActivitiesBySubscriber.getOrDefault(subscriber, new HashSet<>());
188            
189            String pageId = _pageSubscriptionType.getTarget(subscription);
190            Activity lastActivity = _pageNotificationHelper.getLastActivity(site.getName(), pageId, subscription.getFrequency());
191            if (lastActivity != null)
192            {
193                // Add page only if it has a recent activity for the current frequency
194                pages.add(lastActivity);
195            }
196            pageActivitiesBySubscriber.put(subscriber, pages);
197        }
198        return pageActivitiesBySubscriber;
199    }
200    
201    private String _getLanguage(User user)
202    {
203        String language = null;
204        if (user != null)
205        {
206            // First try to get the user's language
207            language = user.getLanguage();
208        }
209        
210        language = StringUtils.defaultIfBlank(language, _userLanguagesManager.getDefaultLanguage());
211        
212        return language;
213    }
214    
215    /**
216     * Get the subject of the mail
217     * @param site the site
218     * @return the subject of the mail
219     */
220    protected I18nizableText _getI18nSubject(Site site)
221    {
222        Map<String, I18nizableTextParameter> params = Map.of(
223                "site", new I18nizableText(site.getTitle())
224            );
225        return new I18nizableText(_mailSubject.getCatalogue(), _mailSubject.getKey(), params);
226    }
227    
228    /**
229     * Get the mail body of the mail
230     * @param site the site
231     * @param pages the set of pages with it last activity
232     * @param lang the language
233     * @return the mail body of the mail
234     * @throws IOException if an error occurred
235     */
236    protected String _getMailBody(Site site, Set<Activity> pages, String lang) throws IOException
237    {
238        List<org.ametys.web.mail.ReportActivitiesMailBodyHelper.MailBodyBuilder.Activity> activities = pages.stream()
239            .map(a -> this._wrapActivity(a, lang))
240            .toList();
241        
242        MailBodyBuilder bodyBuilder =  ReportActivitiesMailBodyHelper.newHTMLBody()
243            .withLanguage(lang)
244            .withTitle(_mailTitle)
245            .withMessage(_mailMessage)
246            .withActivities(activities)
247            .withLink(site.getUrl(), new I18nizableText("plugin.page-subscription", "PLUGINS_PAGE_SUBSCRIPTION_MAIL_NOTIFICATIONS_MAIL_SITE_LINK"));
248        
249        Optional<String> pageTagUrl = _pageDAO.findPagedIdsByTag(site.getName(), lang, FOLLOWED_PAGES_CONFIG_PAGE_TAG)
250                .stream()
251                .map(id -> ResolveURIComponent.resolve("page", id, false, true))
252                .findFirst();
253        if (pageTagUrl.isPresent())
254        {
255            bodyBuilder.withFooterLink(pageTagUrl.get(), new I18nizableText("plugin.page-subscription", "PLUGINS_PAGE_SUBSCRIPTION_MAIL_PAGE_NOTIFICATIONS_MAIL_PREFERENCE_LINK"), "core-ui", "img/mail/icon-letter.png");
256        }
257        
258        return bodyBuilder.build();
259    }
260    
261    private org.ametys.web.mail.ReportActivitiesMailBodyHelper.MailBodyBuilder.Activity _wrapActivity(Activity activity, String lang)
262    {
263        Map<String, I18nizableTextParameter> parameters = new HashMap<>();
264        
265        parameters.put("pageTitle", new I18nizableText(activity.getValue(AbstractPageActivityType.PAGE_TITLE)));
266        
267        String pageId = activity.getValue(AbstractPageActivityType.PAGE_ID);
268        String pageUrl = ResolveURIComponent.resolve("page", pageId, false, true);
269        parameters.put("pageUrl", new I18nizableText(pageUrl));
270        
271        I18nizableText messageI18n = new I18nizableText("plugin.page-subscription", "PLUGINS_PAGE_SUBSCRIPTION_MAIL_PAGE_NOTIFICATIONS_MAIL_CONTENT_MESSAGE", parameters);
272        String message = _i18nUtils.translate(messageI18n, lang);
273        
274        // Activity is anonymized (no user avatar nor name)
275        return new org.ametys.web.mail.ReportActivitiesMailBodyHelper.MailBodyBuilder.Activity(null, activity.getDate(), message);
276    }
277    
278}