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