001/*
002 *  Copyright 2011 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.translationflagging;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Locale;
024import java.util.Map;
025import java.util.Set;
026
027import javax.mail.MessagingException;
028
029import org.apache.avalon.framework.activity.Initializable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.commons.lang.StringUtils;
033
034import org.ametys.cms.repository.WorkflowAwareContent;
035import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
036import org.ametys.core.right.RightManager;
037import org.ametys.core.user.User;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.core.user.UserManager;
040import org.ametys.core.util.I18nUtils;
041import org.ametys.core.util.mail.SendMailHelper;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.data.holder.ModelLessDataHolder;
044import org.ametys.runtime.config.Config;
045import org.ametys.runtime.i18n.I18nizableText;
046import org.ametys.runtime.plugin.component.PluginAware;
047import org.ametys.web.repository.content.WebContent;
048import org.ametys.web.repository.page.Page;
049import org.ametys.web.repository.site.Site;
050
051import com.opensymphony.module.propertyset.PropertySet;
052import com.opensymphony.workflow.FunctionProvider;
053import com.opensymphony.workflow.WorkflowException;
054
055/**
056 * When a content is saved, this workflow function looks if the pages it belongs to are translated in other languages.
057 * If this is the case, an alert e-mail is sent to all the persons who are responsible for modifying the translated pages,
058 * to inform them that a new version is available.
059 */
060public class TranslationAlertFunction extends AbstractContentWorkflowComponent implements FunctionProvider, Initializable, PluginAware
061{
062    
063    /** The e-mail subject i18n key. */
064    public static final String I18N_KEY_SUBJECT = "PLUGINS_TRANSLATIONFLAGGING_ALERT_EMAIL_SUBJECT";
065    
066    /** The e-mail body i18n key. */
067    public static final String I18N_KEY_BODY = "PLUGINS_TRANSLATIONFLAGGING_ALERT_EMAIL_BODY";
068    
069    /** The users manager. */
070    protected UserManager _userManager;
071    
072    /** The rights manager. */
073    protected RightManager _rightManager;
074    
075    /** The i18n utils. */
076    protected I18nUtils _i18nUtils;
077    
078    /** The ametys object resolver. */
079    protected AmetysObjectResolver _resolver;
080    
081    /** The plugin name. */
082    protected String _pluginName;
083    
084    /** The server base URL. */
085    protected String _baseUrl;
086    
087    @Override
088    public void setPluginInfo(String pluginName, String featureName, String id)
089    {
090        _pluginName = pluginName;
091    }
092    
093    @Override
094    public void initialize() throws Exception
095    {
096        _baseUrl = StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html");
097        if (!_baseUrl.endsWith("/"))
098        {
099            _baseUrl = _baseUrl + "/";
100        }
101    }
102    
103    @Override
104    public void service(ServiceManager serviceManager) throws ServiceException
105    {
106        super.service(serviceManager);
107        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
108        _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE);
109        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
110        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
111    }
112    
113    @Override
114    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
115    {
116        _logger.info("Performing translation alerts workflow function.");
117        
118        // Retrieve current content.
119        WorkflowAwareContent content = getContent(transientVars);
120        
121        if (content instanceof WebContent)
122        {
123            WebContent webContent = (WebContent) content;
124            Site site = webContent.getSite();
125            
126            // The content has to be a web content to be referenced by pages.
127            boolean enabled = site.getValue("translationflagging-enable-alerts", false, false);
128            if (enabled)
129            {
130                sendAlerts((WebContent) content);
131            }
132        }
133    }
134    
135    /**
136     * Send the alerts to tell users the translated content was modified.
137     * @param content the modified content.
138     */
139    protected void sendAlerts(WebContent content)
140    {
141        // Process all the pages which reference the content.
142        for (Page page : content.getReferencingPages())
143        {
144            Site site = content.getSite();
145            // Get the master language for this site.
146            String masterLanguage = site.getValue("master-language");
147            
148            // Process the page only if it's in the master language, or there is no master language.
149            if (StringUtils.isEmpty(masterLanguage) || page.getSitemapName().equals(masterLanguage))
150            {
151                // Get the translated versions of the page.
152                Collection<Page> translatedPages = getTranslations(page).values();
153                
154                for (Page translatedPage : translatedPages)
155                {
156                    // Get the users to sent the alert to.
157                    HashSet<UserIdentity> users = getUsersToNotify(translatedPage);
158                    
159                    // Build and send the alert.
160                    sendAlert(page, content, translatedPage, users);
161                }
162            }
163        }
164    }
165    
166    /**
167     * Build and send an alert e-mail to inform of a translation to a list of users.
168     * @param page the modified page.
169     * @param content the content which was modified.
170     * @param translatedPage the translated page.
171     * @param users the users to send the e-mail to.
172     */
173    protected void sendAlert(Page page, WebContent content, Page translatedPage, Set<UserIdentity> users)
174    {
175        List<String> params = new ArrayList<>();
176        
177        Site site = page.getSite();
178        String mailFrom = site.getValue("site-mail-from");
179        
180        // Get a human-readable version of the languages.
181        String pageLang = _i18nUtils.translate(new I18nizableText("plugin.web", "I18NKEY_LANGUAGE_" + page.getSitemapName().toUpperCase()));
182        String translatedLang = _i18nUtils.translate(new I18nizableText("plugin.web", "I18NKEY_LANGUAGE_" + translatedPage.getSitemapName().toUpperCase()));
183        
184        // Build a list of the parameters.
185        params.add(page.getSite().getTitle());
186        params.add(content.getTitle(new Locale(page.getSitemapName())));
187        params.add(page.getTitle());
188        params.add(pageLang.toLowerCase());
189        params.add(translatedPage.getTitle());
190        params.add(translatedLang.toLowerCase());
191        params.add(getPageUrl(page));
192        params.add(getPageUrl(translatedPage));
193        
194        String catalogue = "plugin." + _pluginName;
195        
196        // Get the e-mail subject and body.
197        I18nizableText i18nSubject = new I18nizableText(catalogue, I18N_KEY_SUBJECT, params);
198        I18nizableText i18nBody = new I18nizableText(catalogue, I18N_KEY_BODY, params);
199        
200        String subject = _i18nUtils.translate(i18nSubject);
201        String body = _i18nUtils.translate(i18nBody);
202        
203        // Send the e-mails.
204        sendMails(subject, body, users, mailFrom);
205    }
206    
207    /**
208     * Send a translation alert e-mail to the specified users.
209     * @param subject the e-mail subject.
210     * @param body the e-mail body.
211     * @param users the users to send the e-mail to.
212     * @param from the e-mail will be sent with this "from" header.
213     */
214    protected void sendMails(String subject, String body, Set<UserIdentity> users, String from)
215    {
216        for (UserIdentity userIdentity : users)
217        {
218            User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
219            
220            if (user != null && StringUtils.isNotEmpty(user.getEmail()))
221            {
222                String mail = user.getEmail();
223                
224                try
225                {
226                    SendMailHelper.sendMail(subject, null, body, mail, from);
227                }
228                catch (MessagingException e)
229                {
230                    if (_logger.isWarnEnabled())
231                    {
232                        _logger.warn("Could not send a translation alert e-mail to " + mail, e);
233                    }
234                }
235            }
236        }
237    }
238    
239    /**
240     * Get the users to notify about the page translation.
241     * @param translatedPage the translated version of the page.
242     * @return the logins of the users to notify.
243     */
244    protected HashSet<UserIdentity> getUsersToNotify(Page translatedPage)
245    {
246        HashSet<UserIdentity> users = new HashSet<>();
247        
248        // Get the users which have the right to modify the page AND to receive the notification.
249        Set<UserIdentity> editors = _rightManager.getAllowedUsers("Workflow_Rights_Edition_Online", translatedPage).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"));
250        Set<UserIdentity> usersToNotify = _rightManager.getAllowedUsers("TranslationFlagging_Rights_Notification", translatedPage).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"));
251        
252        users.addAll(editors);
253        users.retainAll(usersToNotify);
254        
255        return users;
256    }
257    
258    /**
259     * Get the translations of a given page.
260     * @param page the page.
261     * @return the translated pages as a Map of pages, indexed by sitemap name (language).
262     */
263    protected Map<String, Page> getTranslations(Page page)
264    {
265        Map<String, Page> translations = new HashMap<>();
266        
267        ModelLessDataHolder translationsComposite = page.getComposite(TranslationFlaggingClientSideElement.TRANSLATIONS_META);
268        
269        if (translationsComposite != null)
270        {
271            for (String lang : translationsComposite.getDataNames())
272            {
273                String translatedPageId = translationsComposite.getValue(lang);
274                Page translatedPage = _resolver.resolveById(translatedPageId);
275                
276                translations.put(lang, translatedPage);
277            }
278        }
279        else
280        {
281            // Ignore : the translations composite data doesn't exist, just return an empty map.
282        }        
283        
284        return translations;
285    }
286    
287    /**
288     * Get the URL of the back-office, opening on the page tool.
289     * @param page the page to open on.
290     * @return the page URL.
291     */
292    protected String getPageUrl(Page page)
293    {
294        StringBuilder url = new StringBuilder(_baseUrl);
295        url.append(page.getSite().getName()).append("/index.html?uitool=uitool-page,id:%27").append(page.getId()).append("%27");
296        return url.toString();
297    }
298    
299}