001/* 002 * Copyright 2017 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 */ 016 017package org.ametys.web.frontoffice; 018 019import java.io.IOException; 020import java.time.LocalDate; 021import java.time.format.DateTimeFormatter; 022import java.time.format.DateTimeParseException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.Date; 029import java.util.Enumeration; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Locale; 035import java.util.Map; 036import java.util.Map.Entry; 037import java.util.Optional; 038import java.util.Set; 039import java.util.stream.Collectors; 040import java.util.stream.Stream; 041 042import org.apache.avalon.framework.service.ServiceException; 043import org.apache.avalon.framework.service.ServiceManager; 044import org.apache.cocoon.environment.ObjectModelHelper; 045import org.apache.cocoon.environment.Request; 046import org.apache.cocoon.xml.AttributesImpl; 047import org.apache.cocoon.xml.XMLUtils; 048import org.apache.commons.collections.MapUtils; 049import org.apache.commons.lang.StringEscapeUtils; 050import org.apache.commons.lang3.ArrayUtils; 051import org.apache.commons.lang3.StringUtils; 052import org.apache.solr.client.solrj.util.ClientUtils; 053import org.xml.sax.SAXException; 054 055import org.ametys.cms.content.indexing.solr.SolrFieldNames; 056import org.ametys.cms.contenttype.ContentType; 057import org.ametys.cms.contenttype.MetadataDefinition; 058import org.ametys.cms.contenttype.MetadataType; 059import org.ametys.cms.repository.Content; 060import org.ametys.cms.repository.ContentLanguageExpression; 061import org.ametys.cms.repository.ContentTypeExpression; 062import org.ametys.cms.search.SearchField; 063import org.ametys.cms.search.SearchResult; 064import org.ametys.cms.search.SearchResults; 065import org.ametys.cms.search.SearchResultsIterable; 066import org.ametys.cms.search.SearchResultsIterator; 067import org.ametys.cms.search.Sort; 068import org.ametys.cms.search.Sort.Order; 069import org.ametys.cms.search.query.AndQuery; 070import org.ametys.cms.search.query.ContentTypeQuery; 071import org.ametys.cms.search.query.DateQuery; 072import org.ametys.cms.search.query.DocumentTypeQuery; 073import org.ametys.cms.search.query.FullTextQuery; 074import org.ametys.cms.search.query.JoinQuery; 075import org.ametys.cms.search.query.MatchAllQuery; 076import org.ametys.cms.search.query.NotQuery; 077import org.ametys.cms.search.query.OrQuery; 078import org.ametys.cms.search.query.Query; 079import org.ametys.cms.search.query.Query.Operator; 080import org.ametys.cms.search.query.QuerySyntaxException; 081import org.ametys.cms.search.query.StringQuery; 082import org.ametys.cms.search.query.TagQuery; 083import org.ametys.cms.search.solr.SearcherFactory.Searcher; 084import org.ametys.cms.search.solr.field.StringSearchField; 085import org.ametys.cms.tag.Tag; 086import org.ametys.cms.tag.TagProvider; 087import org.ametys.core.util.AvalonLoggerAdapter; 088import org.ametys.core.util.DateUtils; 089import org.ametys.plugins.explorer.resources.Resource; 090import org.ametys.plugins.repository.AmetysObject; 091import org.ametys.plugins.repository.AmetysObjectIterable; 092import org.ametys.plugins.repository.RepositoryConstants; 093import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 094import org.ametys.plugins.repository.query.QueryHelper; 095import org.ametys.plugins.repository.query.expression.AndExpression; 096import org.ametys.plugins.repository.query.expression.Expression; 097import org.ametys.runtime.i18n.I18nizableText; 098import org.ametys.web.frontoffice.FrontOfficeSearcherFactory.FrontOfficeSearcher; 099import org.ametys.web.frontoffice.FrontOfficeSearcherFactory.FrontOfficeSolrSearchResults; 100import org.ametys.web.frontoffice.FrontOfficeSearcherFactory.QueryFacet; 101import org.ametys.web.indexing.solr.SolrWebFieldNames; 102import org.ametys.web.repository.page.Page; 103import org.ametys.web.repository.page.ZoneItem; 104import org.ametys.web.repository.site.Site; 105import org.ametys.web.repository.site.SiteTypesExtensionPoint; 106import org.ametys.web.search.query.PageContentQuery; 107import org.ametys.web.search.query.PageQuery; 108import org.ametys.web.search.query.SiteQuery; 109import org.ametys.web.search.query.SitemapQuery; 110 111/** 112 * Generates the results of a search performed on front office 113 */ 114public class SearchGenerator extends AbstractSearchGenerator 115{ 116 private static final String __FACETS_CACHE = SearchGenerator.class.getName() + "$Cache-Facets"; 117 118 /** The query adapter extension point */ 119 protected QueryAdapterFOSearchExtensionPoint _queryAdapterFOSearchEP; 120 /** The site type manager */ 121 protected SiteTypesExtensionPoint _siteTypeEP; 122 123 /** 124 * Enumeration for content type search 125 */ 126 protected enum ContentTypeSearch 127 { 128 /** To search by content type with filter (facets)*/ 129 FILTER("filter"), 130 /** To search by content type with combo box selection */ 131 LIST("list"), 132 /** To search by content type with checkbox */ 133 CHECKBOX("checkbox"), 134 /** To search by content type with checkbox and filter (facets)*/ 135 CHECKBOX_FILTER("checkbox-filter"), 136 /** To not allow to search by content type */ 137 NONE("none"); 138 139 private String _name; 140 141 private ContentTypeSearch(String name) 142 { 143 this._name = name; 144 } 145 146 @Override 147 public String toString() 148 { 149 return this._name; 150 } 151 152 /** 153 * Get enumeration value 154 * @param value The value 155 * @return The enum 156 */ 157 public static ContentTypeSearch getEnum(String value) 158 { 159 for (ContentTypeSearch ct : ContentTypeSearch.values()) 160 { 161 if (ct.toString().equals(value)) 162 { 163 return ct; 164 } 165 } 166 167 throw new IllegalArgumentException("Invalid ContentTypeChoice value: " + value); 168 } 169 } 170 171 @Override 172 public void service(ServiceManager smanager) throws ServiceException 173 { 174 super.service(smanager); 175 _queryAdapterFOSearchEP = (QueryAdapterFOSearchExtensionPoint) smanager.lookup(QueryAdapterFOSearchExtensionPoint.ROLE); 176 _siteTypeEP = (SiteTypesExtensionPoint) smanager.lookup(SiteTypesExtensionPoint.ROLE); 177 } 178 179 /** 180 * Return the type of content type's search 181 * @param request The request 182 * @return The type of search 183 */ 184 protected ContentTypeSearch getContentTypeSearch(Request request) 185 { 186 return ContentTypeSearch.getEnum(parameters.getParameter("search-by-content-types-choice", "none")); 187 } 188 189 @Override 190 protected SearchResults<AmetysObject> search(Request request, Collection<String> siteNames, String language, int pageIndex, int start, int maxResults) throws Exception 191 { 192 ContentTypeSearch searchCTypeType = getContentTypeSearch(request); 193 try 194 { 195 if (searchCTypeType.equals(ContentTypeSearch.CHECKBOX_FILTER) || searchCTypeType.equals(ContentTypeSearch.FILTER)) 196 { 197 // Retrieve current workspace 198 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 199 200 try 201 { 202 XMLUtils.startElement(contentHandler, "content-types"); 203 204 List<Sort> sorts = new ArrayList<>(); 205 Sort sort = getSortField(request); 206 sorts.addAll(getPrimarySortFields(request)); 207 sorts.add(sort); 208 209 // Get first sort field 210 saxSort(sort); 211 212 Query queryObject = getQuery(request, siteNames, language); 213 Collection<Query> filterQueries = getFilterQueries(request, siteNames, language); 214 215 Collection<SearchField> facets = getFacets(request).values().stream().map(f -> f.getSearchField()).collect(Collectors.toList()); 216 Map<String, List<String>> facetValues = getFacetValues(request, siteNames, language); 217 218 SearchResults<AmetysObject> results = null; 219 220 String currentCtype = getContentTypeFilterValue(request); 221 if (currentCtype == null) 222 { 223 // First, do search without 'content-types' facet value 224 results = _getResultsForContentType(queryObject, filterQueries, facets, sorts, facetValues, null); 225 226 Map<String, Map<String, Integer>> facetResults = results.getFacetResults(); 227 Map<String, Integer> cTypeFacets = facetResults.get(SolrWebFieldNames.PAGE_CONTENT_TYPES); 228 Map<String, Integer> facetQueryResults = _getFacetQueryResults(results); 229 230 long totalCount = results.getTotalCount(); 231 if (totalCount > 0) 232 { 233 Collection<String> cTypes = getContentTypes(request); 234 235 // Get the first content types (in order) with at least one result 236 for (String cType : cTypes) 237 { 238 long cTypeCount; 239 if (cType.equals("resource")) 240 { 241 cTypeCount = _getNbResource(facetQueryResults); 242 } 243 else if (cTypeFacets.containsKey(cType)) 244 { 245 cTypeCount = Long.valueOf(cTypeFacets.get(cType)); 246 } 247 else 248 { 249 continue; 250 } 251 252 if (cTypeCount > 0) 253 { 254 // If facet count is equals to total count, no need to do the new search 255 if (cTypeCount < totalCount) 256 { 257 results = _getResultsForContentType(queryObject, filterQueries, facets, sorts, facetValues, cType); 258 } 259 260 currentCtype = cType; 261 break; 262 } 263 } 264 } 265 } 266 else 267 { 268 Searcher searcher = _searcherFactory.create() 269 .withQuery(queryObject) 270 .withFilterQueries(filterQueries) 271 .withFacets(facets) 272 .withFacetValues(facetValues) 273 .withLimits(0, Integer.MAX_VALUE) 274 .withSort(sorts) 275 .setCheckRights(true); 276 277 _additionalSearchProcessing(searcher); 278 279 results = searcher.searchWithFacets(); 280 } 281 282 _handleFacetResults(request, start, maxResults, results, currentCtype); 283 284 XMLUtils.endElement(contentHandler, "content-types"); 285 286 return results; 287 } 288 finally 289 { 290 // Restore context 291 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 292 } 293 } 294 else 295 { 296 return super.search(request, siteNames, language, pageIndex, start, maxResults); 297 } 298 } 299 catch (QuerySyntaxException e) 300 { 301 throw new IOException("Query syntax error while searching.", e); 302 } 303 } 304 305 private Map<String, Integer> _getFacetQueryResults(SearchResults<AmetysObject> results) 306 { 307 return Optional.of(results) 308 .filter(FrontOfficeSolrSearchResults.class::isInstance) 309 .map(FrontOfficeSolrSearchResults.class::cast) 310 .map(FrontOfficeSolrSearchResults::getFacetQueryResults) 311 .orElseGet(Collections::emptyMap); 312 } 313 314 private int _getNbResource(Map<String, Integer> facetQueryResults) 315 { 316 return Optional.ofNullable(facetQueryResults.get(DOCUMENT_TYPE_IS_PAGE_RESOURCE_FACET_NAME)).orElse(0); 317 } 318 319 private SearchResults<AmetysObject> _getResultsForContentType(Query queryObject, Collection<Query> filterQueries, Collection<SearchField> facets, List<Sort> sorts, Map<String, List<String>> initialFacetValues, String forceContentType) throws Exception 320 { 321 Map<String, List<String>> facetValues = new HashMap<>(initialFacetValues); 322 323 if (forceContentType == null) 324 { 325 facetValues.remove(SolrWebFieldNames.PAGE_CONTENT_TYPES); 326 } 327 else 328 { 329 facetValues.put(SolrWebFieldNames.PAGE_CONTENT_TYPES, new ArrayList<>()); 330 facetValues.get(SolrWebFieldNames.PAGE_CONTENT_TYPES).add(forceContentType); 331 } 332 333 Searcher searcher = _searcherFactory.create() 334 .withQuery(queryObject) 335 .withFilterQueries(filterQueries) 336 .withFacets(facets) 337 .withFacetValues(facetValues) 338 .withLimits(0, Integer.MAX_VALUE) 339 .withSort(sorts) 340 .setCheckRights(true); 341 342 _additionalSearchProcessing(searcher); 343 344 return searcher.searchWithFacets(); 345 } 346 347 @Override 348 protected void _additionalSearchProcessing(Searcher searcher) 349 { 350 Request request = ObjectModelHelper.getRequest(objectModel); 351 352 if (searcher instanceof FrontOfficeSearcher) 353 { 354 FrontOfficeSearcher foSearcher = (FrontOfficeSearcher) searcher; 355 Collection<QueryFacet> queryFacets = getQueryFacets(request); 356 if (!queryFacets.isEmpty()) 357 { 358 foSearcher.withQueryFacets(queryFacets) 359 .withQueryFacetValues(getQueryFacetValues(request)); 360 } 361 } 362 } 363 364 private void _handleFacetResults(Request request, int start, int maxResults, SearchResults<AmetysObject> results, String currentCtype) throws SAXException, IOException 365 { 366 int count = 0; 367 Map<String, Map<String, Integer>> facetResults = results.getFacetResults(); 368 Map<String, Integer> facetQueryResults = _getFacetQueryResults(results); 369 if (facetResults.containsKey(SolrWebFieldNames.PAGE_CONTENT_TYPES) || facetQueryResults.containsKey(DOCUMENT_TYPE_IS_PAGE_RESOURCE_FACET_NAME)) 370 { 371 Map<String, Integer> cTypeFacets = Optional.ofNullable(facetResults.get(SolrWebFieldNames.PAGE_CONTENT_TYPES)).orElseGet(Collections::emptyMap); 372 373 for (String contentTypeId : getContentTypes(request)) 374 { 375 int nbResults; 376 if ("resource".equals(contentTypeId)) 377 { 378 nbResults = _getNbResource(facetQueryResults); 379 } 380 else if (cTypeFacets.containsKey(contentTypeId) && cTypeFacets.get(contentTypeId) > 0) 381 { 382 nbResults = cTypeFacets.get(contentTypeId); 383 } 384 else 385 { 386 continue; 387 } 388 389 boolean current = contentTypeId.equals(currentCtype) || currentCtype == null && count == 0; 390 count++; 391 392 AttributesImpl attr = new AttributesImpl(); 393 if (current) 394 { 395 attr.addCDATAAttribute("current", "true"); 396 } 397 398 XMLUtils.startElement(contentHandler, contentTypeId, attr); 399 400 if (contentTypeId.equals("resource")) 401 { 402 new I18nizableText("plugin.web", "PLUGINS_WEB_SERVICE_FRONT_SEARCH_ON_DOCUMENTS").toSAX(contentHandler, "label"); 403 } 404 else 405 { 406 ContentType contentType = _cTypeExtPt.getExtension(contentTypeId); 407 if (contentType != null) 408 { 409 contentType.getLabel().toSAX(contentHandler, "label"); 410 } 411 } 412 413 // SAX results 414 AttributesImpl atts = new AttributesImpl(); 415 atts.addCDATAAttribute("total", String.valueOf(nbResults)); 416 atts.addCDATAAttribute("maxScore", String.valueOf(results.getMaxScore())); 417 418 if (current) 419 { 420 XMLUtils.startElement(contentHandler, "hits", atts); 421 saxHits(results, start, maxResults); 422 XMLUtils.endElement(contentHandler, "hits"); 423 424 // SAX pagination 425 saxPagination(results.getTotalCount(), start, maxResults); 426 } 427 else 428 { 429 XMLUtils.createElement(contentHandler, "hits", atts); 430 } 431 432 XMLUtils.endElement(contentHandler, contentTypeId); 433 } 434 } 435 } 436 437 @Override 438 protected Query getQuery(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException 439 { 440 List<Query> wordingQueries = getWordingQueries(request, siteNames, language); 441 442 // Query to execute on joined contents 443 List<Query> contentQueries = new ArrayList<>(wordingQueries); // add wording queries 444 contentQueries.addAll(getContentQueries(request, siteNames, language)); // add specific queries to contents 445 Query contentQuery = new AndQuery(contentQueries); 446 447 List<Query> contentOrResourcesQueries = new ArrayList<>(); 448 contentOrResourcesQueries.add(contentQuery); 449 if (!wordingQueries.isEmpty()) 450 { 451 contentOrResourcesQueries.addAll(getContentResourcesOrAttachmentQueries(new AndQuery(wordingQueries))); // add queries on join content's resources 452 } 453 454 Query finalContentQuery = new PageContentQuery(new OrQuery(contentOrResourcesQueries)); 455 456 // Query to execute on pages 457 List<Query> pagesQueries = new ArrayList<>(wordingQueries); // add wording queries 458 pagesQueries.addAll(getPageQueries(request, siteNames, language)); // add specific queries to pages 459 Query pageQuery = pagesQueries.isEmpty() && contentQueries.isEmpty() ? new MatchAllQuery() : new AndQuery(pagesQueries); 460 461 List<Query> pageOrResourcesQueries = new ArrayList<>(); 462 pageOrResourcesQueries.add(pageQuery); 463 if (!wordingQueries.isEmpty()) 464 { 465 pageOrResourcesQueries.addAll(getPageResourcesOrAttachmentQueries(new AndQuery(wordingQueries))); // add queries on join page's resources 466 } 467 468 Query finalPageQuery = new OrQuery(pageOrResourcesQueries); 469 470 Query finalQuery = new OrQuery(finalPageQuery, finalContentQuery); 471 472 for (QueryAdapterFOSearch queryAdapter : _getSortedListQueryAdapter()) 473 { 474 finalQuery = queryAdapter.modifyQuery(finalQuery, request, siteNames, language); 475 } 476 477 return finalQuery; 478 } 479 480 /** 481 * Get the queries on wording (keywords, no words, exact wording, no words) 482 * @param request the request 483 * @param siteNames the site names 484 * @param language the language 485 * @return the queries on wording 486 */ 487 protected List<Query> getWordingQueries(Request request, Collection<String> siteNames, String language) 488 { 489 List<Query> queries = new ArrayList<>(); 490 491 addTextFieldQuery(queries, language, request); 492 addAllWordsTextFieldQuery(queries, language, request); 493 addExactWordingTextFieldQuery(queries, language, request); 494 addNoWordsTextFieldQuery(queries, language, request); 495 496 return queries; 497 } 498 499 /** 500 * Get the queries to be apply on joined contents ONLY 501 * @param request the request 502 * @param siteNames the site names 503 * @param language the language 504 * @return the queries for contents only 505 */ 506 protected List<Query> getContentQueries(Request request, Collection<String> siteNames, String language) 507 { 508 List<Query> queries = new ArrayList<>(); 509 addMetadataQuery(queries, language, request); 510 return queries; 511 } 512 513 /** 514 * Get the queries to be apply on pages ONLY 515 * @param request the request 516 * @param siteNames the site names 517 * @param language the language 518 * @return the queries for pages only 519 */ 520 protected List<Query> getPageQueries(Request request, Collection<String> siteNames, String language) 521 { 522 return new ArrayList<>(); 523 } 524 525 @Override 526 protected Collection<Query> getFilterQueries(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException 527 { 528 List<Query> queries = new ArrayList<>(); 529 530 Query siteQuery = new SiteQuery(siteNames); 531 Query sitemapQuery = new SitemapQuery(language); 532 for (QueryAdapterFOSearch queryAdapter : _getSortedListQueryAdapter()) 533 { 534 siteQuery = queryAdapter.modifySiteQueryFilter(siteQuery, request, siteNames, language); 535 sitemapQuery = queryAdapter.modifySitemapQueryFilter(sitemapQuery, request, siteNames, language); 536 } 537 538 queries.add(siteQuery); 539 queries.add(sitemapQuery); 540 541 addContentTypeQuery(queries, request); 542 addTagsQuery(queries, request); 543 addPagesQuery(queries, request); 544 addDateQuery(queries, request); 545 546 return queries; 547 } 548 549 @Override 550 protected Map<String, List<String>> getFacetValues(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException 551 { 552 Map<String, List<String>> facetValues = new HashMap<>(); 553 554 String currentCType = getContentTypeFilterValue(request); 555 if (currentCType != null) 556 { 557 facetValues.put(SolrWebFieldNames.PAGE_CONTENT_TYPES, new ArrayList<>()); 558 if (!"resource".equals(currentCType)) 559 { 560 facetValues.get(SolrWebFieldNames.PAGE_CONTENT_TYPES).add(currentCType); 561 } 562 } 563 else if (getContentTypeSearch(request).equals(ContentTypeSearch.FILTER) || getContentTypeSearch(request).equals(ContentTypeSearch.CHECKBOX_FILTER)) 564 { 565 // Get the first content types as facet value 566 facetValues.put(SolrWebFieldNames.PAGE_CONTENT_TYPES, new ArrayList<>()); 567 String firstContentType = getContentTypes(request).iterator().next(); 568 if (!"resource".equals(firstContentType)) 569 { 570 facetValues.get(SolrWebFieldNames.PAGE_CONTENT_TYPES).add(firstContentType); 571 } 572 } 573 574 Map<String, FacetField> facets = getFacets(request); 575 for (String fieldName : facets.keySet()) 576 { 577 String[] parameterValues = request.getParameterValues("metadata-" + fieldName); 578 if (parameterValues != null && parameterValues.length > 0 && StringUtils.isNotEmpty(parameterValues[0])) 579 { 580 facetValues.put(SolrWebFieldNames.FACETABLE_CONTENT_FIELD_PREFIX + fieldName, new ArrayList<>()); 581 List<String> preparedParameterValues = Stream.of(parameterValues) 582 .map(StringEscapeUtils::unescapeXml) 583 .map(ClientUtils::escapeQueryChars) 584 .collect(Collectors.toList()); 585 facetValues.get(SolrWebFieldNames.FACETABLE_CONTENT_FIELD_PREFIX + fieldName).addAll(preparedParameterValues); 586 } 587 } 588 589 return facetValues; 590 } 591 592 @Override 593 protected Collection<String> getQueryFacetValues(Request request) 594 { 595 List<String> queryFacetValues = new ArrayList<>(); 596 597 String currentCType = getContentTypeFilterValue(request); 598 if (currentCType != null) 599 { 600 if ("resource".equals(currentCType)) 601 { 602 queryFacetValues.add(DOCUMENT_TYPE_IS_PAGE_RESOURCE_FACET_NAME); 603 } 604 } 605 else if (getContentTypeSearch(request).equals(ContentTypeSearch.FILTER) || getContentTypeSearch(request).equals(ContentTypeSearch.CHECKBOX_FILTER)) 606 { 607 // Get the first content types as facet value 608 String firstContentType = getContentTypes(request).iterator().next(); 609 if ("resource".equals(firstContentType)) 610 { 611 queryFacetValues.add(DOCUMENT_TYPE_IS_PAGE_RESOURCE_FACET_NAME); 612 } 613 } 614 615 return queryFacetValues; 616 } 617 618 @Override 619 protected Map<String, FacetField> getFacets(Request request) throws IllegalArgumentException 620 { 621 ZoneItem zoneItem = getZoneItem(request); 622 String facetCacheAttrName = __FACETS_CACHE + "$" + Optional.ofNullable(zoneItem).map(ZoneItem::getId).orElse("null"); 623 @SuppressWarnings("unchecked") 624 Map<String, FacetField> cache = (Map<String, FacetField>) request.getAttribute(facetCacheAttrName); 625 if (cache != null) 626 { 627 return cache; 628 } 629 630 Map<String, FacetField> facets = new HashMap<>(); 631 632 // Facet for content types 633 ContentTypeSearch contentTypeSearch = getContentTypeSearch(request); 634 if (contentTypeSearch.equals(ContentTypeSearch.FILTER) || contentTypeSearch.equals(ContentTypeSearch.CHECKBOX_FILTER)) 635 { 636 SearchField cTypeField = new SearchField() 637 { 638 @Override 639 public String getSortField() 640 { 641 return PAGE_CONTENT_TYPES; 642 } 643 644 @Override 645 public String getName() 646 { 647 return PAGE_CONTENT_TYPES; 648 } 649 650 @Override 651 public String getFacetField() 652 { 653 return PAGE_CONTENT_TYPES + "_s_dv"; 654 } 655 }; 656 657 facets.put(PAGE_CONTENT_TYPES, new ContentTypeFacetField(cTypeField)); 658 } 659 660 if (useFacets()) 661 { 662 Collection<String> contentTypes = getContentTypes(request); 663 664 if (zoneItem != null && zoneItem.getServiceParameters().hasMetadata("search-by-metadata")) 665 { 666 String[] metadataPaths = zoneItem.getServiceParameters().getStringArray("search-by-metadata"); 667 for (String metadataPath : metadataPaths) 668 { 669 MetadataDefinition metadataDefinition = contentTypes.isEmpty() ? null 670 : _contentTypesHelper.getMetadataDefinition(metadataPath, (String[]) contentTypes.toArray(), null); 671 672 if (metadataDefinition != null && (metadataDefinition.getEnumerator() != null || metadataDefinition.getType().equals(MetadataType.CONTENT))) 673 { 674 SearchField searchField = _searchHelper.getSearchField(contentTypes, metadataPath); 675 if (searchField != null) 676 { 677 StringSearchField facetField = new StringSearchField(SolrWebFieldNames.FACETABLE_CONTENT_FIELD_PREFIX + searchField.getName()); 678 facets.put(searchField.getName(), new MetadataFacetField(facetField, metadataDefinition, new AvalonLoggerAdapter(getLogger()))); 679 } 680 } 681 } 682 } 683 } 684 685 request.setAttribute(facetCacheAttrName, facets); 686 687 return facets; 688 } 689 690 @Override 691 protected Set<QueryFacet> getQueryFacets(Request request) 692 { 693 Set<QueryFacet> queryFacets = new HashSet<>(); 694 Collection<String> contentTypes = getContentTypes(request); 695 696 // For handling "resource" content type in facets for content types 697 ContentTypeSearch contentTypeSearch = getContentTypeSearch(request); 698 if ((contentTypeSearch.equals(ContentTypeSearch.FILTER) || contentTypeSearch.equals(ContentTypeSearch.CHECKBOX_FILTER)) 699 && contentTypes.contains("resource")) 700 { 701 queryFacets.add(new QueryFacet(DOCUMENT_TYPE_IS_PAGE_RESOURCE_FACET_NAME, 702 PAGE_CONTENT_TYPES, 703 DOCUMENT_TYPE + ":" + TYPE_PAGE_RESOURCE)); 704 } 705 706 for (QueryAdapterFOSearch queryAdapter : _getSortedListQueryAdapter()) 707 { 708 queryFacets = queryAdapter.modifyQueryFacets(queryFacets, request); 709 } 710 711 return queryFacets; 712 } 713 714 /** 715 * Get the filter queries for a single fixed content type. 716 * 717 * @param request The request. 718 * @param cType The fixed content type. 719 * @param siteNames the site names. 720 * @param language The language. 721 * @return The filter queries. 722 */ 723 protected Collection<Query> getFixedCTypeFilterQueries(Request request, String cType, Collection<String> siteNames, String language) 724 { 725 List<Query> queries = new ArrayList<>(); 726 727 queries.add(new SiteQuery(siteNames)); 728 queries.add(new SitemapQuery(language)); 729 730 if ("resource".equals(cType)) 731 { 732 queries.add(new DocumentTypeQuery(TYPE_PAGE_RESOURCE)); 733 } 734 else 735 { 736 // Fixed content type query. 737 queries.add(new PageContentQuery(new ContentTypeQuery(cType))); 738 } 739 addTagsQuery(queries, request); 740 addPagesQuery(queries, request); 741 addDateQuery(queries, request); 742 743 return queries; 744 } 745 746 @Override 747 protected Collection<String> getDocumentTypes(Request request) 748 { 749 List<String> documentTypes = new ArrayList<>(); 750 751 Collection<String> cTypes = getContentTypes(request); 752 if (cTypes.size() == 1 && "resource".equals(cTypes.iterator().next())) 753 { 754 documentTypes.add(TYPE_PAGE_RESOURCE); 755 } 756 // An empty collections means "all". 757 else if (cTypes.isEmpty() || cTypes.contains("resource")) 758 { 759 documentTypes.addAll(Arrays.asList(TYPE_PAGE, TYPE_PAGE_RESOURCE)); 760 } 761 else 762 { 763 documentTypes.add(TYPE_PAGE); 764 } 765 766 // Add other documents types (by priority order) 767 for (QueryAdapterFOSearch queryAdapter : _getSortedListQueryAdapter()) 768 { 769 queryAdapter.addDocumentType(documentTypes); 770 } 771 772 return documentTypes; 773 } 774 775 @Override 776 protected Collection<String> getFields() 777 { 778 return Collections.emptyList(); 779 } 780 781 @Override 782 protected void saxHits(SearchResults<AmetysObject> results, int start, int maxResults) throws SAXException, IOException 783 { 784 SearchResultsIterable<SearchResult<AmetysObject>> resultsIt = results.getResults(); 785 long limit = Math.min(start + maxResults, resultsIt.getSize()); 786 float maxScore = results.getMaxScore(); 787 788 SearchResultsIterator<SearchResult<AmetysObject>> it = resultsIt.iterator(); 789 it.skip(start); 790 for (int i = start; i < limit; i++) 791 { 792 if (it.hasNext()) // this should return true except if there is a inconsistency between repository and Solr index 793 { 794 SearchResult<AmetysObject> searchResult = it.next(); 795 float score = searchResult.getScore(); 796 AmetysObject ametysObject = searchResult.getObject(); 797 if (ametysObject instanceof Page) 798 { 799 saxPageHit(score, maxScore, (Page) ametysObject); 800 } 801 else if (ametysObject instanceof Resource) 802 { 803 saxResourceHit(score, maxScore, (Resource) ametysObject); 804 } 805 } 806 807 } 808 } 809 810 @Override 811 protected Sort getSortField(Request request) 812 { 813 if (request.getParameter("sort-by-title-for-sorting") != null || request.getParameter("sort-by-title") != null) 814 { 815 return new Sort(TITLE_SORT, Order.ASC); 816 } 817 else if (request.getParameter("sort-by-lastValidation") != null) 818 { 819 return new Sort(LAST_VALIDATION, Order.DESC); 820 } 821 else 822 { 823 // Generic sort field (with hardcorded descending order) 824 Enumeration paramNames = request.getParameterNames(); 825 while (paramNames.hasMoreElements()) 826 { 827 String param = (String) paramNames.nextElement(); 828 if (param.startsWith("sort-by-")) 829 { 830 String fieldName = StringUtils.removeStart(param, "sort-by-"); 831 return new Sort(fieldName, Order.ASC); 832 } 833 } 834 } 835 836 return new Sort("score", Order.DESC); 837 } 838 839 @Override 840 protected List<Sort> getPrimarySortFields(Request request) 841 { 842 return new ArrayList<>(); 843 } 844 845 @Override 846 protected void saxFormFields(Request request, String siteName, String lang) throws SAXException 847 { 848 XMLUtils.createElement(contentHandler, "textfield"); 849 850 boolean advancedSearch = parameters.getParameterAsBoolean("advanced-search", true); 851 if (advancedSearch) 852 { 853 XMLUtils.createElement(contentHandler, "all-words"); 854 XMLUtils.createElement(contentHandler, "exact-wording"); 855 XMLUtils.createElement(contentHandler, "no-words"); 856 } 857 858 ContentTypeSearch contentTypeSearch = getContentTypeSearch(request); 859 XMLUtils.createElement(contentHandler, "content-types-choice", contentTypeSearch.toString()); 860 861 _saxContentTypeCriteria(request); 862 _saxMetadataCriteria(request, lang); 863 _saxTagsCriteria(siteName); 864 _saxSitemapCriteria(); 865 866 boolean multisite = parameters.getParameterAsBoolean("search-multisite", false); 867 if (multisite) 868 { 869 XMLUtils.createElement(contentHandler, "multisite"); 870 871 XMLUtils.startElement(contentHandler, "sites"); 872 Collection<String> allSites = _siteManager.getSiteNames(); 873 for (String name : allSites) 874 { 875 Site site = _siteManager.getSite(name); 876 if (!_isPrivate(site)) 877 { 878 AttributesImpl attr = new AttributesImpl(); 879 attr.addCDATAAttribute("name", name); 880 if (name.equals(siteName)) 881 { 882 attr.addCDATAAttribute("current", "true"); 883 } 884 XMLUtils.createElement(contentHandler, "site", attr, StringUtils.defaultString(site.getTitle())); 885 } 886 } 887 XMLUtils.endElement(contentHandler, "sites"); 888 } 889 890 if (StringUtils.isNotBlank(parameters.getParameter("startDate", ""))) 891 { 892 XMLUtils.createElement(contentHandler, "dates", "true"); 893 } 894 } 895 896 private boolean _isPrivate(Site site) 897 { 898 String type = site.getType(); 899 return _siteTypeEP.getExtension(type).isPrivateType(); 900 } 901 902 private void _saxMetadataCriteria(Request request, String language) throws SAXException 903 { 904 ZoneItem zoneItem = getZoneItem(request); 905 if (zoneItem != null && zoneItem.getServiceParameters().hasMetadata("search-by-metadata")) 906 { 907 String[] metadataPaths = zoneItem.getServiceParameters().getStringArray("search-by-metadata"); 908 if (metadataPaths.length > 0) 909 { 910 Collection<String> cTypes = new ArrayList<>(getContentTypes(request)); 911 912 for (String metadataPath : metadataPaths) 913 { 914 MetadataDefinition metadataDef = _contentTypesHelper.getMetadataDefinitionByMetadataValuePath(metadataPath, cTypes.toArray(new String[cTypes.size()]), 915 new String[0]); 916 917 saxMetadataDef(metadataDef, metadataPath, language); 918 } 919 } 920 } 921 } 922 923 /** 924 * Sax metadata set information for metadata 925 * 926 * @param metadataDef The metadata definition. 927 * @param metadataPath The metadata path 928 * @param language The current language 929 * @throws SAXException If an error occurred while saxing 930 */ 931 protected void saxMetadataDef(MetadataDefinition metadataDef, String metadataPath, String language) throws SAXException 932 { 933 AttributesImpl attrs = new AttributesImpl(); 934 attrs.addCDATAAttribute("name", metadataPath); 935 936 XMLUtils.startElement(contentHandler, "metadata", attrs); 937 if (metadataDef != null) 938 { 939 metadataDef.getLabel().toSAX(contentHandler, "label"); 940 } 941 else 942 { 943 XMLUtils.startElement(contentHandler, "label"); 944 XMLUtils.data(contentHandler, metadataPath); 945 XMLUtils.endElement(contentHandler, "label"); 946 } 947 948 saxEnumeratorValueForMetadata(metadataDef, metadataPath, language); 949 XMLUtils.endElement(contentHandler, "metadata"); 950 } 951 952 /** 953 * Sax enumeration value for enum or a content metadata 954 * @param metadataDef The metadata definition. 955 * @param metadataPath The metadata path 956 * @param language The current language 957 * @throws SAXException If an error occurred while saxing 958 */ 959 protected void saxEnumeratorValueForMetadata(MetadataDefinition metadataDef, String metadataPath, String language) throws SAXException 960 { 961 if (metadataDef != null && metadataDef.getEnumerator() != null) 962 { 963 XMLUtils.startElement(contentHandler, "enumeration"); 964 try 965 { 966 Map<Object, I18nizableText> entries = metadataDef.getEnumerator().getEntries(); 967 for (Object key : entries.keySet()) 968 { 969 AttributesImpl attrItem = new AttributesImpl(); 970 attrItem.addCDATAAttribute("value", (String) key); 971 XMLUtils.startElement(contentHandler, "item", attrItem); 972 entries.get(key).toSAX(contentHandler, "label"); 973 XMLUtils.endElement(contentHandler, "item"); 974 } 975 } 976 catch (Exception e) 977 { 978 getLogger().error("An error occurred getting enumerator items for metadata : " + metadataPath, e); 979 } 980 XMLUtils.endElement(contentHandler, "enumeration"); 981 } 982 else if (metadataDef != null && metadataDef.getType().equals(MetadataType.CONTENT)) 983 { 984 XMLUtils.startElement(contentHandler, "enumeration"); 985 Map<String, String> values = getContentValues(metadataDef.getContentType(), language); 986 for (Entry<String, String> entry : values.entrySet()) 987 { 988 AttributesImpl attrItem = new AttributesImpl(); 989 attrItem.addCDATAAttribute("value", entry.getKey()); 990 XMLUtils.startElement(contentHandler, "item", attrItem); 991 XMLUtils.createElement(contentHandler, "label", entry.getValue()); 992 XMLUtils.endElement(contentHandler, "item"); 993 } 994 XMLUtils.endElement(contentHandler, "enumeration"); 995 } 996 } 997 998 /** 999 * Get values for contents enumeration 1000 * @param cTypeId The id of content type 1001 * @param language The current language 1002 * @return The contents 1003 */ 1004 protected Map<String, String> getContentValues(String cTypeId, String language) 1005 { 1006 try 1007 { 1008 boolean multilingual = _cTypeExtPt.getExtension(cTypeId).isMultilingual(); 1009 Expression expr = new AndExpression( 1010 new ContentTypeExpression(org.ametys.plugins.repository.query.expression.Expression.Operator.EQ, cTypeId), 1011 multilingual ? null : new ContentLanguageExpression(org.ametys.plugins.repository.query.expression.Expression.Operator.EQ, language)); 1012 AmetysObjectIterable<Content> contents = _resolver.query(QueryHelper.getXPathQuery(null, RepositoryConstants.NAMESPACE_PREFIX + ":content", expr)); 1013 1014 return contents.stream() 1015 .collect(Collectors.toMap(Content::getId, c -> c.getTitle(new Locale(language)))) 1016 .entrySet() 1017 .stream() 1018 .sorted(Map.Entry.comparingByValue()) // sort by title 1019 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); 1020 } 1021 catch (Exception e) 1022 { 1023 getLogger().error("Failed to get content enumeration for content type " + cTypeId, e); 1024 return MapUtils.EMPTY_MAP; 1025 } 1026 } 1027 1028 @Override 1029 protected void saxFormValues(Request request, int start, int offset) throws SAXException 1030 { 1031 _saxTextField(request); 1032 _saxMetadataValues(request); 1033 _saxAllWords(request); 1034 _saxExactWording(request); 1035 _saxNoWords(request); 1036 _saxContentType(request); 1037 _saxTags(request); 1038 _saxPages(request); 1039 _saxMultisite(request); 1040 _saxDates(request); 1041 } 1042 1043 private void _saxDates(Request request) throws SAXException 1044 { 1045 String startDate = request.getParameter("startDate"); 1046 String endDate = request.getParameter("endDate"); 1047 1048 if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate) && startDate.compareTo(endDate) > 0) 1049 { 1050 String tmp = startDate; 1051 startDate = endDate; 1052 endDate = tmp; 1053 } 1054 1055 if (StringUtils.isNotBlank(startDate)) 1056 { 1057 XMLUtils.createElement(contentHandler, "startDate", startDate); 1058 } 1059 if (StringUtils.isNotBlank(endDate)) 1060 { 1061 XMLUtils.createElement(contentHandler, "endDate", endDate); 1062 } 1063 } 1064 1065 private void _saxTextField(Request request) throws SAXException 1066 { 1067 String textfield = request.getParameter("textfield"); 1068 XMLUtils.createElement(contentHandler, "textfield", textfield != null ? textfield : ""); 1069 } 1070 1071 private void _saxMetadataValues(Request request) throws SAXException 1072 { 1073 ZoneItem zoneItem = getZoneItem(request); 1074 if (zoneItem != null && zoneItem.getServiceParameters().hasMetadata("search-by-metadata")) 1075 { 1076 String[] metadataPaths = zoneItem.getServiceParameters().getStringArray("search-by-metadata"); 1077 if (metadataPaths.length > 0) 1078 { 1079 XMLUtils.startElement(contentHandler, "metadata"); 1080 for (String metadataPath : metadataPaths) 1081 { 1082 String[] values = request.getParameterValues("metadata-" + metadataPath.replaceAll("/", ".")); 1083 if (values != null) 1084 { 1085 for (String value : values) 1086 { 1087 AttributesImpl attrs = new AttributesImpl(); 1088 attrs.addCDATAAttribute("name", metadataPath); 1089 XMLUtils.createElement(contentHandler, "metadata", attrs, value != null ? value : ""); 1090 } 1091 } 1092 } 1093 XMLUtils.endElement(contentHandler, "metadata"); 1094 } 1095 } 1096 } 1097 1098 private void _saxAllWords(Request request) throws SAXException 1099 { 1100 String textfield = request.getParameter("all-words"); 1101 XMLUtils.createElement(contentHandler, "all-words", textfield != null ? textfield : ""); 1102 } 1103 1104 private void _saxExactWording(Request request) throws SAXException 1105 { 1106 String textfield = request.getParameter("exact-wording"); 1107 XMLUtils.createElement(contentHandler, "exact-wording", textfield != null ? textfield : ""); 1108 } 1109 1110 private void _saxNoWords(Request request) throws SAXException 1111 { 1112 String textfield = request.getParameter("no-words"); 1113 XMLUtils.createElement(contentHandler, "no-words", textfield != null ? textfield : ""); 1114 } 1115 1116 private void _saxContentType(Request request) throws SAXException 1117 { 1118 String[] cTypes = request.getParameterValues("content-types"); 1119 if (cTypes != null && cTypes.length > 0 && !(cTypes.length == 1 && cTypes[0].equals(""))) 1120 { 1121 for (String cType : cTypes) 1122 { 1123 XMLUtils.createElement(contentHandler, "content-type", cType); 1124 } 1125 } 1126 } 1127 1128 private void _saxTags(Request request) throws SAXException 1129 { 1130 String size = request.getParameter("tags-size"); 1131 if (!StringUtils.isEmpty(size)) 1132 { 1133 int nbCat = Integer.parseInt(size); 1134 for (int i = 1; i < nbCat + 1; i++) 1135 { 1136 String[] tags = request.getParameterValues("tags-" + i); 1137 if (tags != null && tags.length > 0 && !(tags.length == 1 && tags[0].equals(""))) 1138 { 1139 if (tags.length == 1) 1140 { 1141 tags = tags[0].split(","); 1142 } 1143 1144 for (String tag : tags) 1145 { 1146 XMLUtils.createElement(contentHandler, "tag", tag); 1147 } 1148 1149 } 1150 } 1151 } 1152 1153 String[] tags = request.getParameterValues("tags"); 1154 if (tags != null && tags.length > 0 && !(tags.length == 1 && tags[0].equals(""))) 1155 { 1156 for (String tag : tags) 1157 { 1158 XMLUtils.createElement(contentHandler, "tag", tag); 1159 } 1160 } 1161 } 1162 1163 private void _saxPages(Request request) throws SAXException 1164 { 1165 String[] pages = request.getParameterValues("pages"); 1166 if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals(""))) 1167 { 1168 for (String id : pages) 1169 { 1170 XMLUtils.createElement(contentHandler, "page", id); 1171 } 1172 } 1173 } 1174 1175 private void _saxMultisite(Request request) throws SAXException 1176 { 1177 boolean multisite = request.getParameter("multisite") != null; 1178 if (multisite) 1179 { 1180 XMLUtils.createElement(contentHandler, "multisite"); 1181 1182 String[] sites = request.getParameterValues("sites"); 1183 if (sites != null && sites.length > 0 && !(sites.length == 1 && sites[0].equals(""))) 1184 { 1185 for (String site : sites) 1186 { 1187 XMLUtils.createElement(contentHandler, "site", site); 1188 } 1189 } 1190 1191 } 1192 } 1193 1194 /** 1195 * Get the content type's 1196 * 1197 * @param request The request 1198 * @return the content type's 1199 */ 1200 protected Collection<String> getContentTypes(Request request) 1201 { 1202 String[] cTypes = request.getParameterValues("content-types"); 1203 1204 if (cTypes != null && cTypes.length > 0 && !(cTypes.length == 1 && cTypes[0].equals(""))) 1205 { 1206 if (cTypes.length == 1) 1207 { 1208 cTypes = cTypes[0].split(","); 1209 } 1210 1211 return Arrays.asList(cTypes); 1212 } 1213 return _getAvailableContentTypes(request); 1214 } 1215 1216 private Collection<String> _getAvailableContentTypes(Request request) 1217 { 1218 @SuppressWarnings("unchecked") 1219 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 1220 if (parentContext == null) 1221 { 1222 parentContext = Collections.emptyMap(); 1223 } 1224 1225 String[] serviceCTypes = (String[]) parentContext.get("search-by-content-types"); 1226 1227 ContentTypeSearch contentTypeSearch = getContentTypeSearch(request); 1228 1229 if (ArrayUtils.isNotEmpty(serviceCTypes) && StringUtils.isNotBlank(serviceCTypes[0])) 1230 { 1231 return Arrays.asList(serviceCTypes); 1232 } 1233 else if (!contentTypeSearch.equals(ContentTypeSearch.NONE)) 1234 { 1235 return _getAllContentTypes(); 1236 } 1237 1238 return Collections.emptyList(); 1239 } 1240 1241 private Set<String> _getAllContentTypes() 1242 { 1243 Set<String> allCTypes = new HashSet<>(_cTypeExtPt.getExtensionsIds()); 1244 allCTypes.add("resource"); 1245 return allCTypes; 1246 } 1247 1248 private void _saxContentTypeCriteria(Request request) throws SAXException 1249 { 1250 Collection<String> cTypes = _getAvailableContentTypes(request); 1251 1252 XMLUtils.startElement(contentHandler, "content-types"); 1253 for (String cTypeId : cTypes) 1254 { 1255 if ("resource".equals(cTypeId)) 1256 { 1257 AttributesImpl attr = new AttributesImpl(); 1258 attr.addCDATAAttribute("id", cTypeId); 1259 XMLUtils.startElement(contentHandler, "type", attr); 1260 new I18nizableText("plugin.web", "PLUGINS_WEB_SERVICE_FRONT_SEARCH_ON_DOCUMENTS").toSAX(contentHandler); 1261 XMLUtils.endElement(contentHandler, "type"); 1262 } 1263 else if (StringUtils.isNotEmpty(cTypeId)) 1264 { 1265 ContentType cType = _cTypeExtPt.getExtension(cTypeId); 1266 1267 if (cType != null) 1268 { 1269 AttributesImpl attr = new AttributesImpl(); 1270 attr.addCDATAAttribute("id", cTypeId); 1271 XMLUtils.startElement(contentHandler, "type", attr); 1272 cType.getLabel().toSAX(contentHandler); 1273 XMLUtils.endElement(contentHandler, "type"); 1274 } 1275 else 1276 { 1277 getLogger().warn("Cannot sax information about the unexising ContentType '" + cTypeId + "' for url " + request.getRequestURI()); 1278 } 1279 } 1280 1281 } 1282 XMLUtils.endElement(contentHandler, "content-types"); 1283 } 1284 1285 private void _saxTagsCriteria(String siteName) throws SAXException 1286 { 1287 @SuppressWarnings("unchecked") 1288 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 1289 if (parentContext == null) 1290 { 1291 parentContext = Collections.emptyMap(); 1292 } 1293 1294 Map<String, Object> contextParameters = new HashMap<>(); 1295 contextParameters.put("siteName", siteName); 1296 1297 String[] tagArray = (String[]) parentContext.get("search-by-tags"); 1298 if (ArrayUtils.isNotEmpty(tagArray)) 1299 { 1300 XMLUtils.startElement(contentHandler, "tags"); 1301 for (String tagName : tagArray) 1302 { 1303 if (StringUtils.isNotEmpty(tagName)) 1304 { 1305 boolean found = false; 1306 Set<String> extensionsIds = _tagExtPt.getExtensionsIds(); 1307 1308 for (String id : extensionsIds) 1309 { 1310 if (found) 1311 { 1312 continue; 1313 } 1314 1315 I18nizableText label = null; 1316 Map<String, Tag> tags = null; 1317 1318 TagProvider tagProvider = _tagExtPt.getExtension(id); 1319 if (tagName.startsWith("provider_") && id.equals(tagName.substring("provider_".length()))) 1320 { 1321 found = true; 1322 1323 label = tagProvider.getLabel(); 1324 1325 tags = tagProvider.getTags(contextParameters); 1326 } 1327 else if (tagProvider.hasTag(tagName, contextParameters)) 1328 { 1329 found = true; 1330 1331 Tag tag = tagProvider.getTag(tagName, contextParameters); 1332 label = tag.getTitle(); 1333 tags = tag.getTags(); 1334 } 1335 1336 if (found) 1337 { 1338 AttributesImpl attr = new AttributesImpl(); 1339 attr.addCDATAAttribute("id", tagName); 1340 XMLUtils.startElement(contentHandler, "tag", attr); 1341 1342 if (label != null) 1343 { 1344 label.toSAX(contentHandler, "title"); 1345 } 1346 1347 if (tags != null) 1348 { 1349 for (Tag child : tags.values()) 1350 { 1351 if (child.getTarget().getName().equals("CONTENT")) 1352 { 1353 _saxTag(child, true); 1354 } 1355 } 1356 } 1357 1358 XMLUtils.endElement(contentHandler, "tag"); 1359 } 1360 } 1361 } 1362 } 1363 XMLUtils.endElement(contentHandler, "tags"); 1364 } 1365 } 1366 1367 private void _saxTag(Tag tag, boolean recursive) throws SAXException 1368 { 1369 AttributesImpl attr = new AttributesImpl(); 1370 attr.addCDATAAttribute("id", tag.getName()); 1371 XMLUtils.startElement(contentHandler, "tag", attr); 1372 tag.getTitle().toSAX(contentHandler, "title"); 1373 1374 if (recursive) 1375 { 1376 for (Tag child : tag.getTags().values()) 1377 { 1378 _saxTag(child, true); 1379 } 1380 } 1381 1382 XMLUtils.endElement(contentHandler, "tag"); 1383 } 1384 1385 private void _saxSitemapCriteria() throws SAXException 1386 { 1387 @SuppressWarnings("unchecked") 1388 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 1389 if (parentContext == null) 1390 { 1391 parentContext = Collections.emptyMap(); 1392 } 1393 1394 String[] pages = (String[]) parentContext.get("search-by-pages"); 1395 if (ArrayUtils.isNotEmpty(pages)) 1396 { 1397 XMLUtils.startElement(contentHandler, "pages"); 1398 1399 for (String pageID : pages) 1400 { 1401 if (StringUtils.isNotEmpty(pageID)) 1402 { 1403 Page page = _resolver.resolveById(pageID); 1404 AttributesImpl attr = new AttributesImpl(); 1405 attr.addCDATAAttribute("id", pageID); 1406 attr.addCDATAAttribute("path", page.getSitemap().getName() + "/" + page.getPathInSitemap()); 1407 attr.addCDATAAttribute("title", page.getTitle()); 1408 attr.addCDATAAttribute("long-title", page.getLongTitle()); 1409 XMLUtils.createElement(contentHandler, "page", attr); 1410 } 1411 } 1412 1413 XMLUtils.endElement(contentHandler, "pages"); 1414 } 1415 } 1416 1417 /** 1418 * Get the content type's filter value 1419 * 1420 * @param request The request 1421 * @return the content type's filter value 1422 */ 1423 protected String getContentTypeFilterValue(Request request) 1424 { 1425 Enumeration<String> paramNames = request.getParameterNames(); 1426 while (paramNames.hasMoreElements()) 1427 { 1428 String paramName = paramNames.nextElement(); 1429 if (paramName.startsWith("ctype-filter-")) 1430 { 1431 return paramName.substring("ctype-filter-".length()); 1432 } 1433 } 1434 1435 if (request.getParameter("current-ctype-filter") != null) 1436 { 1437 return request.getParameter("current-ctype-filter"); 1438 } 1439 1440 return null; 1441 } 1442 1443 /** 1444 * Add the text field query 1445 * @param queries the queries 1446 * @param language the language 1447 * @param request the request 1448 */ 1449 protected void addTextFieldQuery(Collection<Query> queries, String language, Request request) 1450 { 1451 String text = request.getParameter("textfield"); 1452 1453 if (StringUtils.isNotBlank(text)) 1454 { 1455 String trimText = text.trim(); 1456 String escapedText = _escapeQueryCharsButNotQuotes(trimText); 1457 1458 queries.add(new FullTextQuery(escapedText, language, Operator.SEARCH, true)); 1459 } 1460 } 1461 1462 /** 1463 * Get the queries for content's attachments and resources 1464 * @param subQuery the query for attachments and resources 1465 * @return the join query for content's attachments and resources 1466 */ 1467 protected List<Query> getContentResourcesOrAttachmentQueries(Query subQuery) 1468 { 1469 if (subQuery instanceof MatchAllQuery) 1470 { 1471 return Collections.emptyList(); 1472 } 1473 1474 List<Query> queries = new ArrayList<>(); 1475 // join query on outgoing resources 1476 queries.add(new JoinQuery(subQuery, CONTENT_OUTGOING_REFEERENCES_RESOURCE_IDS)); 1477 // join query on content's attachments 1478 queries.add(() -> "{!join from=" + RESOURCE_ANCESTOR_IDS + " to=" + CONTENT_OUTGOING_REFEERENCES_RESOURCE_IDS + " v=\"" + _buildSubQuery(subQuery) + "\"}"); 1479 return queries; 1480 } 1481 1482 /** 1483 * Get the queries for page's attachments and resources 1484 * @param subQuery the query for attachments and resources 1485 * @return the join query for content's attachments and resources 1486 */ 1487 protected List<Query> getPageResourcesOrAttachmentQueries(Query subQuery) 1488 { 1489 List<Query> queries = new ArrayList<>(); 1490 // join query on outgoing resources 1491 queries.add(new JoinQuery(subQuery, PAGE_OUTGOING_REFEERENCES_RESOURCE_IDS)); 1492 // join query on page's attachments or explorer folder service 1493 queries.add(() -> "{!join from=" + RESOURCE_ANCESTOR_IDS + " to=" + PAGE_OUTGOING_REFEERENCES_RESOURCE_IDS + " v=\"" + _buildSubQuery(subQuery) + "\"}"); 1494 return queries; 1495 } 1496 1497 private String _buildSubQuery(Query subQuery) throws QuerySyntaxException 1498 { 1499 return ClientUtils.escapeQueryChars(subQuery.build()); 1500 } 1501 1502 /* 1503 * Do not escape double quotes for exact searching 1504 */ 1505 private String _escapeQueryCharsButNotQuotes(String text) 1506 { 1507 StringBuilder sb = new StringBuilder(); 1508 String[] parts = StringUtils.splitPreserveAllTokens(text, '"'); 1509 for (int i = 0; i < parts.length; i++) 1510 { 1511 if (i != 0) 1512 { 1513 sb.append("\""); 1514 } 1515 String part = parts[i]; 1516 sb.append(ClientUtils.escapeQueryChars(part)); 1517 } 1518 1519 return sb.toString(); 1520 } 1521 1522 /** 1523 * Add query for each metadata 1524 * 1525 * @param queries The list of query 1526 * @param language The lang 1527 * @param request The request 1528 * @throws IllegalArgumentException If an error occurred 1529 */ 1530 protected void addMetadataQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException 1531 { 1532 ZoneItem zoneItem = getZoneItem(request); 1533 Set<String> facetFields = getFacets(request).keySet(); 1534 if (zoneItem != null && zoneItem.getServiceParameters().hasMetadata("search-by-metadata")) 1535 { 1536 String[] metadataPaths = zoneItem.getServiceParameters().getStringArray("search-by-metadata"); 1537 for (String metadataPath : metadataPaths) 1538 { 1539 if (facetFields.contains(metadataPath)) 1540 { 1541 // will be handled in org.ametys.web.frontoffice.SearchGenerator.getFacetValues(Request, Collection<String>, String) 1542 continue; 1543 } 1544 String value = request.getParameter("metadata-" + metadataPath.replaceAll("/", ".")); 1545 1546 if (StringUtils.isNotBlank(value)) 1547 { 1548 Collection<String> contentTypes = getContentTypes(request); 1549 1550 MetadataDefinition metadataDefinition = contentTypes.isEmpty() ? null 1551 : _contentTypesHelper.getMetadataDefinition(metadataPath, (String[]) contentTypes.toArray(), null); 1552 1553 Query query; 1554 if (metadataDefinition != null && (metadataDefinition.getEnumerator() != null || metadataDefinition.getType().equals(MetadataType.CONTENT))) 1555 { 1556 // Exact search 1557 query = new StringQuery(metadataPath, Operator.EQ, value, language); 1558 } 1559 else if (value.startsWith("\"") && value.endsWith("\"")) 1560 { 1561 String escapedText = ClientUtils.escapeQueryChars(value.substring(1, value.length() - 1)); // the StringQuery with EQ operator will add quotes characters anyway, so remove them 1562 query = new StringQuery(metadataPath, Operator.EQ, escapedText, language, true); 1563 } 1564 else 1565 { 1566 String escapedText = _escapedTextForSearch(value); 1567 Operator op = escapedText.startsWith("*") || escapedText.endsWith("*") ? Operator.LIKE : Operator.SEARCH; 1568 boolean doNotUseLanguage = op == Operator.LIKE && metadataDefinition != null && metadataDefinition.getType() != MetadataType.MULTILINGUAL_STRING; 1569 query = new StringQuery(metadataPath, op, escapedText, doNotUseLanguage ? null : language, true); 1570 } 1571 1572 queries.add(query); 1573 } 1574 } 1575 } 1576 } 1577 1578 private String _escapedTextForSearch(String value) 1579 { 1580 // Allow wildcards at the beginning and at the end 1581 // Ex.: 1582 // zert will stay zert to not match azerty 1583 // *zert will stay *zert to not match azerty and match azert 1584 // zert* will stay zert* to not match azerty and match zerty 1585 // *zert* will stay *zert* to match azerty 1586 // where zert is escaped 1587 1588 String valueToEscape = value; 1589 String begin = ""; 1590 String end = ""; 1591 boolean startsWithStar = value.startsWith("*"); 1592 boolean endsWithStar = value.endsWith("*"); 1593 if (startsWithStar) 1594 { 1595 valueToEscape = valueToEscape.substring(1); 1596 begin = "*"; 1597 } 1598 if (endsWithStar) 1599 { 1600 valueToEscape = valueToEscape.substring(0, valueToEscape.length() - 1); 1601 end = "*"; 1602 } 1603 1604 // escape special characters 1605 String escapedValue = ClientUtils.escapeQueryChars(valueToEscape).toLowerCase(); 1606 String escapedText = begin + escapedValue + end; 1607 return escapedText; 1608 } 1609 1610 /** 1611 * Add content query for "all words" 1612 * @param queries The queries 1613 * @param language The current language 1614 * @param request the request 1615 */ 1616 protected void addAllWordsTextFieldQuery(Collection<Query> queries, String language, Request request) 1617 { 1618 // TODO All words? OrQuery of AndQuery instead? 1619 String words = request.getParameter("all-words"); 1620 1621 if (StringUtils.isNotBlank(words)) 1622 { 1623 StringBuilder allWords = new StringBuilder(); 1624 for (String word : StringUtils.split(words)) 1625 { 1626 String escapedWord = ClientUtils.escapeQueryChars(word); 1627 if (allWords.length() > 0) 1628 { 1629 allWords.append(' '); 1630 } 1631 allWords.append('+').append(escapedWord); 1632 } 1633 1634 // queries.add(new FullTextQuery(allWords.toString(), language, 1635 // Operator.SEARCH)); 1636 1637 queries.add(new FullTextQuery(allWords.toString(), language, Operator.SEARCH, true)); 1638 } 1639 } 1640 1641 /** 1642 * Add content query for exact wording or phrase 1643 * @param queries The queries 1644 * @param language The current language 1645 * @param request the request 1646 */ 1647 protected void addExactWordingTextFieldQuery(Collection<Query> queries, String language, Request request) 1648 { 1649 String exact = request.getParameter("exact-wording"); 1650 1651 if (StringUtils.isNotBlank(exact)) 1652 { 1653 queries.add(new FullTextQuery(exact, language, Operator.EQ)); 1654 } 1655 } 1656 1657 /** 1658 * Add content query for "none of these words" 1659 * @param queries The queries 1660 * @param language The current language 1661 * @param request the request 1662 */ 1663 protected void addNoWordsTextFieldQuery(Collection<Query> queries, String language, Request request) 1664 { 1665 String noWords = request.getParameter("no-words"); 1666 1667 if (StringUtils.isNotBlank(noWords)) 1668 { 1669 StringBuilder allWords = new StringBuilder(); 1670 for (String word : StringUtils.split(noWords)) 1671 { 1672 String escapedWord = ClientUtils.escapeQueryChars(word); 1673 if (allWords.length() > 0) 1674 { 1675 allWords.append(' '); 1676 } 1677 allWords.append('+').append(escapedWord); 1678 } 1679 1680 // queries.add(new NotQuery(new FullTextQuery(allWords.toString(), 1681 // language, Operator.SEARCH))); 1682 1683 queries.add(new NotQuery(new FullTextQuery(allWords.toString(), language, Operator.SEARCH, true))); 1684 } 1685 } 1686 1687 /** 1688 * Add the content type query 1689 * @param queries The queries 1690 * @param request The request 1691 */ 1692 protected void addContentTypeQuery(Collection<Query> queries, Request request) 1693 { 1694 Collection<String> cTypes = new ArrayList<>(getContentTypes(request)); 1695 1696 // Resource is handled in the "document types" filter. 1697 cTypes.remove("resource"); 1698 1699 if (!cTypes.isEmpty()) 1700 { 1701 Query notAPageQuery = new DocumentTypeQuery(TYPE_PAGE, false); 1702 Query pageWithContentOfTypesQuery = new PageContentQuery(new ContentTypeQuery(cTypes)); 1703 // If document is not a page, it will match this fq (it will be filtered if necessary in the "document types" filter, see #getDocumentTypesQuery()) 1704 // If document is a page, then one of its contents must match one of the given content types 1705 queries.add(new OrQuery(notAPageQuery, pageWithContentOfTypesQuery)); 1706 } 1707 } 1708 1709 /** 1710 * Add the tags query 1711 * @param queries The queries 1712 * @param request The request 1713 */ 1714 protected void addTagsQuery(Collection<Query> queries, Request request) 1715 { 1716 String size = request.getParameter("tags-size"); 1717 if (!StringUtils.isEmpty(size)) 1718 { 1719 @SuppressWarnings("unchecked") 1720 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 1721 if (parentContext == null) 1722 { 1723 parentContext = Collections.emptyMap(); 1724 } 1725 1726 boolean isStrictSearch = parameters.getParameterAsBoolean("strict-search-on-tags", true); 1727 1728 int nbCat = Integer.parseInt(size); 1729 for (int i = 1; i < nbCat + 1; i++) 1730 { 1731 String[] tags = request.getParameterValues("tags-" + i); 1732 if (tags != null && tags.length > 0 && !(tags.length == 1 && tags[0].equals(""))) 1733 { 1734 List<Query> tagQueries = new ArrayList<>(); 1735 1736 for (String tag : tags) 1737 { 1738 String[] values = StringUtils.split(tag, ','); 1739 1740 tagQueries.add(new TagQuery(Operator.EQ, !isStrictSearch, values)); 1741 } 1742 1743 queries.add(new PageContentQuery(new OrQuery(tagQueries))); 1744 } 1745 } 1746 } 1747 } 1748 1749 /** 1750 * Add the pages query 1751 * @param queries The queries 1752 * @param request The request 1753 */ 1754 protected void addPagesQuery(Collection<Query> queries, Request request) 1755 { 1756 List<Query> pageQueries = new ArrayList<>(); 1757 1758 String[] pages = request.getParameterValues("pages"); 1759 if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals(""))) 1760 { 1761 for (String pageIds : pages) 1762 { 1763 for (String pageId : StringUtils.split(pageIds, ",")) 1764 { 1765 pageQueries.add(new PageQuery(pageId, true)); 1766 } 1767 } 1768 } 1769 1770 queries.add(new OrQuery(pageQueries)); 1771 } 1772 1773 /** 1774 * Add the query on start and end dates 1775 * @param queries The queries 1776 * @param request The request 1777 */ 1778 protected void addDateQuery(Collection<Query> queries, Request request) 1779 { 1780 String startDateId = parameters.getParameter("startDate", ""); 1781 String endDateId = parameters.getParameter("endDate", ""); 1782 1783 String startDateStr = request.getParameter("startDate"); 1784 String endDateStr = request.getParameter("endDate"); 1785 1786 // We check if startDateStr < endDateStr. If not, we swap. 1787 if (StringUtils.isNotBlank(endDateStr) && StringUtils.isNotBlank(startDateStr) && startDateStr.compareTo(endDateStr) > 0) 1788 { 1789 String dateAux = startDateStr; 1790 startDateStr = endDateStr; 1791 endDateStr = dateAux; 1792 } 1793 1794 String startPropId = startDateId.equals("last-modified") ? SolrFieldNames.LAST_MODIFIED : startDateId; 1795 String endPropId = endDateId.equals("last-modified") ? SolrFieldNames.LAST_MODIFIED : endDateId; 1796 1797 if (StringUtils.isNotBlank(endDateStr)) 1798 { 1799 Date endDate = _toDate(endDateStr); 1800 // Join for returning page documents 1801 queries.add(new PageContentQuery(new DateQuery(startPropId, Operator.LE, endDate))); 1802 } 1803 1804 if (StringUtils.isNotBlank(startDateStr)) 1805 { 1806 Date startDate = _toDate(startDateStr); 1807 1808 if (startDate != null) 1809 { 1810 // Two cases are possible : 1811 // An event could have an end date (query 1) 1812 // If not, the start date is also the end date (query 2) 1813 List<Query> endDateQueries = new ArrayList<>(); 1814 1815 if (StringUtils.isNotBlank(endPropId)) 1816 { 1817 endDateQueries.add(new DateQuery(endPropId, Operator.GE, startDate)); 1818 } 1819 endDateQueries.add(new DateQuery(startPropId, Operator.GE, startDate)); 1820 1821 // Join for returning page documents 1822 queries.add(new PageContentQuery(new OrQuery(endDateQueries))); 1823 } 1824 } 1825 } 1826 1827 private Date _toDate(String dateStr) 1828 { 1829 try 1830 { 1831 LocalDate dt = LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE); 1832 return DateUtils.asDate(dt); 1833 } 1834 catch (DateTimeParseException e) 1835 { 1836 getLogger().error("Invalid date format " + dateStr, e); 1837 } 1838 return null; 1839 } 1840 1841 private List<QueryAdapterFOSearch> _getSortedListQueryAdapter() 1842 { 1843 List<QueryAdapterFOSearch> queryAdapters = new ArrayList<>(); 1844 for (String queryAdapterFOSearchId : _queryAdapterFOSearchEP.getExtensionsIds()) 1845 { 1846 queryAdapters.add(_queryAdapterFOSearchEP.getExtension(queryAdapterFOSearchId)); 1847 } 1848 1849 // Order queryAdapters (0 is first, Integer.MAX_INT is last) 1850 Collections.sort(queryAdapters, new Comparator<QueryAdapterFOSearch>() 1851 { 1852 @Override 1853 public int compare(QueryAdapterFOSearch q1, QueryAdapterFOSearch q2) 1854 { 1855 return new Integer(q1.getPriority()).compareTo(new Integer(q2.getPriority())); 1856 } 1857 }); 1858 1859 return queryAdapters; 1860 } 1861 1862}