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