/** * Get singleton instance for filter * @param string $filterID * @return eZFindExtendedAttributeFilterInterface|false */ public static function getInstance($filterID) { if (!isset(self::$instances[$filterID])) { try { if (!self::$filtersList) { $ini = eZINI::instance('ezfind.ini'); self::$filtersList = $ini->variable('ExtendedAttributeFilters', 'FiltersList'); } if (!isset(self::$filtersList[$filterID])) { throw new Exception($filterID . ' extended attribute filter is not defined'); } $className = self::$filtersList[$filterID]; if (!class_exists($className)) { throw new Exception('Could not find class ' . $className); } $instance = new $className(); if (!$instance instanceof eZFindExtendedAttributeFilterInterface) { throw new Exception($className . ' is not a valid eZFindExtendedAttributeFilterInterface'); } self::$instances[$filterID] = $instance; } catch (Exception $e) { eZDebug::writeWarning($e->getMessage(), __METHOD__); self::$instances[$filterID] = false; } } return self::$instances[$filterID]; }
/** * Search on the Solr search server * * @param string search term * @param array parameters. * Example: * <code> * array( 'SearchOffset' => <offset>, * 'SearchLimit' => <limit>, * 'SearchSubTreeArray' => array( <node ID1>[, <node ID2>]... ), * 'SearchContentClassID' => array( <class ID1>[, <class ID2>]... ), * 'SearchContentClassAttributeID' => <class attribute ID>, * 'Facet' => array( array( 'field' => <class identifier>/<attribute identifier>[/<option>], ... ) ) ), * 'Filter' => array( <base_name> => <value>, <base_name2> => <value2> ), * 'SortBy' => array( <field> => <asc|desc> [, <field2> => <asc|desc> [,...]] ) | * array( array( <field> => <asc|desc> )[, array( <field2> => <asc|desc> )[,...]] ), * 'BoostFunctions' => array( 'fields' => array( * 'article/title' => 2, * 'modified:5' * ), * 'functions' => array( 'rord(meta_modified_dt)^10' ) * ), * 'ForceElevation' => false, * 'EnableElevation' => true * 'DistributedSearch" => array ( 'shards', array( 'shard1', 'shard2' , ... ) * 'searchfields', array ( 'myfield1, 'myfield2', ... ) * 'returnfields', array ( 'myfield1, 'myfield2', ... ) * 'rawfilterlist, array ( 'foreignfield:a', '(foreignfield:b AND otherfield:c)', ... ) * ) * ); * </code> * For full facet description, see facets design document. * For full description about 'ForceElevation', see elevate support design document ( elevate_support.rst.txt ) * * the rawFilterList in distributed search is appended to the policyfilterlist with an 'OR' for each entry, as the policy list will * in general not be applicable to foreign indexes. To be used with care! * * @param array Search types. Reserved. * * @return array Solr query results. * * @see ezfeZPSolrQueryBuilder::buildBoostFunctions() */ public function buildSearch($searchText, $params = array(), $searchTypes = array()) { eZDebugSetting::writeDebug('extension-ezfind-query', $params, 'search params'); $searchCount = 0; $eZFindIni = eZINI::instance('ezfind.ini'); $solrIni = eZINI::instance('solr.ini'); $siteIni = eZINI::instance('site.ini'); $offset = isset($params['SearchOffset']) ? $params['SearchOffset'] : 0; $limit = isset($params['SearchLimit']) ? $params['SearchLimit'] : 10; $subtrees = isset($params['SearchSubTreeArray']) ? $params['SearchSubTreeArray'] : array(); $contentClassID = isset($params['SearchContentClassID']) && $params['SearchContentClassID'] != -1 ? $params['SearchContentClassID'] : false; $contentClassAttributeID = isset($params['SearchContentClassAttributeID']) && $params['SearchContentClassAttributeID'] != -1 ? $params['SearchContentClassAttributeID'] : false; $sectionID = isset($params['SearchSectionID']) && $params['SearchSectionID'] > 0 ? $params['SearchSectionID'] : false; $dateFilter = isset($params['SearchDate']) && $params['SearchDate'] > 0 ? $params['SearchDate'] : false; $asObjects = isset($params['AsObjects']) ? $params['AsObjects'] : true; $spellCheck = isset($params['SpellCheck']) && $params['SpellCheck'] > 0 ? $params['SpellCheck'] : array(); $queryHandler = isset($params['QueryHandler']) ? $params['QueryHandler'] : $eZFindIni->variable('SearchHandler', 'DefaultSearchHandler'); // eZFInd 2.3: check ini setting and take it as a default instead of false $visibilityDefaultSetting = $siteIni->variable('SiteAccessSettings', 'ShowHiddenNodes'); $visibilityDefault = $visibilityDefaultSetting === 'true' ? true : false; $ignoreVisibility = isset($params['IgnoreVisibility']) ? $params['IgnoreVisibility'] : $visibilityDefault; $this->searchPluginInstance->postSearchProcessingData['ignore_visibility'] = $ignoreVisibility; $limitation = isset($params['Limitation']) ? $params['Limitation'] : null; $boostFunctions = isset($params['BoostFunctions']) ? $params['BoostFunctions'] : null; $forceElevation = isset($params['ForceElevation']) ? $params['ForceElevation'] : false; $enableElevation = isset($params['EnableElevation']) ? $params['EnableElevation'] : true; $distributedSearch = isset($params['DistributedSearch']) ? $params['DistributedSearch'] : false; $fieldsToReturn = isset($params['FieldsToReturn']) ? $params['FieldsToReturn'] : array(); $highlightParams = isset($params['HighLightParams']) ? $params['HighLightParams'] : array(); $searchResultClusterParams = isset($params['SearchResultClustering']) ? $params['SearchResultClustering'] : array(); $extendedAttributeFilter = isset($params['ExtendedAttributeFilter']) ? $params['ExtendedAttributeFilter'] : array(); // distributed search option // @since ezfind 2.2 $extraFieldsToSearch = array(); $extraFieldsToReturn = array(); $shardURLs = array(); $iniShards = $solrIni->variable('SolrBase', 'Shards'); $shardQuery = NULL; $shardFilterQuery = array(); if (isset($distributedSearch['shards'])) { foreach ($distributedSearch['shards'] as $shard) { $shardURLs[] = $iniShards[$shard]; } $shardQuery = implode(',', $shardURLs); } if (isset($distributedSearch['searchfields'])) { $extraFieldsToSearch = $distributedSearch['searchfields']; } if (isset($distributedSearch['returnfields'])) { $extraFieldsToReturn = $distributedSearch['returnfields']; } if (isset($distributedSearch['rawfilterlist'])) { $shardFilterQuery = $distributedSearch['rawfilterlist']; } // check if filter parameter is indeed an array, and set it otherwise if (isset($params['Filter']) && !is_array($params['Filter'])) { $params['Filter'] = array($params['Filter']); } $pathFieldName = eZSolr::getMetaFieldName($ignoreVisibility ? 'path' : 'visible_path'); $filterQuery = array(); // Add subtree query filter if (!empty($subtrees)) { $this->searchPluginInstance->postSearchProcessingData['subtree_array'] = $subtrees; $subtreeQueryParts = array(); foreach ($subtrees as $subtreeNodeID) { $subtreeQueryParts[] = $pathFieldName . ':' . $subtreeNodeID; } $filterQuery[] = implode(' OR ', $subtreeQueryParts); } // Add policy limitation query filter $policyLimitationFilterQuery = $this->policyLimitationFilterQuery($limitation, $ignoreVisibility); if ($policyLimitationFilterQuery !== false) { $filterQuery[] = $policyLimitationFilterQuery; } // Add time/date query filter if ($dateFilter > 0) { switch ($dateFilter) { // last day case 1: $searchTimestamp = strtotime('-1 day'); break; // last week // last week case 2: $searchTimestamp = strtotime('-1 week'); break; // last month // last month case 3: $searchTimestamp = strtotime('-1 month'); break; // last three month // last three month case 4: $searchTimestamp = strtotime('-3 month'); break; // last year // last year case 5: $searchTimestamp = strtotime('-1 year'); break; } $filterQuery[] = eZSolr::getMetaFieldName('published') . ':[' . ezfSolrDocumentFieldBase::preProcessValue($searchTimestamp, 'date') . '/DAY TO *]'; } if ((!eZContentObjectTreeNode::showInvisibleNodes() || !$ignoreVisibility) && $eZFindIni->variable('SearchFilters', 'FilterHiddenFromDB') == 'enabled') { $db = eZDB::instance(); $invisibleNodeIDArray = $db->arrayQuery('SELECT node_id FROM ezcontentobject_tree WHERE ezcontentobject_tree.is_invisible = 1', array('column' => 0)); $hiddenNodesQueryText = 'meta_main_node_id_si:[* TO *] -meta_main_node_id_si:('; foreach ($invisibleNodeIDArray as $element) { $hiddenNodesQueryText = $hiddenNodesQueryText . $element['node_id'] . ' '; } $hiddenNodesQueryText = $hiddenNodesQueryText . ')'; // only add filter if there are hidden nodes after all if ($invisibleNodeIDArray) { $filterQuery[] = $hiddenNodesQueryText; } } // Add content class query filter $classLimitationFilter = $this->getContentClassFilterQuery($contentClassID); if ($classLimitationFilter !== null) { $filterQuery[] = $classLimitationFilter; } // Add section to query filter. if ($sectionID) { $filterQuery[] = eZSolr::getMetaFieldName('section_id') . ':' . $sectionID; } $languageFilterQuery = $this->buildLanguageFilterQuery(); if ($languageFilterQuery) { $filterQuery[] = $languageFilterQuery; } $paramFilterQuery = $this->getParamFilterQuery($params); if (!empty($paramFilterQuery)) { $filterQuery = array_merge($filterQuery, $paramFilterQuery); } //add raw filters if ($eZFindIni->hasVariable('SearchFilters', 'RawFilterList')) { $rawFilters = $eZFindIni->variable('SearchFilters', 'RawFilterList'); if (is_array($rawFilters)) { $filterQuery = array_merge($filterQuery, $rawFilters); } } // Build and get facet query prameters. $facetQueryParamList = $this->buildFacetQueryParamList($params); // search only text type declared fields $fieldTypeExcludeList = $this->fieldTypeExludeList(NULL); // Create sort parameters based on the parameters. $sortParameter = $this->buildSortParameter($params); //the array_unique below is necessary because attribute identifiers are not unique .. and we get as //much highlight snippets as there are duplicate attribute identifiers //these are also in the list of query fields (dismax, ezpublish) request handlers $queryFields = array_unique($this->getClassAttributes($contentClassID, $contentClassAttributeID, $fieldTypeExcludeList)); //highlighting only in the attributes, otherwise the object name is repeated in the highlight, which is already //partly true as it is mostly composed of one or more attributes. //maybe we should add meta data to the index to filter them out. $highLightFields = $queryFields; //@since eZ Find 2.3 //when dedicated attributes are searched for, don't add meta-fields to the $queryfields list if (!$contentClassAttributeID) { $queryFields[] = eZSolr::getMetaFieldName('name'); if (!$eZFindIni->hasVariable('SearchFilters', 'ExcludeOwnerName') || $eZFindIni->variable('SearchFilters', 'ExcludeOwnerName') !== 'enabled') { $queryFields[] = eZSolr::getMetaFieldName('owner_name'); } } $spellCheckParamList = array(); // @param $spellCheck expects array (true|false, dictionary identifier, ...) if (isset($spellCheck[0]) and $spellCheck[0] or $eZFindIni->variable('SpellCheck', 'SpellCheck') == 'enabled' and (isset($spellCheck[0]) and !$spellCheck[0])) { $dictionary = isset($spellCheck[1]) ? $spellCheck[1] : $eZFindIni->variable('SpellCheck', 'DefaultDictionary'); $spellCheckParamList = array('spellcheck' => 'true', 'spellcheck.q' => $searchText, 'spellcheck.dictionary' => $dictionary, 'spellcheck.collate' => 'true', 'spellcheck.extendedResults' => 'true', 'spellcheck.onlyMorePopular' => 'true', 'spellcheck.count' => 1); } // Create the Elevate-related parameters here : $elevateParamList = eZFindElevateConfiguration::getRuntimeQueryParameters($forceElevation, $enableElevation, $searchText); // process query handler: standard, simplestandard, ezpublish, heuristic // first determine which implemented handler to use when heuristic is specified if (strtolower($queryHandler) === 'heuristic') { // @todo: this code will evolve of course if (preg_match('/[\\^\\*\\~]|AND|OR/', $searchText) > 0) { $queryHandler = 'simplestandard'; } else { $queryHandler = 'ezpublish'; } } $handlerParameters = array(); $queryHandler = strtolower($queryHandler); switch ($queryHandler) { case 'standard': // @todo: this is more complicated // build the query against all "text" like fields // should take into account all the filter fields and class filters to shorten the query // need to build: Solr q if (array_key_exists('fields', $boostFunctions)) { $handlerParameters = array('q' => $this->buildMultiFieldQuery($searchText, array_merge($queryFields, $extraFieldsToSearch), $boostFunctions['fields']), 'qt' => 'standard'); } else { $handlerParameters = array('q' => $this->buildMultiFieldQuery($searchText, array_merge($queryFields, $extraFieldsToSearch)), 'qt' => 'standard'); } break; case 'simplestandard': // not to do much, searching is against the default aggregated field // only highlightfields $highLightFields = array('ezf_df_text'); $handlerParameters = array('q' => $searchText, 'qt' => 'standard', 'hl.usePhraseHighlighter' => 'true', 'hl.highlightMultiTerm' => 'true'); break; case 'ezpublish': // the dismax based handler, just keywordss input, most useful for ordinary queries by users // need to build: Solr q, qf, dismax specific parameters // the dismax based handler, just keywordss input, most useful for ordinary queries by users // need to build: Solr q, qf, dismax specific parameters default: // ezpublish of course, this to not break BC and is the most "general" // if another value is specified, it is supposed to be a dismax like handler // with possible other tuning variables then the stock provided 'ezpublish' in solrconfi.xml // remark it should be lowercase in solrconfig.xml! $boostQueryString = $this->boostQuery(); $rawBoostQueries = $eZFindIni->variable('QueryBoost', 'RawBoostQueries'); if (is_array($rawBoostQueries) && !empty($rawBoostQueries)) { $boostQueryString .= ' ' . implode(' ', $rawBoostQueries); } $handlerParameters = array('q' => $searchText, 'bq' => $boostQueryString, 'qf' => implode(' ', array_merge($queryFields, $extraFieldsToSearch)), 'qt' => $queryHandler); } // Handle boost functions : $boostFunctionsParamList = $this->buildBoostFunctions($boostFunctions, $handlerParameters); // special handling of filters in the case of distributed search filters // incorporate distributed search filters if defined with an OR expression, and AND-ing all others // need to do this as multiple fq elements are otherwise AND-ed by the Solr backend // when using this to search across a dedicated set of languages, it will still be valid with the ezp permission // scheme if (!empty($shardFilterQuery)) { $fqString = '((' . implode(') AND (', $filterQuery) . ')) OR ((' . implode(') OR (', $shardFilterQuery) . '))'; // modify the filterQuery array with this single string as the only element $filterQuery = array($fqString); } // Document transformer fields since eZ Find 5.4 $docTransformerFields = array('[elevated]'); $fieldsToReturnString = eZSolr::getMetaFieldName('guid') . ' ' . eZSolr::getMetaFieldName('installation_id') . ' ' . eZSolr::getMetaFieldName('main_url_alias') . ' ' . eZSolr::getMetaFieldName('installation_url') . ' ' . eZSolr::getMetaFieldName('id') . ' ' . eZSolr::getMetaFieldName('main_node_id') . ' ' . eZSolr::getMetaFieldName('language_code') . ' ' . eZSolr::getMetaFieldName('name') . ' score ' . eZSolr::getMetaFieldName('published') . ' ' . eZSolr::getMetaFieldName('path_string') . ' ' . eZSolr::getMetaFieldName('main_path_string') . ' ' . eZSolr::getMetaFieldName('is_invisible') . ' ' . implode(' ', $docTransformerFields) . ' ' . implode(' ', $extraFieldsToReturn); if (!$asObjects) { if (empty($fieldsToReturn)) { // @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 $fieldsToReturnString = 'score, *'; } else { $fieldsToReturnString .= ' ' . implode(',', $fieldsToReturn); } } $searchResultClusterParamList = array('clustering' => 'true'); $searchResultClusterParamList = $this->buildSearchResultClusterQuery($searchResultClusterParams); eZDebugSetting::writeDebug('extension-ezfind-query', $searchResultClusterParamList, 'Cluster params'); $queryParams = array_merge($handlerParameters, array('start' => $offset, 'rows' => $limit, 'sort' => $sortParameter, 'indent' => 'on', 'version' => '2.2', 'fl' => $fieldsToReturnString, 'fq' => $filterQuery, 'hl' => $eZFindIni->variable('HighLighting', 'Enabled'), 'hl.fl' => implode(' ', $highLightFields), 'hl.snippets' => $eZFindIni->variable('HighLighting', 'SnippetsPerField'), 'hl.fragsize' => $eZFindIni->variable('HighLighting', 'FragmentSize'), 'hl.requireFieldMatch' => $eZFindIni->variable('HighLighting', 'RequireFieldMatch'), 'hl.simple.pre' => $eZFindIni->variable('HighLighting', 'SimplePre'), 'hl.simple.post' => $eZFindIni->variable('HighLighting', 'SimplePost'), 'wt' => 'php'), $facetQueryParamList, $spellCheckParamList, $boostFunctionsParamList, $elevateParamList, $searchResultClusterParamList); if (isset($extendedAttributeFilter['id']) && isset($extendedAttributeFilter['params'])) { //single filter $extendedAttributeFilter = array($extendedAttributeFilter); } foreach ($extendedAttributeFilter as $filterDefinition) { if (isset($filterDefinition['id'])) { $filter = eZFindExtendedAttributeFilterFactory::getInstance($filterDefinition['id']); if ($filter) { $filterParams = isset($filterDefinition['params']) ? $filterDefinition['params'] : array(); $queryParams = $filter->filterQueryParams($queryParams, $filterParams); } } } return $queryParams; }