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 return getEventUri(project, calendarId, eventId, null); 239 } 240 241 /** 242 * Get the URI of a thread in project'site 243 * @param project The project 244 * @param calendarId The id of calendar 245 * @param eventId The id of event 246 * @param occurrenceDate the occurrence date to open in case of recurrent event, can be null 247 * @return The thread uri 248 */ 249 public String getEventUri(Project project, String calendarId, String eventId, ZonedDateTime occurrenceDate) 250 { 251 String moduleUrl = getModuleUrl(project); 252 if (moduleUrl != null) 253 { 254 StringBuilder sb = new StringBuilder(); 255 sb.append(moduleUrl); 256 sb.append("?route=event-").append(eventId); 257 258 if (occurrenceDate != null) 259 { 260 sb.append("$").append(DateUtils.zonedDateTimeToString(occurrenceDate)); 261 } 262 263 return sb.toString(); 264 } 265 266 return null; 267 } 268 269 /** 270 * Add additional information on project and parent calendar 271 * @param event The event 272 * @param eventData The event data to complete 273 */ 274 @SuppressWarnings("unchecked") 275 protected void _addAdditionalEventData(CalendarEvent event, Map<String, Object> eventData) 276 { 277 Request request = ContextHelper.getRequest(_context); 278 279 Calendar calendar = event.getCalendar(); 280 Project project = calendar.getProject(); 281 282 // Try to get calendar from cache if request is not null 283 if (request.getAttribute(__CALENDAR_CACHE_REQUEST_ATTR) == null) 284 { 285 request.setAttribute(__CALENDAR_CACHE_REQUEST_ATTR, new HashMap<>()); 286 } 287 288 Map<String, Object> calendarCache = (Map<String, Object>) request.getAttribute(__CALENDAR_CACHE_REQUEST_ATTR); 289 290 if (!calendarCache.containsKey(calendar.getId())) 291 { 292 Map<String, Object> calendarInfo = new HashMap<>(); 293 294 calendarInfo.put("calendarName", calendar.getName()); 295 calendarInfo.put("calendarIsPublic", CalendarVisibility.PUBLIC.equals(calendar.getVisibility())); 296 calendarInfo.put("calendarHasViewRight", canView(calendar)); 297 298 calendarInfo.put("projectId", project.getId()); 299 calendarInfo.put("projectTitle", project.getTitle()); 300 301 Set<Page> calendarModulePages = _projectManager.getModulePages(project, this); 302 if (!calendarModulePages.isEmpty()) 303 { 304 Page calendarModulePage = calendarModulePages.iterator().next(); 305 calendarInfo.put("calendarModulePageId", calendarModulePage.getId()); 306 } 307 308 calendarCache.put(calendar.getId(), calendarInfo); 309 } 310 311 eventData.putAll((Map<String, Object>) calendarCache.get(calendar.getId())); 312 313 eventData.put("eventUrl", getEventUri(project, calendar.getId(), event.getId())); 314 } 315 316 /** 317 * Get the upcoming events of the calendars on which the user has a right 318 * @param months the amount of months from today in which look for upcoming events 319 * @param maxResults the maximum results to display 320 * @param calendarIds the ids of the calendars to gather events from, null for all calendars 321 * @param tagIds the ids of the valid tags for the events, null for any tag 322 * @return the upcoming events 323 */ 324 public List<Map<String, Object>> getUpcomingEvents(int months, int maxResults, List<String> calendarIds, List<String> tagIds) 325 { 326 List<Map<String, Object>> basicEventList = new ArrayList<> (); 327 List<Map<String, Object>> recurrentEventList = new ArrayList<> (); 328 329 java.util.Calendar cal = java.util.Calendar.getInstance(); 330 cal.set(java.util.Calendar.HOUR_OF_DAY, 0); 331 cal.set(java.util.Calendar.MINUTE, 0); 332 cal.set(java.util.Calendar.SECOND, 0); 333 cal.set(java.util.Calendar.MILLISECOND, 0); 334 ZonedDateTime startDate = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS); 335 336 ZonedDateTime endDate = startDate.plusMonths(months); 337 338 Expression nonRecurrentExpr = new StringExpression(JCRCalendarEvent.ATTRIBUTE_RECURRENCE_TYPE, Operator.EQ, "NEVER"); 339 Expression startDateExpr = new DateExpression(JCRCalendarEvent.ATTRIBUTE_START_DATE, Operator.GE, startDate); 340 Expression endDateExpr = new DateExpression(JCRCalendarEvent.ATTRIBUTE_START_DATE, Operator.LE, endDate); 341 342 Expression keywordsExpr = null; 343 344 if (tagIds != null && !tagIds.isEmpty()) 345 { 346 List<Expression> orExpr = new ArrayList<>(); 347 for (String tagId : tagIds) 348 { 349 orExpr.add(new StringExpression(JCRCalendarEvent.ATTRIBUTE_KEYWORDS, Operator.EQ, tagId)); 350 } 351 keywordsExpr = new OrExpression(orExpr.toArray(new Expression[orExpr.size()])); 352 } 353 354 // Get the non recurrent events sorted by ascending date and within the configured range 355 Expression eventExpr = new AndExpression(nonRecurrentExpr, startDateExpr, endDateExpr, keywordsExpr); 356 SortCriteria sortCriteria = new SortCriteria(); 357 sortCriteria.addCriterion(JCRCalendarEvent.ATTRIBUTE_START_DATE, true, false); 358 359 String basicEventQuery = QueryHelper.getXPathQuery(null, JCRCalendarEventFactory.CALENDAR_EVENT_NODETYPE, eventExpr, sortCriteria); 360 AmetysObjectIterable<CalendarEvent> basicEvents = _resolver.query(basicEventQuery); 361 AmetysObjectIterator<CalendarEvent> basicEventIt = basicEvents.iterator(); 362 363 int processed = 0; 364 while (basicEventIt.hasNext() && processed < maxResults) 365 { 366 CalendarEvent event = basicEventIt.next(); 367 Calendar holdingCalendar = event.getCalendar(); 368 369 if (_filterEvent(calendarIds, event) && _hasAccess(holdingCalendar)) 370 { 371 // The event is in the list of selected calendars and has the appropriate tags (can be none if tagIds == null) 372 373 // FIXME should use something like an EventInfo object with some data + calendar, project name 374 // And use a function to process the transformation... 375 // Function<EventInfo, Map<String, Object>> fn. eventData.putAll(fn.apply(info)); 376 377 // standard set of event data 378 Map<String, Object> eventData = _calendarEventJSONHelper.eventAsJson(event, false); 379 basicEventList.add(eventData); 380 processed++; 381 382 // add additional info 383 _addAdditionalEventData(event, eventData); 384 } 385 } 386 387 Expression recurrentExpr = new StringExpression(JCRCalendarEvent.ATTRIBUTE_RECURRENCE_TYPE, Operator.NE, "NEVER"); 388 eventExpr = new AndExpression(recurrentExpr, keywordsExpr); 389 390 String recurrentEventQuery = QueryHelper.getXPathQuery(null, JCRCalendarEventFactory.CALENDAR_EVENT_NODETYPE, eventExpr, sortCriteria); 391 AmetysObjectIterable<CalendarEvent> recurrentEvents = _resolver.query(recurrentEventQuery); 392 AmetysObjectIterator<CalendarEvent> recurrentEventIt = recurrentEvents.iterator(); 393 394 // FIXME cannot count processed here... 395 processed = 0; 396 while (recurrentEventIt.hasNext() /*&& processed < maxResultsAsInt*/) 397 { 398 CalendarEvent event = recurrentEventIt.next(); 399 Optional<CalendarEventOccurrence> nextOccurrence = event.getNextOccurrence(new CalendarEventOccurrence(event, startDate)); 400 401 // The recurrent event first occurrence is within the range 402 if (nextOccurrence.isPresent() && nextOccurrence.get().before(endDate)) 403 { 404 // FIXME calculate occurrences only if keep event... 405 List<CalendarEventOccurrence> occurrences = event.getOccurrences(nextOccurrence.get().getStartDate(), endDate); 406 Calendar holdingCalendar = event.getCalendar(); 407 408 if (_filterEvent(calendarIds, event) && _hasAccess(holdingCalendar)) 409 { 410 // The event is in the list of selected calendars and has the appropriate tags (can be none if tagIds == null) 411 412 // Add all its occurrences that are within the range 413 for (CalendarEventOccurrence occurrence : occurrences) 414 { 415 Map<String, Object> eventData = _calendarEventJSONHelper.eventAsJsonWithOccurrence(event, occurrence.getStartDate()); 416 recurrentEventList.add(eventData); 417 processed++; 418 419 _addAdditionalEventData(event, eventData); 420 421 } 422 } 423 } 424 } 425 426 // Re-sort chronologically the events' union 427 List<Map<String, Object>> allEvents = ListUtils.union(basicEventList, recurrentEventList); 428 Collections.sort(allEvents, new StartDateComparator()); 429 430 // Return the first maxResults events 431 return allEvents.size() <= maxResults ? allEvents : allEvents.subList(0, maxResults); 432 } 433 434 /** 435 * Determine whether the given event has to be kept or not depending on the given calendars 436 * @param calendarIds the ids of the calendars 437 * @param event the event 438 * @return true if the event can be kept, false otherwise 439 */ 440 private boolean _filterEvent(List<String> calendarIds, CalendarEvent event) 441 { 442 Calendar holdingCalendar = event.getCalendar(); 443 // FIXME calendarIds.get(0) == null means "All calendars" selected in the select calendar widget ?? 444 // need cleaner code 445 return calendarIds == null || calendarIds.get(0) == null || calendarIds.contains(holdingCalendar.getId()); 446 } 447 448 private boolean _hasAccess(Calendar calendar) 449 { 450 return CalendarVisibility.PUBLIC.equals(calendar.getVisibility()) || canView(calendar); 451 } 452 453 /** 454 * Indicates if the current user can view the calendar 455 * @param calendar The calendar to test 456 * @return true if the calendar can be viewed 457 */ 458 public boolean canView(Calendar calendar) 459 { 460 if (calendar instanceof TaskCalendar) 461 { 462 // Check if the user has read access to the task module 463 return _calendarDAO.hasTaskCalendarReadAccess(calendar.getProject()); 464 } 465 return _rightManager.currentUserHasReadAccess(calendar); 466 } 467 468 /** 469 * Indicates if the current user can view the event 470 * @param event The event to test 471 * @return true if the event can be viewed 472 */ 473 public boolean canView(CalendarEvent event) 474 { 475 if (event instanceof TaskCalendarEvent taskEvent) 476 { 477 // Check if the user has read access to the task 478 return _rightManager.currentUserHasReadAccess(taskEvent.getTask()); 479 } 480 return _rightManager.currentUserHasReadAccess(event.getCalendar()); 481 } 482 483 /** 484 * Compares events on their starting date 485 */ 486 protected static class StartDateComparator implements Comparator<Map<String, Object>> 487 { 488 @Override 489 public int compare(Map<String, Object> calendarEventInfo1, Map<String, Object> calendarEventInfo2) 490 { 491 String startDate1asString = (String) calendarEventInfo1.get("startDate"); 492 String startDate2asString = (String) calendarEventInfo2.get("startDate"); 493 494 Date startDate1 = DateUtils.parse(startDate1asString); 495 Date startDate2 = DateUtils.parse(startDate2asString); 496 497 // The start date is before if 498 return startDate1.compareTo(startDate2); 499 } 500 } 501 502 @Override 503 public Set<String> getAllowedEventTypes() 504 { 505 return ImmutableSet.of("calendar.event.created", "calendar.event.updated", "calendar.event.deleting"); 506 } 507 508 @Override 509 protected void _internalActivateModule(Project project, Map<String, Object> additionalValues) 510 { 511 createResourceCalendar(project, additionalValues); 512 _createDefaultCalendar(project, additionalValues); 513 } 514 515 /** 516 * Create a calendar to store resources if needed 517 * @param project the project 518 * @param additionalValues A list of optional additional values. Accepted values are : description, mailingList, inscriptionStatus, defaultProfile, tags, categoryTags, keywords and language 519 * @return The resource calendar 520 */ 521 public Calendar createResourceCalendar(Project project, Map<String, Object> additionalValues) 522 { 523 ModifiableResourceCollection resourceCalendarRoot = getResourceCalendarRoot(project, true); 524 525 String lang; 526 if (additionalValues.containsKey("language")) 527 { 528 lang = (String) additionalValues.get("language"); 529 } 530 else 531 { 532 lang = _workspaceHelper.getLang(project); 533 } 534 535 Calendar resourceCalendar = resourceCalendarRoot.getChildren() 536 .stream() 537 .filter(Calendar.class::isInstance) 538 .map(Calendar.class::cast) 539 .findFirst() 540 .orElse(null); 541 542 if (resourceCalendar == null) 543 { 544 Boolean renameIfExists = false; 545 Boolean checkRights = false; 546 String description = _i18nUtils.translate(_resourceCalendarDescription, lang); 547 String inputName = _i18nUtils.translate(_resourceCalendarTitle, lang); 548 String templateDesc = _i18nUtils.translate(_resourceCalendarTemplateDesc, lang); 549 try 550 { 551 Map result = _calendarDAO.addCalendar(resourceCalendarRoot, inputName, description, templateDesc, _resourceCalendarColor, _resourceCalendarVisibility, _resourceCalendarWorkflowName, renameIfExists, checkRights, false); 552 553 resourceCalendar = _resolver.resolveById((String) result.get("id")); 554 } 555 catch (Exception e) 556 { 557 getLogger().error("Error while trying to create the first calendar in a newly created project", e); 558 } 559 } 560 return resourceCalendar; 561 } 562 563 private void _createDefaultCalendar(Project project, Map<String, Object> additionalValues) 564 { 565 ModifiableResourceCollection moduleRoot = getCalendarsRoot(project, true); 566 567 if (moduleRoot != null && !_hasOtherCalendar(project)) 568 { 569 Boolean renameIfExists = false; 570 Boolean checkRights = false; 571 572 String lang; 573 if (additionalValues.containsKey("language")) 574 { 575 lang = (String) additionalValues.get("language"); 576 } 577 else 578 { 579 lang = _workspaceHelper.getLang(project); 580 } 581 582 String description = _i18nUtils.translate(_defaultCalendarDescription, lang); 583 String inputName = _i18nUtils.translate(_defaultCalendarTitle, lang); 584 String templateDesc = _i18nUtils.translate(_defaultCalendarTemplateDesc, lang); 585 try 586 { 587 _calendarDAO.addCalendar(moduleRoot, inputName, description, templateDesc, _defaultCalendarColor, _defaultCalendarVisibility, _defaultCalendarWorkflowName, renameIfExists, checkRights, false); 588 } 589 catch (Exception e) 590 { 591 getLogger().error("Error while trying to create the first calendar in a newly created project", e); 592 } 593 594 } 595 } 596 597 private boolean _hasOtherCalendar(Project project) 598 { 599 List<Calendar> calendars = getCalendars(project, false); 600 return calendars.size() > 0; 601 } 602 603 /** 604 * Get the calendars of a project 605 * @param project The project 606 * @return The list of calendar 607 */ 608 public Calendar getResourceCalendar(Project project) 609 { 610 ModifiableResourceCollection resourceCalendarRoot = getResourceCalendarRoot(project, true); 611 return resourceCalendarRoot.getChildren() 612 .stream() 613 .filter(Calendar.class::isInstance) 614 .map(Calendar.class::cast) 615 .findFirst() 616 .orElse(createResourceCalendar(project, new HashMap<>())); 617 } 618 619 /** 620 * Get the root for calendars's resources 621 * @param project The project 622 * @param create true to create root if not exists 623 * @return The root for calendars 624 */ 625 public ModifiableResourceCollection getCalendarResourcesRoot(Project project, boolean create) 626 { 627 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 628 return _getAmetysObject(moduleRoot, __WORKSPACES_CALENDAR_RESOURCES_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 629 } 630 631 /** 632 * Get the root for calendars 633 * @param project The project 634 * @param create true to create root if not exists 635 * @return The root for tasks 636 */ 637 public ModifiableResourceCollection getCalendarsRoot(Project project, boolean create) 638 { 639 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 640 return _getAmetysObject(moduleRoot, __WORKSPACES_CALENDARS_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 641 } 642 643 /** 644 * Get the root for tasks 645 * @param project The project 646 * @param create true to create root if not exists 647 * @return The root for tasks 648 */ 649 public ModifiableResourceCollection getResourceCalendarRoot(Project project, boolean create) 650 { 651 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 652 return _getAmetysObject(moduleRoot, __WORKSPACES_RESOURCE_CALENDAR_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 653 } 654 655 @Override 656 public Map<String, Object> _getInternalStatistics(Project project, boolean isActive) 657 { 658 if (isActive) 659 { 660 List<Calendar> calendars = getCalendars(project, true); 661 Calendar ressourceCalendar = getResourceCalendar(project); 662 663 // concatenate both type of calendars 664 long eventNumber = Stream.concat(calendars.stream(), Stream.of(ressourceCalendar)) 665 // get all events for each calendar 666 .map(Calendar::getAllEvents) 667 // use flatMap to have a stream with all events from all calendars 668 .flatMap(List::stream) 669 // count the number of events 670 .count(); 671 672 return Map.of(__EVENT_NUMBER_HEADER_ID, eventNumber); 673 } 674 else 675 { 676 return Map.of(__EVENT_NUMBER_HEADER_ID, __SIZE_INACTIVE); 677 } 678 } 679 680 @Override 681 public List<StatisticColumn> _getInternalStatisticModel() 682 { 683 return List.of(new StatisticColumn(__EVENT_NUMBER_HEADER_ID, new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_EVENT_NUMBER")) 684 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderElements") 685 .withType(StatisticsColumnType.LONG) 686 .withGroup(GROUP_HEADER_ELEMENTS_ID)); 687 } 688 689 @Override 690 public Set<String> getAllEventTypes() 691 { 692 return Set.of(ObservationConstants.EVENT_CALENDAR_CREATED, 693 ObservationConstants.EVENT_CALENDAR_DELETED, 694 ObservationConstants.EVENT_CALENDAR_EVENT_CREATED, 695 ObservationConstants.EVENT_CALENDAR_EVENT_DELETING, 696 ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED, 697 ObservationConstants.EVENT_CALENDAR_MOVED, 698 ObservationConstants.EVENT_CALENDAR_RESOURCE_CREATED, 699 ObservationConstants.EVENT_CALENDAR_RESOURCE_DELETED, 700 ObservationConstants.EVENT_CALENDAR_RESOURCE_UPDATED, 701 ObservationConstants.EVENT_CALENDAR_UPDATED); 702 } 703} 704