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.calendars; 017 018import java.time.ZonedDateTime; 019import java.time.temporal.ChronoUnit; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.Date; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Optional; 028import java.util.Set; 029import java.util.stream.Stream; 030 031import org.apache.avalon.framework.configuration.Configurable; 032import org.apache.avalon.framework.configuration.Configuration; 033import org.apache.avalon.framework.configuration.ConfigurationException; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.cocoon.components.ContextHelper; 037import org.apache.cocoon.environment.Request; 038import org.apache.commons.collections.ListUtils; 039 040import org.ametys.core.util.DateUtils; 041import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 042import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory; 043import org.ametys.plugins.repository.AmetysObjectIterable; 044import org.ametys.plugins.repository.AmetysObjectIterator; 045import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 046import org.ametys.plugins.repository.query.QueryHelper; 047import org.ametys.plugins.repository.query.SortCriteria; 048import org.ametys.plugins.repository.query.expression.AndExpression; 049import org.ametys.plugins.repository.query.expression.DateExpression; 050import org.ametys.plugins.repository.query.expression.Expression; 051import org.ametys.plugins.repository.query.expression.Expression.Operator; 052import org.ametys.plugins.repository.query.expression.OrExpression; 053import org.ametys.plugins.repository.query.expression.StringExpression; 054import org.ametys.plugins.workspaces.AbstractWorkspaceModule; 055import org.ametys.plugins.workspaces.WorkspacesHelper; 056import org.ametys.plugins.workspaces.calendars.Calendar.CalendarVisibility; 057import org.ametys.plugins.workspaces.calendars.events.CalendarEvent; 058import org.ametys.plugins.workspaces.calendars.events.CalendarEventJSONHelper; 059import org.ametys.plugins.workspaces.calendars.events.CalendarEventOccurrence; 060import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendarEvent; 061import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendarEventFactory; 062import org.ametys.plugins.workspaces.calendars.task.TaskCalendar; 063import org.ametys.plugins.workspaces.calendars.task.TaskCalendarEvent; 064import org.ametys.plugins.workspaces.project.objects.Project; 065import org.ametys.plugins.workspaces.util.StatisticColumn; 066import org.ametys.plugins.workspaces.util.StatisticsColumnType; 067import org.ametys.runtime.i18n.I18nizableText; 068import org.ametys.web.repository.page.ModifiablePage; 069import org.ametys.web.repository.page.ModifiableZone; 070import org.ametys.web.repository.page.ModifiableZoneItem; 071import org.ametys.web.repository.page.Page; 072import org.ametys.web.repository.page.ZoneItem.ZoneType; 073 074import com.google.common.collect.ImmutableSet; 075 076/** 077 * Helper component for managing calendars 078 */ 079public class CalendarWorkspaceModule extends AbstractWorkspaceModule implements Configurable 080{ 081 /** The id of calendar module */ 082 public static final String CALENDAR_MODULE_ID = CalendarWorkspaceModule.class.getName(); 083 084 /** Workspaces calendars node name */ 085 private static final String __WORKSPACES_CALENDARS_NODE_NAME = "calendars"; 086 087 /** Workspaces root tasks node name */ 088 private static final String __WORKSPACES_CALENDARS_ROOT_NODE_NAME = "calendars-root"; 089 090 /** Workspaces root tasks node name */ 091 private static final String __WORKSPACES_CALENDAR_RESOURCES_ROOT_NODE_NAME = "calendar-resources-root"; 092 093 /** Workspaces root tasks node name */ 094 private static final String __WORKSPACES_RESOURCE_CALENDAR_ROOT_NODE_NAME = "resource-calendar-root"; 095 096 private static final String __CALENDAR_CACHE_REQUEST_ATTR = CalendarWorkspaceModule.class.getName() + "$calendarCache"; 097 098 private static final String __EVENT_NUMBER_HEADER_ID = __WORKSPACES_CALENDARS_NODE_NAME + "$event_number"; 099 100 /** The Workspaces helper */ 101 protected WorkspacesHelper _workspaceHelper; 102 103 private CalendarDAO _calendarDAO; 104 private CalendarEventJSONHelper _calendarEventJSONHelper; 105 106 private I18nizableText _defaultCalendarTemplateDesc; 107 private String _defaultCalendarColor; 108 private String _defaultCalendarVisibility; 109 private String _defaultCalendarWorkflowName; 110 private I18nizableText _defaultCalendarTitle; 111 private I18nizableText _defaultCalendarDescription; 112 113 private I18nizableText _resourceCalendarTemplateDesc; 114 private String _resourceCalendarColor; 115 private String _resourceCalendarVisibility; 116 private String _resourceCalendarWorkflowName; 117 private I18nizableText _resourceCalendarTitle; 118 private I18nizableText _resourceCalendarDescription; 119 120 121 @Override 122 public void service(ServiceManager manager) throws ServiceException 123 { 124 super.service(manager); 125 _calendarDAO = (CalendarDAO) manager.lookup(CalendarDAO.ROLE); 126 _calendarEventJSONHelper = (CalendarEventJSONHelper) manager.lookup(CalendarEventJSONHelper.ROLE); 127 _workspaceHelper = (WorkspacesHelper) manager.lookup(WorkspacesHelper.ROLE); 128 } 129 130 public void configure(Configuration configuration) throws ConfigurationException 131 { 132 _defaultCalendarTemplateDesc = I18nizableText.parseI18nizableText(configuration.getChild("template-desc"), "plugin." + _pluginName, ""); 133 _defaultCalendarColor = configuration.getChild("color").getValue("col1"); 134 _defaultCalendarVisibility = configuration.getChild("visibility").getValue(CalendarVisibility.PRIVATE.name()); 135 _defaultCalendarWorkflowName = configuration.getChild("workflow").getValue("calendar-default"); 136 _defaultCalendarTitle = I18nizableText.parseI18nizableText(configuration.getChild("title"), "plugin." + _pluginName); 137 _defaultCalendarDescription = I18nizableText.parseI18nizableText(configuration.getChild("description"), "plugin." + _pluginName, ""); 138 139 _resourceCalendarTemplateDesc = I18nizableText.parseI18nizableText(configuration.getChild("resource-template-desc"), "plugin." + _pluginName, ""); 140 _resourceCalendarColor = configuration.getChild("resource-color").getValue("resourcecol0"); 141 _resourceCalendarVisibility = configuration.getChild("resource-visibility").getValue(CalendarVisibility.PRIVATE.name()); 142 _resourceCalendarWorkflowName = configuration.getChild("resource-workflow").getValue("calendar-default"); 143 _resourceCalendarTitle = I18nizableText.parseI18nizableText(configuration.getChild("resource-title"), "plugin." + _pluginName); 144 _resourceCalendarDescription = I18nizableText.parseI18nizableText(configuration.getChild("resource-description"), "plugin." + _pluginName, ""); 145 146 } 147 148 @Override 149 public String getId() 150 { 151 return CALENDAR_MODULE_ID; 152 } 153 154 public int getOrder() 155 { 156 return ORDER_CALENDAR; 157 } 158 159 public String getModuleName() 160 { 161 return __WORKSPACES_CALENDARS_NODE_NAME; 162 } 163 164 @Override 165 protected String getModulePageName() 166 { 167 return "calendars"; 168 } 169 170 public I18nizableText getModuleTitle() 171 { 172 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_CALENDAR_LABEL"); 173 } 174 public I18nizableText getModuleDescription() 175 { 176 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_CALENDAR_DESCRIPTION"); 177 } 178 @Override 179 protected I18nizableText getModulePageTitle() 180 { 181 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_CALENDARS_TITLE"); 182 } 183 184 @Override 185 protected void initializeModulePage(ModifiablePage calendarPage) 186 { 187 ModifiableZone defaultZone = calendarPage.createZone("default"); 188 189 String serviceId = "org.ametys.plugins.workspaces.module.Calendar"; 190 ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem(); 191 defaultZoneItem.setType(ZoneType.SERVICE); 192 defaultZoneItem.setServiceId(serviceId); 193 194 ModifiableModelAwareDataHolder serviceDataHolder = defaultZoneItem.getServiceParameters(); 195 serviceDataHolder.setValue("xslt", _getDefaultXslt(serviceId)); 196 } 197 198 /** 199 * Get the calendars of a project 200 * @param project The project 201 * @param withTaskCalendar <code>true</code> to get the task calendar 202 * @return The list of calendar 203 */ 204 public List<Calendar> getCalendars(Project project, boolean withTaskCalendar) 205 { 206 List<Calendar> calendars = new ArrayList<>(); 207 ModifiableResourceCollection calendarRoot = getCalendarsRoot(project, false); 208 if (calendarRoot != null) 209 { 210 calendarRoot.getChildren() 211 .stream() 212 .filter(Calendar.class::isInstance) 213 .map(Calendar.class::cast) 214 .forEach(calendars::add); 215 216 if (withTaskCalendar) 217 { 218 TaskCalendar taskCalendar = _calendarDAO.getTaskCalendar(project, false); 219 if (taskCalendar != null) 220 { 221 calendars.add(taskCalendar); 222 } 223 } 224 } 225 226 return calendars; 227 } 228 229 /** 230 * Get the URI of a thread in project'site 231 * @param project The project 232 * @param calendarId The id of calendar 233 * @param eventId The id of event 234 * @return The thread uri 235 */ 236 public String getEventUri(Project project, String calendarId, String eventId) 237 { 238 String moduleUrl = getModuleUrl(project); 239 if (moduleUrl != null) 240 { 241 StringBuilder sb = new StringBuilder(); 242 sb.append(moduleUrl); 243 sb.append("#event-").append(eventId); 244 245 return sb.toString(); 246 } 247 248 return null; 249 } 250 251 /** 252 * Add additional information on project and parent calendar 253 * @param event The event 254 * @param eventData The event data to complete 255 */ 256 @SuppressWarnings("unchecked") 257 protected void _addAdditionalEventData(CalendarEvent event, Map<String, Object> eventData) 258 { 259 Request request = ContextHelper.getRequest(_context); 260 261 Calendar calendar = event.getCalendar(); 262 Project project = calendar.getProject(); 263 264 // Try to get calendar from cache if request is not null 265 if (request.getAttribute(__CALENDAR_CACHE_REQUEST_ATTR) == null) 266 { 267 request.setAttribute(__CALENDAR_CACHE_REQUEST_ATTR, new HashMap<>()); 268 } 269 270 Map<String, Object> calendarCache = (Map<String, Object>) request.getAttribute(__CALENDAR_CACHE_REQUEST_ATTR); 271 272 if (!calendarCache.containsKey(calendar.getId())) 273 { 274 Map<String, Object> calendarInfo = new HashMap<>(); 275 276 calendarInfo.put("calendarName", calendar.getName()); 277 calendarInfo.put("calendarIsPublic", CalendarVisibility.PUBLIC.equals(calendar.getVisibility())); 278 calendarInfo.put("calendarHasViewRight", canView(calendar)); 279 280 calendarInfo.put("projectId", project.getId()); 281 calendarInfo.put("projectTitle", project.getTitle()); 282 283 Set<Page> calendarModulePages = _projectManager.getModulePages(project, this); 284 if (!calendarModulePages.isEmpty()) 285 { 286 Page calendarModulePage = calendarModulePages.iterator().next(); 287 calendarInfo.put("calendarModulePageId", calendarModulePage.getId()); 288 } 289 290 calendarCache.put(calendar.getId(), calendarInfo); 291 } 292 293 eventData.putAll((Map<String, Object>) calendarCache.get(calendar.getId())); 294 295 eventData.put("eventUrl", getEventUri(project, calendar.getId(), event.getId())); 296 } 297 298 /** 299 * Get the upcoming events of the calendars on which the user has a right 300 * @param months the amount of months from today in which look for upcoming events 301 * @param maxResults the maximum results to display 302 * @param calendarIds the ids of the calendars to gather events from, null for all calendars 303 * @param tagIds the ids of the valid tags for the events, null for any tag 304 * @return the upcoming events 305 */ 306 public List<Map<String, Object>> getUpcomingEvents(int months, int maxResults, List<String> calendarIds, List<String> tagIds) 307 { 308 List<Map<String, Object>> basicEventList = new ArrayList<> (); 309 List<Map<String, Object>> recurrentEventList = new ArrayList<> (); 310 311 java.util.Calendar cal = java.util.Calendar.getInstance(); 312 cal.set(java.util.Calendar.HOUR_OF_DAY, 0); 313 cal.set(java.util.Calendar.MINUTE, 0); 314 cal.set(java.util.Calendar.SECOND, 0); 315 cal.set(java.util.Calendar.MILLISECOND, 0); 316 ZonedDateTime startDate = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS); 317 318 ZonedDateTime endDate = startDate.plusMonths(months); 319 320 Expression nonRecurrentExpr = new StringExpression(JCRCalendarEvent.ATTRIBUTE_RECURRENCE_TYPE, Operator.EQ, "NEVER"); 321 Expression startDateExpr = new DateExpression(JCRCalendarEvent.ATTRIBUTE_START_DATE, Operator.GE, startDate); 322 Expression endDateExpr = new DateExpression(JCRCalendarEvent.ATTRIBUTE_START_DATE, Operator.LE, endDate); 323 324 Expression keywordsExpr = null; 325 326 if (tagIds != null && !tagIds.isEmpty()) 327 { 328 List<Expression> orExpr = new ArrayList<>(); 329 for (String tagId : tagIds) 330 { 331 orExpr.add(new StringExpression(JCRCalendarEvent.ATTRIBUTE_KEYWORDS, Operator.EQ, tagId)); 332 } 333 keywordsExpr = new OrExpression(orExpr.toArray(new Expression[orExpr.size()])); 334 } 335 336 // Get the non recurrent events sorted by ascending date and within the configured range 337 Expression eventExpr = new AndExpression(nonRecurrentExpr, startDateExpr, endDateExpr, keywordsExpr); 338 SortCriteria sortCriteria = new SortCriteria(); 339 sortCriteria.addCriterion(JCRCalendarEvent.ATTRIBUTE_START_DATE, true, false); 340 341 String basicEventQuery = QueryHelper.getXPathQuery(null, JCRCalendarEventFactory.CALENDAR_EVENT_NODETYPE, eventExpr, sortCriteria); 342 AmetysObjectIterable<CalendarEvent> basicEvents = _resolver.query(basicEventQuery); 343 AmetysObjectIterator<CalendarEvent> basicEventIt = basicEvents.iterator(); 344 345 int processed = 0; 346 while (basicEventIt.hasNext() && processed < maxResults) 347 { 348 CalendarEvent event = basicEventIt.next(); 349 Calendar holdingCalendar = event.getCalendar(); 350 351 if (_filterEvent(calendarIds, event) && _hasAccess(holdingCalendar)) 352 { 353 // The event is in the list of selected calendars and has the appropriate tags (can be none if tagIds == null) 354 355 // FIXME should use something like an EventInfo object with some data + calendar, project name 356 // And use a function to process the transformation... 357 // Function<EventInfo, Map<String, Object>> fn. eventData.putAll(fn.apply(info)); 358 359 // standard set of event data 360 Map<String, Object> eventData = _calendarEventJSONHelper.eventAsJson(event, false, false); 361 basicEventList.add(eventData); 362 processed++; 363 364 // add additional info 365 _addAdditionalEventData(event, eventData); 366 } 367 } 368 369 Expression recurrentExpr = new StringExpression(JCRCalendarEvent.ATTRIBUTE_RECURRENCE_TYPE, Operator.NE, "NEVER"); 370 eventExpr = new AndExpression(recurrentExpr, keywordsExpr); 371 372 String recurrentEventQuery = QueryHelper.getXPathQuery(null, JCRCalendarEventFactory.CALENDAR_EVENT_NODETYPE, eventExpr, sortCriteria); 373 AmetysObjectIterable<CalendarEvent> recurrentEvents = _resolver.query(recurrentEventQuery); 374 AmetysObjectIterator<CalendarEvent> recurrentEventIt = recurrentEvents.iterator(); 375 376 // FIXME cannot count processed here... 377 processed = 0; 378 while (recurrentEventIt.hasNext() /*&& processed < maxResultsAsInt*/) 379 { 380 CalendarEvent event = recurrentEventIt.next(); 381 Optional<CalendarEventOccurrence> nextOccurrence = event.getNextOccurrence(new CalendarEventOccurrence(event, startDate)); 382 383 // The recurrent event first occurrence is within the range 384 if (nextOccurrence.isPresent() && nextOccurrence.get().before(endDate)) 385 { 386 // FIXME calculate occurrences only if keep event... 387 List<CalendarEventOccurrence> occurrences = event.getOccurrences(nextOccurrence.get().getStartDate(), endDate); 388 Calendar holdingCalendar = event.getCalendar(); 389 390 if (_filterEvent(calendarIds, event) && _hasAccess(holdingCalendar)) 391 { 392 // The event is in the list of selected calendars and has the appropriate tags (can be none if tagIds == null) 393 394 // Add all its occurrences that are within the range 395 for (CalendarEventOccurrence occurrence : occurrences) 396 { 397 Map<String, Object> eventData = _calendarEventJSONHelper.eventAsJsonWithOccurrence(event, occurrence.getStartDate(), false); 398 recurrentEventList.add(eventData); 399 processed++; 400 401 _addAdditionalEventData(event, eventData); 402 403 } 404 } 405 } 406 } 407 408 // Re-sort chronologically the events' union 409 List<Map<String, Object>> allEvents = ListUtils.union(basicEventList, recurrentEventList); 410 Collections.sort(allEvents, new StartDateComparator()); 411 412 // Return the first maxResults events 413 return allEvents.size() <= maxResults ? allEvents : allEvents.subList(0, maxResults); 414 } 415 416 /** 417 * Determine whether the given event has to be kept or not depending on the given calendars 418 * @param calendarIds the ids of the calendars 419 * @param event the event 420 * @return true if the event can be kept, false otherwise 421 */ 422 private boolean _filterEvent(List<String> calendarIds, CalendarEvent event) 423 { 424 Calendar holdingCalendar = event.getCalendar(); 425 // FIXME calendarIds.get(0) == null means "All calendars" selected in the select calendar widget ?? 426 // need cleaner code 427 return calendarIds == null || calendarIds.get(0) == null || calendarIds.contains(holdingCalendar.getId()); 428 } 429 430 private boolean _hasAccess(Calendar calendar) 431 { 432 return CalendarVisibility.PUBLIC.equals(calendar.getVisibility()) || canView(calendar); 433 } 434 435 /** 436 * Indicates if the current user can view the calendar 437 * @param calendar The calendar to test 438 * @return true if the calendar can be viewed 439 */ 440 public boolean canView(Calendar calendar) 441 { 442 if (calendar instanceof TaskCalendar) 443 { 444 // Check if the user has read access to the task module 445 return _calendarDAO.hasTaskCalendarReadAccess(calendar.getProject()); 446 } 447 return _rightManager.currentUserHasReadAccess(calendar); 448 } 449 450 /** 451 * Indicates if the current user can view the event 452 * @param event The event to test 453 * @return true if the event can be viewed 454 */ 455 public boolean canView(CalendarEvent event) 456 { 457 if (event instanceof TaskCalendarEvent taskEvent) 458 { 459 // Check if the user has read access to the task 460 return _rightManager.currentUserHasReadAccess(taskEvent.getTask()); 461 } 462 return _rightManager.currentUserHasReadAccess(event.getCalendar()); 463 } 464 465 /** 466 * Compares events on their starting date 467 */ 468 protected static class StartDateComparator implements Comparator<Map<String, Object>> 469 { 470 @Override 471 public int compare(Map<String, Object> calendarEventInfo1, Map<String, Object> calendarEventInfo2) 472 { 473 String startDate1asString = (String) calendarEventInfo1.get("startDate"); 474 String startDate2asString = (String) calendarEventInfo2.get("startDate"); 475 476 Date startDate1 = DateUtils.parse(startDate1asString); 477 Date startDate2 = DateUtils.parse(startDate2asString); 478 479 // The start date is before if 480 return startDate1.compareTo(startDate2); 481 } 482 } 483 484 @Override 485 public Set<String> getAllowedEventTypes() 486 { 487 return ImmutableSet.of("calendar.event.created", "calendar.event.updated", "calendar.event.deleting"); 488 } 489 490 @Override 491 protected void _internalActivateModule(Project project, Map<String, Object> additionalValues) 492 { 493 createResourceCalendar(project, additionalValues); 494 _createDefaultCalendar(project, additionalValues); 495 } 496 497 /** 498 * Create a calendar to store resources if needed 499 * @param project the project 500 * @param additionalValues A list of optional additional values. Accepted values are : description, mailingList, inscriptionStatus, defaultProfile, tags, categoryTags, keywords and language 501 * @return The resource calendar 502 */ 503 public Calendar createResourceCalendar(Project project, Map<String, Object> additionalValues) 504 { 505 ModifiableResourceCollection resourceCalendarRoot = getResourceCalendarRoot(project, true); 506 507 String lang; 508 if (additionalValues.containsKey("language")) 509 { 510 lang = (String) additionalValues.get("language"); 511 } 512 else 513 { 514 lang = _workspaceHelper.getLang(project); 515 } 516 517 Calendar resourceCalendar = resourceCalendarRoot.getChildren() 518 .stream() 519 .filter(Calendar.class::isInstance) 520 .map(Calendar.class::cast) 521 .findFirst() 522 .orElse(null); 523 524 if (resourceCalendar == null) 525 { 526 Boolean renameIfExists = false; 527 Boolean checkRights = false; 528 String description = _i18nUtils.translate(_resourceCalendarDescription, lang); 529 String inputName = _i18nUtils.translate(_resourceCalendarTitle, lang); 530 String templateDesc = _i18nUtils.translate(_resourceCalendarTemplateDesc, lang); 531 try 532 { 533 Map result = _calendarDAO.addCalendar(resourceCalendarRoot, inputName, description, templateDesc, _resourceCalendarColor, _resourceCalendarVisibility, _resourceCalendarWorkflowName, renameIfExists, checkRights, false); 534 535 resourceCalendar = _resolver.resolveById((String) result.get("id")); 536 } 537 catch (Exception e) 538 { 539 getLogger().error("Error while trying to create the first calendar in a newly created project", e); 540 } 541 } 542 return resourceCalendar; 543 } 544 545 private void _createDefaultCalendar(Project project, Map<String, Object> additionalValues) 546 { 547 ModifiableResourceCollection moduleRoot = getCalendarsRoot(project, true); 548 549 if (moduleRoot != null && !_hasOtherCalendar(project)) 550 { 551 Boolean renameIfExists = false; 552 Boolean checkRights = false; 553 554 String lang; 555 if (additionalValues.containsKey("language")) 556 { 557 lang = (String) additionalValues.get("language"); 558 } 559 else 560 { 561 lang = _workspaceHelper.getLang(project); 562 } 563 564 String description = _i18nUtils.translate(_defaultCalendarDescription, lang); 565 String inputName = _i18nUtils.translate(_defaultCalendarTitle, lang); 566 String templateDesc = _i18nUtils.translate(_defaultCalendarTemplateDesc, lang); 567 try 568 { 569 _calendarDAO.addCalendar(moduleRoot, inputName, description, templateDesc, _defaultCalendarColor, _defaultCalendarVisibility, _defaultCalendarWorkflowName, renameIfExists, checkRights, false); 570 } 571 catch (Exception e) 572 { 573 getLogger().error("Error while trying to create the first calendar in a newly created project", e); 574 } 575 576 } 577 } 578 579 private boolean _hasOtherCalendar(Project project) 580 { 581 List<Calendar> calendars = getCalendars(project, false); 582 return calendars.size() > 0; 583 } 584 585 /** 586 * Get the calendars of a project 587 * @param project The project 588 * @return The list of calendar 589 */ 590 public Calendar getResourceCalendar(Project project) 591 { 592 ModifiableResourceCollection resourceCalendarRoot = getResourceCalendarRoot(project, true); 593 return resourceCalendarRoot.getChildren() 594 .stream() 595 .filter(Calendar.class::isInstance) 596 .map(Calendar.class::cast) 597 .findFirst() 598 .orElse(createResourceCalendar(project, new HashMap<>())); 599 } 600 601 /** 602 * Get the root for calendars's resources 603 * @param project The project 604 * @param create true to create root if not exists 605 * @return The root for calendars 606 */ 607 public ModifiableResourceCollection getCalendarResourcesRoot(Project project, boolean create) 608 { 609 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 610 return _getAmetysObject(moduleRoot, __WORKSPACES_CALENDAR_RESOURCES_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 611 } 612 613 /** 614 * Get the root for calendars 615 * @param project The project 616 * @param create true to create root if not exists 617 * @return The root for tasks 618 */ 619 public ModifiableResourceCollection getCalendarsRoot(Project project, boolean create) 620 { 621 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 622 return _getAmetysObject(moduleRoot, __WORKSPACES_CALENDARS_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 623 } 624 625 /** 626 * Get the root for tasks 627 * @param project The project 628 * @param create true to create root if not exists 629 * @return The root for tasks 630 */ 631 public ModifiableResourceCollection getResourceCalendarRoot(Project project, boolean create) 632 { 633 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 634 return _getAmetysObject(moduleRoot, __WORKSPACES_RESOURCE_CALENDAR_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 635 } 636 637 @Override 638 public Map<String, Object> _getInternalStatistics(Project project, boolean isActive) 639 { 640 if (isActive) 641 { 642 List<Calendar> calendars = getCalendars(project, true); 643 Calendar ressourceCalendar = getResourceCalendar(project); 644 645 // concatenate both type of calendars 646 long eventNumber = Stream.concat(calendars.stream(), Stream.of(ressourceCalendar)) 647 // get all events for each calendar 648 .map(Calendar::getAllEvents) 649 // use flatMap to have a stream with all events from all calendars 650 .flatMap(List::stream) 651 // count the number of events 652 .count(); 653 654 return Map.of(__EVENT_NUMBER_HEADER_ID, eventNumber); 655 } 656 else 657 { 658 return Map.of(__EVENT_NUMBER_HEADER_ID, __SIZE_INACTIVE); 659 } 660 } 661 662 @Override 663 public List<StatisticColumn> _getInternalStatisticModel() 664 { 665 return List.of(new StatisticColumn(__EVENT_NUMBER_HEADER_ID, new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_EVENT_NUMBER")) 666 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderElements") 667 .withType(StatisticsColumnType.LONG) 668 .withGroup(GROUP_HEADER_ELEMENTS_ID)); 669 } 670 671 @Override 672 public Set<String> getAllEventTypes() 673 { 674 return Set.of(ObservationConstants.EVENT_CALENDAR_CREATED, 675 ObservationConstants.EVENT_CALENDAR_DELETED, 676 ObservationConstants.EVENT_CALENDAR_EVENT_CREATED, 677 ObservationConstants.EVENT_CALENDAR_EVENT_DELETING, 678 ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED, 679 ObservationConstants.EVENT_CALENDAR_MOVED, 680 ObservationConstants.EVENT_CALENDAR_RESOURCE_CREATED, 681 ObservationConstants.EVENT_CALENDAR_RESOURCE_DELETED, 682 ObservationConstants.EVENT_CALENDAR_RESOURCE_UPDATED, 683 ObservationConstants.EVENT_CALENDAR_UPDATED); 684 } 685} 686