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