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 */
016package org.ametys.plugins.workspaces.tasks;
017
018import java.time.LocalDate;
019import java.time.ZonedDateTime;
020import java.time.format.DateTimeFormatter;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.cocoon.servlet.multipart.Part;
031import org.apache.commons.lang.IllegalClassException;
032import org.apache.commons.lang3.StringUtils;
033
034import org.ametys.cms.repository.comment.Comment;
035import org.ametys.core.observation.Event;
036import org.ametys.core.right.RightManager.RightResult;
037import org.ametys.core.ui.Callable;
038import org.ametys.core.user.User;
039import org.ametys.core.user.UserIdentity;
040import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
041import org.ametys.plugins.repository.AmetysObject;
042import org.ametys.plugins.repository.AmetysRepositoryException;
043import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
044import org.ametys.plugins.workspaces.ObservationConstants;
045import org.ametys.plugins.workspaces.members.ProjectMemberManager;
046import org.ametys.plugins.workspaces.project.objects.Project;
047import org.ametys.plugins.workspaces.tags.ProjectTagProviderExtensionPoint;
048import org.ametys.plugins.workspaces.tasks.Task.CheckItem;
049import org.ametys.plugins.workspaces.tasks.jcr.JCRTask;
050import org.ametys.plugins.workspaces.tasks.jcr.JCRTaskFactory;
051import org.ametys.plugins.workspaces.tasks.json.TaskJSONHelper;
052
053/**
054 * DAO for interacting with tasks of a project
055 */
056public class WorkspaceTaskDAO extends AbstractWorkspaceTaskDAO
057{
058    /** The Avalon role */
059    public static final String ROLE = WorkspaceTaskDAO.class.getName();
060    
061    /** The project member manager */
062    protected ProjectMemberManager _projectMemberManager;
063    
064    /** The tag provider extension point */
065    protected ProjectTagProviderExtensionPoint _tagProviderExtPt;
066        
067    /** The task JSON helper */
068    protected TaskJSONHelper _taskJSONHelper;
069    
070    /** The task list DAO */
071    protected WorkspaceTasksListDAO _workspaceTasksListDAO;
072    
073    @Override
074    public void service(ServiceManager manager) throws ServiceException
075    {
076        super.service(manager);
077        _projectMemberManager = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE);
078        _tagProviderExtPt = (ProjectTagProviderExtensionPoint) manager.lookup(ProjectTagProviderExtensionPoint.ROLE);
079        _taskJSONHelper = (TaskJSONHelper) manager.lookup(TaskJSONHelper.ROLE);
080        _workspaceTasksListDAO = (WorkspaceTasksListDAO) manager.lookup(WorkspaceTasksListDAO.ROLE);
081    }
082
083    /**
084     * Get the tasks from project
085     * @return the list of tasks
086     * @throws IllegalAccessException If an error occurs when checking the rights
087     */
088    @Callable
089    public List<Map<String, Object>> getTasks() throws IllegalAccessException
090    {
091        String projectName = _getProjectName();
092        
093        ModifiableResourceCollection moduleRoot = _getModuleRoot(projectName);
094        if (!_rightManager.currentUserHasReadAccess(moduleRoot))
095        {
096            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to get tasks without reader right");
097        }
098        
099        List<Map<String, Object>> tasksInfo = new ArrayList<>();
100        Project project = _projectManager.getProject(projectName);
101        for (Task task : getProjectTasks(project))
102        {
103            tasksInfo.add(_taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName()));
104        }
105        
106        return tasksInfo;
107    }
108    
109    /**
110     * Add a new task to the tasks list
111     * @param tasksListId the tasks list id
112     * @param parameters The task parameters
113     * @param newFiles the files to add
114     * @param newFileNames the file names to add
115     * @return The task data
116     * @throws IllegalAccessException If an error occurs when checking the rights
117     */
118    @Callable
119    public Map<String, Object> addTask(String tasksListId, Map<String, Object> parameters, List<Part> newFiles, List<String> newFileNames) throws IllegalAccessException
120    {
121        String projectName = _getProjectName();
122        
123        if (StringUtils.isBlank(tasksListId))
124        {
125            throw new IllegalArgumentException("Tasks list id is mandatory to create a new task");
126        }
127        
128        ModifiableTraversableAmetysObject tasksRoot = _getTasksRoot(projectName);
129        
130        // Check user right
131        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_HANDLE_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
132        {
133            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to add task without convenient right [" + RIGHTS_HANDLE_TASK + "]");
134        }
135
136        int index = 1;
137        String name = "task-1";
138        while (tasksRoot.hasChild(name))
139        {
140            index++;
141            name = "task-" + index;
142        }
143        
144        JCRTask task = (JCRTask) tasksRoot.createChild(name, JCRTaskFactory.TASK_NODETYPE);
145        task.setTasksListId(tasksListId);
146        task.setPosition(Long.valueOf(_workspaceTasksListDAO.getChildTask(tasksListId).size()));
147        
148        ZonedDateTime now = ZonedDateTime.now();
149        task.setCreationDate(now);
150        task.setLastModified(now);
151        task.setAuthor(_currentUserProvider.getUser());
152
153        Map<String, Object> attributesResults = _setTaskAttributes(task, parameters, newFiles, newFileNames, new ArrayList<>());
154        
155        tasksRoot.saveChanges();
156        
157        Map<String, Object> eventParams = new HashMap<>();
158        eventParams.put(ObservationConstants.ARGS_TASK, task);
159        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_ID, task.getId());
160        
161        _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_CREATED, _currentUserProvider.getUser(), eventParams));
162
163        Map<String, Object> results = new HashMap<>();
164        results.put("task", _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName()));
165        results.putAll(attributesResults);
166        return results;
167    }
168    
169    /**
170     * Edit a task
171     * @param taskId The id of the task to edit
172     * @param parameters The JS parameters
173     * @param newFiles the new file to add
174     * @param newFileNames the file names to add
175     * @param deleteFiles the file to delete
176     * @return The task data
177     * @throws IllegalAccessException If an error occurs when checking the rights
178     */
179    @Callable
180    public Map<String, Object> editTask(String taskId, Map<String, Object> parameters, List<Part> newFiles, List<String> newFileNames, List<String> deleteFiles) throws IllegalAccessException
181    {
182        AmetysObject object = _resolver.resolveById(taskId);
183        if (!(object instanceof JCRTask))
184        {
185            throw new IllegalClassException(JCRTask.class, object.getClass());
186        }
187        
188        // Check user right
189        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
190        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_HANDLE_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
191        {
192            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to edit task without convenient right [" + RIGHTS_HANDLE_TASK + "]");
193        }
194        
195        JCRTask task = (JCRTask) object;
196        Map<String, Object> attributesResults = _setTaskAttributes(task, parameters, newFiles, newFileNames, deleteFiles);
197        
198        ZonedDateTime now = ZonedDateTime.now();
199        task.setLastModified(now);
200            
201        task.saveChanges();
202            
203        Map<String, Object> eventParams = new HashMap<>();
204        eventParams.put(ObservationConstants.ARGS_TASK, task);
205        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_ID, taskId);
206        
207        _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_UPDATED, _currentUserProvider.getUser(), eventParams));
208        
209        // Closed status has changed
210        if (attributesResults.containsKey("isClosed"))
211        {
212            _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_CLOSED_STATUS_CHANGED, _currentUserProvider.getUser(), eventParams));
213        }
214
215        // Assigments have changed
216        if (attributesResults.containsKey("changedAssignments"))
217        {
218            _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_ASSIGNED, _currentUserProvider.getUser(), eventParams));
219        }
220        
221         
222        Map<String, Object> results = new HashMap<>();
223        results.put("task", _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName()));
224        results.putAll(attributesResults);
225        return results;
226    }
227    
228    /**
229     * Move task to new position
230     * @param tasksListId the tasks list id
231     * @param taskId the task id to move
232     * @param newPosition the new position
233     * @return The task data
234     * @throws IllegalAccessException If an error occurs when checking the rights
235     */
236    @Callable
237    public Map<String, Object> moveTask(String tasksListId, String taskId, long newPosition) throws IllegalAccessException
238    {
239        AmetysObject object = _resolver.resolveById(taskId);
240        if (!(object instanceof Task))
241        {
242            throw new IllegalClassException(Task.class, object.getClass());
243        }
244        
245        // Check user right
246        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
247        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_HANDLE_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
248        {
249            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to move task without convenient right [" + RIGHTS_HANDLE_TASK + "]");
250        }
251        
252        Task task = (Task) object;
253        if (tasksListId != task.getTaskListId())
254        {
255            List<Task> childTasks = _workspaceTasksListDAO.getChildTask(task.getTaskListId());
256            long position = 0;
257            for (Task childTask : childTasks)
258            {
259                if (!childTask.getId().equals(taskId))
260                {
261                    childTask.setPosition(position);
262                    position++;
263                }
264            }
265        }
266        
267        task.setTasksListId(tasksListId);
268        List<Task> childTasks = _workspaceTasksListDAO.getChildTask(tasksListId);
269        int size = childTasks.size();
270        if (newPosition > size)
271        {
272            throw new IllegalArgumentException("New position (" + newPosition + ") can't be greater than tasks child size (" + size + ")");
273        }
274        
275        long position = 0;
276        task.setPosition(newPosition);
277        for (Task childTask : childTasks)
278        {
279            if (position == newPosition)
280            {
281                position++;
282            }
283            
284            if (childTask.getId().equals(taskId))
285            {
286                childTask.setPosition(newPosition);
287            }
288            else
289            {
290                childTask.setPosition(position);
291                position++;
292            }
293        }
294        
295        tasksRoot.saveChanges();
296        
297        return _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName());
298    }
299    
300    /**
301     * Remove a task
302     * @param taskId the task id to remove
303     * @return The task data
304     * @throws IllegalAccessException If an error occurs when checking the rights
305     */
306    @Callable
307    public Map<String, Object> deleteTask(String taskId) throws IllegalAccessException
308    {
309        AmetysObject object = _resolver.resolveById(taskId);
310        if (!(object instanceof Task))
311        {
312            throw new IllegalClassException(Task.class, object.getClass());
313        }
314        
315        // Check user right
316        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
317        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_DELETE_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
318        {
319            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to delete task without convenient right [" + RIGHTS_DELETE_TASK + "]");
320        }
321        
322        Map<String, Object> results = new HashMap<>();
323        Task jcrTask = (Task) object;
324        
325        Map<String, Object> eventParams = new HashMap<>();
326        eventParams.put(ObservationConstants.ARGS_TASK, jcrTask);
327        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_ID, taskId);
328        _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_DELETING, _currentUserProvider.getUser(), eventParams));
329        
330        String tasksListId = jcrTask.getTaskListId();
331        jcrTask.remove();
332        
333        // Reorder tasks position
334        long position = 0;
335        for (Task childTask : _workspaceTasksListDAO.getChildTask(tasksListId))
336        {
337            childTask.setPosition(position);
338            position++;
339        }
340        
341        tasksRoot.saveChanges();
342
343        eventParams = new HashMap<>();
344        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_ID, taskId);
345        _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_DELETED, _currentUserProvider.getUser(), eventParams));
346
347        return results;
348    }
349    
350    /**
351     * Comment a task
352     * @param taskId the task id
353     * @param commentText the comment text
354     * @return The task data
355     * @throws IllegalAccessException If an error occurs when checking the rights
356     */
357    @Callable
358    public Map<String, Object> commentTask(String taskId, String commentText) throws IllegalAccessException
359    {
360        AmetysObject object = _resolver.resolveById(taskId);
361        if (!(object instanceof Task))
362        {
363            throw new IllegalClassException(Task.class, object.getClass());
364        }
365        
366        // Check user right
367        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
368        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_COMMENT_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
369        {
370            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to comment task without convenient right [" + RIGHTS_COMMENT_TASK + "]");
371        }
372        
373        Task task = (Task) object;
374        
375        createComment(task, commentText, tasksRoot);
376        
377        return _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName());
378    }
379    
380    /**
381     * Edit a task comment
382     * @param taskId the task id
383     * @param commentId the comment Id
384     * @param commentText the comment text
385     * @return The task data
386     * @throws IllegalAccessException If an error occurs when checking the rights
387     */
388    @Callable
389    public Map<String, Object> editCommentTask(String taskId, String commentId, String commentText) throws IllegalAccessException
390    {
391        AmetysObject object = _resolver.resolveById(taskId);
392        if (!(object instanceof Task))
393        {
394            throw new IllegalClassException(Task.class, object.getClass());
395        }
396        
397        // Check user right
398        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
399        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_COMMENT_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
400        {
401            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to edit task without convenient right [" + RIGHTS_COMMENT_TASK + "]");
402        }
403        
404        Task task = (Task) object;
405
406        editComment(task, commentId, commentText, tasksRoot);
407        
408        return _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName());
409    }
410    
411    /**
412     * Answer to a task's comment
413     * @param taskId the task id
414     * @param commentId the comment id
415     * @param commentText the comment text
416     * @return The task data
417     * @throws IllegalAccessException If an error occurs when checking the rights
418     */
419    @Callable
420    public Map<String, Object> answerCommentTask(String taskId, String commentId, String commentText) throws IllegalAccessException
421    {
422        AmetysObject object = _resolver.resolveById(taskId);
423        if (!(object instanceof Task))
424        {
425            throw new IllegalClassException(Task.class, object.getClass());
426        }
427        
428        // Check user right
429        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
430        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_COMMENT_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
431        {
432            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to comment task without convenient right [" + RIGHTS_COMMENT_TASK + "]");
433        }
434        
435        Task task = (Task) object;
436
437        answerComment(task, commentId, commentText, tasksRoot);
438        
439        return _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName());
440    }
441    
442    /**
443     * Delete a task's comment
444     * @param taskId the task id
445     * @param commentId the comment id
446     * @return The task data
447     * @throws IllegalAccessException If an error occurs when checking the rights
448     */
449    @Callable
450    public Map<String, Object> deleteCommentTask(String taskId, String commentId) throws IllegalAccessException
451    {
452        AmetysObject object = _resolver.resolveById(taskId);
453        if (!(object instanceof Task))
454        {
455            throw new IllegalClassException(Task.class, object.getClass());
456        }
457        
458        // Check user right
459        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
460        
461        UserIdentity userIdentity = _currentUserProvider.getUser();
462        User user = _userManager.getUser(userIdentity);
463        
464        Task task = (Task) object;
465        Comment comment = task.getComment(commentId);
466        String authorEmail = comment.getAuthorEmail();
467        if (!authorEmail.equals(user.getEmail()))
468        {
469            if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_COMMENT_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
470            {
471                throw new IllegalAccessException("User '" + userIdentity + "' tried to delete an other user's comment task");
472            }
473        }
474        
475        deleteComment(task, commentId, tasksRoot);
476              
477        return _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName());
478    }
479    
480    /**
481     * Like or unlike a task's comment
482     * @param taskId the task id
483     * @param commentId the comment id
484     * @param liked true if the comment is liked, otherwise the comment is unliked
485     * @return The task data
486     * @throws IllegalAccessException If an error occurs when checking the rights
487     */
488    @Callable
489    public Map<String, Object> likeOrUnlikeCommentTask(String taskId, String commentId, Boolean liked) throws IllegalAccessException
490    {
491        AmetysObject object = _resolver.resolveById(taskId);
492        if (!(object instanceof Task))
493        {
494            throw new IllegalClassException(Task.class, object.getClass());
495        }
496        
497        // Check user right
498        ModifiableTraversableAmetysObject tasksRoot = object.getParent();
499        if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_COMMENT_TASK, tasksRoot) != RightResult.RIGHT_ALLOW)
500        {
501            throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to react a comment task without convenient right [" + RIGHTS_COMMENT_TASK + "]");
502        }
503        
504        Task task = (Task) object;
505        
506        likeOrUnlikeComment(task, commentId, liked, tasksRoot);
507        
508        return _taskJSONHelper.taskAsJSON(task, _getSitemapLanguage(), _getSiteName());
509    }
510    
511    /**
512     * Set task's attributes
513     * @param task The task to edit
514     * @param parameters The JS parameters
515     * @param newFiles the new file to add to the task
516     * @param newFileNames the new file names to add to the task
517     * @param deleteFiles the file to remove from the task
518     * @return the map of results
519     */
520    protected Map<String, Object> _setTaskAttributes(JCRTask task, Map<String, Object> parameters, List<Part> newFiles, List<String> newFileNames, List<String> deleteFiles)
521    {
522        Map<String, Object> results = new HashMap<>();
523        
524        String label = (String) parameters.get(JCRTask.ATTRIBUTE_LABEL);
525        task.setLabel(label);
526
527        String description = (String) parameters.get(JCRTask.ATTRIBUTE_DESCRIPTION);
528        task.setDescription(description);
529        
530        _setTaskDates(task, parameters);
531        _setTaskCloseInfo(task, parameters, results);
532        _setAttachments(task, newFiles, newFileNames, deleteFiles);
533        
534        @SuppressWarnings("unchecked")
535        List<Map<String, Object>> assignmentIds = (List<Map<String, Object>>) parameters.getOrDefault(JCRTask.ATTRIBUTE_ASSIGNMENTS, new ArrayList<>());
536        List<UserIdentity> users = assignmentIds.stream()
537            .map(m -> (String) m.get("id"))
538            .map(UserIdentity::stringToUserIdentity)
539            .collect(Collectors.toList());
540        
541        if (!task.getAssignments().equals(users))
542        {
543            task.setAssignments(users);
544            results.put("changedAssignments", true);
545        }
546        
547        @SuppressWarnings("unchecked")
548        List<Map<String, Object>> checkListItems = (List<Map<String, Object>>) parameters.getOrDefault(JCRTask.ATTRIBUTE_CHECKLIST, new ArrayList<>());
549        List<CheckItem> checkItems = checkListItems.stream()
550            .map(e -> new CheckItem((String) e.get(JCRTask.ATTRIBUTE_CHECKLIST_LABEL), (boolean) e.get(JCRTask.ATTRIBUTE_CHECKLIST_ISCHECKED)))
551            .collect(Collectors.toList());
552        task.setCheckListItem(checkItems);
553
554        @SuppressWarnings("unchecked")
555        List<Object> tags = (List<Object>) parameters.getOrDefault(JCRTask.ATTRIBUTE_TAGS, new ArrayList<>());
556
557        List<Map<String, Object>> createdTagsJson = _handleTags(task, tags);
558        
559        results.put("newTags", createdTagsJson);
560        return results;
561    }
562    
563    private void _setTaskDates(JCRTask task, Map<String, Object> parameters)
564    {
565        String startDateAsStr = (String) parameters.get(JCRTask.ATTRIBUTE_STARTDATE);
566        LocalDate startDate = Optional.ofNullable(startDateAsStr)
567                .map(date -> LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE))
568                .orElse(null);
569        task.setStartDate(startDate);
570        
571        String dueDateAsStr = (String) parameters.get(JCRTask.ATTRIBUTE_DUEDATE);
572        LocalDate dueDate = Optional.ofNullable(dueDateAsStr)
573                .map(date -> LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE))
574                .orElse(null);
575        task.setDueDate(dueDate);
576    }
577    
578    private void _setTaskCloseInfo(JCRTask task, Map<String, Object> parameters, Map<String, Object> results)
579    {
580        @SuppressWarnings("unchecked")
581        Map<String, Object> closeInfo = (Map<String, Object>) parameters.get("closeInfo");
582        if (closeInfo != null && !task.isClosed())
583        {
584            task.close(true);
585            task.setCloseAuthor(_currentUserProvider.getUser());
586            task.setCloseDate(LocalDate.now());
587
588            results.put("isClosed", true);
589        }
590        else if (closeInfo == null && task.isClosed())
591        {
592            task.close(false);
593            task.setCloseAuthor(null);
594            task.setCloseDate(null);
595
596            results.put("isClosed", false);
597        }
598    }
599    
600    
601    /**
602     * Get all tasks from given projets
603     * @param project the project
604     * @return All tasks as JSON
605     */
606    public List<Task> getProjectTasks(Project project)
607    {
608        TasksWorkspaceModule taskModule = _workspaceModuleEP.getModule(TasksWorkspaceModule.TASK_MODULE_ID);
609        ModifiableTraversableAmetysObject tasksRoot = taskModule.getTasksRoot(project, true);
610        return tasksRoot.getChildren()
611            .stream()
612            .filter(Task.class::isInstance)
613            .map(Task.class::cast)
614            .collect(Collectors.toList());
615    }
616    
617    /**
618     * Get the total number of tasks of the project
619     * @param project The project
620     * @return The number of tasks, or null if the module is not activated
621     */
622    public Long getTasksCount(Project project)
623    {
624        return Long.valueOf(getProjectTasks(project).size());
625    }
626    
627    /**
628     * Get project members
629     * @return the project members
630     * @throws IllegalAccessException if an error occurred
631     * @throws AmetysRepositoryException if an error occurred
632     */
633    @Callable
634    public Map<String, Object> getProjectMembers() throws IllegalAccessException, AmetysRepositoryException
635    {
636        String projectName = _getProjectName();
637        String lang = _getSitemapLanguage();
638        
639        return _projectMemberManager.getProjectMembers(projectName, lang, true);
640    }
641}