001/*
002 *  Copyright 2015 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.calendars;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.UUID;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.commons.lang.BooleanUtils;
029import org.apache.commons.lang3.StringUtils;
030import org.apache.jackrabbit.util.Text;
031
032import org.ametys.core.observation.Event;
033import org.ametys.core.right.RightManager.RightResult;
034import org.ametys.core.ui.Callable;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.plugins.explorer.ModifiableExplorerNode;
037import org.ametys.plugins.explorer.ObservationConstants;
038import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollection;
039import org.ametys.plugins.repository.AmetysObjectIterable;
040import org.ametys.plugins.repository.AmetysObjectIterator;
041import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
042import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
043import org.ametys.plugins.repository.query.QueryHelper;
044import org.ametys.plugins.repository.query.expression.Expression;
045import org.ametys.plugins.repository.query.expression.Expression.Operator;
046import org.ametys.plugins.repository.query.expression.StringExpression;
047import org.ametys.plugins.workspaces.calendars.Calendar.CalendarVisibility;
048import org.ametys.plugins.workspaces.calendars.events.CalendarEvent;
049import org.ametys.plugins.workspaces.calendars.events.CalendarEventJSONHelper;
050import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendar;
051import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendarFactory;
052import org.ametys.plugins.workspaces.calendars.task.TaskCalendar;
053import org.ametys.plugins.workspaces.project.ProjectConstants;
054import org.ametys.plugins.workspaces.project.objects.Project;
055import org.ametys.plugins.workspaces.tasks.TasksWorkspaceModule;
056import org.ametys.plugins.workspaces.tasks.WorkspaceTaskDAO;
057
058/**
059 * Calendar DAO
060 */
061public class CalendarDAO extends AbstractCalendarDAO
062{
063    /** Avalon Role */
064    public static final String ROLE = CalendarDAO.class.getName();
065
066    /** The tasks list JSON helper */
067    protected CalendarEventJSONHelper _calendarEventJSONHelper;
068
069    /** The task DAO */
070    protected WorkspaceTaskDAO _taskDAO;
071    
072    @Override
073    public void service(ServiceManager manager) throws ServiceException
074    {
075        super.service(manager);
076        _calendarEventJSONHelper = (CalendarEventJSONHelper) manager.lookup(CalendarEventJSONHelper.ROLE);
077        _taskDAO = (WorkspaceTaskDAO) manager.lookup(WorkspaceTaskDAO.ROLE);
078    }
079        
080    /**
081     * Get calendar info
082     * @param calendar The calendar
083     * @param recursive True to get data for sub calendars
084     * @param includeEvents True to also include child events
085     * @param useICSFormat true to use ICS Format for dates
086     * @return the calendar data in a map
087     */
088    public Map<String, Object> getCalendarData(Calendar calendar, boolean recursive, boolean includeEvents, boolean useICSFormat)
089    {
090        Map<String, Object> result = new HashMap<>();
091        
092        result.put("id", calendar.getId());
093        result.put("title", Text.unescapeIllegalJcrChars(calendar.getName()));
094        result.put("description", calendar.getDescription());
095        result.put("templateDesc", calendar.getTemplateDescription());
096        result.put("color", calendar.getColor());
097        result.put("visibility", calendar.getVisibility().name().toLowerCase());
098        result.put("public", calendar.getVisibility() == CalendarVisibility.PUBLIC);
099        
100        if (calendar instanceof WorkflowAwareCalendar calendarWA)
101        {
102            result.put("workflowName", calendarWA.getWorkflowName());
103        }
104        
105        result.put("isTaskCalendar", calendar instanceof TaskCalendar);
106        result.put("isTaskCalendarDisabled", calendar instanceof TaskCalendar cal && cal.isDisabled());
107        
108        if (recursive)
109        {
110            List<Map<String, Object>> calendarList = new LinkedList<>();
111            result.put("calendars", calendarList);
112            for (Calendar child : calendar.getChildCalendars())
113            {
114                calendarList.add(getCalendarData(child, recursive, includeEvents, useICSFormat));
115            }
116        }
117        
118        if (includeEvents)
119        {
120            List<Map<String, Object>> eventList = new LinkedList<>();
121            result.put("events", eventList);
122            
123            for (CalendarEvent event : calendar.getAllEvents())
124            {
125                eventList.add(_calendarEventJSONHelper.eventAsJson(event, false, useICSFormat));
126            }
127        }
128
129        result.put("rights", _extractCalendarRightData(calendar));
130        result.put("token", getCalendarIcsToken(calendar, true));
131        
132        
133        return result;
134    }
135    
136    /**
137     * Get calendar info
138     * @param calendar The calendar
139     * @return the calendar data in a map
140     */
141    public Map<String, Object> getCalendarProperties(Calendar calendar)
142    {
143        return getCalendarData(calendar, false, false, false);
144    }
145    /**
146     * Add a calendar
147     * @param inputName The desired name for the calendar
148     * @param color The calendar color
149     * @param isPublic true if the calendar is public
150     * @return The result map with id, parentId and name keys
151     */
152    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
153    public Map<String, Object> addCalendar(String inputName, String color, boolean isPublic)
154    {
155        String rootId = _getCalendarRoot(true).getId();
156        return addCalendar(rootId, inputName, StringUtils.EMPTY, StringUtils.EMPTY, color, isPublic ? CalendarVisibility.PUBLIC.name() : CalendarVisibility.PRIVATE.name(), "calendar-default", false);
157    }
158    
159    /**
160     * Add a calendar
161     * @param id The identifier of the parent in which the calendar will be added
162     * @param inputName The desired name for the calendar
163     * @param description The calendar description
164     * @param templateDesc The calendar template description
165     * @param color The calendar color
166     * @param visibility The calendar visibility
167     * @param workflowName The calendar workflow name
168     * @param renameIfExists True to rename if existing
169     * @return The result map with id, parentId and name keys
170     */
171    public Map<String, Object> addCalendar(String id, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists)
172    {
173        return addCalendar(_resolver.resolveById(id), inputName, description, templateDesc, color, visibility, workflowName, renameIfExists, true, true);
174    }
175        
176    /**
177     * Add a calendar
178     * @param parent The parent in which the calendar will be added
179     * @param inputName The desired name for the calendar
180     * @param description The calendar description
181     * @param templateDesc The calendar template description
182     * @param color The calendar color
183     * @param visibility The calendar visibility
184     * @param workflowName The calendar workflow name
185     * @param renameIfExists True to rename if existing
186     * @param checkRights true to check if the current user have enough rights to create the calendar
187     * @param notify True to notify the calendar creation
188     * @return The result map with id, parentId and name keys
189     */
190    public Map<String, Object> addCalendar(ModifiableTraversableAmetysObject parent, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists, Boolean checkRights, boolean notify)
191    {
192        String originalName = Text.escapeIllegalJcrChars(inputName);
193        
194        // Check user right
195        if (checkRights)
196        {
197            _checkUserRights(parent, RIGHTS_CALENDAR_ADD);
198        }
199        
200        if (BooleanUtils.isNotTrue(renameIfExists) && parent.hasChild(originalName))
201        {
202            getLogger().warn("Cannot create the calendar with name '" + originalName + "', an object with same name already exists.");
203            return Map.of("message", "already-exist");
204        }
205        
206        if (!_explorerResourcesDAO.checkLock(parent))
207        {
208            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify the object '" + parent.getName() + "' but it is locked by another user");
209            return Map.of("message", "locked");
210        }
211        
212        int index = 2;
213        String name = originalName;
214        while (parent.hasChild(name))
215        {
216            name = originalName + " (" + index + ")";
217            index++;
218        }
219        
220        JCRCalendar calendar = parent.createChild(name, JCRCalendarFactory.CALENDAR_NODETYPE);
221        calendar.setWorkflowName(workflowName);
222        calendar.setDescription(description);
223        calendar.setTemplateDescription(templateDesc);
224        calendar.setColor(color);
225        calendar.setVisibility(StringUtils.isNotEmpty(visibility) ? CalendarVisibility.valueOf(visibility.toUpperCase()) : CalendarVisibility.PRIVATE);
226        parent.saveChanges();
227        
228        // Notify listeners
229        Map<String, Object> eventParams = new HashMap<>();
230        eventParams.put(ObservationConstants.ARGS_ID, calendar.getId());
231        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId());
232        eventParams.put(ObservationConstants.ARGS_NAME, name);
233        eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath());
234        
235        if (notify)
236        {
237            _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_CREATED, _currentUserProvider.getUser(), eventParams));
238        }
239        
240        return getCalendarProperties(calendar);
241    }
242
243    /**
244     * Edit a calendar
245     * @param id The identifier of the calendar
246     * @param inputName The new name
247     * @param templateDesc The new calendar template description
248     * @param color The calendar color
249     * @param isPublic true if the calendar is public
250     * @return The result map with id and name keys
251     */
252    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
253    public Map<String, Object> editCalendar(String id, String inputName, String templateDesc, String color, boolean isPublic)
254    {
255        CalendarVisibility visibility = isPublic ? CalendarVisibility.PUBLIC : CalendarVisibility.PRIVATE;
256        
257        assert id != null;
258        String rename = Text.escapeIllegalJcrChars(inputName);
259        
260        JCRCalendar calendar = _resolver.resolveById(id);
261
262        _checkUserRights(calendar, RIGHTS_CALENDAR_EDIT);
263        
264        String name = calendar.getName();
265        ModifiableTraversableAmetysObject parent = calendar.getParent();
266        
267        if (!StringUtils.equals(rename, name) && parent.hasChild(rename))
268        {
269            getLogger().warn("Cannot edit the calendar with the new name '" + inputName + "', an object with same name already exists.");
270            return Map.of("message", "already-exist");
271        }
272        
273        if (!_explorerResourcesDAO.checkLock(calendar))
274        {
275            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify calendar '" + calendar.getName() + "' but it is locked by another user");
276            return Map.of("message", "locked");
277        }
278        
279        if (!StringUtils.equals(name, rename))
280        {
281            int index = 2;
282            name = Text.escapeIllegalJcrChars(rename);
283            while (parent.hasChild(name))
284            {
285                name = rename + " (" + index + ")";
286                index++;
287            }
288            calendar.rename(name);
289        }
290        
291        calendar.setTemplateDescription(templateDesc);
292        calendar.setColor(color);
293        calendar.setVisibility(visibility);
294        
295        parent.saveChanges();
296        
297        // Notify listeners
298        Map<String, Object> eventParams = new HashMap<>();
299        eventParams.put(ObservationConstants.ARGS_ID, calendar.getId());
300        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId());
301        eventParams.put(ObservationConstants.ARGS_NAME, name);
302        eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath());
303        
304        _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_UPDATED, _currentUserProvider.getUser(), eventParams));
305
306        return getCalendarProperties(calendar);
307    }
308    
309    /**
310     * Edit the task calendar
311     * @param inputName the input name
312     * @param color the color
313     * @param isPublic <code>true</code> if the calendar is public
314     * @param disabled <code>true</code> if the calendar is disabled
315     * @return the calendar properties
316     * @throws IllegalAccessException if a right error occurred 
317     */
318    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
319    public Map<String, Object> editTaskCalendar(String inputName, String color, boolean isPublic, boolean disabled) throws IllegalAccessException
320    {
321        Project project = _workspaceHelper.getProjectFromRequest();
322        TaskCalendar taskCalendar = getTaskCalendar(project, false);
323        
324        // Check user right
325        _checkUserRights(_getCalendarRoot(project, false), RIGHTS_CALENDAR_EDIT);
326        
327        if (taskCalendar != null)
328        {
329            taskCalendar.rename(inputName);
330            taskCalendar.setColor(color);
331            taskCalendar.setVisibility(isPublic ? CalendarVisibility.PUBLIC : CalendarVisibility.PRIVATE);
332            taskCalendar.disable(disabled);
333        }
334        return getCalendarProperties(taskCalendar);
335    }
336    
337    /**
338     * Delete a calendar
339     * @param id The id of the calendar
340     * @return The result map with id, parent id and message keys
341     */
342    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
343    public Map<String, Object> deleteCalendar(String id)
344    {
345        Map<String, Object> result = new HashMap<>();
346
347        assert id != null;
348        
349        JCRCalendar calendar = _resolver.resolveById(id);
350
351        _checkUserRights(calendar, RIGHTS_CALENDAR_DELETE);
352        
353        if (!_explorerResourcesDAO.checkLock(calendar))
354        {
355            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete calendar'" + calendar.getName() + "' but it is locked by another user");
356            result.put("message", "locked");
357            return result;
358        }
359        
360        ModifiableExplorerNode parent = calendar.getParent();
361        String parentId = parent.getId();
362        String name = calendar.getName();
363        String path = calendar.getPath();
364        
365        calendar.remove();
366        parent.saveChanges();
367     
368        // Notify listeners
369        Map<String, Object> eventParams = new HashMap<>();
370        eventParams.put(ObservationConstants.ARGS_ID, id);
371        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parentId);
372        eventParams.put(ObservationConstants.ARGS_NAME, name);
373        eventParams.put(ObservationConstants.ARGS_PATH, path);
374        
375        _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_DELETED, _currentUserProvider.getUser(), eventParams));
376        
377        result.put("id", id);
378        result.put("parentId", parentId);
379        
380        return result;
381    }
382        
383    /**
384     * Get or create the calendar ICS token
385     * @param calendar The calendar
386     * @param createIfNotExisting Create the token if none exists for the given calendar
387     * @return The token
388     */
389    public String getCalendarIcsToken(Calendar calendar, boolean createIfNotExisting)
390    {
391        String token = calendar.getIcsUrlToken();
392        
393        if (createIfNotExisting && token == null && calendar instanceof JCRCalendar)
394        {
395            token = UUID.randomUUID().toString();
396            ((JCRCalendar) calendar).setIcsUrlToken(token);
397            ((JCRCalendar) calendar).saveChanges();
398        }
399        
400        return token;
401    }
402    
403    /**
404     * Retrieve the calendar for the matching ICS token
405     * @param token The ICS token
406     * @return The calendar, or null if not found
407     */
408    public Calendar getCalendarFromIcsToken(String token)
409    {
410        if (StringUtils.isEmpty(token))
411        {
412            return null;
413        }
414        
415        Expression expr = new StringExpression(JCRCalendar.CALENDAR_ICS_TOKEN, Operator.EQ, token);
416        String calendarsQuery = QueryHelper.getXPathQuery(null, JCRCalendarFactory.CALENDAR_NODETYPE, expr);
417        AmetysObjectIterable<JCRCalendar> calendars = _resolver.query(calendarsQuery);
418        AmetysObjectIterator<JCRCalendar> calendarsIterator = calendars.iterator();
419        
420        if (calendarsIterator.getSize() > 0)
421        {
422            return calendarsIterator.next();
423        }
424        
425        // Don't find a token in default calendars, so check in the task calendars
426        return _projectManager.getProjects()
427            .stream()
428            .map(p -> this.getTaskCalendar(p, true))
429            .filter(Objects::nonNull)
430            .filter(c -> c.getIcsUrlToken().equals(token))
431            .findFirst()
432            .orElse(null);
433    }
434    
435    /**
436     * Internal method to extract the data concerning the right of the current user for a calendar
437     * @param calendar The calendar
438     * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not.
439     */
440    protected  Map<String, Object> _extractCalendarRightData(Calendar calendar)
441    {
442        Map<String, Object> rightsData = new HashMap<>();
443        
444        UserIdentity user = _currentUserProvider.getUser();
445        boolean isTaskCalendar = calendar instanceof TaskCalendar;
446        
447        // Add
448        rightsData.put("add-event", !isTaskCalendar && _rightManager.hasRight(user, RIGHTS_EVENT_ADD, calendar) == RightResult.RIGHT_ALLOW);
449        
450        // edit - delete
451        rightsData.put("edit", !isTaskCalendar && _rightManager.hasRight(user, RIGHTS_CALENDAR_EDIT, calendar) == RightResult.RIGHT_ALLOW);
452        rightsData.put("delete", !isTaskCalendar && _rightManager.hasRight(user, RIGHTS_CALENDAR_DELETE, calendar) == RightResult.RIGHT_ALLOW);
453        
454        return rightsData;
455    }
456    
457    /**
458     * Get the data of every available calendar for the current project
459     * @return the list of calendars
460     */
461    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
462    public List<Map<String, Object>> getCalendars()
463    {
464        Project project = _workspaceHelper.getProjectFromRequest();
465        
466        _checkReadAccess(project, CalendarWorkspaceModule.CALENDAR_MODULE_ID);
467        
468        List<Map<String, Object>> calendarsData = new ArrayList<>();
469        CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
470        
471        for (Calendar calendar : calendarModule.getCalendars(project, true))
472        {
473            if (calendarModule.canView(calendar))
474            {
475                calendarsData.add(this.getCalendarProperties(calendar));
476            }
477        }
478        
479        return calendarsData;
480    }
481
482    /**
483     * Get the colors of calendars
484     * @return colors
485     */
486    @Callable (rights = Callable.NO_CHECK_REQUIRED)
487    public Map<String, CalendarColorsComponent.CalendarColor> getColors()
488    {
489        return _calendarColors.getColors();
490    }
491    
492    /**
493     * Get user rights on root calendar of current project
494     * @return the user rights
495     */
496    @Callable (rights = Callable.NO_CHECK_REQUIRED)
497    public Map<String, Object> getUserRights()
498    {
499        Map<String, Object> results = new HashMap<>();
500        ModifiableTraversableAmetysObject calendarRoot = _getCalendarRoot(false);
501        
502        UserIdentity user = _currentUserProvider.getUser();
503        results.put("canCreateCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_CALENDAR_ADD, calendarRoot) == RightResult.RIGHT_ALLOW);
504        results.put("canEditCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_CALENDAR_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW);
505        results.put("canRemoveCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_CALENDAR_DELETE, calendarRoot) == RightResult.RIGHT_ALLOW);
506        results.put("canCreateEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_ADD, calendarRoot) == RightResult.RIGHT_ALLOW);
507        results.put("canEditEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW);
508        results.put("canRemoveAnyEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_DELETE, calendarRoot) == RightResult.RIGHT_ALLOW);
509        results.put("canRemoveSelfEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_DELETE_OWN, calendarRoot) == RightResult.RIGHT_ALLOW);
510        results.put("canCreateTags", calendarRoot != null && _rightManager.hasRight(user, ProjectConstants.RIGHT_PROJECT_ADD_TAG, calendarRoot) == RightResult.RIGHT_ALLOW);
511        results.put("canHandleResource", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_HANDLE_RESOURCE, calendarRoot) == RightResult.RIGHT_ALLOW);
512        results.put("canBookResource", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_BOOK_RESOURCE, calendarRoot) == RightResult.RIGHT_ALLOW);
513        results.put("sharePrivateCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW);
514        
515        return results;
516    }
517
518    /**
519     * Get the calendar root
520     * @param createIfNotExist true to create root if not exist yet
521     * @return the calendar root
522     */
523    protected ModifiableTraversableAmetysObject _getCalendarRoot(boolean createIfNotExist)
524    {
525        return _getCalendarRoot(_workspaceHelper.getProjectFromRequest(), createIfNotExist);
526    }
527    
528    /**
529     * Get the calendar root form the project
530     * @param project the project
531     * @param createIfNotExist true to create root if not exist yet
532     * @return the calendar root
533     */
534    protected ModifiableTraversableAmetysObject _getCalendarRoot(Project project, boolean createIfNotExist)
535    {
536        CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
537        return calendarModule.getCalendarsRoot(project, createIfNotExist);
538    }
539    
540    /**
541     * Get the data of calendar used to store resources
542     * @return the calendar used to store resources
543     */
544    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
545    public Map<String, Object> getResourceCalendar()
546    {
547        Project project = _workspaceHelper.getProjectFromRequest();
548        _checkReadAccess(project, CalendarWorkspaceModule.CALENDAR_MODULE_ID);
549        
550        CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
551        Calendar calendar = calendarModule.getResourceCalendar(project);
552        
553        return this.getCalendarProperties(calendar);
554    }
555    
556    /**
557     * Get the task calendar
558     * @param project the project
559     * @param onlyIfEnabled <code>true</code> to return the task calendar only if it is enabled
560     * @return the task calendar
561     */
562    public TaskCalendar getTaskCalendar(Project project, boolean onlyIfEnabled)
563    {
564        if (_projectManager.isModuleActivated(project, TasksWorkspaceModule.TASK_MODULE_ID))
565        {
566            JCRResourcesCollection root = (JCRResourcesCollection) _getCalendarRoot(project, false);
567            TaskCalendar calendar = new TaskCalendar(project, root, _taskDAO);
568            return !onlyIfEnabled || !calendar.isDisabled() ? calendar : null;
569        }
570        
571        return null;
572    }
573    
574    /**
575     * <code>true</code> if the current user has read access on the task calendar
576     * @param project the project
577     * @return <code>true</code> if the current user has read access on the task calendar
578     */
579    public boolean hasTaskCalendarReadAccess(Project project)
580    {
581        TasksWorkspaceModule taskModule = (TasksWorkspaceModule) _workspaceModuleEP.getModule(TasksWorkspaceModule.TASK_MODULE_ID);
582        DefaultTraversableAmetysObject tasksRoot = taskModule.getTasksRoot(project, true);
583        return _rightManager.currentUserHasReadAccess(tasksRoot);
584    }
585}