001/* 002 * Copyright 2013 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.time.ZoneId; 019import java.time.ZonedDateTime; 020import java.time.temporal.ChronoField; 021import java.time.temporal.ChronoUnit; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Calendar; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Date; 028import java.util.GregorianCalendar; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.LinkedHashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Optional; 035import java.util.Set; 036import java.util.stream.Collectors; 037 038import org.apache.avalon.framework.component.Component; 039import org.apache.avalon.framework.logger.AbstractLogEnabled; 040import org.apache.avalon.framework.service.ServiceException; 041import org.apache.avalon.framework.service.ServiceManager; 042import org.apache.avalon.framework.service.Serviceable; 043import org.apache.commons.lang3.ArrayUtils; 044import org.apache.commons.lang3.StringUtils; 045 046import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 047import org.ametys.cms.filter.ContentFilter; 048import org.ametys.cms.filter.ContentFilterExtensionPoint; 049import org.ametys.cms.tag.Tag; 050import org.ametys.cms.tag.TagProviderExtensionPoint; 051import org.ametys.core.util.DateUtils; 052import org.ametys.core.util.JSONUtils; 053import org.ametys.plugins.calendar.events.EventsFilter.EventFilterSearchContext; 054import org.ametys.plugins.repository.AmetysObjectResolver; 055import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 056import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeater; 057import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeaterEntry; 058import org.ametys.plugins.repository.query.expression.AndExpression; 059import org.ametys.plugins.repository.query.expression.DateExpression; 060import org.ametys.plugins.repository.query.expression.Expression; 061import org.ametys.plugins.repository.query.expression.Expression.Operator; 062import org.ametys.plugins.repository.query.expression.MetadataExpression; 063import org.ametys.plugins.repository.query.expression.NotExpression; 064import org.ametys.plugins.repository.query.expression.OrExpression; 065import org.ametys.web.filter.WebContentFilter.AccessLimitation; 066import org.ametys.web.filter.WebContentFilter.Context; 067import org.ametys.web.filter.WebContentFilter.FilterSearchContext; 068import org.ametys.web.frontoffice.search.instance.model.SiteContextType; 069import org.ametys.web.repository.page.ZoneItem; 070import org.ametys.web.repository.site.SiteManager; 071 072/** 073 * Helper for events filter 074 * 075 */ 076public class EventsFilterHelper extends AbstractLogEnabled implements Serviceable, Component 077{ 078 /** The avalon role */ 079 public static final String ROLE = EventsFilterHelper.class.getName(); 080 081 /** The start date metadata. */ 082 public static final String START_DATE_META = "start-date"; 083 084 /** The end date metadata. */ 085 public static final String END_DATE_META = "end-date"; 086 087 /** The events filter ID. */ 088 public static final String EVENTS_FILTER_ID = "events"; 089 090 091 /** The tag provider extension point. */ 092 protected TagProviderExtensionPoint _tagProviderEP; 093 /** Extension point for content filters */ 094 protected ContentFilterExtensionPoint _filterExtPt; 095 /** The Ametys object resolver */ 096 protected AmetysObjectResolver _ametysResolver; 097 /** Extension point for content type */ 098 protected ContentTypeExtensionPoint _contentTypeEP; 099 /** The site manager */ 100 protected SiteManager _siteManager; 101 /** The JSON utils */ 102 protected JSONUtils _jsonUtils; 103 104 @Override 105 public void service(ServiceManager manager) throws ServiceException 106 { 107 _tagProviderEP = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 108 _filterExtPt = (ContentFilterExtensionPoint) manager.lookup(ContentFilterExtensionPoint.ROLE); 109 _ametysResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 110 _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 111 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 112 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 113 } 114 115 /** 116 * Create a events filter from the static "events" filter 117 * @param id the id of filter to create 118 * @return the created events filter 119 */ 120 public EventsFilter createEventsFilter(String id) 121 { 122 EventsFilter eventsFilter = (EventsFilter) _filterExtPt.getExtension(EventsFilterHelper.EVENTS_FILTER_ID); 123 return new EventsFilter(id, eventsFilter, _ametysResolver, _contentTypeEP, _siteManager, _tagProviderEP); 124 } 125 126 /** 127 * Get the service title. 128 * 129 * @param zoneItem the zone item. 130 * @return the service title. 131 */ 132 public String getTitle(ZoneItem zoneItem) 133 { 134 return zoneItem.getServiceParameters().getValue("title", false, ""); 135 } 136 137 /** 138 * Get the link. 139 * 140 * @param zoneItem the zone item. 141 * @return the link. 142 */ 143 public String getLink(ZoneItem zoneItem) 144 { 145 return zoneItem.getServiceParameters().getValue("link", false, ""); 146 } 147 148 /** 149 * Get the link title. 150 * 151 * @param zoneItem the zone item. 152 * @return the link title. 153 */ 154 public String getLinkTitle(ZoneItem zoneItem) 155 { 156 return zoneItem.getServiceParameters().getValue("link-title", false, ""); 157 } 158 159 /** 160 * Get the default range type parameter value. 161 * 162 * @param zoneItem the zone item. 163 * @return the default range type. 164 */ 165 public String getDefaultRangeType(ZoneItem zoneItem) 166 { 167 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 168 // First check that there is a definition because calendar service doesn't define the default-range parameter 169 return serviceParameters.hasDefinition("default-range") ? serviceParameters.getValue("default-range", false, StringUtils.EMPTY) : StringUtils.EMPTY; 170 } 171 172 /** 173 * Mask orphan? 174 * 175 * @param zoneItem the zone item. 176 * @return mask orphan. 177 */ 178 public boolean getMaskOrphan(ZoneItem zoneItem) 179 { 180 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 181 // First check that there is a definition because calendar service doesn't define the mask-orphan parameter 182 return serviceParameters.hasDefinition("mask-orphan") ? serviceParameters.getValue("mask-orphan", false, false) : false; 183 } 184 185 /** 186 * Get the access limitation policy. 187 * @param zoneItem the zone item. 188 * @return the access limitation policy. 189 */ 190 public AccessLimitation getAccessLimitation(ZoneItem zoneItem) 191 { 192 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 193 // First check that there is a definition because calendar service doesn't define the handle-user-access parameter 194 if (serviceParameters.hasDefinition("handle-user-access")) 195 { 196 boolean handleUserAccess = serviceParameters.getValue("handle-user-access", false, false); 197 return handleUserAccess ? AccessLimitation.USER_ACCESS : AccessLimitation.PAGE_ACCESS; 198 } 199 else 200 { 201 return AccessLimitation.PAGE_ACCESS; 202 } 203 } 204 205 /** 206 * Is there a PDF download link on the service? 207 * 208 * @param zoneItem the zone item. 209 * @return true if a PDF download link will be present, false otherwise. 210 */ 211 protected boolean getPdfDownload(ZoneItem zoneItem) 212 { 213 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 214 // First check that there is a definition because calendar service doesn't define the pdf-download parameter 215 return serviceParameters.hasDefinition("pdf-download") ? serviceParameters.getValue("pdf-download", false, false) : false; 216 } 217 218 /** 219 * Is there an iCalendar export link on the service? 220 * 221 * @param zoneItem the zone item. 222 * @return true if an iCalendar export link will be present, false 223 * otherwise. 224 */ 225 protected boolean getIcalDownload(ZoneItem zoneItem) 226 { 227 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 228 // First check that there is a definition because calendar service doesn't define the ical-download parameter 229 return serviceParameters.hasDefinition("ical-download") ? serviceParameters.getValue("ical-download", false, false) : false; 230 } 231 232 /** 233 * Get the content types through the service parameters. 234 * 235 * @param zoneItem the zone item. 236 * @return the content types. 237 */ 238 public Set<String> getContentTypes(ZoneItem zoneItem) 239 { 240 Set<String> contentTypes = new LinkedHashSet<>(); 241 242 String[] cTypes = zoneItem.getServiceParameters().getValue("content-types", false, ArrayUtils.EMPTY_STRING_ARRAY); 243 if (cTypes.length > 0 && cTypes[0].length() > 0) 244 { 245 for (String cType : cTypes) 246 { 247 if (!"".equals(cType)) 248 { 249 contentTypes.add(cType); 250 } 251 } 252 } 253 254 return contentTypes; 255 } 256 257 /** 258 * Get the all tags from the search contexts 259 * @param zoneItem the zone item. 260 * @param searchContexts the search contexts 261 * @return the tags. 262 */ 263 public Set<String> getTags(ZoneItem zoneItem, List<Map<String, Object>> searchContexts) 264 { 265 HashSet<String> hashSet = new HashSet<>(); 266 if (searchContexts == null) 267 { 268 // get search contexts from zoneitem 269 ModifiableModelAwareRepeater search = zoneItem.getServiceParameters().getValue("search"); 270 for (ModifiableModelAwareRepeaterEntry repeaterEntry : search.getEntries()) 271 { 272 hashSet.addAll(Arrays.asList(repeaterEntry.getValue("tags", false, ArrayUtils.EMPTY_STRING_ARRAY))); 273 } 274 } 275 else 276 { 277 for (Map<String, Object> entry : searchContexts) 278 { 279 hashSet.addAll(Arrays.asList((String[]) entry.get("tags"))); 280 } 281 } 282 283 return hashSet; 284 } 285 286 /** 287 * Get the all selected tags to be used as categories from the search contexts 288 * @param zoneItem the zone item. 289 * @param searchContexts the search contexts 290 * @param currentSiteName the current site name. 291 * @return the tags categories. 292 */ 293 public Set<Tag> getTagCategories(ZoneItem zoneItem, List<Map<String, Object>> searchContexts, String currentSiteName) 294 { 295 Set<Tag> categories = new LinkedHashSet<>(); 296 List<Map<String, Object>> search = searchContexts; 297 298 if (search == null) 299 { 300 // if null, get search contexts from zoneitem (only need sites and tag categories values) 301 search = new ArrayList<>(); 302 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 303 304 HashSet<String> hashSet = new HashSet<>(); 305 ModifiableModelAwareRepeater searchRepeater = serviceParameters.getValue("search"); 306 307 for (ModifiableModelAwareRepeaterEntry repeaterEntry : searchRepeater.getEntries()) 308 { 309 Map<String, Object> mapEntry = new HashMap<>(); 310 // First check that there is a definition because calendar service doesn't define the tag-categories parameter 311 if (repeaterEntry.hasDefinition("tag-categories")) 312 { 313 mapEntry.put("tag-categories", repeaterEntry.getValue("tag-categories", false, ArrayUtils.EMPTY_STRING_ARRAY)); 314 hashSet.addAll(Arrays.asList(repeaterEntry.getValue("tag-categories", false, ArrayUtils.EMPTY_STRING_ARRAY))); 315 } 316 if (repeaterEntry.hasNonEmptyValue("sites")) 317 { 318 mapEntry.put("sites", repeaterEntry.getValue("sites", false, ArrayUtils.EMPTY_STRING_ARRAY)); 319 } 320 search.add(mapEntry); 321 } 322 323 } 324 325 326 search.forEach(searchContext -> 327 { 328 if (searchContext.containsKey("tag-categories")) 329 { 330 String[] categoriesArray = (String[]) searchContext.get("tag-categories"); 331 for (int i = 0; i < categoriesArray.length; i++) 332 { 333 Map<String, Object> sitesData = _jsonUtils.convertJsonToMap((String) searchContext.get("sites")); 334 335 @SuppressWarnings("unchecked") 336 List<String> sites = (List<String>) sitesData.get("sites"); 337 String context = (String) sitesData.get("context"); 338 SiteContextType siteContextType = SiteContextType.fromClientSideName(context); 339 340 Map<String, Object> tagParameters = new HashMap<>(); 341 if (SiteContextType.CURRENT.equals(siteContextType)) 342 { 343 tagParameters.put("siteName", currentSiteName); 344 } 345 else if (SiteContextType.AMONG.equals(siteContextType) && sites.size() == 1) 346 { 347 tagParameters.put("siteName", sites.get(0)); 348 } 349 else 350 { 351 tagParameters.put("siteName", null); 352 } 353 354 Tag category = _tagProviderEP.getTag(categoriesArray[i], tagParameters); 355 if (category != null) 356 { 357 categories.add(category); 358 } 359 360 } 361 } 362 }); 363 364 365 return categories; 366 } 367 368 /** 369 * Get all the descendant tags belonging to a collection of input tags. 370 * 371 * @param tags the collection of input tag. 372 * @return an exhaustive set of the tags. 373 */ 374 public Set<String> getAllTags(Collection<? extends Tag> tags) 375 { 376 Set<String> allTags = new LinkedHashSet<>(); 377 378 for (Tag tag : tags) 379 { 380 Map<String, ? extends Tag> childTagsMap = tag.getTags(); 381 382 if (childTagsMap != null) 383 { 384 Collection<? extends Tag> childTags = childTagsMap.values(); 385 for (Tag child : childTags) 386 { 387 allTags.add(child.getName()); 388 } 389 390 // recursive add. 391 allTags.addAll(getAllTags(childTags)); 392 } 393 } 394 395 return allTags; 396 } 397 398 /** 399 * Get all the descendant tags belonging to a single tag. 400 * 401 * @param tag the tag. 402 * @return an exhaustive set of the tags. 403 */ 404 protected Set<Tag> getAllTags(Tag tag) 405 { 406 Set<Tag> allTags = new LinkedHashSet<>(); 407 408 Map<String, ? extends Tag> childTagsMap = tag.getTags(); 409 410 if (childTagsMap != null) 411 { 412 Collection<? extends Tag> childTags = childTagsMap.values(); 413 allTags.addAll(childTags); 414 415 for (Tag child : childTags) 416 { 417 allTags.addAll(getAllTags(child)); 418 } 419 } 420 421 return allTags; 422 } 423 424 /** 425 * Get the date range from the calendar type and parameters. 426 * 427 * @param type the calendar mode: "calendar", "single-day" or "agenda". 428 * @param year the year. 429 * @param month the month. 430 * @param day the day. 431 * @param monthsBefore get x months before. 432 * @param monthsAfter get x months after. 433 * @param rangeType the range type, "month" or "week". 434 * @return the date range. 435 */ 436 public DateRange getDateRange(String type, int year, int month, int day, int monthsBefore, int monthsAfter, String rangeType) 437 { 438 DateRange dateRange = null; 439 440 // Single day mode. 441 if ("single-day".equals(type)) 442 { 443 Calendar date = _getDate(year, month, day); 444 dateRange = new DateRange(date.getTime(), date.getTime()); 445 } 446 // JS calendar mode. 447 else if ("calendar".equals(type) || "agenda".equals(type)) 448 { 449 dateRange = _getDateRange(monthsBefore, monthsAfter); 450 } 451 else if ("period".equals(type)) 452 { 453 Calendar date = _getDate(year, month, day); 454 455 dateRange = _getDateRange(date, monthsBefore, monthsAfter); 456 } 457 458 if (getLogger().isInfoEnabled() && dateRange != null) 459 { 460 String start = DateUtils.getISODateTimeFormatter().format(dateRange.getStartDate().toInstant().atZone(ZoneId.systemDefault())); 461 String end = DateUtils.getISODateTimeFormatter().format(dateRange.getEndDate().toInstant().atZone(ZoneId.systemDefault())); 462 getLogger().info("Getting contents between " + start + " and " + end); 463 } 464 465 return dateRange; 466 } 467 468 /** 469 * Create a DateRange between 2 dates 470 * @param startDate stard date 471 * @param endDate end date 472 * @return the DateRange between those dates 473 */ 474 public DateRange getDateRange(Date startDate, Date endDate) 475 { 476 return new DateRange(startDate, endDate); 477 } 478 479 /** 480 * Get a date range from a period (x months before now and x months after). 481 * 482 * @param monthsBefore the wanted number of months before the current month. 483 * @param monthsAfter the wanted number of months after the current month. 484 * @return the date range. 485 */ 486 protected DateRange _getDateRange(int monthsBefore, int monthsAfter) 487 { 488 return _getDateRange(new GregorianCalendar(), monthsBefore, monthsAfter); 489 } 490 491 /** 492 * Get a date range from a period, around a given date (x months before and 493 * x months after the date). 494 * 495 * @param date the date. 496 * @param monthsBefore the wanted number of months before the current month. 497 * @param monthsAfter the wanted number of months after the current month. 498 * @return the date range. 499 */ 500 protected DateRange _getDateRange(Calendar date, int monthsBefore, int monthsAfter) 501 { 502 DateRange dateRange = new DateRange(); 503 504 Calendar start = (Calendar) date.clone(); 505 Calendar end = null; 506 507 start.set(Calendar.HOUR_OF_DAY, 0); 508 start.set(Calendar.MINUTE, 0); 509 start.set(Calendar.SECOND, 0); 510 start.set(Calendar.MILLISECOND, 0); 511 start.set(Calendar.DAY_OF_MONTH, 1); 512 513 end = (Calendar) start.clone(); 514 515 // Subtract the wanted number of months before. 516 start.add(Calendar.MONTH, 0 - monthsBefore); 517 518 // Add the wanted number of months after. 519 end.add(Calendar.MONTH, monthsAfter + 1); 520 521 dateRange.setStartDate(start.getTime()); 522 dateRange.setEndDate(end.getTime()); 523 524 return dateRange; 525 } 526 527 /** 528 * Get a date range from a date and a range type. 529 * 530 * @param date the date around which the range is to be. 531 * @param rangeType the range type, "month" or "week". 532 * @return the date range. 533 */ 534 protected DateRange _getDateRange(Calendar date, String rangeType) 535 { 536 // Remove the "time" part. 537 ZonedDateTime dateTime = date.toInstant().atZone(date.getTimeZone().toZoneId()).truncatedTo(ChronoUnit.DAYS); 538 539 ZonedDateTime start = null; 540 ZonedDateTime end = null; 541 542 if ("month".equals(rangeType)) 543 { 544 // First day of month. 545 start = dateTime.withDayOfMonth(1); 546 // First day of next month. 547 end = start.plusMonths(1); 548 } 549 else 550 { 551 // First day of week. 552 start = dateTime.with(ChronoField.DAY_OF_WEEK, 1); 553 // First day of next week. 554 end = start.plusWeeks(1); 555 } 556 557 return new DateRange(Date.from(start.toInstant()), Date.from(end.toInstant())); 558 } 559 560 /** 561 * Get a date object from a year-month-day set. 562 * 563 * @param year the year. 564 * @param month the month (1 to 12). 565 * @param day the day (1 to 31). 566 * @return the date object. 567 */ 568 protected Calendar _getDate(int year, int month, int day) 569 { 570 Calendar cal = new GregorianCalendar(); 571 572 cal.set(Calendar.HOUR_OF_DAY, 0); 573 cal.set(Calendar.MINUTE, 0); 574 cal.set(Calendar.SECOND, 0); 575 cal.set(Calendar.MILLISECOND, 0); 576 if (year > 0 && month > 0 && day > 0) 577 { 578 cal.set(Calendar.DAY_OF_MONTH, day); 579 cal.set(Calendar.MONTH, month - 1); 580 cal.set(Calendar.YEAR, year); 581 } 582 583 return cal; 584 } 585 586 /** 587 * Get the metadata expression from the calendar type and date range. 588 * 589 * @param type the calendar type, may be "calendar", "single-day", 590 * "agenda" or "period" (agenda = calendar = period). 591 * @param dateRange the date range. Only the start date is used for 592 * "single-day" type. 593 * @return the metadata Expression on contents. 594 */ 595 public Expression getExpression(String type, DateRange dateRange) 596 { 597 Expression expression = null; 598 599 if (dateRange != null) 600 { 601 if ("single-day".equals(type)) 602 { 603 expression = _getMetadataExpression(dateRange.getStartDate()); 604 } 605 else if ("calendar".equals(type) || "agenda".equals(type) || "period".equals(type)) 606 { 607 expression = _getMetadataExpression(dateRange); 608 } 609 } 610 611 return expression; 612 } 613 614 /** 615 * Get a metadata expression from a date range. 616 * 617 * @param dateRange the date range. 618 * @return the date range metadata expression. 619 */ 620 protected Expression _getMetadataExpression(DateRange dateRange) 621 { 622 Expression expression = null; 623 624 if (dateRange != null) 625 { 626 Expression startExpr = dateRange.getEndDate() != null ? new DateExpression(START_DATE_META, Operator.LT, dateRange.getEndDate()) : null; 627 628 Expression fullEndExpr = null; 629 if (dateRange.getStartDate() != null) 630 { 631 Expression endExpr = new DateExpression(END_DATE_META, Operator.GE, dateRange.getStartDate()); 632 Expression startAfterQueryBeginExpr = new DateExpression(START_DATE_META, Operator.GE, dateRange.getStartDate()); 633 Expression noEndDate = new NotExpression(new MetadataExpression(END_DATE_META)); 634 fullEndExpr = new AndExpression(noEndDate, startAfterQueryBeginExpr); 635 fullEndExpr = new OrExpression(endExpr, fullEndExpr); 636 } 637 638 if (dateRange.getStartDate() == null && dateRange.getEndDate() != null && startExpr != null) 639 { 640 expression = startExpr; 641 } 642 else if (dateRange.getStartDate() != null && dateRange.getEndDate() == null && fullEndExpr != null) 643 { 644 expression = fullEndExpr; 645 } 646 else 647 { 648 expression = new AndExpression(startExpr, fullEndExpr); 649 } 650 } 651 652 return expression; 653 } 654 655 /** 656 * Get a metadata expression from a single date. 657 * 658 * @param date the date. 659 * @return the date metadata expression. 660 */ 661 protected Expression _getMetadataExpression(Date date) 662 { 663 Expression expression = null; 664 665 if (date != null) 666 { 667 Date nextDay = Date.from(date.toInstant().atZone(ZoneId.systemDefault()).plusDays(1).toInstant()); 668 669 Expression noEndDate = new NotExpression(new MetadataExpression(END_DATE_META)); 670 Expression startDate = new DateExpression(START_DATE_META, Operator.GE, date); 671 Expression startDateNextDay = new DateExpression(START_DATE_META, Operator.LT, nextDay); 672 Expression onlyStartDate = new AndExpression(noEndDate, startDate, startDateNextDay); 673 674 Expression noStartDate = new NotExpression(new MetadataExpression(START_DATE_META)); 675 Expression endDate = new DateExpression(END_DATE_META, Operator.GE, date); 676 Expression endDateNextDay = new DateExpression(END_DATE_META, Operator.LT, nextDay); 677 Expression onlyEndDate = new AndExpression(noStartDate, endDate, endDateNextDay); 678 679 Expression afterStart = new DateExpression(START_DATE_META, Operator.LT, nextDay); 680 Expression beforeEnd = new DateExpression(END_DATE_META, Operator.GE, date); 681 Expression bothDates = new AndExpression(afterStart, beforeEnd); 682 683 expression = new OrExpression(onlyStartDate, onlyEndDate, bothDates); 684 } 685 686 return expression; 687 } 688 689 /** 690 * Get last day of the week at a given date. 691 * 692 * @param cal the date as a Calendar. 693 * @return the last day of the week. 694 */ 695 protected int getLastDayOfWeek(Calendar cal) 696 { 697 return Calendar.SUNDAY; 698 } 699 700 /** 701 * Filter a list of categories depending on the service parameters (categoriesArray) and request tags (requestedCategoriesArray) 702 * @param searchContexts the search contexts 703 * @param requestedCategories list of requested categories (can be "all" to get all the categories from the service, or a list of categories) 704 * @param zoneItem zone Item 705 * @param siteName site name 706 * @return a set of tags 707 */ 708 public Set<String> getFilteredCategories(List<Map<String, Object>> searchContexts, String[] requestedCategories, ZoneItem zoneItem, String siteName) 709 { 710 // Select a single tag or "all". 711 List<String> requestedTags = Arrays.asList(requestedCategories); 712 // Get the categories of the tags to match, from the zone item or from 713 // the parameters. 714 Set<Tag> categoriesSet = getTagCategories(zoneItem, searchContexts, siteName); 715 // Get all tags belonging to the categories. 716 Set<String> allCategoriesNames = getAllTags(categoriesSet); 717 // Restrain the list to the provided tags. 718 Set<String> filteredCategory = new HashSet<>(); 719 if (requestedTags.contains("all")) 720 { 721 filteredCategory = allCategoriesNames; 722 } 723 else 724 { 725 filteredCategory = requestedTags.stream() 726 .map(String::toUpperCase) 727 .filter(allCategoriesNames::contains) 728 .collect(Collectors.toSet()); 729 } 730 731 return filteredCategory; 732 } 733 734 /** 735 * Generate the filter 736 * @param dateRange range of dates to search 737 * @param zoneItem used to get the tags 738 * @param view metadataset used 739 * @param type calendar, agenda, period (same thing) or "single-day" to get a single day 740 * @param filteredCategories list of categories filtered by those requested by the user 741 * @param searchContexts the search contexts 742 * @return an EventFilter to match the parameters 743 */ 744 public EventsFilter generateEventFilter(DateRange dateRange, ZoneItem zoneItem, String view, String type, Set<String> filteredCategories, List<Map<String, Object>> searchContexts) 745 { 746 String zoneItemId = zoneItem.getId(); 747 748 Set<String> contentTypes = getContentTypes(zoneItem); 749 boolean maskOrphan = getMaskOrphan(zoneItem); 750 AccessLimitation accessLimitation = getAccessLimitation(zoneItem); 751 752 EventsFilter eventsFilter = createEventsFilter(zoneItemId); 753 754 Expression expression = getExpression(type, dateRange); // calendar, agenda, period do the same thing 755 configureFilter(eventsFilter, expression, contentTypes, searchContexts, filteredCategories, view, maskOrphan, accessLimitation); 756 757 return eventsFilter; 758 } 759 760 /** 761 * Configure the filter to return the wanted contents. 762 * 763 * @param eventsFilter the events filter. 764 * @param expression the metadata Expression, can be null. 765 * @param contentTypes a list of content types that will restrict the scope of the request. 766 * @param searchContexts the input searchContext 767 * @param orTags a list of tags among which only one is required for the 768 * content to match. 769 * @param view the view. 770 * @param maskOrphan true to prevent getting orphan contents. 771 * @param accessLimitation The access limitation policy. 772 */ 773 public void configureFilter(EventsFilter eventsFilter, Expression expression, Collection<String> contentTypes, List<Map<String, Object>> searchContexts, Set<String> orTags, String view, boolean maskOrphan, AccessLimitation accessLimitation) 774 { 775 // Set the content types expression, even if null 776 if (eventsFilter.getContentTypes() != null) 777 { 778 eventsFilter.getContentTypes().clear(); 779 } 780 781 for (String contentType : contentTypes) 782 { 783 eventsFilter.addContentType(contentType); 784 } 785 786 // Set the metadata expression, even if null. 787 eventsFilter.setMetadataExpression(expression); 788 789 if (StringUtils.isNotEmpty(view)) 790 { 791 eventsFilter.setView(view); 792 } 793 794 eventsFilter.clearSearchContexts(); 795 _setSearchContext(eventsFilter, searchContexts, orTags); 796 797 eventsFilter.setMaskOrphanContents(maskOrphan); 798 799 eventsFilter.setAccessLimitation(accessLimitation); 800 } 801 802 /** 803 * Set the search contexts in a filter from a service instance attributes. 804 * @param zoneItem the service parameters data holder. 805 * @param inputSearchContexts the input search contexts 806 * @return a list of search context 807 */ 808 public List<Map<String, Object>> getSearchContext(ZoneItem zoneItem, List<Map<String, Object>> inputSearchContexts) 809 { 810 List<Map<String, Object>> searchContexts = inputSearchContexts; 811 812 if (searchContexts == null) 813 { 814 // if null, get search contexts from zoneitem 815 searchContexts = new ArrayList<>(); 816 817 ModifiableModelAwareRepeater searchRepeater = zoneItem.getServiceParameters().getValue("search"); 818 819 for (ModifiableModelAwareRepeaterEntry repeaterEntry : searchRepeater.getEntries()) 820 { 821 Map<String, Object> mapEntry = new HashMap<>(); 822 // First check that there is a definition because calendar service doesn't define the tag-categories parameter 823 if (repeaterEntry.hasDefinition("tag-categories")) 824 { 825 mapEntry.put("tag-categories", repeaterEntry.getValue("tag-categories", false, ArrayUtils.EMPTY_STRING_ARRAY)); 826 } 827 828 mapEntry.put("sites", repeaterEntry.getValue("sites", false, ArrayUtils.EMPTY_STRING_ARRAY)); 829 mapEntry.put("tags", repeaterEntry.getValue("tags", false, ArrayUtils.EMPTY_STRING_ARRAY)); 830 mapEntry.put("search-context", repeaterEntry.getValue("search-context", false, ArrayUtils.EMPTY_STRING_ARRAY)); 831 mapEntry.put("context-lang", repeaterEntry.getValue("context-lang", false, StringUtils.EMPTY)); 832 mapEntry.put("strict-search-on-tags", repeaterEntry.getValue("strict-search-on-tags", false, false)); 833 834 searchContexts.add(mapEntry); 835 } 836 837 } 838 return searchContexts; 839 } 840 841 /** 842 * Get the search parameters from the search values. 843 * @param filter the filter to configure. 844 * @param searchContextValues the input searchContext 845 * @param orTags a list of tags among which only one is required for the 846 * content to match. 847 */ 848 protected void _setSearchContext(EventsFilter filter, List<Map<String, Object>> searchContextValues, Set<String> orTags) 849 { 850 List<Map<String, Object>> nonNullSearchContextValues = Optional.ofNullable(searchContextValues) 851 .orElse(Collections.EMPTY_LIST); 852 853 for (Map<String, Object> entry : nonNullSearchContextValues) 854 { 855 FilterSearchContext filterContext = filter.addSearchContext(); 856 857 Map<String, Object> sitesData = _jsonUtils.convertJsonToMap((String) entry.get("sites")); 858 String searchContext = (String) sitesData.get("context"); 859 860 Map<String, Object> searchContextMap = _jsonUtils.convertJsonToMap((String) entry.get("search-context")); 861 String subSearchContext = (String) searchContextMap.get("context"); 862 if (StringUtils.isEmpty(searchContext) || (subSearchContext != null && !Context.CURRENT_SITE.name().equals(subSearchContext))) 863 { 864 // Delegate to the search-context enumeration context 865 searchContext = subSearchContext; 866 } 867 868 if ("CHILD_PAGES".equals(searchContext) || "CHILD_PAGES_OF".equals(searchContext)) 869 { 870 filterContext.setContext(Context.CHILD_PAGES); 871 } 872 else if ("DIRECT_CHILD_PAGES".equals(searchContext) || "DIRECT_CHILD_PAGES_OF".equals(searchContext)) 873 { 874 // Direct child pages are child pages context with depth 1. 875 filterContext.setContext(Context.CHILD_PAGES); 876 filterContext.setDepth(1); 877 } 878 else 879 { 880 // Else, parse the context. 881 filterContext.setContext(Context.valueOf(searchContext)); 882 } 883 884 if (searchContextMap.get("page") != null) 885 { 886 filterContext.setPageId((String) searchContextMap.get("page")); 887 } 888 889 @SuppressWarnings("unchecked") 890 List<String> sites = Optional.of("sites") 891 .map(sitesData::get) 892 .map(s -> (List<String>) s) 893 .orElse(Collections.EMPTY_LIST); 894 for (String site : sites) 895 { 896 filterContext.addSite(site); 897 } 898 899 String contextLang = (String) entry.get("context-lang"); 900 if (StringUtils.isNotEmpty(contextLang)) 901 { 902 filterContext.setContextLanguage(ContentFilter.ContextLanguage.valueOf(contextLang)); 903 } 904 905 String[] tags = Optional.of("tags") 906 .map(entry::get) 907 .map(t -> (String[]) t) 908 .orElse(new String[0]); 909 for (String tag : tags) 910 { 911 filterContext.addTag(tag); 912 } 913 914 915 ((EventFilterSearchContext) filterContext).setOrTags(orTags); 916 917 if (entry.containsKey("strict-search-on-tags")) 918 { 919 boolean strictSearchOnTags = (boolean) entry.get("strict-search-on-tags"); 920 filterContext.setTagsAutoPosting(!strictSearchOnTags); 921 } 922 } 923 } 924 925 /** 926 * Class representing a date range. 927 */ 928 public class DateRange 929 { 930 /** The start date. */ 931 protected Date _startDate; 932 933 /** The start date. */ 934 protected Date _endDate; 935 936 /** 937 * Default constructor. 938 */ 939 public DateRange() 940 { 941 this(null, null); 942 } 943 944 /** 945 * Constructor with params. 946 * @param startDate The event's start date 947 * @param endDate The event's end date 948 */ 949 public DateRange(Date startDate, Date endDate) 950 { 951 this._startDate = startDate; 952 this._endDate = endDate; 953 } 954 955 /** 956 * Get the startDate. 957 * 958 * @return the startDate 959 */ 960 public Date getStartDate() 961 { 962 return _startDate; 963 } 964 965 /** 966 * Set the startDate. 967 * 968 * @param startDate the startDate to set 969 */ 970 public void setStartDate(Date startDate) 971 { 972 this._startDate = startDate; 973 } 974 975 /** 976 * Get the endDate. 977 * 978 * @return the endDate 979 */ 980 public Date getEndDate() 981 { 982 return _endDate; 983 } 984 985 /** 986 * Set the endDate. 987 * 988 * @param endDate the endDate to set 989 */ 990 public void setEndDate(Date endDate) 991 { 992 this._endDate = endDate; 993 } 994 } 995 996 997 998}