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