PageRenderTime 51ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/ezfezpsolrquerybuilder.php

https://github.com/pedroresende/ezfind
PHP | 1929 lines | 1264 code | 214 blank | 451 comment | 174 complexity | 8dd80181eee95da8bbef591df003fea7 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. //
  3. //
  4. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  5. // SOFTWARE NAME: eZ Find
  6. // SOFTWARE RELEASE: 1.0.x
  7. // COPYRIGHT NOTICE: Copyright (C) 1999-2013 eZ Systems AS
  8. // SOFTWARE LICENSE: GNU General Public License v2.0
  9. // NOTICE: >
  10. // This program is free software; you can redistribute it and/or
  11. // modify it under the terms of version 2.0 of the GNU General
  12. // Public License as published by the Free Software Foundation.
  13. //
  14. // This program is distributed in the hope that it will be useful,
  15. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. // GNU General Public License for more details.
  18. //
  19. // You should have received a copy of version 2.0 of the GNU General
  20. // Public License along with this program; if not, write to the Free
  21. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  22. // MA 02110-1301, USA.
  23. //
  24. //
  25. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  26. //
  27. /*! \file ezfezpsolrquerybuilder.php
  28. */
  29. /*!
  30. \class ezfeZPSolrQueryBuilder ezfezpsolrquerybuilder.php
  31. \brief The class ezfeZPSolrQueryBuilder does
  32. */
  33. class ezfeZPSolrQueryBuilder
  34. {
  35. /**
  36. * Constructor
  37. *
  38. * Sets variables for creating a new instance of ezfeZPSolrQueryBuilder
  39. * @param Object $searchPluginInstance Search engine instance. Allows the query builder to
  40. * communicate with the caller ( eZSolr instance ).
  41. */
  42. function ezfeZPSolrQueryBuilder( $searchPluginInstance )
  43. {
  44. $this->searchPluginInstance = $searchPluginInstance;
  45. }
  46. /**
  47. * @since eZ Find 2.0
  48. * build a multi field query, basically doing the same as a Lucene MultiField query
  49. * not always safe
  50. * @param string $searchText
  51. * @param array $solrFields
  52. * @param string $boostFields a hash array
  53. *
  54. */
  55. public function buildMultiFieldQuery( $searchText, $solrFields = array(), $boostFields = array() )
  56. {
  57. // simple implode implying an OR functionality
  58. $multiFieldQuery = '';
  59. // prepare boostfields arguments if any
  60. $processedBoostFields = array();
  61. foreach ( $boostFields as $baseName => $boostValue )
  62. {
  63. if ( strpos( $boostValue, ':' ) !== false && is_numeric( $baseName ) )
  64. {
  65. // split at the first colon, leave the rest intact
  66. list( $baseName, $boostValue ) = explode( ':', $boostValue, 2 );
  67. }
  68. if ( is_numeric( $boostValue ) )
  69. {
  70. // Get internal field name.
  71. $baseName = eZSolr::getFieldName( $baseName );
  72. $processedBoostFields[$baseName] = $boostValue;
  73. }
  74. }
  75. foreach ( $solrFields as $field )
  76. {
  77. //don't mind the last extra space, it's ignored by Solr
  78. $multiFieldQuery .= $field . ':(' . $searchText . ')';
  79. // check if we need to apply a boost
  80. if ( array_key_exists( $field, $processedBoostFields ) )
  81. {
  82. $multiFieldQuery .= '^' . $processedBoostFields[$field];
  83. }
  84. $multiFieldQuery .= ' ';
  85. }
  86. return $multiFieldQuery;
  87. }
  88. /**
  89. * Search on the Solr search server
  90. *
  91. * @param string search term
  92. * @param array parameters.
  93. * Example:
  94. * <code>
  95. * array( 'SearchOffset' => <offset>,
  96. * 'SearchLimit' => <limit>,
  97. * 'SearchSubTreeArray' => array( <node ID1>[, <node ID2>]... ),
  98. * 'SearchContentClassID' => array( <class ID1>[, <class ID2>]... ),
  99. * 'SearchContentClassAttributeID' => <class attribute ID>,
  100. * 'Facet' => array( array( 'field' => <class identifier>/<attribute identifier>[/<option>], ... ) ) ),
  101. * 'Filter' => array( <base_name> => <value>, <base_name2> => <value2> ),
  102. * 'SortBy' => array( <field> => <asc|desc> [, <field2> => <asc|desc> [,...]] ) |
  103. * array( array( <field> => <asc|desc> )[, array( <field2> => <asc|desc> )[,...]] ),
  104. * 'BoostFunctions' => array( 'fields' => array(
  105. * 'article/title' => 2,
  106. * 'modified:5'
  107. * ),
  108. * 'functions' => array( 'rord(meta_modified_dt)^10' )
  109. * ),
  110. * 'ForceElevation' => false,
  111. * 'EnableElevation' => true
  112. * 'DistributedSearch" => array ( 'shards', array( 'shard1', 'shard2' , ... )
  113. * 'searchfields', array ( 'myfield1, 'myfield2', ... )
  114. * 'returnfields', array ( 'myfield1, 'myfield2', ... )
  115. * 'rawfilterlist, array ( 'foreignfield:a', '(foreignfield:b AND otherfield:c)', ... )
  116. * )
  117. * );
  118. * </code>
  119. * For full facet description, see facets design document.
  120. * For full description about 'ForceElevation', see elevate support design document ( elevate_support.rst.txt )
  121. *
  122. * the rawFilterList in distributed search is appended to the policyfilterlist with an 'OR' for each entry, as the policy list will
  123. * in general not be applicable to foreign indexes. To be used with care!
  124. *
  125. * @param array Search types. Reserved.
  126. *
  127. * @return array Solr query results.
  128. *
  129. * @see ezfeZPSolrQueryBuilder::buildBoostFunctions()
  130. */
  131. public function buildSearch( $searchText, $params = array(), $searchTypes = array() )
  132. {
  133. eZDebugSetting::writeDebug( 'extension-ezfind-query', $params, 'search params' );
  134. $searchCount = 0;
  135. $offset = ( isset( $params['SearchOffset'] ) && $params['SearchOffset'] ) ? $params['SearchOffset'] : 0;
  136. $limit = ( isset( $params['SearchLimit'] ) && $params['SearchLimit'] ) ? $params['SearchLimit'] : 10;
  137. $subtrees = isset( $params['SearchSubTreeArray'] ) ? $params['SearchSubTreeArray'] : array();
  138. $contentClassID = ( isset( $params['SearchContentClassID'] ) && $params['SearchContentClassID'] <> -1 ) ? $params['SearchContentClassID'] : false;
  139. $contentClassAttributeID = ( isset( $params['SearchContentClassAttributeID'] ) && $params['SearchContentClassAttributeID'] <> -1 ) ? $params['SearchContentClassAttributeID'] : false;
  140. $sectionID = isset( $params['SearchSectionID'] ) && $params['SearchSectionID'] > 0 ? $params['SearchSectionID'] : false;
  141. $dateFilter = isset( $params['SearchDate'] ) && $params['SearchDate'] > 0 ? $params['SearchDate'] : false;
  142. $asObjects = isset( $params['AsObjects'] ) ? $params['AsObjects'] : true;
  143. $spellCheck = isset( $params['SpellCheck'] ) && $params['SpellCheck'] > 0 ? $params['SpellCheck'] : array();
  144. $queryHandler = isset( $params['QueryHandler'] ) ? $params['QueryHandler'] : self::$FindINI->variable( 'SearchHandler', 'DefaultSearchHandler' );
  145. // eZFInd 2.3: check ini setting and take it as a default instead of false
  146. $visibilityDefaultSetting = self::$SiteINI->variable( 'SiteAccessSettings', 'ShowHiddenNodes' );
  147. $visibilityDefault = ( $visibilityDefaultSetting === 'true' ) ? true : false;
  148. $ignoreVisibility = isset( $params['IgnoreVisibility'] ) ? $params['IgnoreVisibility'] : $visibilityDefault;
  149. $this->searchPluginInstance->postSearchProcessingData['ignore_visibility'] = $ignoreVisibility;
  150. $limitation = isset( $params['Limitation'] ) ? $params['Limitation'] : null;
  151. $boostFunctions = isset( $params['BoostFunctions'] ) ? $params['BoostFunctions'] : null;
  152. $forceElevation = isset( $params['ForceElevation'] ) ? $params['ForceElevation'] : false;
  153. $enableElevation = isset( $params['EnableElevation'] ) ? $params['EnableElevation'] : true;
  154. $distributedSearch = isset( $params['DistributedSearch'] ) ? $params['DistributedSearch'] : false;
  155. $fieldsToReturn = isset( $params['FieldsToReturn'] ) ? $params['FieldsToReturn'] : array();
  156. $highlightParams = isset( $params['HighLightParams'] ) ? $params['HighLightParams'] : array();
  157. $searchResultClusterParams = isset( $params['SearchResultClustering'] ) ? $params['SearchResultClustering'] : array();
  158. $extendedAttributeFilter = isset( $params['ExtendedAttributeFilter'] ) ? $params['ExtendedAttributeFilter'] : array();
  159. // distributed search option
  160. // @since ezfind 2.2
  161. $extraFieldsToSearch = array();
  162. $extraFieldsToReturn = array();
  163. $shardURLs = array();
  164. $iniShards = self::$SolrINI->variable( 'SolrBase' , 'Shards' );
  165. $shardQuery = NULL;
  166. $shardFilterQuery = array();
  167. if ( isset( $distributedSearch['shards'] ) )
  168. {
  169. foreach ( $distributedSearch['shards'] as $shard )
  170. {
  171. $shardURLs[] = $iniShards[$shard];
  172. }
  173. $shardQuery = implode( ',', $shardURLs );
  174. }
  175. if ( isset( $distributedSearch['searchfields'] ) )
  176. {
  177. $extraFieldsToSearch = $distributedSearch['searchfields'];
  178. }
  179. if ( isset( $distributedSearch['returnfields'] ) )
  180. {
  181. $extraFieldsToReturn = $distributedSearch['returnfields'];
  182. }
  183. if ( isset( $distributedSearch['rawfilterlist'] ) )
  184. {
  185. $shardFilterQuery = $distributedSearch['rawfilterlist'];
  186. }
  187. // check if filter parameter is indeed an array, and set it otherwise
  188. if ( isset( $params['Filter']) && ! is_array( $params['Filter'] ) )
  189. {
  190. $params['Filter'] = array( $params['Filter'] );
  191. }
  192. $filterQuery = array();
  193. // Add subtree query filter
  194. if ( !empty( $subtrees ) )
  195. {
  196. $this->searchPluginInstance->postSearchProcessingData['subtree_array'] = $subtrees;
  197. $subtreeQueryParts = array();
  198. foreach ( $subtrees as $subtreeNodeID )
  199. {
  200. $subtreeQueryParts[] = eZSolr::getMetaFieldName( 'path' ) . ':' . $subtreeNodeID;
  201. }
  202. $filterQuery[] = implode( ' OR ', $subtreeQueryParts );
  203. }
  204. // Add policy limitation query filter
  205. $policyLimitationFilterQuery = $this->policyLimitationFilterQuery( $limitation, $ignoreVisibility );
  206. if ( $policyLimitationFilterQuery !== false )
  207. {
  208. $filterQuery[] = $policyLimitationFilterQuery;
  209. }
  210. // Add time/date query filter
  211. if ( $dateFilter > 0 )
  212. {
  213. switch ( $dateFilter )
  214. {
  215. // last day
  216. case 1:
  217. $searchTimestamp = strtotime( '-1 day' );
  218. break;
  219. // last week
  220. case 2:
  221. $searchTimestamp = strtotime( '-1 week' );
  222. break;
  223. // last month
  224. case 3:
  225. $searchTimestamp = strtotime( '-1 month' );
  226. break;
  227. // last three month
  228. case 4:
  229. $searchTimestamp = strtotime( '-3 month' );
  230. break;
  231. // last year
  232. case 5:
  233. $searchTimestamp = strtotime( '-1 year' );
  234. break;
  235. }
  236. $filterQuery[] = eZSolr::getMetaFieldName( 'published' ) . ':[' . ezfSolrDocumentFieldBase::preProcessValue( $searchTimestamp, 'date' ) .'/DAY TO *]';
  237. }
  238. if ( (!eZContentObjectTreeNode::showInvisibleNodes() || !$ignoreVisibility ) && ( self::$FindINI->variable( 'SearchFilters', 'FilterHiddenFromDB' ) == 'enabled' ) )
  239. {
  240. $db = eZDB::instance();
  241. $invisibleNodeIDArray = $db->arrayQuery( 'SELECT node_id FROM ezcontentobject_tree WHERE ezcontentobject_tree.is_invisible = 1', array( 'column' => 0) );
  242. $hiddenNodesQueryText = 'meta_main_node_id_si:[* TO *] -meta_main_node_id_si:(';
  243. foreach ( $invisibleNodeIDArray as $element )
  244. {
  245. $hiddenNodesQueryText = $hiddenNodesQueryText . $element['node_id'] . ' ';
  246. }
  247. $hiddenNodesQueryText = $hiddenNodesQueryText . ')';
  248. // only add filter if there are hidden nodes after all
  249. if ( $invisibleNodeIDArray )
  250. {
  251. $filterQuery[] = $hiddenNodesQueryText;
  252. }
  253. }
  254. // Add content class query filter
  255. $classLimitationFilter = $this->getContentClassFilterQuery( $contentClassID );
  256. if ( $classLimitationFilter !== null )
  257. {
  258. $filterQuery[] = $classLimitationFilter;
  259. }
  260. // Add section to query filter.
  261. if ( $sectionID )
  262. {
  263. $filterQuery[] = eZSolr::getMetaFieldName( 'section_id' ) . ':' . $sectionID;
  264. }
  265. $languageFilterQuery = $this->buildLanguageFilterQuery();
  266. if ( $languageFilterQuery )
  267. {
  268. $filterQuery[] = $languageFilterQuery;
  269. }
  270. $paramFilterQuery = $this->getParamFilterQuery( $params );
  271. if ( $paramFilterQuery )
  272. {
  273. $filterQuery[] = $paramFilterQuery;
  274. }
  275. //add raw filters
  276. if ( self::$FindINI->hasVariable( 'SearchFilters', 'RawFilterList' ) )
  277. {
  278. $rawFilters = self::$FindINI->variable( 'SearchFilters', 'RawFilterList' );
  279. if ( is_array( $rawFilters ) )
  280. {
  281. $filterQuery = array_merge( $filterQuery, $rawFilters );
  282. }
  283. }
  284. // Build and get facet query prameters.
  285. $facetQueryParamList = $this->buildFacetQueryParamList( $params );
  286. // search only text type declared fields
  287. $fieldTypeExcludeList = $this->fieldTypeExludeList( NULL );
  288. // Create sort parameters based on the parameters.
  289. $sortParameter = $this->buildSortParameter( $params );
  290. //the array_unique below is necessary because attribute identifiers are not unique .. and we get as
  291. //much highlight snippets as there are duplicate attribute identifiers
  292. //these are also in the list of query fields (dismax, ezpublish) request handlers
  293. $queryFields = array_unique( $this->getClassAttributes( $contentClassID, $contentClassAttributeID, $fieldTypeExcludeList ) );
  294. //highlighting only in the attributes, otherwise the object name is repeated in the highlight, which is already
  295. //partly true as it is mostly composed of one or more attributes.
  296. //maybe we should add meta data to the index to filter them out.
  297. $highLightFields = $queryFields;
  298. //@since eZ Find 2.3
  299. //when dedicated attributes are searched for, don't add meta-fields to the $queryfields list
  300. if ( !$contentClassAttributeID )
  301. {
  302. $queryFields[] = eZSolr::getMetaFieldName( 'name' );
  303. $queryFields[] = eZSolr::getMetaFieldName( 'owner_name' );
  304. }
  305. $spellCheckParamList = array();
  306. // @param $spellCheck expects array (true|false, dictionary identifier, ...)
  307. if ( ( isset( $spellCheck[0] ) and $spellCheck[0] ) or
  308. ( self::$FindINI->variable( 'SpellCheck', 'SpellCheck' ) == 'enabled' and ( isset( $spellCheck[0] ) and !$spellCheck[0] ) ) )
  309. {
  310. $dictionary = isset( $spellCheck[1]) ? $spellCheck[1] : self::$FindINI->variable( 'SpellCheck', 'DefaultDictionary' );
  311. $spellCheckParamList = array(
  312. 'spellcheck' => 'true',
  313. // q is manipulated in case of standard request handler, so make it explicit by using spellcheck.q
  314. 'spellcheck.q' => $searchText,
  315. 'spellcheck.dictionary' => $dictionary,
  316. 'spellcheck.collate' => 'true',
  317. 'spellcheck.extendedResults' => 'true',
  318. 'spellcheck.onlyMorePopular' => 'true',
  319. 'spellcheck.count' => 1);
  320. }
  321. // Create the Elevate-related parameters here :
  322. $elevateParamList = eZFindElevateConfiguration::getRuntimeQueryParameters( $forceElevation, $enableElevation, $searchText );
  323. // process query handler: standard, simplestandard, ezpublish, heuristic
  324. // first determine which implemented handler to use when heuristic is specified
  325. if ( strtolower( $queryHandler ) === 'heuristic' )
  326. {
  327. // @todo: this code will evolve of course
  328. if ( preg_match( '/[\^\*\~]|AND|OR/', $searchText) > 0 )
  329. {
  330. $queryHandler = 'simplestandard';
  331. }
  332. else
  333. {
  334. $queryHandler = 'ezpublish';
  335. }
  336. }
  337. $handlerParameters = array();
  338. $queryHandler = strtolower( $queryHandler );
  339. switch ( $queryHandler )
  340. {
  341. case 'standard':
  342. // @todo: this is more complicated
  343. // build the query against all "text" like fields
  344. // should take into account all the filter fields and class filters to shorten the query
  345. // need to build: Solr q
  346. if ( array_key_exists( 'fields', $boostFunctions ) )
  347. {
  348. $handlerParameters = array ( 'q' => $this->buildMultiFieldQuery( $searchText, array_merge( $queryFields, $extraFieldsToSearch ), $boostFunctions['fields'] ),
  349. 'qt' => 'standard' );
  350. }
  351. else
  352. {
  353. $handlerParameters = array ( 'q' => $this->buildMultiFieldQuery( $searchText, array_merge( $queryFields, $extraFieldsToSearch ) ),
  354. 'qt' => 'standard' );
  355. }
  356. break;
  357. case 'simplestandard':
  358. // not to do much, searching is against the default aggregated field
  359. // only highlightfields
  360. $highLightFields = array ( 'ezf_df_text' );
  361. $handlerParameters = array ( 'q' => $searchText,
  362. 'qt' => 'standard',
  363. 'hl.usePhraseHighlighter' => 'true',
  364. 'hl.highlightMultiTerm' => 'true' );
  365. break;
  366. case 'ezpublish':
  367. // the dismax based handler, just keywordss input, most useful for ordinary queries by users
  368. // need to build: Solr q, qf, dismax specific parameters
  369. default:
  370. // ezpublish of course, this to not break BC and is the most "general"
  371. // if another value is specified, it is supposed to be a dismax like handler
  372. // with possible other tuning variables then the stock provided 'ezpublish' in solrconfi.xml
  373. // remark it should be lowercase in solrconfig.xml!
  374. $boostQueryString = $this->boostQuery();
  375. $rawBoostQueries = self::$FindINI->variable( 'QueryBoost', 'RawBoostQueries' );
  376. if ( is_array( $rawBoostQueries ) && !empty( $rawBoostQueries ) )
  377. {
  378. $boostQueryString .= ' ' . implode( ' ', $rawBoostQueries );
  379. }
  380. $handlerParameters = array ( 'q' => $searchText,
  381. 'bq' => $boostQueryString,
  382. 'qf' => implode( ' ', array_merge( $queryFields, $extraFieldsToSearch ) ),
  383. 'qt' => $queryHandler );
  384. }
  385. // Handle boost functions :
  386. $boostFunctionsParamList = $this->buildBoostFunctions( $boostFunctions, $handlerParameters );
  387. // special handling of filters in the case of distributed search filters
  388. // incorporate distributed search filters if defined with an OR expression, and AND-ing all others
  389. // need to do this as multiple fq elements are otherwise AND-ed by the Solr backend
  390. // when using this to search across a dedicated set of languages, it will still be valid with the ezp permission
  391. // scheme
  392. if ( !empty( $shardFilterQuery ) )
  393. {
  394. $fqString = '((' . implode( ') AND (', $filterQuery ) . ')) OR ((' . implode( ') OR (', $shardFilterQuery ) . '))';
  395. // modify the filterQuery array with this single string as the only element
  396. $filterQuery = array( $fqString );
  397. }
  398. $fieldsToReturnString = eZSolr::getMetaFieldName( 'guid' ) . ' ' . eZSolr::getMetaFieldName( 'installation_id' ) . ' ' .
  399. eZSolr::getMetaFieldName( 'main_url_alias' ) . ' ' . eZSolr::getMetaFieldName( 'installation_url' ) . ' ' .
  400. eZSolr::getMetaFieldName( 'id' ) . ' ' . eZSolr::getMetaFieldName( 'main_node_id' ) . ' ' .
  401. eZSolr::getMetaFieldName( 'language_code' ) . ' ' . eZSolr::getMetaFieldName( 'name' ) .
  402. ' score ' . eZSolr::getMetaFieldName( 'published' ) . ' ' . eZSolr::getMetaFieldName( 'path_string' ) . ' ' .
  403. eZSolr::getMetaFieldName( 'main_path_string' ) . ' ' . eZSolr::getMetaFieldName( 'is_invisible' ) . ' ' .
  404. implode( ' ', $extraFieldsToReturn );
  405. if ( ! $asObjects )
  406. {
  407. if ( empty( $fieldsToReturn ))
  408. {
  409. // @todo: needs to be refined with Solr supporting globbing in fl argument, otherwise requests will be to heavy for large fields as for example binary file content
  410. $fieldsToReturnString = 'score, *';
  411. }
  412. else
  413. {
  414. $fieldsToReturnString .= ' ' . implode( ' ', $fieldsToReturn);
  415. }
  416. }
  417. $searchResultClusterParamList = array( 'clustering' => 'true');
  418. $searchResultClusterParamList = $this->buildSearchResultClusterQuery($searchResultClusterParams);
  419. eZDebugSetting::writeDebug( 'extension-ezfind-query', $searchResultClusterParamList, 'Cluster params' );
  420. $queryParams = array_merge(
  421. $handlerParameters,
  422. array(
  423. 'start' => $offset,
  424. 'rows' => $limit,
  425. 'sort' => $sortParameter,
  426. 'indent' => 'on',
  427. 'version' => '2.2',
  428. 'fl' => $fieldsToReturnString,
  429. 'fq' => $filterQuery,
  430. 'hl' => self::$FindINI->variable( 'HighLighting', 'Enabled' ),
  431. 'hl.fl' => implode( ' ', $highLightFields ),
  432. 'hl.snippets' => self::$FindINI->variable( 'HighLighting', 'SnippetsPerField' ),
  433. 'hl.fragsize' => self::$FindINI->variable( 'HighLighting', 'FragmentSize' ),
  434. 'hl.requireFieldMatch' => self::$FindINI->variable( 'HighLighting', 'RequireFieldMatch' ),
  435. 'hl.simple.pre' => self::$FindINI->variable( 'HighLighting', 'SimplePre' ),
  436. 'hl.simple.post' => self::$FindINI->variable( 'HighLighting', 'SimplePost' ),
  437. 'wt' => 'php'
  438. ),
  439. $facetQueryParamList,
  440. $spellCheckParamList,
  441. $boostFunctionsParamList,
  442. $elevateParamList,
  443. $searchResultClusterParamList
  444. );
  445. if( isset( $extendedAttributeFilter['id'] ) && isset( $extendedAttributeFilter['params'] ) )
  446. {
  447. //single filter
  448. $extendedAttributeFilter = array( $extendedAttributeFilter );
  449. }
  450. foreach( $extendedAttributeFilter as $filterDefinition )
  451. {
  452. if( isset( $filterDefinition['id'] ) )
  453. {
  454. $filter = eZFindExtendedAttributeFilterFactory::getInstance( $filterDefinition['id'] );
  455. if( $filter )
  456. {
  457. $filterParams = isset( $filterDefinition['params'] ) ? $filterDefinition['params'] : array();
  458. $queryParams = $filter->filterQueryParams( $queryParams, $filterParams );
  459. }
  460. }
  461. }
  462. return $queryParams;
  463. }
  464. /**
  465. * @since eZ Find 2.1
  466. *
  467. * Language filtering.
  468. * This method builds the language filter, depending on the following settings :
  469. *
  470. * In site.ini :
  471. * <code>
  472. * # Prioritized list of languages. Only translations in these
  473. * # languages will be shown
  474. *
  475. * [RegionalSettings]
  476. * SiteLanguageList[]
  477. * SiteLanguageList[]=eng-GB
  478. * SiteLanguageList[]=fre-FR
  479. * </code>
  480. *
  481. * And in ezfind.ini :
  482. * <code>
  483. * [LanguageSearch]
  484. * SearchMainLanguageOnly=enabled
  485. * </code>
  486. *
  487. * When SearchMainLanguageOnly is set to 'enabled', only results in the first language in SiteLanguageList[] will be returned.
  488. * When SearchMainLanguageOnly is set to 'disabled', searching will be done across all possible translations defined in
  489. * SiteLanguageList[] (unless ShowUntranslatedObjects is enabled, in this case no language filtering will be done at all)
  490. *
  491. *
  492. * @return string The correct language filtering string, appended to the 'fq' parameter in the Solr request.
  493. */
  494. protected function buildLanguageFilterQuery()
  495. {
  496. $languageFilterString = '';
  497. $ini = eZINI::instance();
  498. $languages = $ini->variable( 'RegionalSettings', 'SiteLanguageList' );
  499. $searchMainLanguageOnly = self::$FindINI->variable( 'LanguageSearch', 'SearchMainLanguageOnly' ) == 'enabled';
  500. $showUntranslatedObjects = $ini->variable( 'RegionalSettings', 'ShowUntranslatedObjects' ) == 'enabled';
  501. $languageCodeMetaName = eZSolr::getMetaFieldName( 'language_code' );
  502. $availableLanguageCodesMetaName = eZSolr::getMetaFieldName( 'available_language_codes' );
  503. if ( $searchMainLanguageOnly )
  504. {
  505. $languageFilterString = $languageCodeMetaName . ':' . $languages[0];
  506. }
  507. else if ( $showUntranslatedObjects === false )
  508. {
  509. $languageFilterString = $languageCodeMetaName . ':(' . implode( ' OR ' , $languages ) . ')';
  510. $languageFilterString .= " OR ( " . eZSolr::getMetaFieldName( 'always_available' ) . ':true )';
  511. }
  512. return $languageFilterString;
  513. }
  514. /**
  515. * @since eZ Find 2.0
  516. *
  517. * Boost Functions support.
  518. * "Allows one to use the actual value of a numeric field and functions of those fields in a relevancy score."
  519. *
  520. * @see http://wiki.apache.org/solr/FunctionQuery
  521. * @param array $boostFunctions Example :
  522. * <code>
  523. * $boostFunctions = array( 'fields' => array(
  524. * 'article/title' => 2,
  525. * 'modified:5'
  526. * ),
  527. * 'functions' => array( 'rord(meta_modified_dt)^10' )
  528. * );
  529. * </code>
  530. * @param array &$handlerParameters The inclusion of boost functions in the final search parameter array depends on which queryHandler is used.
  531. * This parameter shall be modified in one of the cases.
  532. *
  533. * @return array containing the boost expressions for the various request handler boost parameters
  534. */
  535. protected function buildBoostFunctions( $boostFunctions = null, &$handlerParameters )
  536. {
  537. if ( $boostFunctions == null )
  538. return array();
  539. // Build boost function string here.
  540. // Field boosts and functions seems to be mutually exclusive.
  541. $boostString = '';
  542. $processedBoostFunctions = array();
  543. $processedBoostFunctions['fields'] = $processedBoostFunctions['functions'] = array();
  544. // Process simple query-time field boosting first :
  545. if ( array_key_exists( 'fields', $boostFunctions ) )
  546. {
  547. foreach ( $boostFunctions['fields'] as $baseName => $boostValue )
  548. {
  549. if ( strpos( $boostValue, ':' ) !== false && is_numeric( $baseName ) )
  550. {
  551. // split at the first colon, leave the rest intact
  552. list( $baseName, $boostValue ) = explode( ':', $boostValue, 2 );
  553. }
  554. if ( is_numeric( $boostValue ) )
  555. {
  556. // Get internal field name.
  557. $baseName = eZSolr::getFieldName( $baseName );
  558. $processedBoostFunctions['fields'][] = $baseName . '^' . $boostValue;
  559. }
  560. }
  561. }
  562. if ( array_key_exists( 'functions', $boostFunctions ) )
  563. {
  564. // Process simple query-time field boosting first :
  565. foreach ( $boostFunctions['functions'] as $expression )
  566. {
  567. // @TODO : parse $expression. use an ezi18n-like system ( formats ), meaning that the $boostFunctions['functions'] will look like this :
  568. /* <code>
  569. * array( 'product( pow( %rating, 5 ), %modified )' => array( '%rating' => 'article/rating',
  570. * '%modified' => 'modified' )
  571. * );
  572. * </code>
  573. *
  574. * Eventually, one single expression is to be accepted here, as is the case in Solr.
  575. */
  576. $processedBoostFunctions['functions'][] = $expression;
  577. }
  578. }
  579. switch ( $handlerParameters['qt'] )
  580. {
  581. case 'ezpublish' :
  582. {
  583. // The edismax based handler which takes its own boost parameters
  584. // Push the boost expression in the 'bf' parameter, if it is not empty.
  585. //
  586. // for the fields to boost, modify the qf parameter for edismax
  587. // this is set before in the buildSearch method
  588. $queryFields = explode(' ', $handlerParameters['qf']);
  589. foreach ( $processedBoostFunctions['fields'] as $fieldToBoost => $boostString )
  590. {
  591. $key = array_search($fieldToBoost, $queryFields);
  592. if (false !== $key)
  593. {
  594. $queryFields[$key] = $boostString;
  595. }
  596. // might be a custom created field, lets add it implicitely with its boost specification
  597. else
  598. {
  599. $queryFields[] = $boostString;
  600. }
  601. }
  602. $handlerParameters['qf'] = implode( ' ', $queryFields );
  603. $boostReturnArray = array();
  604. //additive boost functions
  605. if ( array_key_exists( 'functions', $boostFunctions ) )
  606. {
  607. $boostReturnArray['bf'] = $boostFunctions['functions'];
  608. }
  609. // multiplicative boost functions
  610. if ( array_key_exists( 'mfunctions', $boostFunctions ) )
  611. {
  612. $boostReturnArray['boost'] = $boostFunctions['mfunctions'];
  613. }
  614. //add the queries to the existing bq edismax parameter
  615. if ( array_key_exists( 'queries', $boostFunctions ) )
  616. {
  617. $handlerParameters['bq'] .= ' ' . implode(' ', $boostFunctions['queries']);
  618. }
  619. return $boostReturnArray;
  620. } break;
  621. default:
  622. {
  623. // Simplestandard or standard search handlers.
  624. // Append the boost expression to the 'q' parameter.
  625. // Alter the $handlerParameters array ( passed as reference )
  626. // @TODO : Handle query-time field boosting through the buildMultiFieldQuery() method.
  627. // Requires a modified 'heuristic' mode.
  628. $boostString = implode( ' ', $processedBoostFunctions['functions'] );
  629. $handlerParameters['q'] .= ' _val_:' . trim( $boostString );
  630. } break;
  631. }
  632. return array();
  633. }
  634. /**
  635. * @since eZ Find 2.0
  636. *
  637. * More Like This similarity searches
  638. * @param query
  639. *
  640. * @return
  641. */
  642. public function buildMoreLikeThis( $queryType, $query, $params = array() )
  643. {
  644. eZDebugSetting::writeDebug( 'extension-ezfind-query-mlt', $queryType, 'mlt querytype' );
  645. eZDebugSetting::writeDebug( 'extension-ezfind-query-mlt', $query, 'mlt query' );
  646. eZDebugSetting::writeDebug( 'extension-ezfind-query-mlt', $params, 'mlt params' );
  647. $searchCount = 0;
  648. $queryInstallationID = ( isset( $params['QueryInstallationID'] ) && $params['QueryInstallationID'] ) ? $params['QueryInstallationID'] : eZSolr::installationID();
  649. $offset = ( isset( $params['SearchOffset'] ) && $params['SearchOffset'] ) ? $params['SearchOffset'] : 0;
  650. $limit = ( isset( $params['SearchLimit'] ) && $params['SearchLimit'] ) ? $params['SearchLimit'] : 10;
  651. $subtrees = isset( $params['SearchSubTreeArray'] ) ? $params['SearchSubTreeArray'] : array();
  652. $contentClassID = ( isset( $params['SearchContentClassID'] ) && $params['SearchContentClassID'] <> -1 ) ? $params['SearchContentClassID'] : false;
  653. $sectionID = isset( $params['SearchSectionID'] ) && $params['SearchSectionID'] > 0 ? $params['SearchSectionID'] : false;
  654. $filterQuery = array();
  655. // Add subtree query filter
  656. if ( !empty( $subtrees ) )
  657. {
  658. $subtreeQueryParts = array();
  659. foreach ( $subtrees as $subtreeNodeID )
  660. {
  661. $subtreeQueryParts[] = eZSolr::getMetaFieldName( 'path' ) . ':' . $subtreeNodeID;
  662. }
  663. $filterQuery[] = implode( ' OR ', $subtreeQueryParts );
  664. }
  665. // Add policy limitation query filter
  666. $policyLimitationFilterQuery = $this->policyLimitationFilterQuery();
  667. if ( $policyLimitationFilterQuery !== false )
  668. {
  669. $filterQuery[] = $policyLimitationFilterQuery;
  670. }
  671. // Add content class query filter
  672. $classLimitationFilter = $this->getContentClassFilterQuery( $contentClassID );
  673. if ( $classLimitationFilter !== null )
  674. {
  675. $filterQuery[] = $classLimitationFilter;
  676. }
  677. // Add section to query filter.
  678. if ( $sectionID )
  679. {
  680. $filterQuery[] = eZSolr::getMetaFieldName( 'section_id' ) . ':' . $sectionID;
  681. }
  682. $languageFilterQuery = $this->buildLanguageFilterQuery();
  683. if ( $languageFilterQuery )
  684. {
  685. $filterQuery[] = $languageFilterQuery;
  686. }
  687. $paramFilterQuery = $this->getParamFilterQuery( $params );
  688. if ( $paramFilterQuery )
  689. {
  690. $filterQuery[] = $paramFilterQuery;
  691. }
  692. //add raw filters
  693. if ( self::$FindINI->hasVariable( 'SearchFilters', 'RawFilterList' ) )
  694. {
  695. $rawFilters = self::$FindINI->variable( 'SearchFilters', 'RawFilterList' );
  696. if ( is_array( $rawFilters ) )
  697. {
  698. $filterQuery = array_merge( $filterQuery, $rawFilters );
  699. }
  700. }
  701. // Build and get facet query prameters.
  702. $facetQueryParamList = $this->buildFacetQueryParamList( $params );
  703. // return only text searcheable fields by passing NULL
  704. $fieldTypeExcludeList = $this->fieldTypeExludeList( NULL );
  705. // Create sort parameters based on the parameters.
  706. $sortParameter = $this->buildSortParameter( $params );
  707. $iniExtractionFields = self::$FindINI->variable( 'MoreLikeThis', 'ExtractionFields' );
  708. if ( $iniExtractionFields == 'general' )
  709. {
  710. // the collector field for all strings in an object
  711. $queryFields = array( 'ezf_df_text' );
  712. }
  713. else
  714. {
  715. //the array_unique below is necessary because attribute identifiers are not unique .. and we get as
  716. //much highlight snippets as there are duplicate attribute identifiers
  717. //these are also in the list of query fields (dismax, ezpublish) request handlers
  718. $queryFields = array_unique( $this->getClassAttributes( $contentClassID, false, $fieldTypeExcludeList ) );
  719. }
  720. //query type can vary for MLT q, or stream
  721. //if no valid match for the mlt query variant is obtained, it is treated as text
  722. $mltVariant = 'q';
  723. switch ( strtolower( $queryType ) )
  724. {
  725. case 'nid':
  726. $mltQuery = eZSolr::getMetaFieldName( 'node_id' ) . ':' . $query;
  727. $mltQuery .= ' AND ' . eZSolr::getMetaFieldName( 'installation_id' ) . ':' . $queryInstallationID;
  728. break;
  729. case 'oid':
  730. $mltQuery = eZSolr::getMetaFieldName( 'id' ) . ':' . $query;
  731. $mltQuery .= ' AND ' . eZSolr::getMetaFieldName( 'installation_id' ) . ':' . $queryInstallationID;
  732. break;
  733. case 'url':
  734. $mltVariant = 'stream.url';
  735. $mltQuery = $query;
  736. break;
  737. case 'text':
  738. default:
  739. $mltVariant = 'stream.body';
  740. $mltQuery = $query;
  741. break;
  742. }
  743. // fetch the mlt tuning parameters from ini settings
  744. $mintf = self::$FindINI->variable( 'MoreLikeThis', 'MinTermFreq' ) ? self::$FindINI->variable( 'MoreLikeThis', 'MinTermFreq' ) : 1;
  745. $mindf = self::$FindINI->variable( 'MoreLikeThis', 'MinDocFreq' ) ? self::$FindINI->variable( 'MoreLikeThis', 'MinDocFreq' ) : 1;
  746. $minwl = self::$FindINI->variable( 'MoreLikeThis', 'MinWordLength' ) ? self::$FindINI->variable( 'MoreLikeThis', 'MinWordLength' ) : 3;
  747. $maxwl = self::$FindINI->variable( 'MoreLikeThis', 'MaxWordLength' ) ? self::$FindINI->variable( 'MoreLikeThis', 'MaxWordLength' ) : 20;
  748. $maxqt = self::$FindINI->variable( 'MoreLikeThis', 'MaxQueryTerms' ) ? self::$FindINI->variable( 'MoreLikeThis', 'MaxQueryTerms' ) : 5;
  749. $boostmlt = self::$FindINI->variable( 'MoreLikeThis', 'BoostTerms' ) ? self::$FindINI->variable( 'MoreLikeThis', 'BoostTerms' ) : 'true';
  750. // @todo decide which of the hard-coded mlt parameters should become input parameters or ini settings
  751. return array_merge(
  752. array(
  753. $mltVariant => $mltQuery,
  754. 'start' => $offset,
  755. 'rows' => $limit,
  756. 'sort' => $sortParameter,
  757. 'indent' => 'on',
  758. 'version' => '2.2',
  759. 'mlt.match.include' => 'false', // exclude the doc itself
  760. 'mlt.mindf' => $mindf,
  761. 'mlt.mintf' => $mintf,
  762. 'mlt.maxwl' => $maxwl,
  763. 'mlt.minwl' => $minwl, //minimum wordlength
  764. 'mlt.maxqt' => $maxqt,
  765. 'mlt.interestingTerms' => 'details', // useful for debug output & tuning
  766. 'mlt.boost' => $boostmlt, // boost the highest ranking terms
  767. //'mlt.qf' => implode( ' ', $queryFields ),
  768. 'mlt.fl' => implode( ' ', $queryFields ),
  769. 'fl' =>
  770. eZSolr::getMetaFieldName( 'guid' ) . ' ' . eZSolr::getMetaFieldName( 'installation_id' ) . ' ' .
  771. eZSolr::getMetaFieldName( 'main_url_alias' ) . ' ' . eZSolr::getMetaFieldName( 'installation_url' ) . ' ' .
  772. eZSolr::getMetaFieldName( 'id' ) . ' ' . eZSolr::getMetaFieldName( 'main_node_id' ) . ' ' .
  773. eZSolr::getMetaFieldName( 'language_code' ) . ' ' . eZSolr::getMetaFieldName( 'name' ) .
  774. ' score ' . eZSolr::getMetaFieldName( 'published' ) . ' ' .
  775. eZSolr::getMetaFieldName( 'path_string' ) . ' ' . eZSolr::getMetaFieldName( 'is_invisible' ),
  776. 'fq' => $filterQuery,
  777. 'wt' => 'php' ),
  778. $facetQueryParamList );
  779. return $queryParams;
  780. }
  781. /**
  782. * Build sort parameter based on params provided.
  783. * @todo specify dedicated sorting fields
  784. * @param array Parameter list array. SortBy element contains sort
  785. * definition.
  786. *
  787. * @return string Sort description. Default sort string is 'score desc'.
  788. */
  789. protected function buildSortParameter( $parameterList )
  790. {
  791. $sortString = 'score desc';
  792. if ( !empty( $parameterList['SortBy'] ) )
  793. {
  794. $sortString = '';
  795. foreach ( $parameterList['SortBy'] as $field => $order )
  796. {
  797. // If array, set key and order from array values
  798. if ( is_array( $order ) )
  799. {
  800. $field = $order[0];
  801. $order = $order[1];
  802. }
  803. // Fixup field name
  804. switch( $field )
  805. {
  806. case 'score':
  807. case 'relevance':
  808. {
  809. $field = 'score';
  810. } break;
  811. case 'name':
  812. {
  813. $field = eZSolr::getMetaFieldName( 'sort_name', 'sort' );
  814. }break;
  815. case 'published':
  816. case 'modified':
  817. case 'class_name':
  818. case 'class_identifier':
  819. case 'section_id':
  820. {
  821. $field = eZSolr::getMetaFieldName( $field, 'sort' );
  822. } break;
  823. case 'author':
  824. {
  825. $field = eZSolr::getMetaFieldName( 'owner_name', 'sort' );
  826. } break;
  827. case 'class_id':
  828. {
  829. $field = eZSolr::getMetaFieldName( 'contentclass_id', 'sort' );
  830. } break;
  831. case 'path':
  832. {
  833. // Assume sorting on main node path_string as it is not possible to sort on multivalued fields due to Solr limitation
  834. $field = eZSolr::getMetaFieldName( 'main_path_string', 'sort' );
  835. } break;
  836. default:
  837. {
  838. $field = eZSolr::getFieldName( $field, false, 'sort' );
  839. if ( !$field )
  840. {
  841. eZDebug::writeNotice( 'Sort field does not exist in local installation, but may still be valid: ' .
  842. $facetDefinition['field'],
  843. __METHOD__ );
  844. continue;
  845. }
  846. } break;
  847. }
  848. // Fixup order name.
  849. switch( strtolower( $order ) )
  850. {
  851. case 'desc':
  852. case 'asc':
  853. {
  854. $order = strtolower( $order );
  855. } break;
  856. default:
  857. {
  858. eZDebug::writeDebug( 'Unrecognized sort order. Setting for order for default: "desc"',
  859. __METHOD__ );
  860. $order = 'desc';
  861. } break;
  862. }
  863. if ( $sortString !== '' )
  864. {
  865. $sortString .= ',';
  866. }
  867. $sortString .= $field . ' ' . $order;
  868. }
  869. }
  870. return $sortString;
  871. }
  872. /**
  873. * Build filter query from search filter parameter.
  874. * @deprecated api is way too limited now
  875. * @todo for eZ Find 2.0: rework this for recursive boolean combinations and a few more filter types, the possible combinations are almost infinite for pure Solr syntax
  876. * @param array Parameter list array.
  877. * The normal simple use is an array of type: array( '<field name>', <value> ).
  878. * The value may also be an array containing values.
  879. *
  880. * Examples :
  881. * <code>
  882. * $parameters = array( 'article/title:hello' );
  883. * $parameters = array( 'article/title' => 'hello' );
  884. * $parameters = array( 'article/rating' => '[1 TO 10]' );
  885. * $parameters = array( 'article/rating' => '[1 TO 10]',
  886. * 'article/body:hello' );
  887. * $parameters = array( 'or',
  888. * 'article/rating' => '[1 TO 10]',
  889. * 'article/body:hello' );
  890. * $parameters = array( 'or',
  891. * array( 'or',
  892. * 'article/rating' => '[1 TO 10]',
  893. * 'article/body:hello' ),
  894. * array( 'and',
  895. * 'article/rating' => '[10 TO 20]',
  896. * 'article/body:goodbye' ) );
  897. * </code>
  898. * @return string Filter Query. Null if no filter parameters are in
  899. * the $parameterList
  900. */
  901. protected function getParamFilterQuery( $parameterList )
  902. {
  903. if ( empty( $parameterList['Filter'] ) )
  904. {
  905. return null;
  906. }
  907. $booleanOperator = $this->getBooleanOperatorFromFilter( $parameterList['Filter'] );
  908. $filterQueryList = array();
  909. foreach ( $parameterList['Filter'] as $baseName => $value )
  910. {
  911. if ( !is_array( $value ) and strpos( $value, ':' ) !== false && is_numeric( $baseName ) )
  912. {
  913. // split at the first colon, leave the rest intact
  914. list( $baseName, $value ) = explode( ':', $value, 2 );
  915. }
  916. if ( is_array( $value ) )
  917. {
  918. $filterQueryList[] = '( ' . $this->getParamFilterQuery( array( 'Filter' => $value ) ) . ' )';
  919. }
  920. else
  921. {
  922. if ( $value !== null )
  923. {
  924. // Exception to the generic processing : when a subtree filter is applied, the search plugin needs to be notified
  925. // to be able to pick the right URL for objects, the main URL of which is located outside the subtree filter scope.
  926. if ( $baseName == 'path' )
  927. {
  928. if ( isset( $this->searchPluginInstance->postSearchProcessingData['subtree_array'] ) )
  929. $this->searchPluginInstance->postSearchProcessingData['subtree_array'][] = $value;
  930. else
  931. $this->searchPluginInstance->postSearchProcessingData['subtree_array'] = array( $value );
  932. }
  933. // Get internal field name. Returns a class ID filter if applicable. Add it as an implicit filter if needed.
  934. $baseNameInfo = eZSolr::getFieldName( $baseName, true, 'filter' );
  935. if ( is_array( $baseNameInfo ) and isset( $baseNameInfo['contentClassId'] ) )
  936. {
  937. $filterQueryList[] = '( ' . eZSolr::getMetaFieldName( 'contentclass_id' ) . ':' . $baseNameInfo['contentClassId'] . ' AND ' . $baseNameInfo['fieldName'] . ':' . $value . ' )' ;
  938. }
  939. else
  940. {
  941. // Note that $value needs to be escaped if it unintentionally contains Solr reserved characters
  942. $filterQueryList[] = $baseNameInfo . ':' . $value;
  943. }
  944. }
  945. }
  946. }
  947. return implode( " $booleanOperator ", $filterQueryList );
  948. }
  949. /**
  950. * Identifies which boolean operator to use when building the filter string ( fq parameter in the final Solr raw request )
  951. * Removes the operator from the array, if existing.
  952. *
  953. * @param array &$filter Filter array processed in self::getParamFilterQuery
  954. * @returns string The boolean operator to use. Default to 'AND'
  955. * @see ezfeZPSolrQueryBuilder::getParamFilterQuery
  956. */
  957. protected function getBooleanOperatorFromFilter( &$filter )
  958. {
  959. if ( isset( $filter[0] ) and is_string( $filter[0] ) and in_array( $filter[0], self::$allowedBooleanOperators ) )
  960. {
  961. $retVal = strtoupper( $filter[0] );
  962. unset( $filter[0] );
  963. return $retVal;
  964. }
  965. else
  966. return self::DEFAULT_BOOLEAN_OPERATOR;
  967. }
  968. /**
  969. * Analyze the string, and decide if quotes should be added or not.
  970. *
  971. * @param string String
  972. *
  973. * @return string String with quotes added if needed.
  974. * @deprecated
  975. */
  976. static function quoteIfNeeded( $value )
  977. {
  978. $quote = '';
  979. if ( strpos( $value, ' ' ) !== false )
  980. {
  981. $quote = '"';
  982. if ( strpos( trim( $value ), '(' ) === 0 )
  983. {
  984. $quote = '';
  985. }
  986. }
  987. return $quote . $value . $quote;
  988. }
  989. /**
  990. * Build facet parameter list. This function extracts the facet parameter from
  991. * the ezfeZPSolrQueryBuilder::search( ...,$params parameter.
  992. *
  993. * @todo specify dedicated facet fields (may be mapped to sort fields)
  994. *
  995. * @param array Parameter list array
  996. *
  997. * @return array List of Facet query parameter. The facet parameter corrosponds to
  998. * the parameters defined here : http://wiki.apache.org/solr/SimpleFacetParameters
  999. */
  1000. protected function buildFacetQueryParamList( $parameterList )
  1001. {
  1002. $parameterList = array_change_key_case( $parameterList, CASE_LOWER );
  1003. $queryParamList = array();
  1004. if ( empty( $parameterList['facet'] ) )
  1005. {
  1006. return $queryParamList;
  1007. }
  1008. // Loop through facet definitions, and build facet query.
  1009. foreach ( $parameterList['facet'] as $facetDefinition )
  1010. {
  1011. if ( empty( $facetDefinition['field']

Large files files are truncated, but you can click here to view the full file