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