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.util.ArrayList;
019import java.util.List;
020import java.util.Objects;
021import java.util.Optional;
022import java.util.Set;
023
024import org.apache.avalon.framework.context.Context;
025import org.apache.avalon.framework.context.ContextException;
026import org.apache.avalon.framework.context.Contextualizable;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.cocoon.components.ContextHelper;
031import org.apache.cocoon.environment.Request;
032import org.apache.commons.lang3.StringUtils;
033
034import org.ametys.cms.content.ContentHelper;
035import org.ametys.cms.repository.Content;
036import org.ametys.cms.repository.comment.Comment;
037import org.ametys.core.observation.Observer;
038import org.ametys.core.user.User;
039import org.ametys.core.user.UserIdentity;
040import org.ametys.core.user.UserManager;
041import org.ametys.core.user.directory.NotUniqueUserException;
042import org.ametys.core.user.population.PopulationContextHelper;
043import org.ametys.core.util.I18nUtils;
044import org.ametys.core.util.language.UserLanguagesManager;
045import org.ametys.runtime.config.Config;
046import org.ametys.runtime.plugin.component.AbstractLogEnabled;
047import org.ametys.runtime.plugin.component.PluginAware;
048import org.ametys.web.WebHelper;
049import org.ametys.web.repository.site.Site;
050import org.ametys.web.repository.site.SiteManager;
051
052/**
053 * Abstract observer to notify comment authors when a comment is created or updated.
054 */
055public abstract class AbstractNotifyCommentAuthorObserver extends AbstractLogEnabled implements Observer, Serviceable, PluginAware, Contextualizable
056{
057    /** The i18n utils of runtime */
058    protected I18nUtils _i18nUtils;
059    
060    /** The content helper */
061    protected ContentHelper _contentHelper;
062
063    /** The plugin */
064    protected String _pluginName;
065
066    /** The DAO for comments */
067    protected CommentsDAO _commentsDAO;
068
069    /** The user manager */
070    protected UserManager _userManager;
071
072    /** The user languages manager */
073    protected UserLanguagesManager _userLanguagesManager;
074    /** The population context helper */
075    protected PopulationContextHelper _populationContextHelper;
076    /** The site manager */
077    protected SiteManager _siteManager;
078
079    /** The avalon context */
080    protected Context _context;
081    
082    @Override
083    public void service(ServiceManager manager) throws ServiceException
084    {
085        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
086        _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE);
087        _commentsDAO = (CommentsDAO) manager.lookup(org.ametys.cms.repository.comment.CommentsDAO.ROLE);
088        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
089        _userLanguagesManager = (UserLanguagesManager) manager.lookup(UserLanguagesManager.ROLE);
090        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
091        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
092    }
093    
094    public void contextualize(Context context) throws ContextException
095    {
096        _context = context;
097    }
098    
099    public void setPluginInfo(String pluginName, String featureName, String id)
100    {
101        _pluginName = pluginName;
102    }
103    
104    public int getPriority()
105    {
106        return MAX_PRIORITY;
107    }
108    
109    /**
110     * Get the language to use for the mail notification of a comment
111     * @param content The content commented
112     * @param comment The comment it self
113     * @return The language to use for the mail notification of a comment
114     */
115    protected String getMailLanguage(Content content, Comment comment)
116    {
117        return getMailLanguage(content, comment.getAuthor(), comment.getAuthorEmail());
118    }
119    
120    /**
121     * Get the language to use for the mail notification of a comment
122     * @param content The content commented
123     * @param commentAuthor The user identity of the comment's author if it exists (null for unauthenticated comment), used to get the user's language
124     * @param commentAuthorEmail The email of the comment's author, used to find a user with this email on the content's site and get its language if the author user identity is not provided
125     * @return The language to use for the mail notification of a comment
126     */
127    protected String getMailLanguage(Content content, UserIdentity commentAuthor, String commentAuthorEmail)
128    {
129        // If an author user identity is provided from the comment, use its language
130        User authorUser = resolveCommentAuthor(content, commentAuthor, commentAuthorEmail);
131        return getMailLanguage(content, authorUser);
132    }
133    
134    /**
135     * Get the language to use for the mail notification of a comment
136     * @param content The content commented
137     * @param user The user to notify. Can be null, in this case the content's language or the default language will be used
138     * @return The language to use for the mail notification of a comment
139     */
140    protected String getMailLanguage(Content content, User user)
141    {
142        String userLanguage = null;
143        if (user != null)
144        {
145            userLanguage = user.getLanguage();
146        }
147        
148        // If no language was found, use the content's language then the default language
149        return StringUtils.defaultIfBlank(userLanguage, StringUtils.defaultIfBlank(content.getLanguage(), _userLanguagesManager.getDefaultLanguage()));
150    }
151    
152    /**
153     * Try to resolve the user author of the comment as a User from author or email provided in the comment
154     * @param content the commented content
155     * @param comment the comment
156     * @return The user author or null if not found
157     */
158    protected User resolveCommentAuthor(Content content, Comment comment)
159    {
160        return resolveCommentAuthor(content, comment.getAuthor(), comment.getAuthorEmail());
161    }
162    
163    /**
164     * Try to resolve the user author of the comment as a User from author or email provided in the comment
165     * @param content The content commented
166     * @param commentAuthor The user identity of the comment's author if it exists (null for unauthenticated comment), used to get the user's language
167     * @param commentAuthorEmail The email of the comment's author, used to find a user with this email on the content's site and get its language if the author user identity is not provided
168     * @return The user author or null if not found
169     */
170    protected User resolveCommentAuthor(Content content, UserIdentity commentAuthor, String commentAuthorEmail)
171    {
172        if (commentAuthor != null)
173        {
174            return _userManager.getUser(commentAuthor);
175        }
176        else
177        {
178            Request request = ContextHelper.getRequest(_context);
179            String siteName = WebHelper.getSiteName(request, content);
180            
181            List<String> userPopulationsContexts = new ArrayList<>();
182            userPopulationsContexts.add("/sites/" + siteName);
183            userPopulationsContexts.add("/sites-fo/" + siteName);
184            
185            Set<String> userPopulationsOnSite = _populationContextHelper.getUserPopulationsOnContexts(userPopulationsContexts, false, false);
186            try
187            {
188                return _userManager.getUserByEmail(userPopulationsOnSite, commentAuthorEmail);
189            }
190            catch (NotUniqueUserException e)
191            {
192                getLogger().warn("Cannot find user with email '{}'. Multiple users found with this email", commentAuthorEmail, e);
193                return null;
194            }
195        }
196    }
197    
198    /**
199     * Get the email sender for the mail notification of a comment
200     * @param content The content commented
201     * @return The email sender for the mail notification of a comment
202     */
203    protected String getMailSender(Content content)
204    {
205        Request request = ContextHelper.getRequest(_context);
206        String siteName = WebHelper.getSiteName(request, content);
207        Site site = _siteManager.getSite(siteName);
208        if (site != null)
209        {
210            return site.getValue("site-mail-from");
211        }
212        
213        return Config.getInstance().getValue("smtp.mail.from");
214    }
215    
216    /**
217     * Get the email recipient for the mail notification of a comment
218     * @param comment The comment
219     * @return The email recipient for the mail notification of a comment. Can be null or empty.
220     */
221    protected String getMailRecipient(Comment comment)
222    {
223        String recipient = comment.getAuthorEmail();
224        if (StringUtils.isBlank(recipient))
225        {
226            recipient = Optional.ofNullable(comment.getAuthor())
227                    .map(_userManager::getUser)
228                    .filter(Objects::nonNull)
229                    .map(User::getEmail)
230                    .orElse(null);
231                    
232        }
233        return recipient;
234    }
235    
236}