001/*
002 *  Copyright 2020 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.workspaces;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.Optional;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034import org.apache.cocoon.Constants;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.commons.lang3.StringUtils;
038import org.apache.commons.lang3.tuple.Pair;
039
040import org.ametys.cms.content.indexing.solr.SolrResourceGroupedMimeTypes;
041import org.ametys.core.right.RightManager;
042import org.ametys.core.right.RightManager.RightResult;
043import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder;
044import org.ametys.core.user.CurrentUserProvider;
045import org.ametys.core.user.UserIdentity;
046import org.ametys.core.user.UserManager;
047import org.ametys.core.util.I18nUtils;
048import org.ametys.core.util.URIUtils;
049import org.ametys.core.util.language.UserLanguagesManager;
050import org.ametys.core.util.mail.SendMailHelper;
051import org.ametys.core.util.mail.SendMailHelper.MailBuilder;
052import org.ametys.plugins.explorer.resources.Resource;
053import org.ametys.plugins.repository.TraversableAmetysObject;
054import org.ametys.plugins.repository.tag.TaggableAmetysObject;
055import org.ametys.plugins.workspaces.project.ProjectConstants;
056import org.ametys.plugins.workspaces.project.ProjectManager;
057import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
058import org.ametys.plugins.workspaces.project.objects.Project;
059import org.ametys.plugins.workspaces.tags.ProjectTagsDAO;
060import org.ametys.runtime.authentication.AccessDeniedException;
061import org.ametys.runtime.i18n.I18nizableText;
062import org.ametys.runtime.plugin.component.AbstractLogEnabled;
063import org.ametys.web.URIPrefixHandler;
064import org.ametys.web.WebHelper;
065import org.ametys.web.repository.site.Site;
066import org.ametys.web.repository.site.SiteManager;
067import org.ametys.web.repository.sitemap.Sitemap;
068
069import jakarta.mail.MessagingException;
070
071/**
072 * Utility classes for workspaces
073 *
074 */
075public class WorkspacesHelper extends AbstractLogEnabled implements Component, Serviceable, Contextualizable
076{
077    /** Avalon role */
078    public static final String ROLE = WorkspacesHelper.class.getName();
079
080    /** The right manager */
081    protected RightManager _rightManager;
082    
083    /** The workspace module extension point */
084    protected WorkspaceModuleExtensionPoint _workspaceModuleEP;
085
086    /** The current user provider. */
087    protected CurrentUserProvider _currentUserProvider;
088    
089    /**
090     * Enumeration for file type
091     */
092    public enum FileType
093    {
094        /** Text document */
095        TEXT,
096        /** PDF document */
097        PDF,
098        /** Spreadsheet */
099        SPREADSHEET,
100        /** Presentation */
101        PRES,
102        /** Image */
103        IMAGE,
104        /** Video */
105        VIDEO,
106        /** Audio */
107        AUDIO,
108        /** Archive */
109        ARCHIVE,
110        /** Unknown type */
111        UNKNOWN
112    }
113
114    private Context _context;
115    private org.apache.cocoon.environment.Context _cocoonContext;
116    private URIPrefixHandler _prefixHandler;
117    private ProjectTagsDAO _projectTagsDAO;
118    private SiteManager _siteManager;
119    private ProjectManager _projectManager;
120    private UserLanguagesManager _userLanguagesManager;
121    private UserManager _userManager;
122    private I18nUtils _i18nUtils;
123    private WorkspacesHelper _workspacesHelper;
124    
125    public void contextualize(Context context) throws ContextException
126    {
127        _context = context;
128        _cocoonContext = (org.apache.cocoon.environment.Context) _context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
129    }
130    
131    public void service(ServiceManager smanager) throws ServiceException
132    {
133        _prefixHandler = (URIPrefixHandler) smanager.lookup(URIPrefixHandler.ROLE);
134        _projectTagsDAO = (ProjectTagsDAO) smanager.lookup(ProjectTagsDAO.ROLE);
135        _projectManager = (ProjectManager) smanager.lookup(ProjectManager.ROLE);
136        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
137
138        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
139        _workspaceModuleEP = (WorkspaceModuleExtensionPoint) smanager.lookup(WorkspaceModuleExtensionPoint.ROLE);
140        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
141        _userLanguagesManager = (UserLanguagesManager) smanager.lookup(UserLanguagesManager.ROLE);
142        _userManager = (UserManager) smanager.lookup(UserManager.ROLE);
143        _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE);
144        _workspacesHelper = (WorkspacesHelper) smanager.lookup(WorkspacesHelper.ROLE);
145    }
146    
147    /**
148     * Determines if the resource file is an image
149     * @param file the resource
150     * @return true if the resource file is an image
151     */
152    public boolean isImage(Resource file)
153    {
154        return SolrResourceGroupedMimeTypes.getGroup(file.getMimeType())
155            .map(groupMimeType -> FileType.IMAGE == FileType.valueOf(groupMimeType.toUpperCase()))
156            .orElse(false);
157    }
158    
159    /**
160     * Get the file type of a resource file
161     * @param file the resource
162     * @return the {@link FileType}
163     */
164    public FileType getFileType(Resource file)
165    {
166        Optional<String> group = SolrResourceGroupedMimeTypes.getGroup(file.getMimeType());
167        
168        return group
169                .map(groupMimeType -> FileType.valueOf(groupMimeType.toUpperCase()))
170                .orElse(FileType.UNKNOWN);
171    }
172    
173    /**
174     * Get the file type of a resource from its name
175     * @param filename the resource's name
176     * @return the {@link FileType}
177     */
178    public FileType getFileType(String filename)
179    {
180        String mimeType = _cocoonContext.getMimeType(filename);
181        Optional<String> group = SolrResourceGroupedMimeTypes.getGroup(mimeType);
182        
183        return group
184                .map(groupMimeType -> FileType.valueOf(groupMimeType.toUpperCase()))
185                .orElse(FileType.UNKNOWN);
186    }
187
188    /**
189     * Get user avatar
190     * @param userIdentity the user identity
191     * @param lang the lang for user's content
192     * @param size the size in pixel of image
193     * @return the avatar url
194     */
195    public String getAvatar(UserIdentity userIdentity, String lang, int size)
196    {
197        StringBuilder sb = new StringBuilder();
198        
199        String siteName = WebHelper.getSiteName(_getRequest());
200        sb.append(_prefixHandler.getUriPrefix(siteName))
201            .append("/_plugins/core-ui/user/")
202            .append(userIdentity.getPopulationId())
203            .append("/")
204            .append(URIUtils.encodePath(userIdentity.getLogin()))
205            .append("/image_")
206            .append(size)
207            .append("?lang=")
208            .append(lang);
209        
210        return sb.toString();
211    }
212    
213    private Request _getRequest()
214    {
215        return ContextHelper.getRequest(_context);
216    }
217    
218    /**
219     * Get current project from request
220     * @return the current project or null if not found.
221     */
222    public Project getProjectFromRequest()
223    {
224        Request request = _getRequest();
225        String projectName = (String) request.getAttribute(WorkspacesConstants.REQUEST_ATTR_PROJECT_NAME);
226        
227        if (StringUtils.isNotEmpty(projectName))
228        {
229            return _projectManager.getProject(projectName);
230        }
231        
232        String siteName = WebHelper.getSiteName(request);
233        if (StringUtils.isNotEmpty(siteName) && _siteManager.hasSite(siteName))
234        {
235            Site site = _siteManager.getSite(siteName);
236            List<Project> projects = _projectManager.getProjectsForSite(site);
237            if (!projects.isEmpty())
238            {
239                return projects.get(0);
240            }
241        }
242        
243        return null;
244    }
245    
246    /**
247     * Handle tags for the edition of a TaggableAmetysObject
248     * @param taggableAmetysObject the object to edit
249     * @param tags the tags
250     * @param moduleRoot the module root, used to check tag creation rights if needed
251     * @return the created tags
252     */
253    public List<Map<String, Object>> handleTags(TaggableAmetysObject taggableAmetysObject, List<Object> tags, TraversableAmetysObject moduleRoot)
254    {
255        List<String> createdTags = new ArrayList<>();
256        List<Map<String, Object>> createdTagsJson = new ArrayList<>();
257        // Tag new tags
258        for (Object tag : tags)
259        {
260            // Tag doesn't exist so create the tag
261            if (tag instanceof Map)
262            {
263                if (_rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_ADD_TAG, moduleRoot) != RightResult.RIGHT_ALLOW)
264                {
265                    throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to handle tags without convenient right.");
266                }
267                @SuppressWarnings("unchecked")
268                String tagText = (String) ((Map<String, Object>) tag).get("text");
269                List<Map<String, Object>> newTags = _projectTagsDAO.addTags(new String[] {tagText});
270                String newTag = (String) newTags.get(0).get("name");
271                taggableAmetysObject.tag(newTag);
272                createdTags.add(newTag);
273                createdTagsJson.addAll(newTags);
274            }
275            else
276            {
277                taggableAmetysObject.tag((String) tag);
278            }
279        }
280        // Untag unused tags
281        for (String tag : taggableAmetysObject.getTags())
282        {
283            if (!tags.contains(tag) && !createdTags.contains(tag))
284            {
285                taggableAmetysObject.untag(tag);
286            }
287        }
288        return createdTagsJson;
289    }
290
291    /**
292     * Get the lang for this project, or en if none found
293     * @param project the project to check
294     * @return a lang in this project, or en if none found
295     */
296    public String getLang(Project project)
297    {
298        // Return "en" by default if no language is available, or if the site could not be found
299        return getLang(project, "en");
300    }
301    /**
302     * Get the lang for this project, or given default language if none found
303     * @param project the project to check
304     * @param defaultLanguage the default language to return if no language is available for the project or if the site could not be found
305     * @return a lang in this project or given default language
306     */
307    public String getLang(Project project, String defaultLanguage)
308    {
309        /*
310         * get the site, then the sitemaps (transformed back to a stream of sitemap)
311         * then get the name
312         * return the 1st one, or the default language if none is available, or if the site could not be found
313         */
314        
315        Site site = project.getSite();
316        
317        if (site != null)
318        {
319            return site.getSitemaps()
320                       .stream()
321                       .filter(Objects::nonNull)
322                       .map(Sitemap::getName)
323                       .findFirst()
324                       .orElse(defaultLanguage);
325        }
326        else
327        {
328            return defaultLanguage;
329        }
330    }
331    
332    /**
333     * Send mail to all project managers
334     * @param project the project
335     * @param mailFrom the mail from
336     * @param i18nSubject the i18n subject
337     * @param bodyBuilder the mail body builder
338     * @param i18nTextBody the i18n text body
339     * @throws MessagingException if an error occurs while sending the mail
340     * @throws IOException if an error occurs while building the mail body
341     */
342    public void sendMailToManagers(Project project, String mailFrom, I18nizableText i18nSubject, MailBodyBuilder bodyBuilder, I18nizableText i18nTextBody) throws MessagingException, IOException
343    {
344        String defaultLanguage = _workspacesHelper.getLang(project, _userLanguagesManager.getDefaultLanguage());
345        Map<String, List<String>> managersToNotifyByLanguage = Arrays.stream(project.getManagers())
346                .map(manager -> _userManager.getUser(manager))
347                .filter(Objects::nonNull)
348                .map(user -> Pair.of(user, user.getEmail()))
349                .filter(p -> StringUtils.isNotEmpty(p.getRight()))
350                .collect(Collectors.groupingBy(
351                        p -> {
352                            return StringUtils.defaultIfBlank(p.getLeft().getLanguage(), defaultLanguage);
353                        },
354                        Collectors.mapping(
355                                Pair::getRight,
356                                Collectors.toList()
357                        )
358                    )
359                );
360
361        for (String lang : managersToNotifyByLanguage.keySet())
362        {
363            // If no user language, user project site one
364            String subject = _i18nUtils.translate(i18nSubject, lang);
365            
366            String htmlBody = bodyBuilder.withLanguage(lang).build();
367            
368            MailBuilder mail = SendMailHelper.newMail()
369                .withSubject(subject)
370                .withHTMLBody(htmlBody)
371                .withSender(mailFrom)
372                .withRecipients(managersToNotifyByLanguage.get(lang));
373                
374            if (i18nTextBody != null)
375            {
376                String textBody = _i18nUtils.translate(i18nTextBody, lang);
377                mail.withTextBody(textBody);
378            }
379            
380            mail.sendMail();
381        }
382    }
383}