/** * Execute a search. * * @param string $query The XQuery script in binary encoding. * @param string $handler The Query Handler to use (null for default) * @param array $filter The fields and values to filter results on * @param int $start The record to start with * @param int $limit The amount of records to return * @param array $facet An array of faceting options * @param string $spell Phrase to spell check * @param string $dictionary Spell check dictionary to use * @param string $sort Field name to use for sorting * @param string $fields A list of fields to be returned * @param string $method Method to use for sending request (GET/POST) * @param bool $returnSolrError If Solr reports a syntax error, * should we fail outright (false) or * treat it as an empty result set with * an error key set (true)? * @access public * @throws object PEAR Error * @return array An array of query results */ function search($query, $handler = null, $filter = null, $start = 0, $limit = 20, $facet = null, $spell = '', $dictionary = null, $sort = null, $fields = null, $method = HTTP_REQUEST_METHOD_POST, $returnSolrError = false) { global $timer; // Query String Parameters $options = array('q' => $query, 'rows' => $limit, 'start' => $start, 'indent' => 'yes'); //For FRBR, enable this and then update display //$options['group'] = 'true'; //$options['group.field'] = 'grouping_term'; // Add Sorting if ($sort && !empty($sort)) { // There may be multiple sort options (ranked, with tie-breakers); // process each individually, then assemble them back together again: $sortParts = explode(',', $sort); for ($x = 0; $x < count($sortParts); $x++) { $sortParts[$x] = $this->_normalizeSort($sortParts[$x]); } $options['sort'] = implode(',', $sortParts); } //Check to see if we need to automatically convert to a proper case only (no stemming search) //We will do this whenever all or part of a string is surrounded by quotes. if (preg_match('/\\".+?\\"/', $query)) { if ($handler == 'Keyword') { $handler = 'KeywordProper'; } else { if ($handler == 'Subject') { $handler = 'SubjectProper'; } else { if ($handler == 'AllFields') { $handler = 'AllFieldsProper'; } else { if ($handler == 'Title') { $handler = 'TitleProper'; } } } } } // Determine which handler to use if (!$this->isAdvanced($query)) { //Escape : to make sure that the query isn't treated as a field spec. $query = str_replace(':', '\\:', $query); $ss = is_null($handler) ? null : $this->_getSearchSpecs($handler); // Is this a Dismax search? if (isset($ss['DismaxFields'])) { // Specify the fields to do a Dismax search on: $options['qf'] = implode(' ', $ss['DismaxFields']); // Specify the default dismax search handler so we can use any // global settings defined by the user: $options['qt'] = 'dismax'; // Load any custom Dismax parameters from the YAML search spec file: if (isset($ss['DismaxParams']) && is_array($ss['DismaxParams'])) { foreach ($ss['DismaxParams'] as $current) { $options[$current[0]] = $current[1]; } } // Apply search-specific filters if necessary: if (isset($ss['FilterQuery'])) { if (is_array($filter)) { $filter[] = $ss['FilterQuery']; } else { $filter = array($ss['FilterQuery']); } } } else { // Not DisMax... but do we need to format the query based on // a setting in the YAML search specs? If $ss is an array // at this point, it indicates that we found YAML details. if (is_array($ss)) { $options['q'] = $this->_buildQueryComponent($handler, $query); } else { if (!empty($handler)) { $options['q'] = "({$handler}:{$query})"; } } } } else { // Force boolean operators to uppercase if we are in a case-insensitive // mode: if (!$this->caseSensitiveBooleans) { $query = SolrUtils::capitalizeBooleans($query); } // Process advanced search -- if a handler was specified, let's see // if we can adapt the search to work with the appropriate fields. if (!empty($handler)) { $options['q'] = $this->_buildAdvancedQuery($handler, $query); } } $timer->logTime("build query"); // Limit Fields if ($fields) { $options['fl'] = $fields; } else { // This should be an explicit list $options['fl'] = '*,score'; } if (is_object($this->searchSource)) { $defaultFilters = preg_split('/\\r\\n/', $this->searchSource->defaultFilter); foreach ($defaultFilters as $tmpFilter) { $filter[] = $tmpFilter; } } //Apply automatic boosting (only to biblio and econtent queries) if (preg_match('/.*(biblio|econtent).*/i', $this->host)) { //unset($options['qt']); //Force the query to never use dismax handling $searchLibrary = Library::getSearchLibrary($this->searchSource); //Boost items owned at our location $searchLocation = Location::getSearchLocation($this->searchSource); $boostFactors = $this->getBoostFactors($searchLibrary, $searchLocation); if (isset($options['qt']) && $options['qt'] == 'dismax') { //Boost by number of holdings if (count($boostFactors) > 0) { $options['bf'] = "sum(" . implode(',', $boostFactors) . ")"; } //print ($options['bq']); } else { $baseQuery = $options['q']; //Boost items in our system if (count($boostFactors) > 0) { $boost = "sum(" . implode(',', $boostFactors) . ")"; } else { $boost = ''; } $options['q'] = "{!boost b={$boost}} {$baseQuery}"; //echo ("Advanced Query " . $options['q']); } $timer->logTime("apply boosting"); $scopingFilters = $this->getScopingFilters($searchLibrary, $searchLocation); $timer->logTime("apply filters based on location"); } else { //Non book search (genealogy) $scopingFilters = array(); } if ($filter != null && $scopingFilters != null) { if (!is_array($filter)) { $filter = array($filter); } $filters = array_merge($filter, $scopingFilters); } else { if ($filter == null) { $filters = $scopingFilters; } else { $filters = $filter; } } // Build Facet Options if ($facet && !empty($facet['field'])) { $options['facet'] = 'true'; $options['facet.mincount'] = 1; $options['facet.limit'] = isset($facet['limit']) ? $facet['limit'] : null; unset($facet['limit']); if (isset($facet['field']) && is_array($facet['field']) && in_array('date_added', $facet['field'])) { $options['facet.date'] = 'date_added'; $options['facet.date.end'] = 'NOW'; $options['facet.date.start'] = 'NOW-1YEAR'; $options['facet.date.gap'] = '+1WEEK'; foreach ($facet['field'] as $key => $value) { if ($value == 'date_added') { unset($facet['field'][$key]); break; } } } if (isset($facet['field'])) { $options['facet.field'] = $facet['field']; if ($options['facet.field'] && is_array($options['facet.field'])) { foreach ($options['facet.field'] as $key => $facetName) { if (strpos($facetName, 'availability_toggle') === 0) { $options['facet.field'][$key] = '{!ex=avail}' . $facetName; } } } } else { $options['facet.field'] = null; } unset($facet['field']); $options['facet.prefix'] = isset($facet['prefix']) ? $facet['prefix'] : null; unset($facet['prefix']); $options['facet.sort'] = isset($facet['sort']) ? $facet['sort'] : null; unset($facet['sort']); if (isset($facet['offset'])) { $options['facet.offset'] = $facet['offset']; unset($facet['offset']); } if ($searchLibrary && $searchLibrary->showAvailableAtAnyLocation) { $options['f.available_at.facet.missing'] = 'true'; } foreach ($facet as $param => $value) { $options[$param] = $value; } } $timer->logTime("build facet options"); //Check to see if there are filters we want to show all values for if (isset($filters) && is_array($filters)) { foreach ($filters as $key => $value) { if (strpos($value, 'availability_toggle') === 0) { $filters[$key] = '{!tag=avail}' . $value; } } } // Build Filter Query if (is_array($filters) && count($filters)) { $options['fq'] = $filters; } // Enable Spell Checking if ($spell != '') { $options['spellcheck'] = 'true'; $options['spellcheck.q'] = $spell; if ($dictionary != null) { $options['spellcheck.dictionary'] = $dictionary; } } // Enable highlighting if ($this->_highlight) { $options['hl'] = 'true'; $options['hl.fl'] = '*'; $options['hl.simple.pre'] = '{{{{START_HILITE}}}}'; $options['hl.simple.post'] = '{{{{END_HILITE}}}}'; } if ($this->debug) { echo '<pre>Search options: ' . print_r($options, true) . "\n"; if ($filters) { echo "\nFilterQuery: "; foreach ($filters as $filterItem) { echo " {$filterItem}"; } } if ($sort) { echo "\nSort: " . $options['sort']; } echo "</pre>\n"; $options['debugQuery'] = 'on'; } $timer->logTime("end solr setup"); $result = $this->_select($method, $options, $returnSolrError); $timer->logTime("run select"); if (PEAR_Singleton::isError($result)) { PEAR_Singleton::raiseError($result); } return $result; }
/** * Build Query string from search parameters * * @access private * @param array $search An array of search parameters * @return string The query */ private function buildQuery($search) { $groups = array(); $excludes = array(); if (is_array($search)) { $query = ''; foreach ($search as $params) { // Advanced Search if (isset($params['group'])) { $thisGroup = array(); // Process each search group foreach ($params['group'] as $group) { // Build this group individually as a basic search $thisGroup[] = $this->buildQuery(array($group)); } // Is this an exclusion (NOT) group or a normal group? if ($params['group'][0]['bool'] == 'NOT') { $excludes[] = join(" OR ", $thisGroup); } else { $groups[] = join(" " . $params['group'][0]['bool'] . " ", $thisGroup); } } // Basic Search if (isset($params['lookfor']) && $params['lookfor'] != '') { // Clean and validate input -- note that index may be in a different // field depending on whether this is a basic or advanced search. $lookfor = $params['lookfor']; if (isset($params['field'])) { $index = $params['field']; } else { if (isset($params['index'])) { $index = $params['index']; } else { $index = 'AllFields'; } } // Force boolean operators to uppercase if we are in a case-insensitive // mode: if (!$this->caseSensitiveBooleans) { $lookfor = SolrUtils::capitalizeBooleans($lookfor); } // Prepend the index name, unless it's the special "AllFields" index: if ($index != 'AllFields') { $query .= "{$index}:({$lookfor})"; } else { $query .= "{$lookfor}"; } } } } // Put our advanced search together if (count($groups) > 0) { $query = "(" . join(") " . $search[0]['join'] . " (", $groups) . ")"; } // and concatenate exclusion after that if (count($excludes) > 0) { $query .= " NOT ((" . join(") OR (", $excludes) . "))"; } // Ensure we have a valid query to this point return isset($query) ? $query : ''; }