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