001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.search.galleries; 029 030import org.opencms.ade.configuration.CmsFunctionAvailability; 031import org.opencms.ade.galleries.shared.CmsGallerySearchScope; 032import org.opencms.configuration.preferences.CmsGallerySearchWordModePreference; 033import org.opencms.db.CmsUserSettings; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsPropertyDefinition; 036import org.opencms.file.types.CmsResourceTypeFunctionConfig; 037import org.opencms.i18n.CmsLocaleManager; 038import org.opencms.main.CmsLog; 039import org.opencms.main.OpenCms; 040import org.opencms.search.A_CmsSearchIndex; 041import org.opencms.search.CmsSearchUtil; 042import org.opencms.search.fields.CmsSearchField; 043import org.opencms.search.fields.CmsSearchFieldConfiguration; 044import org.opencms.search.solr.CmsSolrQuery; 045import org.opencms.util.CmsPair; 046import org.opencms.util.CmsStringUtil; 047import org.opencms.util.CmsUUID; 048import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler; 049 050import java.util.ArrayList; 051import java.util.Arrays; 052import java.util.Collection; 053import java.util.Collections; 054import java.util.List; 055import java.util.Locale; 056import java.util.Set; 057import java.util.stream.Collectors; 058 059import org.apache.commons.logging.Log; 060import org.apache.solr.client.solrj.request.SolrQuery.ORDER; 061 062import com.google.common.base.Joiner; 063 064/** 065 * Parameters used for the gallery search index.<p> 066 * 067 * @since 8.0.0 068 */ 069public class CmsGallerySearchParameters { 070 071 /** Modes for the pre-processing of the search words entered by the user. */ 072 public enum CmsGallerySearchWordMode { 073 074 /** Search for the words as sub-words, matching anywhere within a word. */ 075 infix, 076 077 /** Search for the words as entered, without any pre-processing. */ 078 plain; 079 080 /** The default search word mode. */ 081 public static final CmsGallerySearchWordMode DEFAULT = plain; 082 083 /** 084 * Returns the search word mode matching the given configuration value.<p> 085 * 086 * In case the value does not match a search word mode, {@link #DEFAULT} is returned.<p> 087 * 088 * @param value the configuration value to look up 089 * 090 * @return the search word mode matching the given configuration value 091 */ 092 public static CmsGallerySearchWordMode lookup(String value) { 093 094 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) { 095 try { 096 return valueOf(value.trim()); 097 } catch (IllegalArgumentException e) { 098 LOG.warn("Invalid gallery search word mode: " + value + ". Using '" + DEFAULT + "' instead."); 099 } 100 } 101 return DEFAULT; 102 } 103 } 104 105 /** Sort parameter constants. */ 106 public enum CmsGallerySortParam { 107 108 /** Sort by date created ascending. */ 109 dateCreated_asc, 110 111 /** Sort by date created descending. */ 112 dateCreated_desc, 113 114 /** Sort date expired ascending. */ 115 dateExpired_asc, 116 117 /** Sort date expired descending. */ 118 dateExpired_desc, 119 120 /** Sort by date modified ascending. */ 121 dateLastModified_asc, 122 123 /** Sort by date modified descending. */ 124 dateLastModified_desc, 125 126 /** Sort date released ascending. */ 127 dateReleased_asc, 128 129 /** Sort date released descending. */ 130 dateReleased_desc, 131 132 /** Sort by length ascending. */ 133 length_asc, 134 135 /** Sort by length descending. */ 136 length_desc, 137 138 /** Sort by VFS root path ascending. */ 139 path_asc, 140 141 /** Sort by VFS root path descending. */ 142 path_desc, 143 144 /** Sort by score descending. */ 145 score, 146 147 /** Sort state ascending. */ 148 state_asc, 149 150 /** Sort state descending. */ 151 state_desc, 152 153 /** Sort by title ascending. */ 154 title_asc, 155 156 /** Sort by title ascending. */ 157 title_desc, 158 159 /** Sort by type ascending. */ 160 type_asc, 161 162 /** Sort by type descending. */ 163 type_desc, 164 165 /** Sort created by ascending. */ 166 userCreated_asc, 167 168 /** Sort created by descending. */ 169 userCreated_desc, 170 171 /** Sort modified by ascending. */ 172 userLastModified_asc, 173 174 /** Sort modified by descending. */ 175 userLastModified_desc; 176 177 /** The default sort parameter. */ 178 public static final CmsGallerySortParam DEFAULT = title_asc; 179 } 180 181 /** 182 * Helper class to store a time range.<p> 183 */ 184 class CmsGallerySearchTimeRange { 185 186 /** The end time of the time range. */ 187 long m_endTime; 188 189 /** The start time of the time range. */ 190 long m_startTime; 191 192 /** 193 * Default constructor.<p> 194 * 195 * This will create an object where the start date is equal to 196 * {@link Long#MIN_VALUE} and the end date is equal to {@link Long#MAX_VALUE}.<p> 197 */ 198 public CmsGallerySearchTimeRange() { 199 200 m_startTime = Long.MIN_VALUE; 201 m_endTime = Long.MAX_VALUE; 202 } 203 204 /** 205 * Constructor with start and end time.<p> 206 * 207 * @param startTime the start time of the time range 208 * @param endTime the end time of the time range 209 */ 210 public CmsGallerySearchTimeRange(long startTime, long endTime) { 211 212 m_startTime = startTime; 213 m_endTime = endTime; 214 } 215 216 /** 217 * Returns the end time of the time range.<p> 218 * 219 * @return the end time of the time range 220 */ 221 public long getEndTime() { 222 223 return m_endTime; 224 } 225 226 /** 227 * Returns the start time of the time range.<p> 228 * 229 * @return the start time of the time range 230 */ 231 public long getStartTime() { 232 233 return m_startTime; 234 } 235 } 236 237 /** Logge instance for this class. */ 238 private static final Log LOG = CmsLog.getLog(CmsGallerySearchParameters.class); 239 240 /** The query operators that must not be extended by wildcards. */ 241 private static final List<String> QUERY_OPERATORS = List.of("AND", "OR", "NOT"); 242 243 /** The categories to search in. */ 244 private List<String> m_categories; 245 246 /** The time range for the date of resource creation to consider in the search. */ 247 private CmsGallerySearchTimeRange m_dateCreatedTimeRange; 248 249 /** The time range for the date of resource last modification to consider in the search. */ 250 private CmsGallerySearchTimeRange m_dateLastModifiedTimeRange; 251 252 /** The set of functions to be excluded from the search result (may be null). */ 253 private Set<CmsUUID> m_excludedFunctions; 254 255 /** The list of folders to search in. */ 256 private List<String> m_folders; 257 258 /** Enlists all VFS folders to perform a search in. */ 259 private List<String> m_foldersToSearchIn; 260 261 /** If true, empty search result should be returned regardless of other settings. */ 262 private boolean m_forceEmptyResult; 263 264 /** Function availability. */ 265 private CmsFunctionAvailability m_functionAvailability; 266 267 /** The galleries to search in. */ 268 private List<String> m_galleries; 269 270 /** Indicates the search exclude property should be ignored. */ 271 private boolean m_ignoreSearchExclude; 272 273 /** Set of functions to include (whitelist). */ 274 private Set<CmsUUID> m_includedFunctions; 275 276 /** Indicates if expired and unreleased resources should be included in the search. */ 277 private boolean m_includeExpired; 278 279 /** The locale for the search. */ 280 private String m_locale; 281 282 /** The number of search results per page. */ 283 private int m_matchesPerPage; 284 285 /** The sitemap reference path. */ 286 private String m_referencePath; 287 288 /** The resource types to search for. */ 289 private List<String> m_resourceTypes; 290 291 /** The requested page of the result. */ 292 private int m_resultPage; 293 294 /** The gallery search scope. */ 295 private CmsGallerySearchScope m_scope; 296 297 /** The sort order for the search result. */ 298 private CmsGallerySortParam m_sortOrder; 299 300 /** The template compatibility. */ 301 private String m_templateCompatibility; 302 303 /** Search words to search for. */ 304 private String m_words; 305 306 /** 307 * Default constructor.<p> 308 */ 309 public CmsGallerySearchParameters() { 310 311 m_resultPage = 1; 312 m_matchesPerPage = 10; 313 } 314 315 /** 316 * Returns the categories that have been included in the search.<p> 317 * 318 * If no categories have been set, then <code>null</code> is returned.<p> 319 * 320 * @return the categories that have been included in the search 321 */ 322 public List<String> getCategories() { 323 324 return m_categories; 325 } 326 327 /** 328 * Returns the time range for the date of creation that has been used for the search result.<p> 329 * 330 * In case this time range has not been set, this will return an object 331 * where the start date is equal to {@link Long#MIN_VALUE} and the end date is equal to {@link Long#MAX_VALUE}.<p> 332 * 333 * @return the time range for the date of creation that has been used for the search result 334 */ 335 public CmsGallerySearchTimeRange getDateCreatedRange() { 336 337 if (m_dateCreatedTimeRange == null) { 338 m_dateCreatedTimeRange = new CmsGallerySearchTimeRange(); 339 } 340 return m_dateCreatedTimeRange; 341 } 342 343 /** 344 * Returns the time range for the dadelete examplete of last modification that has been used for the search result.<p> 345 * 346 * In case this time range has not been set, this will return an object 347 * where the start date is equal to {@link Long#MIN_VALUE} and the end date is equal to {@link Long#MAX_VALUE}.<p> 348 * 349 * @return the time range for the date of last modification that has been used for the search result 350 */ 351 public CmsGallerySearchTimeRange getDateLastModifiedRange() { 352 353 if (m_dateLastModifiedTimeRange == null) { 354 m_dateLastModifiedTimeRange = new CmsGallerySearchTimeRange(); 355 } 356 return m_dateLastModifiedTimeRange; 357 } 358 359 /** 360 * Gets the set of structure IDs of functions to exclude from the search result. 361 * 362 * @return the set of structure IDs to exclude 363 */ 364 public Set<CmsUUID> getExcludedFunctions() { 365 366 return m_excludedFunctions; 367 } 368 369 /** 370 * Returns the list of folders to search in.<p> 371 * 372 * @return a list of paths of VFS folders 373 */ 374 public List<String> getFolders() { 375 376 return m_folders; 377 } 378 379 /** 380 * Returns the galleries that have been included in the search.<p> 381 * 382 * If no galleries have been set, then <code>null</code> is returned.<p> 383 * 384 * @return the galleries that have been included in the search 385 */ 386 public List<String> getGalleries() { 387 388 return m_galleries; 389 } 390 391 /** 392 * Gets the set of ids of functions to include. 393 * 394 * <p>Note: If the id of a function is returned in the ID set returned by thsi method, 395 * the function may still be excluded from search results based on other parameters. 396 * 397 * @return the included functions 398 */ 399 public Set<CmsUUID> getIncludedFunctions() { 400 401 return m_includedFunctions; 402 } 403 404 /** 405 * Returns the locale that has been used for the search.<p> 406 * 407 * If no locale has been set, then <code>null</code> is returned.<p> 408 * 409 * @return the locale that has been used for the search 410 */ 411 public String getLocale() { 412 413 if (m_locale == null) { 414 m_locale = CmsLocaleManager.getDefaultLocale().toString(); 415 } 416 return m_locale; 417 } 418 419 /** 420 * Returns the maximum number of matches per result page.<p> 421 * 422 * @return the the maximum number of matches per result page 423 * 424 * @see #getMatchesPerPage() 425 * @see #setResultPage(int) 426 */ 427 public int getMatchesPerPage() { 428 429 return m_matchesPerPage; 430 } 431 432 /** 433 * Returns a CmsSolrQuery representation of this class. 434 * @param cms the openCms object. 435 * @return CmsSolrQuery representation of this class. 436 */ 437 public CmsSolrQuery getQuery(CmsObject cms) { 438 439 final CmsSolrQuery query = new CmsSolrQuery(); 440 441 // set categories 442 query.setCategories(m_categories); 443 444 // Set date created time filter 445 query.addFilterQuery( 446 CmsSearchUtil.getDateCreatedTimeRangeFilterQuery( 447 CmsSearchField.FIELD_DATE_CREATED, 448 getDateCreatedRange().m_startTime, 449 getDateCreatedRange().m_endTime)); 450 451 // Set date last modified time filter 452 query.addFilterQuery( 453 CmsSearchUtil.getDateCreatedTimeRangeFilterQuery( 454 CmsSearchField.FIELD_DATE_LASTMODIFIED, 455 getDateLastModifiedRange().m_startTime, 456 getDateLastModifiedRange().m_endTime)); 457 458 // set scope / folders to search in 459 m_foldersToSearchIn = new ArrayList<>(); 460 addFoldersToSearchIn(m_folders); 461 addFoldersToSearchIn(m_galleries); 462 setSearchFolders(cms); 463 query.setSearchRoots(new ArrayList<>(m_foldersToSearchIn)); 464 465 if (!m_ignoreSearchExclude) { 466 // Reference for the values: CmsGallerySearchIndex.java, field EXCLUDE_PROPERTY_VALUES 467 query.addFilterQuery( 468 "-" + CmsSearchField.FIELD_SEARCH_EXCLUDE, 469 Arrays.asList( 470 new String[] { 471 A_CmsSearchIndex.PROPERTY_SEARCH_EXCLUDE_VALUE_ALL, 472 A_CmsSearchIndex.PROPERTY_SEARCH_EXCLUDE_VALUE_GALLERY}), 473 false, 474 true); 475 } 476 477 // set matches per page 478 query.setRows(Integer.valueOf(m_matchesPerPage)); 479 480 // set resource types 481 if (null != m_resourceTypes) { 482 List<String> resourceTypes = new ArrayList<>(m_resourceTypes); 483 if (m_resourceTypes.contains(CmsResourceTypeFunctionConfig.TYPE_NAME) 484 && !m_resourceTypes.contains(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION)) { 485 resourceTypes.add(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION); 486 } 487 query.setResourceTypes(resourceTypes); 488 } 489 490 // set result page 491 query.setStart(Integer.valueOf((m_resultPage - 1) * m_matchesPerPage)); 492 493 // set search locale 494 if (null != m_locale) { 495 Locale l = CmsLocaleManager.getLocale(m_locale); 496 List<Locale> locales = new ArrayList<>(3); 497 locales.add(l); 498 if (!l.getVariant().isEmpty()) { 499 locales.add(Locale.of(l.getLanguage(), l.getCountry())); 500 } 501 if (!l.getCountry().isEmpty()) { 502 locales.add(Locale.of(l.getLanguage())); 503 } 504 query.setLocales(locales); 505 } 506 507 // set search words 508 if (null != m_words) { 509 query.setQuery(prepareSearchWords(m_words, getSearchWordMode(cms))); 510 } 511 512 // set sort order 513 query.setSort(getSort().getFirst(), getSort().getSecond()); 514 515 // set result collapsing by id 516 // add sort criteria to possibly speed up performance and prevent sorting by score - not sure if a good solution 517 query.addFilterQuery( 518 "{!collapse field=id sort='" 519 + CmsSearchField.FIELD_INSTANCEDATE 520 + CmsSearchField.FIELD_POSTFIX_DATE 521 + " asc'}"); 522 523 query.setFields(CmsGallerySearchResult.getRequiredSolrFields()); 524 525 if ((m_functionAvailability != null) && m_functionAvailability.isDefined()) { 526 String notFunction = "(*:* AND -type:(" 527 + CmsXmlDynamicFunctionHandler.TYPE_FUNCTION 528 + " OR " 529 + CmsResourceTypeFunctionConfig.TYPE_NAME 530 + "))"; 531 Collection<CmsUUID> whitelist = m_functionAvailability.getWhitelist(); 532 Collection<CmsUUID> blacklist = new ArrayList<>(m_functionAvailability.getBlacklist()); 533 CmsUUID dummyId = CmsUUID.getNullUUID(); 534 blacklist.add(dummyId); 535 String idClause; 536 if (whitelist != null) { 537 whitelist = new ArrayList<>(whitelist); 538 whitelist.add(dummyId); 539 String whitelistIdCondition = whitelist.stream().map(id -> id.toString()).collect( 540 Collectors.joining(" OR ")); 541 idClause = "id:(" + whitelistIdCondition + ")"; 542 } else { 543 idClause = "*:* AND -id:(" 544 + blacklist.stream().map(id -> id.toString()).collect(Collectors.joining(" OR ")) 545 + ") "; 546 } 547 String functionFilter = "(" + notFunction + " OR (" + idClause + "))"; 548 query.addFilterQuery(functionFilter); 549 } 550 551 if ((m_resourceTypes != null) && m_resourceTypes.contains(CmsResourceTypeFunctionConfig.TYPE_NAME)) { 552 if ((m_excludedFunctions != null) && (m_excludedFunctions.size() > 0)) { 553 List<CmsUUID> excludedFunctions = new ArrayList<>(m_excludedFunctions); 554 Collections.sort(excludedFunctions); 555 String orList = Joiner.on(" OR ").join(excludedFunctions); 556 String filter = "*:* AND -id:(" + orList + ")"; 557 query.addFilterQuery(filter); 558 } 559 if (m_includedFunctions != null) { 560 List<CmsUUID> includedFunctions = new ArrayList<>(m_includedFunctions); 561 // not sure if order of terms matters for filter query caching in Solr, so normalize order just in case 562 Collections.sort(includedFunctions); 563 List<String> conditions = new ArrayList<>(); 564 String notFunction = "(*:* AND -type:(" 565 + CmsXmlDynamicFunctionHandler.TYPE_FUNCTION 566 + " OR " 567 + CmsResourceTypeFunctionConfig.TYPE_NAME 568 + "))"; 569 conditions.add(notFunction); 570 for (CmsUUID id : includedFunctions) { 571 conditions.add("id:" + id); 572 } 573 String includedFunctionsFilter = Joiner.on(" OR ").join(conditions); 574 query.addFilterQuery(includedFunctionsFilter); 575 } 576 } 577 578 if (m_templateCompatibility != null) { 579 if (m_templateCompatibility.matches("^[0-9a-zA-Z_]+$")) { 580 String fieldName = CmsPropertyDefinition.PROPERTY_TEMPLATE_COMPATILIBITY + "_prop"; 581 query.addFilterQuery("(*:* NOT " + fieldName + ":*) OR " + fieldName + ":" + m_templateCompatibility); 582 } else { 583 LOG.warn( 584 "Invalid template compatibility value: " 585 + m_templateCompatibility 586 + ". Must only contain digits, letters (a-z) or underscores."); 587 } 588 } 589 590 // include expired/unreleased 591 if (m_includeExpired) { 592 query.removeExpiration(); 593 } 594 595 return query; 596 } 597 598 /** 599 * Gets the reference path.<p> 600 * 601 * @return the gallery reference path 602 */ 603 public String getReferencePath() { 604 605 return m_referencePath; 606 } 607 608 /** 609 * Returns the names of the resource types that have been included in the search result.<p> 610 * 611 * If no resource types have been set, then <code>null</code> is returned.<p> 612 * 613 * @return the names of the resource types that have been included in the search result 614 */ 615 public List<String> getResourceTypes() { 616 617 return m_resourceTypes; 618 } 619 620 /** 621 * Returns the index of the requested result page.<p> 622 * 623 * @return the index of the requested result page 624 * 625 * @see #setResultPage(int) 626 * @see #getMatchesPerPage() 627 * @see #setMatchesPerPage(int) 628 */ 629 public int getResultPage() { 630 631 return m_resultPage; 632 } 633 634 /** 635 * The gallery search scope.<p> 636 * 637 * @return the gallery search scope 638 */ 639 public CmsGallerySearchScope getScope() { 640 641 if (m_scope == null) { 642 return OpenCms.getWorkplaceManager().getGalleryDefaultScope(); 643 } 644 return m_scope; 645 } 646 647 /** 648 * Returns the search word mode to use for the search words pre-processing.<p> 649 * 650 * The mode is taken from the user settings of the current user, with the 651 * system wide default configured in <code>opencms-workplace.xml</code> as fallback.<p> 652 * 653 * @param cms the current OpenCms user context 654 * 655 * @return the search word mode to use for the search words pre-processing 656 */ 657 public CmsGallerySearchWordMode getSearchWordMode(CmsObject cms) { 658 659 String value = CmsUserSettings.getAdditionalPreference( 660 cms, 661 CmsGallerySearchWordModePreference.PREFERENCE_NAME, 662 true); 663 return CmsGallerySearchWordMode.lookup(value); 664 } 665 666 /** 667 * Returns the words (terms) that have been used for the full text search.<p> 668 * 669 * If no search words have been set, then <code>null</code> is returned.<p> 670 * 671 * @return the words (terms) that have been used for the full text search 672 */ 673 public String getSearchWords() { 674 675 return m_words; 676 } 677 678 /** 679 * Returns the sort order that has been used in the search.<p> 680 * 681 * If the sort parameter has not been set the default sort order 682 * defined by {@link CmsGallerySortParam#DEFAULT} is used.<p> 683 * 684 * @return the sort order that has been used in the search 685 */ 686 public CmsGallerySortParam getSortOrder() { 687 688 if (m_sortOrder == null) { 689 690 m_sortOrder = CmsGallerySortParam.DEFAULT; 691 } 692 return m_sortOrder; 693 } 694 695 /** 696 * Gets the template compatibility. 697 * 698 * <p>If set, matches those resources whose template.compatibility property is either empty or contains the value (possibly together with other values, separated by whitespace). 699 * 700 * @return the template compatibility 701 */ 702 public String getTemplateCompatibility() { 703 704 return m_templateCompatibility; 705 } 706 707 /** 708 * If this returns true, an empty search result should be returned, regardless of other settings. 709 * 710 * @return true if an empty search result should be forced 711 */ 712 public boolean isForceEmptyResult() { 713 714 return m_forceEmptyResult; 715 } 716 717 /** 718 * Returns the search exclude property ignore flag.<p> 719 * 720 * @return the search exclude property ignore flag 721 */ 722 public boolean isIgnoreSearchExclude() { 723 724 return m_ignoreSearchExclude; 725 } 726 727 /** 728 * Returns a flag, indicating if release and expiration date should be ignored.<p> 729 * 730 * @return a flag, indicating if release and expiration date should be ignored 731 */ 732 public boolean isIncludeExpired() { 733 734 return m_includeExpired; 735 } 736 737 /** 738 * Sets the categories for the search.<p> 739 * 740 * Results are found only if they are contained in at least one of the given categories. 741 * 742 * @param categories the categories to set 743 */ 744 public void setCategories(List<String> categories) { 745 746 m_categories = categories; 747 } 748 749 /** 750 * Sets the time range for the date of resource creation to consider in the search.<p> 751 * 752 * @param startTime the start time of the time range 753 * @param endTime the end time of the time range 754 */ 755 public void setDateCreatedTimeRange(long startTime, long endTime) { 756 757 if (m_dateCreatedTimeRange == null) { 758 m_dateCreatedTimeRange = new CmsGallerySearchTimeRange(startTime, endTime); 759 } 760 } 761 762 /** 763 * Sets the time range for the date of resource last modification to consider in the search.<p> 764 * 765 * @param startTime the start time of the time range 766 * @param endTime the end time of the time range 767 */ 768 public void setDateLastModifiedTimeRange(long startTime, long endTime) { 769 770 if (m_dateLastModifiedTimeRange == null) { 771 m_dateLastModifiedTimeRange = new CmsGallerySearchTimeRange(startTime, endTime); 772 } 773 } 774 775 /** 776 * Sets the structure IDs of functions to exclude from the search results. 777 * 778 * @param excludedFunctions the structure IDs of functions to exclude 779 */ 780 public void setExcludedFunctions(Set<CmsUUID> excludedFunctions) { 781 782 m_excludedFunctions = excludedFunctions; 783 784 } 785 786 /** 787 * Sets the folders to search in.<p> 788 * 789 * @param folders the list of VFS folders 790 */ 791 public void setFolders(List<String> folders) { 792 793 m_folders = folders; 794 } 795 796 /** 797 * Enables/disables the 'force empty result' flag. 798 * 799 * If this is set to true, an empty search result should be returned regardless of the other parameters. 800 * 801 * @param forceEmptyResult if true, force an empty search result 802 */ 803 public void setForceEmptyResult(boolean forceEmptyResult) { 804 805 m_forceEmptyResult = forceEmptyResult; 806 } 807 808 /** 809 * Sets the dynamic function availability. 810 * 811 * @param dynamicFunctionAvailability the dynamic function availability 812 */ 813 public void setFunctionAvailability(CmsFunctionAvailability dynamicFunctionAvailability) { 814 815 m_functionAvailability = dynamicFunctionAvailability; 816 } 817 818 /** 819 * Sets the galleries for the search.<p> 820 * 821 * Results are found only if they are contained in one of the given galleries. 822 * If no gallery is set, results from all galleries will be returned in the search result.<p> 823 * 824 * @param galleries the galleries to set 825 */ 826 public void setGalleries(List<String> galleries) { 827 828 m_galleries = galleries; 829 } 830 831 /** 832 * Sets the search exclude property ignore flag.<p> 833 * 834 * @param excludeForPageEditor the search exclude property ignore flag 835 */ 836 public void setIgnoreSearchExclude(boolean excludeForPageEditor) { 837 838 m_ignoreSearchExclude = excludeForPageEditor; 839 } 840 841 /** 842 * Sets the ids of functions to include. 843 * 844 * @param includedFunctions the ids of functions to include 845 */ 846 public void setIncludedFunctions(Set<CmsUUID> includedFunctions) { 847 848 m_includedFunctions = includedFunctions; 849 } 850 851 /** 852 * Set the flag, determining if expired and unreleased resources should be shown. 853 * @param includeExpired iff <code>true</code> expired and unreleased resources are shown. 854 */ 855 public void setIncludeExpired(boolean includeExpired) { 856 857 m_includeExpired = includeExpired; 858 859 } 860 861 /** 862 * Sets the maximum number of matches per result page.<p> 863 * 864 * Use this together with {@link #setResultPage(int)} in order to split the result 865 * in more than one page.<p> 866 * 867 * @param matchesPerPage the the maximum number of matches per result page to set 868 * 869 * @see #getMatchesPerPage() 870 * @see #setResultPage(int) 871 */ 872 public void setMatchesPerPage(int matchesPerPage) { 873 874 m_matchesPerPage = matchesPerPage; 875 } 876 877 /** 878 * Sets the gallery reference path.<p> 879 * 880 * @param referencePath the gallery reference path 881 */ 882 public void setReferencePath(String referencePath) { 883 884 m_referencePath = referencePath; 885 } 886 887 /** 888 * Sets the names of the resource types to include in the search result.<p> 889 * 890 * Results are found only if they resources match one of the given resource type names. 891 * If no resource type name is set, all resource types will be returned in the search result.<p> 892 * 893 * @param resourceTypes the names of the resource types to include in the search result 894 */ 895 public void setResourceTypes(List<String> resourceTypes) { 896 897 m_resourceTypes = resourceTypes; 898 } 899 900 /** 901 * Sets the index of the result page that should be returned.<p> 902 * 903 * Use this together with {@link #setMatchesPerPage(int)} in order to split the result 904 * in more than one page.<p> 905 * 906 * @param resultPage the index of the result page to return 907 * 908 * @see #getResultPage() 909 * @see #getMatchesPerPage() 910 * @see #setMatchesPerPage(int) 911 */ 912 public void setResultPage(int resultPage) { 913 914 m_resultPage = resultPage; 915 } 916 917 /** 918 * Sets the search scope.<p> 919 * 920 * @param scope the search scope 921 */ 922 public void setScope(CmsGallerySearchScope scope) { 923 924 m_scope = scope; 925 } 926 927 /** 928 * Sets the locale for the search.<p> 929 * 930 * Results are found only if they match the given locale. 931 * If no locale is set, results for all locales will be returned in the search result.<p> 932 * 933 * @param locale the locale to set 934 */ 935 public void setSearchLocale(String locale) { 936 937 m_locale = locale; 938 } 939 940 /** 941 * Sets the words (terms) for the full text search.<p> 942 * 943 * Results are found only if they text extraction for the resource contains all given search words. 944 * If no search word is set, all resources will be returned in the search result.<p> 945 * 946 * Please note that this should be a list of words separated by white spaces. 947 * Simple Lucene modifiers such as (+), (-) and (*) are allowed, but anything more complex then this 948 * will be removed.<p> 949 * 950 * @param words the words (terms) for the full text search to set 951 */ 952 public void setSearchWords(String words) { 953 954 m_words = words; 955 } 956 957 /** 958 * Sets the sort order for the search.<p> 959 * 960 * @param sortOrder the sort order to set 961 */ 962 public void setSortOrder(CmsGallerySortParam sortOrder) { 963 964 m_sortOrder = sortOrder; 965 } 966 967 /** 968 * Sets the template compatibility string. 969 * 970 * @param compatibility the template compatibility string 971 */ 972 public void setTemplateCompatibility(String compatibility) { 973 974 m_templateCompatibility = compatibility; 975 } 976 977 /** 978 * Pre-processes the search words entered by the user before they are used as Solr query.<p> 979 * 980 * Depending on the given mode, the individual words are extended by 981 * wildcards to allow a sub-word search.<p> 982 * 983 * @param words the search words as entered by the user 984 * @param mode the search word mode to apply 985 * 986 * @return the search words to use in the Solr query 987 */ 988 protected String prepareSearchWords(String words, CmsGallerySearchWordMode mode) { 989 990 // We do not use any modifier when the search itself contains a modifier 991 // Quotes are checked here and not per word, since a phrase can span several words 992 if ((null == words) || words.isBlank() || words.contains("*") || words.contains("\"")) { 993 return words; 994 } 995 switch (mode) { 996 case infix: 997 return addInfixVariants(words); 998 case plain: 999 default: 1000 return words; 1001 } 1002 } 1003 1004 /** 1005 * Adds folders to perform the search in. 1006 * @param folders Folders to search in. 1007 */ 1008 private void addFoldersToSearchIn(final List<String> folders) { 1009 1010 if (null == folders) { 1011 return; 1012 } 1013 1014 // the trailing slash required by the parent-folders index field is added by 1015 // CmsSolrQuery#setSearchRoots, through which this list reaches the query 1016 m_foldersToSearchIn.addAll(folders); 1017 } 1018 1019 /** 1020 * Adds the sub-word variants for an infix search to the given search words.<p> 1021 * 1022 * Each word is replaced by the group <code>(word OR word* OR *word OR *word*)</code>. 1023 * The variants with a leading wildcard are omitted for words that do not 1024 * start with a lowercase letter. Words that use a modifier are left alone.<p> 1025 * 1026 * The variants are grouped and combined with an explicit <code>OR</code> so that the 1027 * default operator of the index only applies between the words entered by the user.<p> 1028 * 1029 * @param words the search words as entered by the user 1030 * 1031 * @return the search words extended by the sub-word variants 1032 */ 1033 private String addInfixVariants(String words) { 1034 1035 return Arrays.stream(words.trim().split("\\s+")).map(w -> { 1036 if (hasModifier(w)) { 1037 return w; 1038 } 1039 StringBuilder result = new StringBuilder("(").append(w).append(" OR ").append(w).append("*"); 1040 if (Character.isLowerCase(w.charAt(0))) { 1041 result.append(" OR *").append(w).append(" OR *").append(w).append("*"); 1042 } 1043 return result.append(")").toString(); 1044 }).collect(Collectors.joining(" ")); 1045 } 1046 1047 /** 1048 * Checks if the given list of resource type names contains a function-like type.<p> 1049 * 1050 * @param resourceTypes the collection of resource types 1051 * @return true if the list contains a function-like type 1052 */ 1053 private boolean containsFunctionType(List<String> resourceTypes) { 1054 1055 if (resourceTypes.contains(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION)) { 1056 return true; 1057 } 1058 if (resourceTypes.contains(CmsResourceTypeFunctionConfig.TYPE_NAME)) { 1059 return true; 1060 } 1061 return false; 1062 } 1063 1064 /** 1065 * Returns the Lucene sort indicated by the selected sort order.<p> 1066 * 1067 * @return the Lucene sort indicated by the selected sort order 1068 * 1069 * @see #getSortOrder() 1070 */ 1071 private CmsPair<String, org.apache.solr.client.solrj.request.SolrQuery.ORDER> getSort() { 1072 1073 final String sortTitle = CmsSearchFieldConfiguration.getLocaleExtendedName( 1074 CmsSearchField.FIELD_DISPTITLE, 1075 getLocale()) + "_sort"; 1076 1077 switch (getSortOrder()) { 1078 case dateCreated_asc: 1079 return CmsPair.create(CmsSearchField.FIELD_DATE_CREATED, ORDER.asc); 1080 case dateCreated_desc: 1081 return CmsPair.create(CmsSearchField.FIELD_DATE_CREATED, ORDER.desc); 1082 case dateExpired_asc: 1083 return CmsPair.create(CmsSearchField.FIELD_DATE_EXPIRED, ORDER.asc); 1084 case dateExpired_desc: 1085 return CmsPair.create(CmsSearchField.FIELD_DATE_EXPIRED, ORDER.desc); 1086 case dateLastModified_asc: 1087 return CmsPair.create(CmsSearchField.FIELD_DATE_LASTMODIFIED, ORDER.asc); 1088 case dateLastModified_desc: 1089 return CmsPair.create(CmsSearchField.FIELD_DATE_LASTMODIFIED, ORDER.desc); 1090 case dateReleased_asc: 1091 return CmsPair.create(CmsSearchField.FIELD_DATE_RELEASED, ORDER.asc); 1092 case dateReleased_desc: 1093 return CmsPair.create(CmsSearchField.FIELD_DATE_RELEASED, ORDER.desc); 1094 case length_asc: 1095 return CmsPair.create(CmsSearchField.FIELD_SIZE, ORDER.asc); 1096 case length_desc: 1097 return CmsPair.create(CmsSearchField.FIELD_SIZE, ORDER.desc); 1098 case path_asc: 1099 return CmsPair.create(CmsSearchField.FIELD_PATH, ORDER.asc); 1100 case path_desc: 1101 return CmsPair.create(CmsSearchField.FIELD_PATH, ORDER.desc); 1102 case score: 1103 return CmsPair.create(CmsSearchField.FIELD_SCORE, ORDER.desc); 1104 case state_asc: 1105 return CmsPair.create(CmsSearchField.FIELD_STATE, ORDER.asc); 1106 case state_desc: 1107 return CmsPair.create(CmsSearchField.FIELD_STATE, ORDER.desc); 1108 case title_asc: 1109 return CmsPair.create(sortTitle, ORDER.asc); 1110 case title_desc: 1111 return CmsPair.create(sortTitle, ORDER.desc); 1112 case type_asc: 1113 return CmsPair.create(CmsSearchField.FIELD_TYPE, ORDER.asc); 1114 case type_desc: 1115 return CmsPair.create(CmsSearchField.FIELD_TYPE, ORDER.desc); 1116 case userCreated_asc: 1117 return CmsPair.create(CmsSearchField.FIELD_USER_CREATED, ORDER.asc); 1118 case userCreated_desc: 1119 return CmsPair.create(CmsSearchField.FIELD_USER_CREATED, ORDER.desc); 1120 case userLastModified_asc: 1121 return CmsPair.create(CmsSearchField.FIELD_USER_LAST_MODIFIED, ORDER.asc); 1122 case userLastModified_desc: 1123 return CmsPair.create(CmsSearchField.FIELD_USER_LAST_MODIFIED, ORDER.desc); 1124 default: 1125 return CmsPair.create(sortTitle, ORDER.asc); 1126 } 1127 } 1128 1129 /** 1130 * Checks if the given search word uses a modifier.<p> 1131 * 1132 * Modifiers like <code>+word</code>, <code>-word</code>, <code>word~</code> or 1133 * <code>word^2</code> are placed before or after the word, so only words that do 1134 * not start and end with a letter are considered to use a modifier. 1135 * Note that a hyphen within a word, as in <code>Lkw-Fahrer</code>, is no modifier.<p> 1136 * 1137 * A field qualifier like <code>title:word</code> and the query operators 1138 * <code>AND</code>, <code>OR</code> and <code>NOT</code> are treated as modifiers, too.<p> 1139 * 1140 * @param word the search word to check 1141 * 1142 * @return <code>true</code> if the given search word uses a modifier 1143 */ 1144 private boolean hasModifier(String word) { 1145 1146 return !Character.isLetter(word.charAt(0)) 1147 || !Character.isLetter(word.charAt(word.length() - 1)) 1148 || word.contains(":") 1149 || QUERY_OPERATORS.contains(word); 1150 } 1151 1152 /** 1153 * Applies the defined search folders to the Solr query. 1154 * 1155 * @param obj The current CmsObject object. 1156 */ 1157 private void setSearchFolders(CmsObject obj) { 1158 1159 // check if parentFolders to search in have been set 1160 // if this evaluates false, the search folders have already been set, so 1161 // there's no need to add a scope filter 1162 if (m_foldersToSearchIn.isEmpty()) { 1163 // only append scope filter if no no folders or galleries given 1164 setSearchScopeFilter(obj); 1165 } 1166 } 1167 1168 /** 1169 * Sets the search scope. 1170 * 1171 * @param cms The current CmsObject object. 1172 */ 1173 private void setSearchScopeFilter(CmsObject cms) { 1174 1175 final List<String> searchRoots = CmsSearchUtil.computeScopeFolders(cms, this); 1176 1177 // If the resource types contain the type "function" also 1178 // add "/system/modules/" to the search path 1179 1180 if ((null != getResourceTypes()) && containsFunctionType(getResourceTypes())) { 1181 searchRoots.add("/system/modules/"); 1182 } 1183 1184 addFoldersToSearchIn(searchRoots); 1185 } 1186}