001/*
002 *  Copyright 2016 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 */
016
017package org.ametys.plugins.workspaces.project.helper;
018
019import java.time.ZonedDateTime;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.avalon.framework.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.logger.LogEnabled;
033import org.apache.avalon.framework.logger.Logger;
034import org.apache.avalon.framework.service.ServiceException;
035import org.apache.avalon.framework.service.ServiceManager;
036import org.apache.avalon.framework.service.Serviceable;
037import org.apache.cocoon.components.ContextHelper;
038import org.apache.cocoon.environment.Request;
039import org.apache.commons.lang3.StringUtils;
040import org.w3c.dom.Node;
041import org.w3c.dom.NodeList;
042
043import org.ametys.cms.languages.LanguagesManager;
044import org.ametys.cms.tag.Tag;
045import org.ametys.core.right.RightManager;
046import org.ametys.core.right.RightManager.RightResult;
047import org.ametys.core.user.CurrentUserProvider;
048import org.ametys.core.user.UserIdentity;
049import org.ametys.core.util.DateUtils;
050import org.ametys.core.util.dom.AmetysNodeList;
051import org.ametys.core.util.dom.MapElement;
052import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
053import org.ametys.plugins.repository.RepositoryConstants;
054import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
055import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
056import org.ametys.plugins.workspaces.ObservationConstants;
057import org.ametys.plugins.workspaces.activities.activitystream.ActivityStreamClientInteraction;
058import org.ametys.plugins.workspaces.alert.AlertWorkspaceModule;
059import org.ametys.plugins.workspaces.categories.Category;
060import org.ametys.plugins.workspaces.categories.CategoryHelper;
061import org.ametys.plugins.workspaces.categories.CategoryProviderExtensionPoint;
062import org.ametys.plugins.workspaces.keywords.KeywordsDAO;
063import org.ametys.plugins.workspaces.news.NewsWorkspaceModule;
064import org.ametys.plugins.workspaces.project.ProjectManager;
065import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
066import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
067import org.ametys.plugins.workspaces.project.objects.Project;
068import org.ametys.runtime.i18n.I18nizableText;
069import org.ametys.web.WebConstants;
070import org.ametys.web.cocoon.I18nUtils;
071import org.ametys.web.repository.page.Page;
072import org.ametys.web.repository.page.ZoneItem;
073import org.ametys.web.repository.site.Site;
074import org.ametys.web.repository.site.SiteManager;
075import org.ametys.web.transformation.xslt.AmetysXSLTHelper;
076
077/**
078 * Helper component to be used from XSL stylesheets to get info related to projects.
079 */
080public class ProjectXsltHelper implements Serviceable, Contextualizable, LogEnabled
081{
082    private static Logger _logger;
083    private static Context _context;
084    private static SiteManager _siteManager;
085    private static ProjectManager _projectManager;
086    private static CategoryProviderExtensionPoint _categoryProviderEP;
087    private static CategoryHelper _categoryHelper;
088    private static I18nUtils _i18nUtils;
089    private static ActivityStreamClientInteraction _activityStream;
090    private static LanguagesManager _languagesManager;
091    private static CurrentUserProvider _currentUserProvider;
092    private static WorkspaceModuleExtensionPoint _workspaceModuleEP;
093    private static RightManager _rightManager;
094    private static KeywordsDAO _keywordsDAO;
095    
096    @Override
097    public void contextualize(Context context) throws ContextException
098    {
099        _context = context;
100    }
101    
102    @Override
103    public void enableLogging(Logger logger)
104    {
105        _logger = logger;
106    }
107    
108    @Override
109    public void service(ServiceManager manager) throws ServiceException
110    {
111        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
112        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
113        _categoryProviderEP = (CategoryProviderExtensionPoint) manager.lookup(CategoryProviderExtensionPoint.ROLE);
114        _categoryHelper = (CategoryHelper) manager.lookup(CategoryHelper.ROLE);
115        _i18nUtils = (I18nUtils) manager.lookup(org.ametys.core.util.I18nUtils.ROLE);
116        _activityStream = (ActivityStreamClientInteraction) manager.lookup(ActivityStreamClientInteraction.ROLE);
117        _languagesManager = (LanguagesManager) manager.lookup(LanguagesManager.ROLE);
118        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
119        _workspaceModuleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
120        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
121        _keywordsDAO = (KeywordsDAO) manager.lookup(KeywordsDAO.ROLE);
122    }
123    
124    /**
125     * Returns the current project
126     * @return the current project
127     */
128    public static String project()
129    {
130        Project project = null;
131        
132        Request request = ContextHelper.getRequest(_context);
133        String siteName = (String) request.getAttribute("site");
134        
135        if (StringUtils.isNotEmpty(siteName) && _siteManager.hasSite(siteName))
136        {
137            Site site = _siteManager.getSite(siteName);
138            List<Project> projects = _projectManager.getProjectsForSite(site);
139            
140            project = !projects.isEmpty() ? projects.get(0) : null;
141        }
142        
143        if (project == null && _logger.isWarnEnabled())
144        {
145            String warnMsg = String.format("No project was found for site '%s'.", siteName);
146            _logger.warn(warnMsg);
147        }
148        
149        return project != null ? project.getName() : null;
150    }
151    
152    /**
153     * Returns the information of current project
154     * @return the information of current project
155     */
156    public static MapElement projectInfo()
157    {
158        return projectInfo(project());
159    }
160    
161    /**
162     * Returns the project information
163     * @param projectName The project name
164     * @return the project information
165     */
166    public static MapElement projectInfo(String projectName)
167    {
168        Map<String, Object> projectInfo = new HashMap<>();
169        
170        Project project = _projectManager.getProject(projectName);
171        if (project != null)
172        {
173            projectInfo.put("name", project.getName());
174            projectInfo.put("id", project.getId());
175            projectInfo.put("title", project.getTitle());
176            projectInfo.put("description", project.getDescription());
177            projectInfo.put("creationDate", _projectCreationDate(project));
178            projectInfo.put("lastActivityDate", projectLastActivityDate(project.getName()));
179            projectInfo.put("categoryLabel", _projectCategoryLabel(project));
180            projectInfo.put("categoryColor", _projectCategoryColor(project));
181            projectInfo.put("keyword", _projectKeywords(project));
182        }
183        
184        return new MapElement("project", projectInfo);
185    }
186    
187    /**
188     * Returns the title of the current project
189     * @return the title of the current project
190     */
191    public static String projectTitle()
192    {
193        return projectTitle(project());
194    }
195    
196    /**
197     * Returns the title of the given project
198     * @param projectName The project to consider
199     * @return the title of the given project or empty otherwise
200     */
201    public static String projectTitle(String projectName)
202    {
203        String title = "";
204        
205        Project project = _projectManager.getProject(projectName);
206        if (project != null)
207        {
208            title = project.getTitle();
209        }
210        
211        return title;
212    }
213    
214    /**
215     * Returns the description of the current project
216     * @return the description of the current project
217     */
218    public static String projectDescription()
219    {
220        return projectDescription(project());
221    }
222    
223    /**
224     * Returns the description of the given project
225     * @param projectName The project to consider
226     * @return the description of the given project or empty otherwise
227     */
228    public static String projectDescription(String projectName)
229    {
230        String title = "";
231        
232        Project project = _projectManager.getProject(projectName);
233        if (project != null)
234        {
235            title = project.getDescription();
236        }
237        
238        return title;
239    }
240    
241    /**
242     * Returns the keywords of the current project
243     * @return keywords of the current project
244     */
245    public static NodeList projectKeywords()
246    {
247        return projectKeywords(project());
248    }
249    
250    /**
251     * Returns the project keywords
252     * @param projectName the project name
253     * @return the project keywords
254     */
255    public static NodeList projectKeywords(String projectName)
256    {
257        List<MapElement> keywordsElmts = new ArrayList<>();
258        
259        Project project = _projectManager.getProject(projectName);
260        if (project != null)
261        {
262            List<Map<String, String>> keywords = _projectKeywords(project);
263            for (Map<String, String> keyword : keywords)
264            {
265                keywordsElmts.add(new MapElement("keyword", keyword));
266            }
267        }
268        
269        return new AmetysNodeList(keywordsElmts);
270    }
271    
272    private static List<Map<String, String>> _projectKeywords(Project project)
273    {
274        List<Map<String, String>> keywords = new ArrayList<>();
275        
276        for (String keyword : project.getKeywords())
277        {
278            Tag tag = _keywordsDAO.getTag(keyword, Collections.emptyMap());
279            if (tag != null)
280            {
281                String title = _i18nUtils.translate(tag.getTitle(), AmetysXSLTHelper.lang());
282                keywords.add(Map.of("title", title, "name", keyword));
283            }
284        }
285        
286        return keywords;
287    }
288    
289    /**
290     * Returns the creation date of the current project
291     * @return the creation date of the current project at the ISO format or empty otherwise
292     */
293    public static String projectCreationDate()
294    {
295        return projectCreationDate(project());
296    }
297    
298    /**
299     * Returns the creation date of the given project
300     * @param projectName The project to consider
301     * @return the creation date of the given project at the ISO format or empty otherwise
302     */
303    public static String projectCreationDate(String projectName)
304    {
305        String creationDate = "";
306        
307        Project project = _projectManager.getProject(projectName);
308        if (project != null)
309        {
310            creationDate = _projectCreationDate(project);
311        }
312        
313        return creationDate;
314    }
315    
316    private static String _projectCreationDate(Project project)
317    {
318        ZonedDateTime date = project.getCreationDate();
319        return date.format(DateUtils.getISODateTimeFormatter());
320    }
321    
322    /**
323     * Return the color associated to the first category associated to the current project
324     * @return the hexa code color or the default color if no color is associated to the first category or if not category is associated.
325     * &lt;color&gt;
326     *     &lt;main&gt;#FFFFFF&lt;/main&gt;
327     *     &lt;text&gt;#000000&lt;/text&gt;
328     * &lt;/color&gt;
329     */
330    public static MapElement projectCategoryColor()
331    {
332        return projectCategoryColor(project());
333    }
334    
335    /**
336     * Return the color associated to the first category associated to the given project
337     * @param projectName The project to consider
338     * @return the hexa code color or the default color if no color is associated to the first category or if not category is associated
339     * &lt;color&gt;
340     *     &lt;main&gt;#FFFFFF&lt;/main&gt;
341     *     &lt;text&gt;#000000&lt;/text&gt;
342     * &lt;/color&gt;
343     */
344    public static MapElement projectCategoryColor(String projectName)
345    {
346        Project project = _projectManager.getProject(projectName);
347        Map<String, String> color = _projectCategoryColor(project);
348        return new MapElement("color", color);
349    }
350    
351    private static Map<String, String> _projectCategoryColor(Project project)
352    {
353        Category category = _getFirstCategory(project);
354        return _categoryColor(category);
355    }
356    
357    /**
358     * Return the color associated to the category
359     * @param categoryId category id
360     * @return the hexa code color or the default color if no color is associated to the category or if null
361     * &lt;color&gt;
362     *     &lt;main&gt;#FFFFFF&lt;/main&gt;
363     *     &lt;text&gt;#000000&lt;/text&gt;
364     * &lt;/color&gt;
365     */
366    public static MapElement categoryColor(String categoryId)
367    {
368        Category category = _categoryProviderEP.getTag(categoryId, null);
369        return categoryColor(category);
370    }
371    
372    /**
373     * Return the color associated to the category
374     * @param category category
375     * @return the hexa code color or the default color if no color is associated to the category or if null
376     * &lt;color&gt;
377     *     &lt;main&gt;#FFFFFF&lt;/main&gt;
378     *     &lt;text&gt;#000000&lt;/text&gt;
379     * &lt;/color&gt;
380     */
381    public static MapElement categoryColor(Category category)
382    {
383        Map<String, String> color = _categoryColor(category);
384        return new MapElement("color", color);
385    }
386    
387    private static Map<String, String> _categoryColor(Category category)
388    {
389        return _categoryHelper.getCategoryColor(category);
390    }
391    
392    private static Category _getFirstCategory(Project project)
393    {
394        if (project != null)
395        {
396            for (String categoryId : project.getCategories())
397            {
398                Category category = _categoryProviderEP.getTag(categoryId, null);
399                if (category != null)
400                {
401                    return category;
402                }
403            }
404        }
405        return null;
406    }
407    
408    
409    /**
410     * Return the color associated to the first category associated to the current project
411     * @return the hexa code color or the default color if no color is associated to the first category or if not category is associated
412     */
413    public static String projectCategoryLabel()
414    {
415        return projectCategoryLabel(project());
416    }
417    
418    /**
419     * Return the color associated to the first category associated to the given project
420     * @param projectName The project to consider
421     * @return the label or empty if no category
422     */
423    public static String projectCategoryLabel(String projectName)
424    {
425        Project project = _projectManager.getProject(projectName);
426        if (project != null)
427        {
428            return _projectCategoryLabel(project);
429        }
430        return null;
431    }
432    
433    private static String _projectCategoryLabel(Project project)
434    {
435        Category category = _getFirstCategory(project);
436        if (category != null)
437        {
438            return _i18nUtils.translate(category.getTitle(), AmetysXSLTHelper.lang());
439        }
440        return null;
441    }
442    
443    /**
444     * Get the date of last activity for current project
445     * @return the formatted date of last activity or creation date if there is no activity yet
446     */
447    public static String projectLastActivityDate()
448    {
449        return projectLastActivityDate(project());
450    }
451    
452    /**
453     * Get the date of last activity for a given project
454     * @param projectName the project's name
455     * @return the formatted date of last activity or creation date if there is no activity yet
456     */
457    public static String projectLastActivityDate(String projectName)
458    {
459        ZonedDateTime lastDate = _activityStream.getDateOfLastActivity(projectName, Arrays.asList(ObservationConstants.EVENT_MEMBER_ADDED, ObservationConstants.EVENT_MEMBER_DELETED));
460        return lastDate != null ? lastDate.format(DateUtils.getISODateTimeFormatter()) : projectCreationDate();
461    }
462    
463    /**
464     * True if the resource comes from workspaces
465     * @param resourceId the resource id
466     * @return true if the resource comes from workspaces
467     */
468    public static boolean isResourceFromWorkspace(String resourceId)
469    {
470        return _projectManager.getParentProject(resourceId) != null;
471    }
472    
473    /**
474     * Get the site of a project's resource
475     * @param projectResourceId The resource id
476     * @return The site &lt;site id="site://xxx" name="siteName"&gt;&lt;title&gt;Site's titleX&lt;/title&gt;&lt;url&gt;http://...&lt;/url&gt;/site&gt;
477     */
478    public static Node resourceSite(String projectResourceId)
479    {
480        Project project = _projectManager.getParentProject(projectResourceId);
481        if (project != null)
482        {
483            Site site = project.getSite();
484            
485            Map<String, String> attributes = new HashMap<>();
486            attributes.put("name", site.getName());
487            attributes.put("id", site.getId());
488            
489            Map<String, String> values = new HashMap<>();
490            values.put("title", site.getTitle());
491            values.put("url", site.getUrl());
492            
493            return new MapElement("site", attributes, values);
494        }
495        
496        return null;
497    }
498    
499    /**
500     * Get the id of news root's page
501     * @return the id of news root's page or null if not exists
502     */
503    public static String getNewsRootPageId()
504    {
505        Request request = ContextHelper.getRequest(_context);
506        
507        // Retrieve the current workspace.
508        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
509        
510        try
511        {
512            // Force default workspace (news root's page can not be publish yet as it is a node page)
513            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
514            
515            return getModulePage(NewsWorkspaceModule.NEWS_MODULE_ID);
516        }
517        finally
518        {
519            // Restore workspace
520            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
521        }
522    }
523    
524    /**
525     * Get the id of alerts root's page
526     * @return the id of alerts root's page or null if not exists
527     */
528    public static String getAlertsRootPageId()
529    {
530        Request request = ContextHelper.getRequest(_context);
531        
532        // Retrieve the current workspace.
533        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
534        
535        try
536        {
537            // Force default workspace (news root's page can not be publish yet as it is a node page)
538            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
539            
540            return getModulePage(AlertWorkspaceModule.ALERT_MODULE_ID);
541        }
542        finally
543        {
544            // Restore workspace
545            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
546        }
547    }
548    
549    /**
550     * Get the module page.
551     * Be careful, the read access is not check !
552     * @param moduleId the module id
553     * @return the module page
554     */
555    public static String getModulePage(String moduleId)
556    {
557        String projectName = project();
558        Project project = _projectManager.getProject(projectName);
559        
560        Set<Page> newsRootPages = _projectManager.getModulePages(project, moduleId);
561        Iterator<Page> it = newsRootPages.iterator();
562        if (it.hasNext())
563        {
564            return it.next().getId();
565        }
566        
567        return null;
568    }
569    
570    /**
571     * Get the translated label associated to the code
572     * @param code The language code
573     * @param language The language in which translate the label
574     * @return The label associated to the code
575     */
576    public static String getLanguageLabel(String code, String language)
577    {
578        I18nizableText label = _languagesManager.getLanguage(code).getLabel();
579        return _i18nUtils.translate(label, language);
580    }
581    
582    /**
583     * Get the language that will be the default value of the available values.
584     * First choice will be the page language, 'en' otherwise, first available language otherwise.
585     * @param language The current page language
586     * @return A language code
587     */
588    public static String computeDefaultLanguage(String language)
589    {
590        Request request = ContextHelper.getRequest(_context);
591        ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM);
592        ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters();
593        
594        String[] availableLanguages = dataHolder.getValue("availableLanguages");
595        if (Arrays.stream(availableLanguages).anyMatch(l -> l.equals(language)))
596        {
597            return language;
598        }
599        else if (Arrays.stream(availableLanguages).anyMatch(l -> l.equals("en")))
600        {
601            return "en";
602        }
603        else
604        {
605            return availableLanguages[0];
606        }
607    }
608    
609    /**
610     * Determines if the current user is a manager of current project
611     * @return true if the current user is manager
612     */
613    public static boolean isManager()
614    {
615        UserIdentity user = _currentUserProvider.getUser();
616        String projectName = project();
617        return _projectManager.isManager(projectName, user);
618    }
619    
620    /**
621     * Determines if a user is a manager
622     * @param login the user's login
623     * @param populationId the user's population
624     * @return true if the user is manager
625     */
626    public static boolean isManager(String login, String populationId)
627    {
628        String projectName = project();
629        UserIdentity user = new UserIdentity(login, populationId);
630        return _projectManager.isManager(projectName, user);
631    }
632    
633    /**
634     * Determines if the current user is a manager for the project
635     * @param projectName the project's name
636     * @return true if the current user is manager
637     */
638    public static boolean isManager(String projectName)
639    {
640        UserIdentity user = _currentUserProvider.getUser();
641        return _projectManager.isManager(projectName, user);
642    }
643    
644    /**
645     * Determines if the given user is a manager for the project
646     * @param projectName the project's name
647     * @param login the user's login
648     * @param populationId the user's population
649     * @return true if the user is manager
650     */
651    public static boolean isManager(String projectName, String login, String populationId)
652    {
653        UserIdentity user = new UserIdentity(login, populationId);
654        return _projectManager.isManager(projectName, user);
655    }
656    
657    
658    /**
659     * Returns true if the current user has the specified right on the specified workspace module
660     * @param rightId Right Id
661     * @param moduleId Module Id
662     * @return true if the current user has the specified right on the specified module
663     */
664    public static boolean hasRightOnModule(String rightId, String moduleId)
665    {
666        String projectName = project();
667        WorkspaceModule module = _workspaceModuleEP.getModule(moduleId);
668        if (module != null && projectName != null)
669        {
670            Project project = _projectManager.getProject(projectName);
671            if (project != null)
672            {
673                ModifiableResourceCollection moduleRoot = module.getModuleRoot(project, false);
674                if (moduleRoot != null)
675                {
676                    return  _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW;
677                }
678            }
679        }
680        
681        return false;
682    }
683
684    /**
685     * Returns true if current user can access to the back-office
686     * @return true if current user can access to the back-office
687     */
688    public static boolean canAccessBO()
689    {
690        String projectName = project();
691        Project project = _projectManager.getProject(projectName);
692        return project != null ? _projectManager.canAccessBO(project) : false;
693    }
694    
695    /**
696     * Returns true if current user can leave the project
697     * @return true if current user can leave the project
698     */
699    public static boolean canLeaveProject()
700    {
701        String projectName = project();
702        Project project = _projectManager.getProject(projectName);
703        return project != null ? _projectManager.canLeaveProject(project) : false;
704    }
705}