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