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