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