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