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