/** * Test capitalizeRanges functionality. * * @return void * @access public */ public function testCapitalizeRanges() { // Set up an array of expected inputs and outputs: // @codingStandardsIgnoreStart $tests = array(array('"{a to b}"', '"{a to b}"'), array('"[a to b]"', '"[a to b]"'), array('[a to b]', '([a TO b] OR [A TO B])'), array('[a TO b]', '([a TO b] OR [A TO B])'), array('[a To b]', '([a TO b] OR [A TO B])'), array('[a tO b]', '([a TO b] OR [A TO B])'), array('{a to b}', '({a TO b} OR {A TO B})'), array('{a TO b}', '({a TO b} OR {A TO B})'), array('{a To b}', '({a TO b} OR {A TO B})'), array('{a tO b}', '({a TO b} OR {A TO B})'), array('[1900 to 1910]', '[1900 TO 1910]'), array('[1900 TO 1910]', '[1900 TO 1910]'), array('{1900 to 1910}', '{1900 TO 1910}'), array('{1900 TO 1910}', '{1900 TO 1910}'), array('[a to b]', '([a TO b] OR [A TO B])'), array('[1900-01-01t00:00:00z to 1900-12-31t23:59:59z]', '[1900-01-01T00:00:00Z TO 1900-12-31T23:59:59Z]'), array('{1900-01-01T00:00:00Z TO 1900-12-31T23:59:59Z}', '{1900-01-01T00:00:00Z TO 1900-12-31T23:59:59Z}')); // @codingStandardsIgnoreEnd // Test all the operations: foreach ($tests as $current) { $this->assertEquals(VuFindSolrUtils::capitalizeRanges($current[0]), $current[1]); } }
/** * Execute a search. * * @param string $query The search query * @param string $handler The Query Handler to use (null for default) * @param array $filter The fields and values to filter results on * @param string $start The record to start with * @param string $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 Fail outright on syntax error (false) or * treat it as an empty result set with an error key set (true)? * * @throws object PEAR Error * @return array An array of query results * @access public */ public 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) { // Query String Parameters $options = array('q' => $query, 'rows' => $limit, 'start' => $start, 'indent' => 'yes'); // Force sort by title_sort for empty search sorted by score if ($limit > 0 && $query == '*:*' && (empty($sort) || $sort == 'score desc')) { $sort = 'title asc'; } // 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); } // Determine which handler to use if (!$this->isAdvanced($query)) { $ss = is_null($handler) ? null : $this->_getSearchSpecs($handler, $query); // 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) { // The way we process the current parameter depends on // whether or not we have previously encountered it. If // we have multiple values for the same parameter, we need // to turn its entry in the $options array into a subarray; // otherwise, one-off parameters can be safely represented // as single values. if (isset($options[$current[0]])) { if (!is_array($options[$current[0]])) { $options[$current[0]] = array($options[$current[0]]); } $options[$current[0]][] = $current[1]; } else { $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 if we have a handler set, we may still need // to build a query using a setting in the YAML search specs or a // simple field name: if (!empty($handler)) { $options['q'] = $this->_buildQueryComponent($handler, $query); } } } else { // Force boolean operators to uppercase if we are in a case-insensitive // mode: if (!$this->_caseSensitiveBooleans) { $query = VuFindSolrUtils::capitalizeBooleans($query); } // Adjust range operators if we are in a case-insensitive mode: if (!$this->_caseSensitiveRanges) { $query = VuFindSolrUtils::capitalizeRanges($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); // If highlighting is enabled, we only want to use the inner query // for highlighting; anything added outside of this is a boost and // should be ignored for highlighting purposes! if ($this->_highlight) { $options['hl.q'] = $this->_buildAdvancedInnerQuery($handler, $query); } } } // Limit Fields if ($fields) { $options['fl'] = $fields; } else { // This should be an explicit list $options['fl'] = '*,score'; } // 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']); $options['facet.field'] = isset($facet['field']) ? $facet['field'] : null; unset($facet['field']); if (isset($facet['prefix'])) { if (is_array($facet['prefix'])) { foreach ($facet['prefix'] as $name => $prefix) { $options["f.{$name}.facet.prefix"] = $prefix; // TODO: This is a kludge, maybe something better is // needed to indicate when we want more than the default // limit for hierarchical facets. $options["f.{$name}.facet.limit"] = 1000; } } else { $options['facet.prefix'] = $facet['prefix']; } } unset($facet['prefix']); $options['facet.sort'] = isset($facet['sort']) ? $facet['sort'] : 'count'; unset($facet['sort']); if (isset($facet['offset'])) { $options['facet.offset'] = $facet['offset']; unset($facet['offset']); } if (isset($facet['query'])) { $options['facet.query'] = $facet['query']; unset($facet['query']); } foreach ($facet as $param => $value) { $options[$param] = $value; } } // Don't use the filters for an id query if ($handler != 'ids') { if ($this->_mergedRecords) { // Filter out merged children by default if (!isset($filter)) { $filter = array(); } $filter[] = '-merged_child_boolean:TRUE'; } elseif ($this->_mergedRecords !== null) { // Filter out merged records by default if (!isset($filter)) { $filter = array(); } $filter[] = '-merged_boolean:TRUE'; } if ($this->_hideComponentParts) { // Filter out component parts by default if (!isset($filter)) { $filter = array(); } $filter[] = '-hidden_component_boolean:TRUE'; } } // Build Filter Query $this->_mergeBuildingPriority = array(); if (is_array($filter) && count($filter)) { // Change 'online_boolean' filter to 'online_str_mv' // if sources contain merged records (i.e. if deduplication is enabled). if (($pos = array_search('online_boolean:"1"', $filter)) !== false) { $searchSettings = getExtraConfigArray('searches'); if (isset($searchSettings['Records']['merged_records']) && $searchSettings['Records']['merged_records']) { if (isset($searchSettings['Records']['sources']) && ($sources = $searchSettings['Records']['sources']) !== '') { $tmp = array(); foreach (explode(',', $sources) as $source) { $tmp[] = "\"{$source}\""; } $filter[$pos] = 'online_str_mv:(' . implode(' OR ', $tmp) . ')'; } else { $filter[$pos] = 'online_str_mv:*'; } } } $options['fq'] = $filter; foreach ($filter as $f) { if (strncmp($f, 'building:', 9) == 0) { // Assume we have a facet hierarchy... $fullHierarchy = substr($f, 12, -1); foreach (explode('/', $fullHierarchy) as $part) { $this->_mergeBuildingPriority[] = $part; } } } } // 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}}}}'; } // Hack to make it possible to see the search debug information if (isset($_REQUEST['debugSolrQuery'])) { $options['debugQuery'] = 'true'; } if ($this->debug) { echo '<pre>Search options: ' . print_r($options, true) . "\n"; if ($filter) { echo "\nFilterQuery: "; foreach ($filter as $filterItem) { echo " {$filterItem}"; } } if ($sort) { echo "\nSort: " . $options['sort']; } echo "</pre>\n"; } $result = $this->_select($method, $options, $returnSolrError); if (PEAR::isError($result)) { PEAR::raiseError($result); } return $result; }