001/*
002 *  Copyright 2018 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.ugc.actions;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.parameters.Parameters;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.cocoon.acting.ServiceableAction;
028import org.apache.cocoon.environment.ObjectModelHelper;
029import org.apache.cocoon.environment.Redirector;
030import org.apache.cocoon.environment.Request;
031import org.apache.cocoon.environment.SourceResolver;
032import org.apache.commons.lang3.StringUtils;
033
034import org.ametys.cms.content.ContentHelper;
035import org.ametys.cms.contenttype.ContentType;
036import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
037import org.ametys.cms.contenttype.ContentTypesHelper;
038import org.ametys.cms.repository.Content;
039import org.ametys.cms.repository.WorkflowAwareContent;
040import org.ametys.core.captcha.CaptchaHelper;
041import org.ametys.core.cocoon.ActionResultGenerator;
042import org.ametys.core.ui.mail.StandardMailBodyHelper;
043import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder;
044import org.ametys.core.user.CurrentUserProvider;
045import org.ametys.core.user.User;
046import org.ametys.core.user.UserIdentity;
047import org.ametys.core.user.UserManager;
048import org.ametys.core.util.I18nUtils;
049import org.ametys.core.util.JSONUtils;
050import org.ametys.core.util.language.UserLanguagesManager;
051import org.ametys.core.util.mail.SendMailHelper;
052import org.ametys.plugins.repository.AmetysObjectResolver;
053import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
054import org.ametys.plugins.ugc.UGCConstants;
055import org.ametys.runtime.i18n.I18nizableText;
056import org.ametys.web.cache.PageHelper;
057import org.ametys.web.content.FOContentCreationHelper;
058import org.ametys.web.repository.content.WebContent;
059import org.ametys.web.repository.page.Page;
060import org.ametys.web.repository.page.ZoneItem;
061import org.ametys.web.repository.site.Site;
062
063import com.google.common.collect.ArrayListMultimap;
064import com.google.common.collect.Multimap;
065
066import jakarta.mail.MessagingException;
067
068/**
069 * Create content action
070 */
071public class ProposeContentAction extends ServiceableAction
072{
073    private static final int _INITIAL_ACTION_ID = 1111;
074    
075    /** Ametys object resolver. */
076    protected AmetysObjectResolver _resolver;
077    
078    /** The content type extension point */
079    protected ContentTypeExtensionPoint _cTypeEP;
080    
081    /** The content type helper */
082    protected ContentTypesHelper _contentTypeHelper;
083    
084    /** The JSON Utils */
085    protected JSONUtils _jsonUtils;
086    
087    /** The current user provider */
088    protected CurrentUserProvider _currentUserProvider;
089    
090    /** The I18n utils */
091    protected I18nUtils _i18nUtils;
092
093    /** The user manager */
094    protected UserManager _userManager;
095
096    /** Helper for FO content creation */
097    protected FOContentCreationHelper _foContentCreationHelper;
098    
099    /** The page helper */
100    protected PageHelper _pageHelper;
101
102    /** The content helper */
103    protected ContentHelper _contentHelper;
104    /** The UserLanguagesManager */
105    protected UserLanguagesManager _userLanguagesManager;
106    
107    @Override
108    public void service(ServiceManager serviceManager) throws ServiceException
109    {
110        super.service(serviceManager);
111        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
112        _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
113        _contentTypeHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
114        _jsonUtils = (JSONUtils) serviceManager.lookup(JSONUtils.ROLE);
115        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
116        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
117        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
118        _foContentCreationHelper = (FOContentCreationHelper) serviceManager.lookup(FOContentCreationHelper.ROLE);
119        _pageHelper = (PageHelper) serviceManager.lookup(PageHelper.ROLE);
120        _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE);
121        _userLanguagesManager = (UserLanguagesManager) serviceManager.lookup(UserLanguagesManager.ROLE);
122    }
123    
124    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
125    {
126        Map<String, Object> result = new HashMap<>();
127        
128        Multimap<String, I18nizableText> errors = ArrayListMultimap.create();
129
130        Request request = ObjectModelHelper.getRequest(objectModel);
131        
132        String zoneId = request.getParameter("zoneId");
133        ZoneItem zoneItem = _resolver.resolveById(zoneId);
134        Page page = zoneItem.getParent().getParent().getParent().getParent();
135        
136        ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters();
137        String workflowName = serviceParameters.getValue("workflow");
138        String contentTypeId = serviceParameters.getValue("content-type");
139        UserIdentity[] adminUsers = serviceParameters.getValue("users");
140        
141        String siteName = request.getParameter("site");
142        String language = request.getParameter("lang");
143        
144        ContentType ugcMixin = _cTypeEP.getExtension(UGCConstants.UGC_MIXIN_TYPE);
145        
146        ContentType contentType = _cTypeEP.getExtension(contentTypeId);
147        Map<String, Object> values = _foContentCreationHelper.getAndValidateFormValues(request, contentType, "main", errors);
148        values.putAll(_foContentCreationHelper.getAndValidateFormValues(request, ugcMixin, "main", errors));
149        
150        // Check captcha if needed
151        if (_pageHelper.isCaptchaRequired(page))
152        {
153            String captchaValue = request.getParameter("captcha");
154            String captchaKey = request.getParameter("captcha-key");
155
156            // Validate the input
157            if (!CaptchaHelper.checkAndInvalidate(captchaKey, captchaValue))
158            {
159                errors.put("captcha", new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_FORM_ERROR_INVALID_CAPTCHA"));
160            }
161        }
162        
163        String gtuMode = zoneItem.getServiceParameters().getValue("general-terms-of-use-mode", false, "NONE");
164        if (!"NONE".equals(gtuMode) && !"true".equals(request.getParameter("gtu")))
165        {
166            errors.put("gtu", new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_FORM_ERROR_INVALID_GTU"));
167        }
168        
169        // If there are no errors
170        if (errors.isEmpty())
171        {
172            String title = request.getParameter("title");
173            
174            // we add a prefix to the title for the content name to prevent issue with numeric title
175            String prefix = StringUtils.substringAfterLast(contentTypeId, ".");
176            
177            result = _foContentCreationHelper.createAndEditContent(_INITIAL_ACTION_ID, new String[] {contentTypeId}, new String[] {UGCConstants.UGC_MIXIN_TYPE}, siteName, prefix + "-" + title, title, language, values, workflowName, null, new HashMap<>());
178            
179            if (result.containsKey(Content.class.getName()))
180            {
181                result.put("success", true);
182
183                // Send mail
184                WorkflowAwareContent content = (WorkflowAwareContent) result.get(Content.class.getName());
185                _sendMail(adminUsers, content);
186            }
187            else
188            {
189                result.put("success", false);
190            }
191        }
192        else
193        {
194            result.put("success", false);
195            result.put("errors", _jsonUtils.convertObjectToJson(errors.asMap()));
196        }
197        
198        request.setAttribute(ActionResultGenerator.MAP_REQUEST_ATTR, result);
199        return EMPTY_MAP;
200    }
201
202    /**
203     * Send mail to inform that a content is created
204     * @param adminUsers the admin users
205     * @param content the created content
206     */
207    protected void _sendMail(UserIdentity[] adminUsers, WorkflowAwareContent content)
208    {
209        if (adminUsers != null)
210        {
211            String creatorFullName = null;
212            UserIdentity creatorIdentity = _currentUserProvider.getUser();
213            if (creatorIdentity != null)
214            {
215                User creator = _userManager.getUser(creatorIdentity);
216                creatorFullName = creator.getFullName();
217            }
218            else
219            {
220                creatorFullName = content.getValue(UGCConstants.ATTRIBUTE_PATH_UGC_AUTHOR);
221            }
222            
223            Site site = _getSite(content);
224            
225            I18nizableText i18nSubject = _getI18nSubject(site);
226            MailBodyBuilder bodyBuilder = _getI18nBody(content, creatorFullName, site);
227            
228            Map<String, MailContent> mailContentByLanguage = new HashMap<>();
229            
230            for (UserIdentity adminUser : adminUsers)
231            {
232                User user = _userManager.getUser(adminUser);
233                if (user == null)
234                {
235                    getLogger().error("Can not send an e-mail for the content creation to the unexisting user " + adminUser);
236                }
237                else
238                {
239                    String userLanguage = StringUtils.defaultIfBlank(user.getLanguage(), _userLanguagesManager.getDefaultLanguage());
240
241                    String subject = null;
242                    String htmlBody = null;
243                    
244                    MailContent mailContent = mailContentByLanguage.computeIfAbsent(userLanguage, lang -> {
245                        try
246                        {
247                            return new MailContent(_i18nUtils.translate(i18nSubject, lang), bodyBuilder.withLanguage(lang).build());
248                        }
249                        catch (IOException e)
250                        {
251                            getLogger().warn("Fail to build HTML body for the content creation", e);
252                        }
253                        
254                        return null;
255                    });
256                    
257                    if (mailContent != null)
258                    {
259                        subject = mailContent.subject();
260                        htmlBody = mailContent.body();
261                        
262                        String email = user.getEmail();
263                        try
264                        {
265                            SendMailHelper.newMail()
266                                .withSubject(subject)
267                                .withHTMLBody(htmlBody)
268                                .withRecipient(email)
269                                .sendMail();
270                        }
271                        catch (MessagingException | IOException e)
272                        {
273                            getLogger().warn("Could not send an e-mail for the content creation to " + email, e);
274                        }
275                    }
276                }
277            }
278        }
279    }
280
281    /**
282     * Get the i18n body
283     * @param content the content
284     * @param creatorFullName the creator full name
285     * @param site the site
286     * @return The i18n body
287     */
288    protected MailBodyBuilder _getI18nBody(WorkflowAwareContent content, String creatorFullName, Site site)
289    {
290        List<String> parametersBody = new ArrayList<>();
291        parametersBody.add(content.getTitle());
292        parametersBody.add(creatorFullName);
293        
294        MailBodyBuilder bodyBuilder = StandardMailBodyHelper.newHTMLBody()
295            .withTitle(_getI18nSubject(site));
296            
297        if (site != null)
298        {
299            parametersBody.add(_getContentUri(content));
300            parametersBody.add(site.getTitle());
301            parametersBody.add(site.getUrl());
302            
303            bodyBuilder.withMessage(new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_FORM_CREATED_CONTENT_MAIL_BODY", parametersBody))
304                .withLink(_getContentUri(content), new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_FORM_CREATED_CONTENT_MAIL_BODY_CONTENT_LINK"));
305        }
306        else
307        {
308            bodyBuilder.withMessage(new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_FORM_CREATED_CONTENT_MAIL_BODY_NO_SITE", parametersBody));
309        }
310        return bodyBuilder;
311    }
312
313    /**
314     * Get the i18n subject
315     * @param site the site
316     * @return The i18n subject
317     */
318    protected I18nizableText _getI18nSubject(Site site)
319    {
320        I18nizableText i18nSubject = null;
321        if (site != null)
322        {
323            List<String> parametersSubject = new ArrayList<>();
324            parametersSubject.add(site.getTitle());
325            i18nSubject = new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_FORM_CREATED_CONTENT_MAIL_SUBJECT", parametersSubject);
326        }
327        else
328        {
329            i18nSubject = new I18nizableText("plugin.ugc", "PLUGINS_UGC_SERVICE_FORM_CREATED_CONTENT_MAIL_SUBJECT_NO_SITE");
330        }
331        return i18nSubject;
332    }
333    
334    /**
335     * Get the site name
336     * @param content The content
337     * @return the site name
338     */
339    protected Site _getSite(WorkflowAwareContent content)
340    {
341        if (content instanceof WebContent)
342        {
343            return ((WebContent) content).getSite();
344        }
345        
346        return null;
347    }
348    
349    
350    /**
351     * Get the content uri
352     * @param content the content
353     * @return the content uri
354     */
355    protected String _getContentUri(WorkflowAwareContent content)
356    {
357        return _contentHelper.getContentBOUrl(content, Map.of());
358    }
359    
360    private record MailContent(String subject, String body) { /* Nothing */ }
361}