001/*
002 *  Copyright 2026 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.repository.comment;
017
018import java.io.IOException;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.cocoon.components.ContextHelper;
026import org.apache.cocoon.environment.Request;
027import org.apache.commons.lang3.StringUtils;
028
029import org.ametys.cms.ObservationConstants;
030import org.ametys.cms.repository.Content;
031import org.ametys.cms.repository.comment.Comment;
032import org.ametys.cms.transformation.xslt.ResolveURIComponent;
033import org.ametys.core.observation.Event;
034import org.ametys.core.ui.mail.StandardMailBodyHelper;
035import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder;
036import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder.UnauthenticatedUserInput;
037import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder.UserInput;
038import org.ametys.core.user.User;
039import org.ametys.core.util.mail.SendMailHelper;
040import org.ametys.runtime.i18n.I18nizableText;
041import org.ametys.runtime.i18n.I18nizableTextParameter;
042import org.ametys.web.WebHelper;
043import org.ametys.web.renderingcontext.RenderingContext;
044import org.ametys.web.renderingcontext.RenderingContextHandler;
045import org.ametys.web.repository.content.WebContent;
046import org.ametys.web.repository.page.Page;
047import org.ametys.web.repository.site.Site;
048
049import jakarta.mail.MessagingException;
050
051/**
052 * Observer to notify the comment's author when someone reply to its commments
053 */
054public class NotifyCommentAuthorWhenReplyingObserver extends AbstractNotifyCommentAuthorObserver
055{
056    /** The rendering context handler */
057    protected RenderingContextHandler _renderingContextHandler;
058
059    @Override
060    public void service(ServiceManager smanager) throws ServiceException
061    {
062        super.service(smanager);
063        _renderingContextHandler = (RenderingContextHandler) smanager.lookup(RenderingContextHandler.ROLE);
064    }
065    
066    public boolean supports(Event event)
067    {
068        if (event.getId().equals(ObservationConstants.EVENT_CONTENT_COMMENT_VALIDATED))
069        {
070            Comment comment = (Comment) event.getArguments().get(ObservationConstants.ARGS_COMMENT);
071            return comment != null && comment.getCommentParent() != null;
072        }
073        return false;
074    }
075    
076    public void observe(Event event, Map<String, Object> transientVars) throws Exception
077    {
078        Map<String, Object> arguments = event.getArguments();
079        Content content = (Content) arguments.get(ObservationConstants.ARGS_CONTENT);
080        Comment comment = (Comment) arguments.get(ObservationConstants.ARGS_COMMENT);
081        
082        Comment parentComment = comment.getCommentParent();
083        String recipient = getMailRecipient(parentComment);
084        if (StringUtils.isEmpty(recipient))
085        {
086            getLogger().info("The parent comment {} of content {} has no author email, no notification mail will be sent", parentComment.getId(), content.getId());
087            return;
088        }
089        
090        // Try to get authenticated users related to parent comment and reply
091        User parentCommentUser = resolveCommentAuthor(content, parentComment);
092        User replyCommentUser = resolveCommentAuthor(content, comment);
093
094        // Do not send mail if the author of the comment and the author of the reply are the same
095        if (parentCommentUser != null && replyCommentUser != null && parentCommentUser == replyCommentUser)
096        {
097            return;
098        }
099        else if (recipient.equalsIgnoreCase(comment.getAuthorEmail()))
100        {
101            return;
102        }
103        
104        String sender = getMailSender(content);
105        String lang = getMailLanguage(content, parentCommentUser);
106        
107        try
108        {
109            Page page = null;
110            if (content instanceof WebContent webContent)
111            {
112                page = webContent.getReferencingPages().stream().findFirst().orElse(null);
113            }
114            
115            I18nizableText i18nSubject = getMailSubject(content, page);
116            String subject = _i18nUtils.translate(i18nSubject, lang);
117            I18nizableText i18nTitle = getMailTitle(content, page);
118            I18nizableText i18nBody = getMailBody(content, page, parentComment, comment);
119            
120            MailBodyBuilder bodyBuilder = StandardMailBodyHelper.newHTMLBody()
121                    .withLanguage(lang)
122                    .withTitle(i18nTitle)
123                    .withMessage(i18nBody)
124                    .withFooterBottomText(StandardMailBodyHelper.AUTOGENERATED_DO_NOT_ANSWER_TEXT);
125            
126            if (replyCommentUser != null)
127            {
128                bodyBuilder.withUserInputs(List.of(new UserInput(replyCommentUser, comment.getCreationDate(), comment.getContent())), new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_REPLY_HEADER"));
129            }
130            else
131            {
132                bodyBuilder.withUnauthenticatedUserInputs(List.of(new UnauthenticatedUserInput(comment.getAuthorName(), comment.getCreationDate(), comment.getContent())), new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_REPLY_HEADER"));
133            }
134            
135            if (parentCommentUser != null)
136            {
137                bodyBuilder.addUserInputs(List.of(new UserInput(parentCommentUser, parentComment.getCreationDate(), parentComment.getContent())), new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_COMMENT_HEADER"));
138            }
139            else
140            {
141                bodyBuilder.addUnauthenticatedUserInput(List.of(new UnauthenticatedUserInput(parentComment.getAuthorName(), parentComment.getCreationDate(), parentComment.getContent())), new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_COMMENT_HEADER"));
142            }
143            
144            String linkUrl = page != null ? getPageUri(page) : getOrphanContentUri(content);
145            if (StringUtils.isNotEmpty(linkUrl))
146            {
147                bodyBuilder.withLink(linkUrl, new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_LINK_TITLE"));
148            }
149            
150            String htmlBody = bodyBuilder.build();
151            
152            SendMailHelper.newMail()
153                          .withSubject(subject)
154                          .withHTMLBody(htmlBody)
155                          .withSender(sender)
156                          .withRecipient(recipient)
157                          .withAsync(true)
158                          .sendMail();
159        }
160        catch (MessagingException | IOException e)
161        {
162            getLogger().warn("Could not send a notification mail to {}", recipient, e);
163        }
164            
165    }
166    
167    /**
168     * Get the mail subject
169     * @param content The commented content
170     * @param page The page holding the commented content. Can be null.
171     * @return The mail subject
172     */
173    protected I18nizableText getMailSubject(Content content, Page page)
174    {
175        return new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_SUBJECT", getSubjectI18nParams(content, page));
176    }
177    
178    /**
179     * Get the mail title
180     * @param content The commented content
181     * @param page The page holding the commented content. Can be null.
182     * @return The mail title
183     */
184    protected I18nizableText getMailTitle(Content content, Page page)
185    {
186        return new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_TITLE", getSubjectI18nParams(content, page));
187    }
188    
189    /**
190     * Get the mail body
191     * @param content The commented content
192     * @param page The page holding the commented content. Can be null.
193     * @param parentComment The initial comment
194     * @param reply The reply to the comment
195     * @return the mail body
196     */
197    protected I18nizableText getMailBody(Content content, Page page, Comment parentComment, Comment reply)
198    {
199        if (page != null)
200        {
201            return new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_BODY", getBodyI18nParams(content, parentComment, reply, page.getSite(), page));
202        }
203        else
204        {
205            Request request = ContextHelper.getRequest(_context);
206            String siteName = WebHelper.getSiteName(request, content);
207            Site site = _siteManager.getSite(siteName);
208            
209            return new I18nizableText("plugin.web", "PLUGINS_WEB_CONTENT_COMMENTS_NEW_REPLY_NO_PAGE_BODY", getBodyI18nParams(content, parentComment, reply, site, null));
210        }
211    }
212    
213    /**
214     * Get the i18n parameters for mail subject 
215     * @param content the commented content
216     * @param page The page holding the commented content. Can be null.
217     * @return the i18n parameters for mail subject 
218     */
219    protected Map<String, I18nizableTextParameter> getSubjectI18nParams(Content content, Page page)
220    {
221        Map<String, I18nizableTextParameter> i18nparam = new HashMap<>();
222        
223        i18nparam.put("contentTitle", new I18nizableText(_contentHelper.getTitle(content)));
224        
225        if (page != null)
226        {
227            i18nparam.put("pageTitle", new I18nizableText(page.getTitle()));
228        }
229        
230        if (content instanceof WebContent webContent)
231        {
232            Site site = webContent.getSite();
233            i18nparam.put("siteTitle", new I18nizableText(site.getTitle())); 
234        }
235        
236        return i18nparam;
237    }
238    
239    /**
240     * Get the i18n parameters for mail body
241     * @param content the commented content
242     * @param parentComment The initial comment
243     * @param subComment The answser to the comment
244     * @param site The site
245     * @param page The page holding the commented content. Can be null.
246     * @return the i18n parameters for mail body
247     */
248    protected Map<String, I18nizableTextParameter> getBodyI18nParams(Content content, Comment parentComment, Comment subComment, Site site, Page page)
249    {
250        Map<String, I18nizableTextParameter> i18nparam = new HashMap<>();
251        
252        i18nparam.put("userName", new I18nizableText(parentComment.getAuthorName()));
253        i18nparam.put("authorName", new I18nizableText(subComment.getAuthorName()));
254        i18nparam.put("contentTitle", new I18nizableText(_contentHelper.getTitle(content)));
255        
256        if (page != null)
257        {
258            i18nparam.put("pageTitle", new I18nizableText(page.getTitle()));
259            i18nparam.put("pageUri", new I18nizableText(getPageUri(page)));
260        }
261        else
262        {
263            i18nparam.put("contentUri", new I18nizableText(getOrphanContentUri(content)));
264        }
265        
266        if (site != null)
267        {
268            i18nparam.put("siteTitle", new I18nizableText(site.getTitle())); 
269            i18nparam.put("siteUrl", new I18nizableText(site.getUrl())); 
270        }
271        
272        return i18nparam;
273    } 
274    
275    /**
276     * Get the absolute front url of an orphan content
277     * @param content the commented content
278     * @return the url of content
279     */
280    protected String getOrphanContentUri(Content content)
281    {
282        return StringUtils.EMPTY;
283    }
284    
285    /**
286     * Get the absolute front url of the page
287     * @param page the page 
288     * @return the page url
289     */
290    protected String getPageUri(Page page)
291    {
292        RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
293        try
294        {
295            _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
296            return ResolveURIComponent.resolve("page", page.getId(), false, true, false);
297        }
298        finally
299        {
300            _renderingContextHandler.setRenderingContext(currentContext);
301        }
302    }
303    
304}