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