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