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.net.URLDecoder; 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.Calendar; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.Date; 029import java.util.Iterator; 030import java.util.Map; 031import java.util.Set; 032 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.cocoon.ProcessingException; 036import org.apache.cocoon.environment.ObjectModelHelper; 037import org.apache.cocoon.environment.Request; 038import org.apache.cocoon.xml.AttributesImpl; 039import org.apache.cocoon.xml.XMLUtils; 040import org.apache.commons.lang.StringUtils; 041import org.apache.commons.lang.time.DateUtils; 042import org.xml.sax.ContentHandler; 043import org.xml.sax.SAXException; 044 045import org.ametys.cms.repository.Content; 046import org.ametys.cms.tag.Tag; 047import org.ametys.plugins.repository.AmetysObjectIterable; 048import org.ametys.plugins.repository.AmetysObjectResolver; 049import org.ametys.plugins.repository.query.expression.Expression; 050import org.ametys.runtime.parameter.ParameterHelper; 051import org.ametys.web.filter.WebContentFilter; 052import org.ametys.web.filter.WebContentFilter.AccessLimitation; 053import org.ametys.web.repository.page.Page; 054import org.ametys.web.repository.page.ZoneItem; 055 056/** 057 * Query and generate news according to many parameters. 058 */ 059public class EventsGenerator extends AbstractEventGenerator 060{ 061 /** The ametys object resolver. */ 062 protected AmetysObjectResolver _ametysResolver; 063 064 /** The events helper */ 065 protected EventsFilterHelper _eventsFilterHelper; 066 067 068 @Override 069 public void service(ServiceManager serviceManager) throws ServiceException 070 { 071 super.service(serviceManager); 072 _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 073 _eventsFilterHelper = (EventsFilterHelper) serviceManager.lookup(EventsFilterHelper.ROLE); 074 } 075 076 @Override 077 public void generate() throws IOException, SAXException, ProcessingException 078 { 079 Request request = ObjectModelHelper.getRequest(objectModel); 080 @SuppressWarnings("unchecked") 081 Map<String, Object> parentContextAttrs = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 082 if (parentContextAttrs == null) 083 { 084 parentContextAttrs = Collections.EMPTY_MAP; 085 } 086 087 LocalDate today = LocalDate.now(); 088 089 // Get site and language in sitemap parameters. Can not be null. 090 String siteName = parameters.getParameter("site", (String) request.getAttribute("site")); 091 String lang = parameters.getParameter("lang", (String) request.getAttribute("renderingLanguage")); 092 if (StringUtils.isEmpty(lang)) 093 { 094 lang = (String) request.getAttribute("sitemapLanguage"); 095 } 096 // Get the parameters. 097 int monthsBefore = parameters.getParameterAsInteger("months-before", 3); 098 int monthsAfter = parameters.getParameterAsInteger("months-after", 3); 099 // Type can be "calendar", "single-day" or "agenda". 100 String type = parameters.getParameter("type", "calendar"); 101 String view = parameters.getParameter("view", ""); 102 int year = parameters.getParameterAsInteger("year", today.getYear()); 103 int month = parameters.getParameterAsInteger("month", today.getMonthValue()); 104 int day = parameters.getParameterAsInteger("day", today.getDayOfMonth()); 105 // Select a single tag or "all". 106 String singleTag = parameters.getParameter("tag", "all"); 107 if (StringUtils.isNotEmpty(singleTag) && !"all".equals(singleTag)) 108 { 109 singleTag = singleTag.toUpperCase(); 110 } 111 112 113 Page page = (Page) request.getAttribute(Page.class.getName()); 114 // Get the zone item, as a request attribute or from the ID in the 115 // parameters. 116 ZoneItem zoneItem = (ZoneItem) request.getAttribute(ZoneItem.class.getName()); 117 String zoneItemId = parameters.getParameter("zoneItemId", ""); 118 if (zoneItem == null && StringUtils.isNotEmpty(zoneItemId)) 119 { 120 zoneItemId = URLDecoder.decode(zoneItemId, "UTF-8"); 121 zoneItem = (ZoneItem) _ametysResolver.resolveById(zoneItemId); 122 } 123 124 if (page == null && zoneItem != null) 125 { 126 // Wrapped page such as _plugins/calendar/page/YEAR/MONTH/DAY/ZONEITEMID/events_1.3.html => get the page from its zone item 127 // The page is needed to get restriction 128 page = zoneItem.getZone().getPage(); 129 } 130 131 ZonedDateTime dateTime = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZoneId.systemDefault()); 132 String title = _eventsFilterHelper.getTitle(zoneItem); 133 String rangeType = parameters.getParameter("rangeType", _eventsFilterHelper.getDefaultRangeType(zoneItem)); 134 boolean maskOrphan = _eventsFilterHelper.getMaskOrphan(zoneItem); 135 AccessLimitation accessLimitation = _eventsFilterHelper.getAccessLimitation(zoneItem); 136 boolean pdfDownload = _eventsFilterHelper.getPdfDownload(zoneItem); 137 boolean icalDownload = _eventsFilterHelper.getIcalDownload(zoneItem); 138 String link = _eventsFilterHelper.getLink(zoneItem); 139 String linkTitle = _eventsFilterHelper.getLinkTitle(zoneItem); 140 141 boolean doRetrieveView = !StringUtils.equalsIgnoreCase("false", parameters.getParameter("do-retrieve-view", "true")); 142 143 // Get the content types to match 144 Set<String> contentTypes = _eventsFilterHelper.getContentTypes(zoneItem); 145 146 // Get the tags to match, from the zone item or from the parameters. 147 String[] tagsArray = (String[]) parentContextAttrs.get("tags"); 148 Set<String> tags = _eventsFilterHelper.getTags(zoneItem, tagsArray); 149 // Get the categories of the tags to match, from the zone item or from 150 // the parameters. 151 String[] tagCategoriesArray = (String[]) parentContextAttrs.get("tag-categories"); 152 Set<Tag> categories = _eventsFilterHelper.getTagCategories(zoneItem, siteName, tagCategoriesArray); 153 // Get all tags belonging to the categories. 154 Set<String> categoryTags = _eventsFilterHelper.getAllTags(categories); 155 // Restrain the list to a single tag if provided. 156 if (categoryTags.contains(singleTag) && StringUtils.isNotEmpty(singleTag) && !"all".equals(singleTag)) 157 { 158 categoryTags = Collections.singleton(singleTag); 159 } 160 161 String pagePath = page != null ? page.getPathInSitemap() : ""; 162 163 // Get the declared events filter. 164 EventsFilter eventsFilter = _eventsFilterHelper.createEventsFilter(zoneItemId); 165 166 // Get the date range and deduce the expression (single day or 167 // month-before to month-after). 168 EventsFilterHelper.DateRange dateRange = _eventsFilterHelper.getDateRange(type, year, month, day, monthsBefore, monthsAfter, rangeType); 169 Expression expression = _eventsFilterHelper.getExpression(type, dateRange); 170 171 // Configure the filter to return the wanted contents. 172 _eventsFilterHelper.configureFilter(eventsFilter, expression, contentTypes, tags, categoryTags, view, maskOrphan, accessLimitation); 173 174 // Get the corresponding contents. 175 AmetysObjectIterable<Content> eventContents = eventsFilter.getMatchingContents(siteName, lang, page); 176 177 _sax(today, monthsBefore, monthsAfter, year, month, day, singleTag, page, zoneItem, dateTime, title, rangeType, maskOrphan, pdfDownload, icalDownload, link, linkTitle, doRetrieveView, tags, categories, pagePath, eventsFilter, dateRange, eventContents); 178 } 179 180 private void _sax(LocalDate today, int monthsBefore, int monthsAfter, int year, int month, int day, String singleTag, Page page, ZoneItem zoneItem, ZonedDateTime dateTime, 181 String title, String rangeType, boolean maskOrphan, boolean pdfDownload, boolean icalDownload, String link, String linkTitle, boolean doRetrieveView, Set<String> tags, 182 Set<Tag> categories, String pagePath, EventsFilter eventsFilter, EventsFilterHelper.DateRange dateRange, AmetysObjectIterable<Content> eventContents) 183 throws SAXException, IOException 184 { 185 AttributesImpl atts = new AttributesImpl(); 186 187 atts.addCDATAAttribute("page-path", pagePath); 188 atts.addCDATAAttribute("today", DateTimeFormatter.ISO_LOCAL_DATE.format(today)); 189 if (dateRange.getStartDate() != null) 190 { 191 atts.addCDATAAttribute("start", DateTimeFormatter.ISO_LOCAL_DATE.format(dateRange.getStartDate().toInstant().atZone(ZoneId.systemDefault()))); 192 } 193 if (dateRange.getEndDate() != null) 194 { 195 atts.addCDATAAttribute("end", DateTimeFormatter.ISO_LOCAL_DATE.format(dateRange.getEndDate().toInstant().atZone(ZoneId.systemDefault()))); 196 } 197 198 atts.addCDATAAttribute("year", Integer.toString(year)); 199 atts.addCDATAAttribute("month", String.format("%02d", month)); 200 atts.addCDATAAttribute("day", String.format("%02d", day)); 201 atts.addCDATAAttribute("months-before", Integer.toString(monthsBefore)); 202 atts.addCDATAAttribute("months-after", Integer.toString(monthsAfter)); 203 204 atts.addCDATAAttribute("title", title); 205 atts.addCDATAAttribute("mask-orphan", Boolean.toString(maskOrphan)); 206 atts.addCDATAAttribute("pdf-download", Boolean.toString(pdfDownload)); 207 atts.addCDATAAttribute("ical-download", Boolean.toString(icalDownload)); 208 atts.addCDATAAttribute("link", link); 209 atts.addCDATAAttribute("link-title", linkTitle); 210 211 if (zoneItem != null) 212 { 213 atts.addCDATAAttribute("zoneItemId", zoneItem.getId()); 214 } 215 if (StringUtils.isNotEmpty(rangeType)) 216 { 217 atts.addCDATAAttribute("range", rangeType); 218 } 219 if (StringUtils.isNotEmpty(singleTag)) 220 { 221 atts.addCDATAAttribute("single-tag", singleTag); 222 } 223 224 contentHandler.startDocument(); 225 XMLUtils.startElement(contentHandler, "events", atts); 226 227 _saxRssUrl(zoneItem); 228 229 // Generate months (used in calendar mode) and days (used in full-page 230 // agenda mode). 231 _saxMonths(dateRange); 232 _saxDays(dateTime, rangeType); 233 234 _saxDaysNew(dateRange, rangeType); 235 236 // Generate tags and categories. 237 _saxTags(tags); 238 _saxCategories(categories); 239 240 // Generate the matching contents. 241 XMLUtils.startElement(contentHandler, "contents"); 242 243 saxMatchingContents(contentHandler, eventsFilter, eventContents, page, doRetrieveView); 244 245 XMLUtils.endElement(contentHandler, "contents"); 246 247 XMLUtils.endElement(contentHandler, "events"); 248 contentHandler.endDocument(); 249 } 250 251 private void _saxRssUrl(ZoneItem zoneItem) throws SAXException 252 { 253 if (zoneItem != null && zoneItem.getServiceParameters().getBoolean("rss", false)) 254 { 255 // Split protocol and id 256 String[] zoneItemId = zoneItem.getId().split("://"); 257 String url = "_plugins/calendar/" + zoneItemId[1] + "/rss.xml"; 258 259 XMLUtils.createElement(contentHandler, "rssUrl", url); 260 } 261 } 262 263 /** 264 * SAX all contents matching the given filter 265 * 266 * @param handler The content handler to SAX into 267 * @param filter The filter 268 * @param contents iterator on the contents. 269 * @param currentPage The current page. 270 * @param saxContentItSelf true to sax the content, false will only sax some meta 271 * @throws SAXException If an error occurs while SAXing 272 * @throws IOException If an error occurs while retrieving content. 273 */ 274 public void saxMatchingContents(ContentHandler handler, WebContentFilter filter, AmetysObjectIterable<Content> contents, Page currentPage, boolean saxContentItSelf) throws SAXException, IOException 275 { 276 boolean checkUserAccess = filter.getAccessLimitation() == AccessLimitation.USER_ACCESS; 277 278 for (Content content : contents) 279 { 280 if (_filterHelper.isContentValid(content, currentPage, filter)) 281 { 282 saxContent(handler, content, saxContentItSelf, filter, checkUserAccess); 283 } 284 } 285 } 286 287 /** 288 * SAX information on the months spanning the date range. 289 * @param dateRange the date range. 290 * @throws SAXException if a error occurs while saxing 291 */ 292 protected void _saxMonths(EventsFilterHelper.DateRange dateRange) throws SAXException 293 { 294 if (dateRange != null && dateRange.getStartDate() != null && dateRange.getEndDate() != null) 295 { 296 AttributesImpl atts = new AttributesImpl(); 297 298 XMLUtils.startElement(contentHandler, "months"); 299 300 ZonedDateTime date = dateRange.getStartDate().toInstant().atZone(ZoneId.systemDefault()); 301 ZonedDateTime end = dateRange.getEndDate().toInstant().atZone(ZoneId.systemDefault()); 302 303 while (date.isBefore(end)) 304 { 305 int year = date.getYear(); 306 int month = date.getMonthValue(); 307 308 String monthStr = String.format("%d-%02d", year, month); 309 String dateStr = ParameterHelper.getISODateTimeFormatter().format(date); 310 311 atts.clear(); 312 atts.addCDATAAttribute("str", monthStr); 313 atts.addCDATAAttribute("raw", dateStr); 314 XMLUtils.startElement(contentHandler, "month", atts); 315 316 XMLUtils.endElement(contentHandler, "month"); 317 318 date = date.plusMonths(1); 319 } 320 321 XMLUtils.endElement(contentHandler, "months"); 322 } 323 } 324 325 /** 326 * Generate days to build a "calendar" view. 327 * 328 * @param dateRange a date belonging to the time span to generate. 329 * @param rangeType the range type, "month" or "week". 330 * @throws SAXException if an error occurs while saxing 331 */ 332 protected void _saxDaysNew(EventsFilterHelper.DateRange dateRange, String rangeType) throws SAXException 333 { 334 XMLUtils.startElement(contentHandler, "calendar-months"); 335 336 ZonedDateTime date = dateRange.getStartDate().toInstant().atZone(ZoneId.systemDefault()); 337 ZonedDateTime end = dateRange.getEndDate().toInstant().atZone(ZoneId.systemDefault()); 338 339 340 while (date.isBefore(end)) 341 { 342 int year = date.getYear(); 343 int month = date.getMonthValue(); 344 345 String monthStr = String.format("%d-%02d", year, month); 346 String dateStr = ParameterHelper.getISODateTimeFormatter().format(date); 347 348 AttributesImpl attrs = new AttributesImpl(); 349 attrs.addCDATAAttribute("str", monthStr); 350 attrs.addCDATAAttribute("raw", dateStr); 351 attrs.addCDATAAttribute("year", Integer.toString(year)); 352 attrs.addCDATAAttribute("month", Integer.toString(month)); 353 XMLUtils.startElement(contentHandler, "month", attrs); 354 355 _saxDays(date, "month"); 356 357 XMLUtils.endElement(contentHandler, "month"); 358 359 date = date.plusMonths(1); 360 } 361 362 XMLUtils.endElement(contentHandler, "calendar-months"); 363 } 364 365 /** 366 * Generate days to build a "calendar" view. 367 * 368 * @param date a date belonging to the time span to generate. 369 * @param type the range type, "month" or "week". 370 * @throws SAXException if an error occurs while saxing 371 */ 372 protected void _saxDays(ZonedDateTime date, String type) throws SAXException 373 { 374 AttributesImpl attrs = new AttributesImpl(); 375 376 int rangeStyle = DateUtils.RANGE_MONTH_MONDAY; 377 ZonedDateTime previousDay = null; 378 ZonedDateTime nextDay = null; 379 380 // Week. 381 if ("week".equals(type)) 382 { 383 rangeStyle = DateUtils.RANGE_WEEK_MONDAY; 384 385 // Get the first day of the week. 386 previousDay = date.with(ChronoField.DAY_OF_WEEK, 1); 387 // First day of next week. 388 nextDay = previousDay.plusWeeks(1); 389 // First day of previous week. 390 previousDay = previousDay.minusWeeks(1); 391 } 392 else 393 { 394 rangeStyle = DateUtils.RANGE_MONTH_MONDAY; 395 396 // Get the first day of the month. 397 previousDay = date.with(ChronoField.DAY_OF_MONTH, 1); 398 // First day of previous month. 399 nextDay = previousDay.plusMonths(1); 400 // First day of next month. 401 previousDay = previousDay.minusMonths(1); 402 } 403 404 addNavAttributes(attrs, date, previousDay, nextDay); 405 406 // Get an iterator on the days to be present on the calendar. 407 408 Iterator<Calendar> days = DateUtils.iterator(Date.from(date.toInstant()), rangeStyle); 409 410 XMLUtils.startElement(contentHandler, "calendar", attrs); 411 412 ZonedDateTime previousWeekDay = date.minusWeeks(1); 413 ZonedDateTime nextWeekDay = date.plusWeeks(1); 414 415 AttributesImpl weekAttrs = new AttributesImpl(); 416 addNavAttributes(weekAttrs, date, previousWeekDay, nextWeekDay); 417 418 XMLUtils.startElement(contentHandler, "week", weekAttrs); 419 420 while (days.hasNext()) 421 { 422 Calendar dayCal = days.next(); 423 ZonedDateTime day = dayCal.toInstant().atZone(dayCal.getTimeZone().toZoneId()); 424 String rawDateStr = ParameterHelper.getISODateTimeFormatter().format(day); 425 String dateStr = DateTimeFormatter.ISO_LOCAL_DATE.format(day); 426 String yearStr = Integer.toString(dayCal.get(Calendar.YEAR)); 427 String monthStr = Integer.toString(dayCal.get(Calendar.MONTH) + 1); 428 String dayStr = Integer.toString(dayCal.get(Calendar.DAY_OF_MONTH)); 429 430 AttributesImpl dayAttrs = new AttributesImpl(); 431 432 dayAttrs.addCDATAAttribute("raw", rawDateStr); 433 dayAttrs.addCDATAAttribute("date", dateStr); 434 dayAttrs.addCDATAAttribute("year", yearStr); 435 dayAttrs.addCDATAAttribute("month", monthStr); 436 dayAttrs.addCDATAAttribute("day", dayStr); 437 438 XMLUtils.createElement(contentHandler, "day", dayAttrs); 439 440 // Break on week on the last day of the week (but not on the last 441 // week). 442 if (dayCal.get(Calendar.DAY_OF_WEEK) == _eventsFilterHelper.getLastDayOfWeek(dayCal) && days.hasNext()) 443 { 444 previousWeekDay = day.minusDays(6); 445 nextWeekDay = day.plusDays(8); 446 weekAttrs.clear(); 447 448 addNavAttributes(weekAttrs, day, previousWeekDay, nextWeekDay); 449 450 XMLUtils.endElement(contentHandler, "week"); 451 XMLUtils.startElement(contentHandler, "week", weekAttrs); 452 } 453 } 454 455 XMLUtils.endElement(contentHandler, "week"); 456 XMLUtils.endElement(contentHandler, "calendar"); 457 } 458 459 /** 460 * Add nav attributes. 461 * 462 * @param attrs the attributes object to fill in. 463 * @param current the current date. 464 * @param previousDay the previous date. 465 * @param nextDay the next date. 466 */ 467 protected void addNavAttributes(AttributesImpl attrs, ZonedDateTime current, ZonedDateTime previousDay, ZonedDateTime nextDay) 468 { 469 attrs.addCDATAAttribute("current", ParameterHelper.getISODateTimeFormatter().format(current)); 470 471 attrs.addCDATAAttribute("previous", ParameterHelper.getISODateTimeFormatter().format(previousDay)); 472 attrs.addCDATAAttribute("previousYear", Integer.toString(previousDay.getYear())); 473 attrs.addCDATAAttribute("previousMonth", Integer.toString(previousDay.getMonthValue())); 474 attrs.addCDATAAttribute("previousDay", Integer.toString(previousDay.getDayOfMonth())); 475 476 attrs.addCDATAAttribute("next", ParameterHelper.getISODateTimeFormatter().format(nextDay)); 477 attrs.addCDATAAttribute("nextYear", Integer.toString(nextDay.getYear())); 478 attrs.addCDATAAttribute("nextMonth", Integer.toString(nextDay.getMonthValue())); 479 attrs.addCDATAAttribute("nextDay", Integer.toString(nextDay.getDayOfMonth())); 480 } 481 482 /** 483 * Generate the list of selected tags. 484 * @param tags the list of tags. 485 * @throws SAXException if an error occurs while saxing 486 */ 487 protected void _saxTags(Collection<String> tags) throws SAXException 488 { 489 XMLUtils.startElement(contentHandler, "tags"); 490 for (String tag : tags) 491 { 492 AttributesImpl attrs = new AttributesImpl(); 493 attrs.addCDATAAttribute("name", tag); 494 XMLUtils.createElement(contentHandler, "tag", attrs); 495 } 496 XMLUtils.endElement(contentHandler, "tags"); 497 } 498 499 /** 500 * Generate the list of selected tags that act as categories and their descendant tags. 501 * @param categories the list of categories to generate. 502 * @throws SAXException if an error occurs while saxing 503 */ 504 protected void _saxCategories(Collection<Tag> categories) throws SAXException 505 { 506 XMLUtils.startElement(contentHandler, "tag-categories"); 507 for (Tag category : categories) 508 { 509 AttributesImpl categoryAttrs = new AttributesImpl(); 510 511 XMLUtils.startElement(contentHandler, "category", categoryAttrs); 512 513 category.getTitle().toSAX(contentHandler, "title"); 514 515 XMLUtils.startElement(contentHandler, "tags"); 516 for (Tag tag : _eventsFilterHelper.getAllTags(category)) 517 { 518 AttributesImpl tagAttrs = new AttributesImpl(); 519 tagAttrs.addCDATAAttribute("name", tag.getName()); 520 XMLUtils.startElement(contentHandler, "tag", tagAttrs); 521 522 tag.getTitle().toSAX(contentHandler); 523 524 XMLUtils.endElement(contentHandler, "tag"); 525 } 526 XMLUtils.endElement(contentHandler, "tags"); 527 528 XMLUtils.endElement(contentHandler, "category"); 529 } 530 XMLUtils.endElement(contentHandler, "tag-categories"); 531 } 532 533 534 535}