001/* 002 * Copyright 2012 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.calendar.events; 017 018import java.io.IOException; 019import java.time.Instant; 020import java.time.LocalDate; 021import java.time.ZoneId; 022import java.time.ZonedDateTime; 023import java.time.format.DateTimeFormatter; 024import java.time.temporal.ChronoField; 025import java.util.ArrayList; 026import java.util.Calendar; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.Iterator; 031import java.util.LinkedHashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Map.Entry; 035import java.util.Set; 036import java.util.UUID; 037 038import org.apache.avalon.framework.service.ServiceException; 039import org.apache.avalon.framework.service.ServiceManager; 040import org.apache.cocoon.ProcessingException; 041import org.apache.cocoon.environment.ObjectModelHelper; 042import org.apache.cocoon.environment.Request; 043import org.apache.cocoon.xml.AttributesImpl; 044import org.apache.cocoon.xml.XMLUtils; 045import org.apache.commons.lang3.StringUtils; 046import org.apache.commons.lang3.time.DateUtils; 047import org.apache.commons.lang3.tuple.Pair; 048import org.xml.sax.ContentHandler; 049import org.xml.sax.SAXException; 050 051import org.ametys.cms.repository.Content; 052import org.ametys.cms.tag.Tag; 053import org.ametys.cms.tag.TagProviderExtensionPoint; 054import org.ametys.core.util.URIUtils; 055import org.ametys.plugins.calendar.icsreader.IcsEventHelper; 056import org.ametys.plugins.calendar.icsreader.IcsReader; 057import org.ametys.plugins.calendar.icsreader.IcsReader.IcsEvents; 058import org.ametys.plugins.calendar.icsreader.LocalVEvent; 059import org.ametys.plugins.repository.AmetysObjectIterable; 060import org.ametys.plugins.repository.AmetysObjectResolver; 061import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 062import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeater; 063import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeaterEntry; 064import org.ametys.runtime.i18n.I18nizableText; 065import org.ametys.web.WebConstants; 066import org.ametys.web.content.GetSiteAction; 067import org.ametys.web.filter.WebContentFilter; 068import org.ametys.web.filter.WebContentFilter.AccessLimitation; 069import org.ametys.web.repository.page.Page; 070import org.ametys.web.repository.page.SitemapElement; 071import org.ametys.web.repository.page.ZoneItem; 072 073import net.fortuna.ical4j.model.Property; 074import net.fortuna.ical4j.model.component.VEvent; 075import net.fortuna.ical4j.model.property.Created; 076import net.fortuna.ical4j.model.property.DtStamp; 077import net.fortuna.ical4j.model.property.LastModified; 078 079/** 080 * Query and generate news according to many parameters. 081 */ 082public class EventsGenerator extends AbstractEventGenerator 083{ 084 /** The ametys object resolver. */ 085 protected AmetysObjectResolver _ametysResolver; 086 087 /** The events helper */ 088 protected EventsFilterHelper _eventsFilterHelper; 089 090 /** The ICS Reader */ 091 protected IcsReader _icsReader; 092 093 /** The tag provider extension point. */ 094 protected TagProviderExtensionPoint _tagProviderEP; 095 096 private IcsEventHelper _icsEventHelper; 097 098 @Override 099 public void service(ServiceManager serviceManager) throws ServiceException 100 { 101 super.service(serviceManager); 102 _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 103 _eventsFilterHelper = (EventsFilterHelper) serviceManager.lookup(EventsFilterHelper.ROLE); 104 _icsEventHelper = (IcsEventHelper) serviceManager.lookup(IcsEventHelper.ROLE); 105 _icsReader = (IcsReader) serviceManager.lookup(IcsReader.ROLE); 106 _tagProviderEP = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 107 } 108 109 @Override 110 public void generate() throws IOException, SAXException, ProcessingException 111 { 112 Request request = ObjectModelHelper.getRequest(objectModel); 113 @SuppressWarnings("unchecked") 114 Map<String, Object> parentContextAttrs = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 115 if (parentContextAttrs == null) 116 { 117 parentContextAttrs = Collections.EMPTY_MAP; 118 } 119 120 LocalDate today = LocalDate.now(); 121 122 // Get site and language in sitemap parameters. Can not be null. 123 String siteName = parameters.getParameter("site", (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME)); 124 String lang = parameters.getParameter("lang", (String) request.getAttribute("renderingLanguage")); 125 if (StringUtils.isEmpty(lang)) 126 { 127 lang = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME); 128 } 129 // Get the parameters. 130 int monthsBefore = parameters.getParameterAsInteger("months-before", 3); 131 int monthsAfter = parameters.getParameterAsInteger("months-after", 3); 132 // Type can be "calendar", "single-day" or "agenda". 133 String type = parameters.getParameter("type", "calendar"); 134 String view = parameters.getParameter("view", ""); 135 int year = parameters.getParameterAsInteger("year", today.getYear()); 136 int month = parameters.getParameterAsInteger("month", today.getMonthValue()); 137 int day = parameters.getParameterAsInteger("day", today.getDayOfMonth()); 138 // Select a single tag or "all". 139 String requestedTagsString = parameters.getParameter("tags", "all"); 140 141 142 Page currentPage = (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE); 143 144 // Get the zone item, as a request attribute or from the ID in the 145 // parameters. 146 ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM); 147 String zoneItemId = parameters.getParameter("zoneItemId", ""); 148 if (zoneItem == null && StringUtils.isNotEmpty(zoneItemId)) 149 { 150 zoneItemId = URIUtils.decode(zoneItemId); 151 zoneItem = (ZoneItem) _ametysResolver.resolveById(zoneItemId); 152 } 153 154 if (currentPage == null && zoneItem != null) 155 { 156 // Wrapped page such as _plugins/calendar/page/YEAR/MONTH/DAY/ZONEITEMID/events_1.3.html => get the page from its zone item 157 // The page is needed to get restriction 158 SitemapElement sitemapElement = zoneItem.getZone().getSitemapElement(); 159 if (sitemapElement instanceof Page page) 160 { 161 currentPage = page; 162 } 163 else 164 { 165 throw new IllegalStateException("The calendar service cannot be inherited from the sitemap root"); 166 } 167 } 168 169 ZonedDateTime dateTime = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZoneId.systemDefault()); 170 String title = _eventsFilterHelper.getTitle(zoneItem); 171 String rangeType = parameters.getParameter("rangeType", _eventsFilterHelper.getDefaultRangeType(zoneItem)); 172 boolean maskOrphan = _eventsFilterHelper.getMaskOrphan(zoneItem); 173 boolean pdfDownload = _eventsFilterHelper.getPdfDownload(zoneItem); 174 boolean icalDownload = _eventsFilterHelper.getIcalDownload(zoneItem); 175 String link = _eventsFilterHelper.getLink(zoneItem); 176 String linkTitle = _eventsFilterHelper.getLinkTitle(zoneItem); 177 178 boolean doRetrieveView = !StringUtils.equalsIgnoreCase("false", parameters.getParameter("do-retrieve-view", "true")); 179 180 // Get the search context to match, from the zone item or from the parameters. 181 @SuppressWarnings("unchecked") 182 List<Map<String, Object>> searchContexts = _eventsFilterHelper.getSearchContext(zoneItem, (List<Map<String, Object>>) parentContextAttrs.get("search")); 183 184 185 Set<String> tags = _eventsFilterHelper.getTags(zoneItem, searchContexts); 186 Set<Tag> categories = _eventsFilterHelper.getTagCategories(zoneItem, searchContexts, siteName); 187 Set<Tag> icsTags = _getIcsTags(zoneItem, siteName); 188 String pagePath = currentPage != null ? currentPage.getPathInSitemap() : ""; 189 190 Set<String> filteredCategories = _eventsFilterHelper.getFilteredCategories(null, requestedTagsString.split(","), zoneItem, siteName); 191 // Get the date range and deduce the expression (single day or month-before to month-after). 192 EventsFilterHelper.DateTimeRange dateRange = _eventsFilterHelper.getDateRange(type, year, month, day, monthsBefore, monthsAfter, rangeType); 193 194 EventsFilter eventsFilter = _eventsFilterHelper.generateEventFilter(dateRange, zoneItem, view, type, filteredCategories, searchContexts); 195 196 // Get the corresponding contents. 197 AmetysObjectIterable<Content> eventContents = eventsFilter.getMatchingContents(siteName, lang, currentPage); 198 199 // Read ICS threads 200 List<IcsEvents> parsedICS; 201 if (icalDownload && zoneItem != null && type.equals("full")) 202 { 203 // If we are exporting the ICS, we do not want to use maximum events or ICS file size limitation 204 parsedICS = _icsEventHelper.getICSEvents(zoneItem, siteName, dateRange, Long.MAX_VALUE, Long.MAX_VALUE); 205 } 206 else 207 { 208 parsedICS = _icsEventHelper.getICSEvents(zoneItem, siteName, dateRange); 209 } 210 211 // CAL-94 (same as CMS-2292 for filtered contents) 212 String currentSiteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME); 213 String currentSkinName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SKIN_ID); 214 String currentTemplateName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_TEMPLATE_ID); 215 String currentLanguage = (String) request.getAttribute("renderingLanguage"); 216 request.setAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR, currentSiteName); 217 request.setAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR, currentSkinName); 218 219 try 220 { 221 _sax(today, monthsBefore, monthsAfter, year, month, day, filteredCategories, currentPage, zoneItem, dateTime, title, rangeType, maskOrphan, pdfDownload, icalDownload, link, linkTitle, doRetrieveView, tags, categories, icsTags, pagePath, eventsFilter, dateRange, eventContents, parsedICS); 222 } 223 finally 224 { 225 request.removeAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR); 226 request.removeAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR); 227 request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, currentSiteName); 228 request.setAttribute("siteName", currentSiteName); 229 request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, currentSkinName); 230 request.setAttribute(WebConstants.REQUEST_ATTR_TEMPLATE_ID, currentTemplateName); 231 request.setAttribute("renderingLanguage", currentLanguage); 232 } 233 } 234 235 private Set<Tag> _getIcsTags(ZoneItem zoneItem, String currentSiteName) 236 { 237 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 238 Set<Tag> categories = new LinkedHashSet<>(); 239 240 // Add the categories defined in the ICS fields 241 if (serviceParameters.hasValue("ics")) 242 { 243 ModifiableModelAwareRepeater icsRepeater = serviceParameters.getValue("ics"); 244 for (ModifiableModelAwareRepeaterEntry repeaterEntry : icsRepeater.getEntries()) 245 { 246 String categoryName = repeaterEntry.getValue("tag"); 247 Map<String, Object> contextualParameters = new HashMap<>(); 248 contextualParameters.put("siteName", currentSiteName); 249 Tag category = _tagProviderEP.getTag(categoryName, contextualParameters); 250 if (category != null) 251 { 252 categories.add(category); 253 } 254 } 255 } 256 return categories; 257 } 258 259 260 261 private void _sax(LocalDate today, int monthsBefore, int monthsAfter, int year, int month, int day, Set<String> filteredCategoryTags, Page page, ZoneItem zoneItem, ZonedDateTime date, 262 String title, String rangeType, boolean maskOrphan, boolean pdfDownload, boolean icalDownload, String link, String linkTitle, boolean doRetrieveView, Set<String> tags, 263 Set<Tag> categories, Set<Tag> icsTags, String pagePath, EventsFilter eventsFilter, EventsFilterHelper.DateTimeRange dateRange, AmetysObjectIterable<Content> eventContents, List<IcsEvents> icsEvents) 264 throws SAXException, IOException 265 { 266 AttributesImpl atts = new AttributesImpl(); 267 268 atts.addCDATAAttribute("page-path", pagePath); 269 atts.addCDATAAttribute("today", DateTimeFormatter.ISO_LOCAL_DATE.format(today)); 270 if (dateRange != null) 271 { 272 if (dateRange.fromDate() != null) 273 { 274 atts.addCDATAAttribute("start", DateTimeFormatter.ISO_LOCAL_DATE.format(dateRange.fromDate())); 275 } 276 if (dateRange.untilDate() != null) 277 { 278 atts.addCDATAAttribute("end", DateTimeFormatter.ISO_LOCAL_DATE.format(dateRange.untilDate())); 279 } 280 } 281 282 atts.addCDATAAttribute("year", Integer.toString(year)); 283 atts.addCDATAAttribute("month", String.format("%02d", month)); 284 atts.addCDATAAttribute("day", String.format("%02d", day)); 285 atts.addCDATAAttribute("months-before", Integer.toString(monthsBefore)); 286 atts.addCDATAAttribute("months-after", Integer.toString(monthsAfter)); 287 288 atts.addCDATAAttribute("title", title); 289 atts.addCDATAAttribute("mask-orphan", Boolean.toString(maskOrphan)); 290 atts.addCDATAAttribute("pdf-download", Boolean.toString(pdfDownload)); 291 atts.addCDATAAttribute("ical-download", Boolean.toString(icalDownload)); 292 atts.addCDATAAttribute("link", link); 293 atts.addCDATAAttribute("link-title", linkTitle); 294 295 if (zoneItem != null) 296 { 297 atts.addCDATAAttribute("zoneItemId", zoneItem.getId()); 298 } 299 if (StringUtils.isNotEmpty(rangeType)) 300 { 301 atts.addCDATAAttribute("range", rangeType); 302 } 303 304 if (!filteredCategoryTags.isEmpty()) 305 { 306 atts.addCDATAAttribute("requested-tags", String.join(",", filteredCategoryTags)); 307 } 308 309 contentHandler.startDocument(); 310 XMLUtils.startElement(contentHandler, "events", atts); 311 312 _saxRssUrl(zoneItem); 313 314 // Generate months (used in calendar mode) and days (used in full-page 315 // agenda mode). 316 _saxMonths(dateRange); 317 _saxDays(date, rangeType); 318 319 _saxDaysNew(dateRange, rangeType); 320 321 // Generate tags and categories. 322 _saxTags(tags); 323 _saxCategories(categories, icsTags); 324 325 Pair<List<LocalVEvent>, String> parsedICSEvents = _icsEventHelper.toLocalIcsEvent(icsEvents, dateRange); 326 List<LocalVEvent> localIcsEvents = parsedICSEvents.getLeft(); 327 String fullICSDistantEvents = parsedICSEvents.getRight(); 328 329 // Generate the matching contents. 330 XMLUtils.startElement(contentHandler, "contents"); 331 332 saxMatchingContents(contentHandler, eventsFilter, eventContents, page, doRetrieveView); 333 334 saxIcsEvents(contentHandler, localIcsEvents); 335 336 XMLUtils.endElement(contentHandler, "contents"); 337 338 XMLUtils.createElement(contentHandler, "rawICS", fullICSDistantEvents); 339 340 // Generate ICS events with errors 341 _icsEventHelper.saxICSErrors(icsEvents, contentHandler); 342 343 if (icalDownload) 344 { 345 // Generate VTimeZones for distant events 346 String timezones = _icsEventHelper.toVTimeZone(icsEvents, dateRange, List.of()); 347 XMLUtils.createElement(contentHandler, "timezones", timezones); 348 } 349 350 XMLUtils.endElement(contentHandler, "events"); 351 352 contentHandler.endDocument(); 353 } 354 355 private void _saxRssUrl(ZoneItem zoneItem) throws SAXException 356 { 357 if (zoneItem != null) 358 { 359 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 360 // First check that there is a value because calendar service doesn't define the rss parameter 361 if (serviceParameters.hasValue("rss") && (boolean) serviceParameters.getValue("rss")) 362 { 363 // Only add RSS if there is a search context 364 if (serviceParameters.hasValue("search")) 365 { 366 ModifiableModelAwareRepeater searchRepeater = serviceParameters.getValue("search"); 367 if (searchRepeater.getSize() > 0) 368 { 369 // Split protocol and id 370 String[] zoneItemId = zoneItem.getId().split("://"); 371 String url = "_plugins/calendar/" + zoneItemId[1] + "/rss.xml"; 372 373 XMLUtils.createElement(contentHandler, "rssUrl", url); 374 } 375 } 376 } 377 } 378 } 379 380 /** 381 * SAX all contents matching the given filter 382 * 383 * @param handler The content handler to SAX into 384 * @param filter The filter 385 * @param contents iterator on the contents. 386 * @param currentPage The current page. 387 * @param saxContentItSelf true to sax the content, false will only sax some meta 388 * @throws SAXException If an error occurs while SAXing 389 * @throws IOException If an error occurs while retrieving content. 390 */ 391 public void saxMatchingContents(ContentHandler handler, WebContentFilter filter, AmetysObjectIterable<Content> contents, Page currentPage, boolean saxContentItSelf) throws SAXException, IOException 392 { 393 boolean checkUserAccess = filter.getAccessLimitation() == AccessLimitation.USER_ACCESS; 394 395 for (Content content : contents) 396 { 397 if (_filterHelper.isContentValid(content, currentPage, filter)) 398 { 399 saxContent(handler, content, saxContentItSelf, filter, checkUserAccess); 400 } 401 } 402 } 403 404 /** 405 * Sax a list of events coming from a distant ICS file 406 * @param handler The content handler to SAX into 407 * @param icsEvents the events to sax 408 * @throws SAXException Something went wrong 409 */ 410 public void saxIcsEvents(ContentHandler handler, List<LocalVEvent> icsEvents) throws SAXException 411 { 412 for (LocalVEvent icsEvent : icsEvents) 413 { 414 saxIcsEvent(handler, icsEvent); 415 } 416 } 417 418 /** 419 * Sax an event coming from a distant ICS file 420 * @param handler The content handler to SAX into 421 * @param icsEvent an event to sax 422 * @throws SAXException Something went wrong 423 */ 424 public void saxIcsEvent(ContentHandler handler, LocalVEvent icsEvent) throws SAXException 425 { 426 VEvent event = icsEvent.getEvent(); 427 AttributesImpl attrs = new AttributesImpl(); 428 429 String start = org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(icsEvent.getStart()); 430 String end = org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(icsEvent.getEnd()); 431 List<String> params = new ArrayList<>(); 432 433 String title = event.getProperty(Property.SUMMARY).map(Property::getValue).orElse(""); 434 String id = event.getProperty(Property.UID).map(Property::getValue).orElse(UUID.randomUUID().toString()); 435 String eventAbstract = event.getProperty(Property.DESCRIPTION).map(Property::getValue).orElse(""); 436 437 params.add(title); 438 439 if (start != null) 440 { 441 String startAttr = icsEvent.getStart().format(DateTimeFormatter.ISO_LOCAL_DATE); 442 params.add(start); 443 attrs.addCDATAAttribute("start", startAttr); 444 } 445 446 if (end != null) 447 { 448 String endAttr = icsEvent.getEnd().format(DateTimeFormatter.ISO_LOCAL_DATE); 449 params.add(end); 450 attrs.addCDATAAttribute("end", endAttr); 451 } 452 453 XMLUtils.startElement(handler, "event", attrs); 454 455 String key = end == null ? "CALENDAR_SERVICE_AGENDA_EVENT_TITLE_SINGLE_DAY" : "CALENDAR_SERVICE_AGENDA_FROM_TO"; 456 I18nizableText description = new I18nizableText(null, key, params); 457 description.toSAX(handler, "description"); 458 459 attrs = new AttributesImpl(); 460 attrs.addCDATAAttribute("id", "ics://" + id); 461 attrs.addCDATAAttribute("title", title); 462 463 DtStamp dateTimeStamp = event.getDateTimeStamp(); 464 Instant dtStamp = dateTimeStamp != null ? dateTimeStamp.getDate() : Instant.now(); 465 466 Created created = event.getCreated(); 467 Instant createdAtDate = created != null ? created.getDate() : dtStamp; 468 469 LastModified lastModified = event.getLastModified(); 470 Instant lastModifiedDate = lastModified != null ? lastModified.getDate() : dtStamp; 471 472 String createdAt = org.ametys.core.util.DateUtils.asZonedDateTime(createdAtDate, null).format(DateTimeFormatter.ISO_INSTANT); 473 attrs.addCDATAAttribute("createdAt", createdAt); 474 475 String lastModifiedAt = org.ametys.core.util.DateUtils.asZonedDateTime(lastModifiedDate, null).format(DateTimeFormatter.ISO_INSTANT); 476 attrs.addCDATAAttribute("lastModifiedAt", lastModifiedAt); 477 478 XMLUtils.startElement(handler, "content", attrs); 479 480 XMLUtils.startElement(handler, "metadata"); 481 attrs = new AttributesImpl(); 482 attrs.addCDATAAttribute("typeId", "string"); 483 attrs.addCDATAAttribute("multiple", "false"); 484 XMLUtils.createElement(handler, "title", attrs, title); 485 XMLUtils.createElement(handler, "abstract", attrs, eventAbstract); 486 487 488 attrs = new AttributesImpl(); 489 attrs.addCDATAAttribute("typeId", "datetime"); 490 attrs.addCDATAAttribute("multiple", "false"); 491 XMLUtils.createElement(handler, "start-date", attrs, start); 492 XMLUtils.createElement(handler, "end-date", attrs, end); 493 494 XMLUtils.endElement(handler, "metadata"); 495 496 Tag tag = icsEvent.getTag(); 497 if (tag != null) 498 { 499 XMLUtils.startElement(handler, "tags"); 500 attrs = new AttributesImpl(); 501 attrs.addCDATAAttribute("parent", tag.getParentName()); 502 XMLUtils.startElement(handler, tag.getName(), attrs); 503 tag.getTitle().toSAX(handler); 504 XMLUtils.endElement(handler, tag.getName()); 505 XMLUtils.endElement(handler, "tags"); 506 } 507 508 XMLUtils.endElement(handler, "content"); 509 XMLUtils.endElement(handler, "event"); 510 } 511 512 /** 513 * SAX information on the months spanning the date range. 514 * @param dateRange the date range. 515 * @throws SAXException if a error occurs while saxing 516 */ 517 protected void _saxMonths(EventsFilterHelper.DateTimeRange dateRange) throws SAXException 518 { 519 if (dateRange != null && dateRange.fromDate() != null && dateRange.untilDate() != null) 520 { 521 AttributesImpl atts = new AttributesImpl(); 522 523 XMLUtils.startElement(contentHandler, "months"); 524 525 ZonedDateTime date = dateRange.fromDate(); 526 ZonedDateTime end = dateRange.untilDate(); 527 528 while (date.isBefore(end)) 529 { 530 int year = date.getYear(); 531 int month = date.getMonthValue(); 532 533 String monthStr = String.format("%d-%02d", year, month); 534 String dateStr = org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(date); 535 536 atts.clear(); 537 atts.addCDATAAttribute("str", monthStr); 538 atts.addCDATAAttribute("raw", dateStr); 539 XMLUtils.startElement(contentHandler, "month", atts); 540 541 XMLUtils.endElement(contentHandler, "month"); 542 543 date = date.plusMonths(1); 544 } 545 546 XMLUtils.endElement(contentHandler, "months"); 547 } 548 } 549 550 /** 551 * Generate days to build a "calendar" view. 552 * 553 * @param dateRange a date belonging to the time span to generate. 554 * @param rangeType the range type, "month" or "week". 555 * @throws SAXException if an error occurs while saxing 556 */ 557 protected void _saxDaysNew(EventsFilterHelper.DateTimeRange dateRange, String rangeType) throws SAXException 558 { 559 if (dateRange != null) 560 { 561 XMLUtils.startElement(contentHandler, "calendar-months"); 562 563 ZonedDateTime date = dateRange.fromDate(); 564 ZonedDateTime end = dateRange.untilDate(); 565 566 while (date.isBefore(end)) 567 { 568 int year = date.getYear(); 569 int month = date.getMonthValue(); 570 571 String monthStr = String.format("%d-%02d", year, month); 572 String dateStr = org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(date); 573 574 AttributesImpl attrs = new AttributesImpl(); 575 attrs.addCDATAAttribute("str", monthStr); 576 attrs.addCDATAAttribute("raw", dateStr); 577 attrs.addCDATAAttribute("year", Integer.toString(year)); 578 attrs.addCDATAAttribute("month", Integer.toString(month)); 579 XMLUtils.startElement(contentHandler, "month", attrs); 580 581 _saxDays(date, "month"); 582 583 XMLUtils.endElement(contentHandler, "month"); 584 585 date = date.plusMonths(1); 586 } 587 588 XMLUtils.endElement(contentHandler, "calendar-months"); 589 } 590 } 591 592 /** 593 * Generate days to build a "calendar" view. 594 * 595 * @param date a date belonging to the time span to generate. 596 * @param type the range type, "month" or "week". 597 * @throws SAXException if an error occurs while saxing 598 */ 599 protected void _saxDays(ZonedDateTime date, String type) throws SAXException 600 { 601 AttributesImpl attrs = new AttributesImpl(); 602 603 int rangeStyle = DateUtils.RANGE_MONTH_MONDAY; 604 ZonedDateTime previousDay = null; 605 ZonedDateTime nextDay = null; 606 607 // Week. 608 if ("week".equals(type)) 609 { 610 rangeStyle = DateUtils.RANGE_WEEK_MONDAY; 611 612 // Get the first day of the week. 613 previousDay = date.with(ChronoField.DAY_OF_WEEK, 1); 614 // First day of next week. 615 nextDay = previousDay.plusWeeks(1); 616 // First day of previous week. 617 previousDay = previousDay.minusWeeks(1); 618 } 619 else 620 { 621 rangeStyle = DateUtils.RANGE_MONTH_MONDAY; 622 623 // Get the first day of the month. 624 previousDay = date.with(ChronoField.DAY_OF_MONTH, 1); 625 // First day of previous month. 626 nextDay = previousDay.plusMonths(1); 627 // First day of next month. 628 previousDay = previousDay.minusMonths(1); 629 } 630 631 addNavAttributes(attrs, date, previousDay, nextDay); 632 633 // Get an iterator on the days to be present on the calendar. 634 635 Iterator<Calendar> days = DateUtils.iterator(org.ametys.core.util.DateUtils.asDate(date), rangeStyle); 636 637 XMLUtils.startElement(contentHandler, "calendar", attrs); 638 639 ZonedDateTime previousWeekDay = date.minusWeeks(1); 640 ZonedDateTime nextWeekDay = date.plusWeeks(1); 641 642 AttributesImpl weekAttrs = new AttributesImpl(); 643 addNavAttributes(weekAttrs, date, previousWeekDay, nextWeekDay); 644 645 XMLUtils.startElement(contentHandler, "week", weekAttrs); 646 647 while (days.hasNext()) 648 { 649 Calendar dayCal = days.next(); 650 651 ZonedDateTime day = dayCal.toInstant().atZone(dayCal.getTimeZone().toZoneId()); 652 String rawDateStr = org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(day); 653 String dateStr = DateTimeFormatter.ISO_LOCAL_DATE.format(day); 654 String yearStr = Integer.toString(dayCal.get(Calendar.YEAR)); 655 String monthStr = Integer.toString(dayCal.get(Calendar.MONTH) + 1); 656 String dayStr = Integer.toString(dayCal.get(Calendar.DAY_OF_MONTH)); 657 658 AttributesImpl dayAttrs = new AttributesImpl(); 659 660 dayAttrs.addCDATAAttribute("raw", rawDateStr); 661 dayAttrs.addCDATAAttribute("date", dateStr); 662 dayAttrs.addCDATAAttribute("year", yearStr); 663 dayAttrs.addCDATAAttribute("month", monthStr); 664 dayAttrs.addCDATAAttribute("day", dayStr); 665 666 XMLUtils.createElement(contentHandler, "day", dayAttrs); 667 668 // Break on week on the last day of the week (but not on the last 669 // week). 670 if (dayCal.get(Calendar.DAY_OF_WEEK) == _eventsFilterHelper.getLastDayOfWeek(dayCal) && days.hasNext()) 671 { 672 previousWeekDay = day.minusDays(6); 673 nextWeekDay = day.plusDays(8); 674 675 weekAttrs.clear(); 676 addNavAttributes(weekAttrs, day, previousWeekDay, nextWeekDay); 677 678 XMLUtils.endElement(contentHandler, "week"); 679 XMLUtils.startElement(contentHandler, "week", weekAttrs); 680 } 681 } 682 683 XMLUtils.endElement(contentHandler, "week"); 684 XMLUtils.endElement(contentHandler, "calendar"); 685 } 686 687 /** 688 * Add nav attributes. 689 * 690 * @param attrs the attributes object to fill in. 691 * @param current the current date. 692 * @param previousDay the previous date. 693 * @param nextDay the next date. 694 */ 695 protected void addNavAttributes(AttributesImpl attrs, ZonedDateTime current, ZonedDateTime previousDay, ZonedDateTime nextDay) 696 { 697 attrs.addCDATAAttribute("current", org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(current)); 698 699 attrs.addCDATAAttribute("previous", org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(previousDay)); 700 attrs.addCDATAAttribute("previousYear", Integer.toString(previousDay.getYear())); 701 attrs.addCDATAAttribute("previousMonth", Integer.toString(previousDay.getMonthValue())); 702 attrs.addCDATAAttribute("previousDay", Integer.toString(previousDay.getDayOfMonth())); 703 704 attrs.addCDATAAttribute("next", org.ametys.core.util.DateUtils.getISODateTimeFormatter().format(nextDay)); 705 attrs.addCDATAAttribute("nextYear", Integer.toString(nextDay.getYear())); 706 attrs.addCDATAAttribute("nextMonth", Integer.toString(nextDay.getMonthValue())); 707 attrs.addCDATAAttribute("nextDay", Integer.toString(nextDay.getDayOfMonth())); 708 } 709 710 /** 711 * Generate the list of selected tags. 712 * @param tags the list of tags. 713 * @throws SAXException if an error occurs while saxing 714 */ 715 protected void _saxTags(Collection<String> tags) throws SAXException 716 { 717 XMLUtils.startElement(contentHandler, "tags"); 718 for (String tag : tags) 719 { 720 AttributesImpl attrs = new AttributesImpl(); 721 attrs.addCDATAAttribute("name", tag); 722 XMLUtils.createElement(contentHandler, "tag", attrs); 723 } 724 XMLUtils.endElement(contentHandler, "tags"); 725 } 726 727 /** 728 * Generate the list of selected tags that act as categories and their descendant tags. 729 * @param categories the list of categories to generate. 730 * @param icsTags list of tags for the ICS feeds (tags, not parents) 731 * @throws SAXException if an error occurs while saxing 732 */ 733 protected void _saxCategories(Collection<Tag> categories, Collection<Tag> icsTags) throws SAXException 734 { 735 Map<Tag, Set<Tag>> icsTagsToAdd = new HashMap<>(); 736 737 // Only the tags that are not already in the ones from the search contexts 738 for (Tag tag : icsTags) 739 { 740 Tag parent = tag.getParent(); 741 if (icsTagsToAdd.containsKey(parent)) 742 { 743 icsTagsToAdd.get(parent).add(tag); 744 } 745 else if (categories == null || !categories.contains(tag.getParent())) 746 { 747 icsTagsToAdd.put(parent, new LinkedHashSet<>()); 748 icsTagsToAdd.get(parent).add(tag); 749 } 750 } 751 752 XMLUtils.startElement(contentHandler, "tag-categories"); 753 754 // Add the tags from the search contexts 755 if (categories != null) 756 { 757 for (Tag category : categories) 758 { 759 XMLUtils.startElement(contentHandler, "category"); 760 761 category.getTitle().toSAX(contentHandler, "title"); 762 763 _saxTags(_eventsFilterHelper.getAllTags(category)); 764 765 XMLUtils.endElement(contentHandler, "category"); 766 } 767 } 768 769 // Add the tags from the ICS feeds 770 for (Entry<Tag, Set<Tag>> entry : icsTagsToAdd.entrySet()) 771 { 772 Tag parent = entry.getKey(); 773 Set<Tag> tags = entry.getValue(); 774 775 XMLUtils.startElement(contentHandler, "category"); 776 777 // As in the ICS, we select directly a tag and not a category, it is possible that there are no parent. 778 // To keep the XML equivalent, the category is still created, possibly with an empty title 779 if (parent != null) 780 { 781 parent.getTitle().toSAX(contentHandler, "title"); 782 } 783 else 784 { 785 XMLUtils.createElement(contentHandler, "title"); 786 } 787 788 _saxTags(tags); 789 790 XMLUtils.endElement(contentHandler, "category"); 791 } 792 793 XMLUtils.endElement(contentHandler, "tag-categories"); 794 } 795 796 /** 797 * Sax a list of tags 798 * @param tags the list of tags to sax 799 * @throws SAXException if an error occurs while saxing 800 */ 801 protected void _saxTags(Set<Tag> tags) throws SAXException 802 { 803 XMLUtils.startElement(contentHandler, "tags"); 804 for (Tag tag : tags) 805 { 806 AttributesImpl tagAttrs = new AttributesImpl(); 807 tagAttrs.addCDATAAttribute("name", tag.getName()); 808 XMLUtils.startElement(contentHandler, "tag", tagAttrs); 809 810 tag.getTitle().toSAX(contentHandler); 811 812 XMLUtils.endElement(contentHandler, "tag"); 813 } 814 XMLUtils.endElement(contentHandler, "tags"); 815 } 816 817}