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