001/*
002 *  Copyright 2023 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.mail;
017
018import java.io.IOException;
019import java.io.InputStreamReader;
020import java.io.Reader;
021import java.time.ZonedDateTime;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.Request;
035import org.apache.commons.io.IOUtils;
036import org.apache.commons.lang.StringUtils;
037import org.apache.excalibur.source.Source;
038import org.apache.excalibur.source.SourceResolver;
039
040import org.ametys.core.cocoon.ActionResultGenerator;
041import org.ametys.core.user.User;
042import org.ametys.core.util.DateUtils;
043import org.ametys.plugins.core.user.UserHelper;
044import org.ametys.runtime.i18n.I18nizableText;
045import org.ametys.web.WebHelper;
046
047/**
048 * Component to build wrapped HTML mail body for report activities
049 */
050public class ReportActivitiesMailBodyHelper implements Contextualizable, Serviceable, Initializable
051{
052    /** The avalon context */
053    protected static Context _context;
054    
055    private static SourceResolver _srcResolver;
056    private static UserHelper _userHelper;
057
058    private static ReportActivitiesMailBodyHelper __instance;
059
060    @Override
061    public void contextualize(Context context)
062    {
063        _context = context;
064    }
065    
066    public void initialize() throws Exception
067    {
068        __instance = this;
069    }
070    
071    @Override
072    public void service(ServiceManager manager) throws ServiceException
073    {
074        _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
075        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
076    }
077
078    /**
079     * Get the URI to resolve wrapped HTML email body
080     * @return the uri
081     */
082    public String getMailBodyUri()
083    {
084        Request request = ContextHelper.getRequest(_context);
085        
086        String siteName = WebHelper.getSiteName(request);
087        
088        return "cocoon://_plugins/web/" + siteName + "/wrapped-mail/activities/body.html";
089    }
090    
091    /**
092     * Creates a new mail body builder.
093     * @return the newly created {@link MailBodyBuilder}.
094     */
095    public static MailBodyBuilder newHTMLBody()
096    {
097        return new MailBodyBuilder(__instance.getMailBodyUri());
098    }
099    
100    /**
101     * Implements the builder pattern for creating and sending emails.
102     */
103    public static class MailBodyBuilder
104    {
105        private Object _title;
106        private List<Object> _messages;
107        private List<Activity> _activities;
108        private I18nizableText _linkText;
109        private String _linkUrl;
110        private I18nizableText _footerLinkText;
111        private String _footerLinkUrl;
112        private String _footerImgPluginName;
113        private String _footerImgPath;
114        private String _lang;
115        private String _uri;
116        
117        MailBodyBuilder(String uri)
118        {
119            _uri = uri;
120        }
121        
122        /**
123         * Set the language
124         * @param lang the language
125         * @return this builder
126         */
127        public MailBodyBuilder withLanguage(String lang)
128        {
129            _lang = lang;
130            return this;
131        }
132        /**
133         * Set the title
134         * @param title the title as String or {@link I18nizableText}
135         * @return this builder
136         */
137        public MailBodyBuilder withTitle(Object title)
138        {
139            _title = title;
140            return this;
141        }
142        
143        /**
144         * Set the main message
145         * @param message the message as String or {@link I18nizableText}
146         * @return this builder
147         */
148        public MailBodyBuilder withMessage(Object message)
149        {
150            _messages = List.of(message);
151            return this;
152        }
153        
154        /**
155         * Add a new message
156         * @param message the message as String or {@link I18nizableText}
157         * @return this builder
158         */
159        public MailBodyBuilder addMessage(Object message)
160        {
161            if (_messages == null)
162            {
163                _messages = new ArrayList<>();
164            }
165            
166            _messages.add(message);
167            return this;
168        }
169        
170        /**
171         * Set the activities
172         * @param activities the activities
173         * @return this builder
174         */
175        public MailBodyBuilder withActivities(List<Activity> activities)
176        {
177            _activities = activities;
178            return this;
179        }
180        
181        /**
182         * Add an activity
183         * @param activity the activity
184         * @return this builder
185         */
186        public MailBodyBuilder addActivity(Activity activity)
187        {
188            if (_activities == null)
189            {
190                _activities = new ArrayList<>();
191            }
192            
193            _activities.add(activity);
194            return this;
195        }
196        
197        /**
198         * Add a main link to be displayed as a button
199         * @param linkUrl the link url
200         * @param linkText the link text
201         * @return this builder
202         */
203        public MailBodyBuilder withLink(String linkUrl, I18nizableText linkText)
204        {
205            _linkText = linkText;
206            _linkUrl = linkUrl;
207            return this;
208        }
209        
210        /**
211         * Add a link to be displayed on footer
212         * @param linkUrl the link url
213         * @param linkText the link text
214         * @return this builder
215         */
216        public MailBodyBuilder withFooterLink(String linkUrl, I18nizableText linkText)
217        {
218            return withFooterLink(linkUrl, linkText, null, null);
219        }
220        
221        /**
222         * Add a link to be displayed on footer
223         * @param linkUrl the link url
224         * @param linkText the link text
225         * @param imgPluginName the plugin name that constains the image
226         * @param imgPath the path of the image in the plugin
227         * @return this builder
228         */
229        public MailBodyBuilder withFooterLink(String linkUrl, I18nizableText linkText, String imgPluginName, String imgPath)
230        {
231            _footerLinkText = linkText;
232            _footerLinkUrl = linkUrl;
233            _footerImgPluginName = imgPluginName;
234            _footerImgPath = imgPath;
235            return this;
236        }
237        
238        /**
239         * Build the body of the email as HTML
240         * @return the HTML body
241         * @throws IOException if an error occurred when building the email
242         */
243        public String build() throws IOException
244        {
245            Source src = null;
246            Request request = ContextHelper.getRequest(_context);
247            
248            try
249            {
250                request.setAttribute(ActionResultGenerator.MAP_REQUEST_ATTR, toJson());
251                
252                Map<String, Object> parameters = new HashMap<>();
253                parameters.put("lang", _lang);
254                
255                src = _srcResolver.resolveURI(_uri, null, parameters);
256                Reader reader = new InputStreamReader(src.getInputStream(), "UTF-8");
257                return IOUtils.toString(reader);
258            }
259            finally
260            {
261                _srcResolver.release(src);
262            }
263        }
264        
265        /**
266         * Get the JSON representation of the email ingredients
267         * @return the JSON ingredients
268         */
269        public Map<String, Object> toJson()
270        {
271            Map<String, Object> json = new HashMap<>();
272            
273            json.put("title", _title);
274            
275            if (_messages != null && !_messages.isEmpty())
276            {
277                json.put("message", _messages);
278            }
279            
280            if (StringUtils.isNotEmpty(_linkUrl))
281            {
282                json.put("link", Map.of("url", _linkUrl, "text", _linkText));
283            }
284            
285            if (_activities != null && !_activities.isEmpty())
286            {
287                json.put("activity", _activities.stream()
288                        .map(this::_activity2json)
289                        .toList());
290            }
291            
292            json.put("footer", _footer2json());
293            
294            return json;
295        }
296        
297        private Map<String, Object> _footer2json()
298        {
299            Map<String, Object> footer2json = new HashMap<>();
300            
301            if (_footerLinkUrl != null && _footerLinkText != null)
302            {
303                Map<String, Object> link2json = new HashMap<>();
304                
305                link2json.put("text", _footerLinkText);
306                link2json.put("url", _footerLinkUrl);
307                if (_footerImgPath != null && _footerImgPluginName != null)
308                {
309                    link2json.put("imgPluginName", _footerImgPluginName);
310                    link2json.put("imgPath", _footerImgPath);
311                }
312                footer2json.put("link", link2json);
313            }
314            
315            return footer2json;
316        }
317        
318        private Map<String, Object> _activity2json(Activity activity)
319        {
320            Map<String, Object> activity2json = new HashMap<>();
321            activity2json.put("author", _userHelper.user2json(activity.author()));
322            activity2json.put("date", DateUtils.zonedDateTimeToString(activity.date()));
323            activity2json.put("description", activity.description());
324            return activity2json;
325        }
326        
327        /**
328         * Record for an activity
329         * @param author The author.
330         * @param date the date of the action
331         * @param description the description if the activity as String or {@link I18nizableText}
332         */
333        public record Activity(User author, ZonedDateTime date, Object description) { /* empty */ }
334    }
335    
336}