/** * Test parseRange functionality. * * @return void * @access public */ public function testParseRange() { // basic range test: $result = VuFindSolrUtils::parseRange("[1 TO 100]"); $this->assertEquals('1', $result['from']); $this->assertEquals('100', $result['to']); // test whitespace handling: $result = VuFindSolrUtils::parseRange("[1 TO 100]"); $this->assertEquals('1', $result['from']); $this->assertEquals('100', $result['to']); // test invalid ranges: $this->assertFalse(VuFindSolrUtils::parseRange('1 TO 100')); $this->assertFalse(VuFindSolrUtils::parseRange('[not a range to me]')); }
/** * Support method for getVisData() -- extract details from applied filters. * * @param array $filters Current filter list * * @return array * @access private */ private function _processDateFacets($filters) { $result = array(); foreach ($this->_dateFacets as $current) { $from = $to = ''; if (isset($filters[$current])) { foreach ($filters[$current] as $filter) { if ($range = VuFindSolrUtils::parseRange($filter)) { $from = $range['from'] == '*' ? '' : $range['from']; $to = $range['to'] == '*' ? '' : $range['to']; break; } } } $result[$current] = array($from, $to); } return $result; }
/** * Get the current settings for the date range facet, if it is set: * * @param object $savedSearch Saved search object (false if none) * * @return array Date range: Key 0 = from, Key 1 = to. * @access private */ private function _getDateRangeSettings($savedSearch = false) { // Default to blank strings: $from = $to = ''; // Check to see if there is an existing range in the search object: if ($savedSearch) { $filters = $savedSearch->getFilters(); if (isset($filters['main_date_str'])) { foreach ($filters['main_date_str'] as $current) { if ($range = VuFindSolrUtils::parseRange($current)) { $from = $range['from'] == '*' ? '' : $range['from']; $to = $range['to'] == '*' ? '' : $range['to']; $savedSearch->removeFilter('main_date_str:' . $current); break; } } } } // Send back the settings: return array($from, $to); }
/** * Support method for getVisData() -- extract details from applied filters. * * @param array $filters Current filter list * * @return array * @access protected */ protected function processDateFacets($filters) { $result = array(); foreach ($this->dateFacets as $current) { $from = $to = ''; if (isset($filters[$current])) { foreach ($filters[$current] as $filter) { if ($range = VuFindSolrUtils::parseRange($filter)) { $from = $range['from'] == '*' ? '' : $range['from']; $to = $range['to'] == '*' ? '' : $range['to']; break; } } } $result[$current] = array($from, $to); $result[$current]['label'] = $this->searchObject->getFacetLabel($current); } return $result; }
/** * Returns the stored list of facets for the last search * * @param array $filter Array of field => on-screen description listing * all of the desired facet fields; set to null to get all configured values. * @param bool $expandingLinks If true, we will include expanding URLs (i.e. * get all matches for a facet, not just a limit to the current search) in the * return array. * * @return array Facets data arrays * @access public */ public function getFacetList($filter = null, $expandingLinks = false) { // If there is no filter, we'll use all facets as the filter: if (is_null($filter)) { $filter = $this->facetConfig; } // Start building the facet list: $list = array(); // If we have no facets to process, give up now if (!isset($this->indexResult['facet_counts']['facet_fields']) && !isset($this->indexResult['facet_counts']['facet_queries']) || !is_array($this->indexResult['facet_counts']['facet_fields']) && !is_array($this->indexResult['facet_counts']['facet_queries'])) { return $list; } $translationPrefix = isset($this->facetTranslationPrefix) ? $this->facetTranslationPrefix : ''; // Loop through every field returned by the result set $validFields = array_keys($filter); foreach ($this->indexResult['facet_counts']['facet_fields'] as $field => $data) { // Skip filtered fields and empty arrays: if (!in_array($field, $validFields) || count($data) < 1) { continue; } // Initialize the settings for the current field $list[$field] = array(); // Add the on-screen label $list[$field]['label'] = $filter[$field]; // This tells us whether there is a selection made in the facet group // Useful so we will not have to loop through the list later on $list[$field]['isApplied'] = false; // Build our array of values for this field $list[$field]['list'] = array(); // Should we translate values for the current facet? $translate = in_array($field, $this->translatedFacets); // Hierarchical facets $hierarchical = $this->getFacetSetting('SpecialFacets', 'hierarchical'); // Loop through values: foreach ($data as $facet) { // Initialize the array of data about the current facet: $currentSettings = array(); if ($translate) { if (is_array($hierarchical) && in_array($field, $hierarchical)) { $facetValue = $facet[0]; // Remove trailing slash $facetValue = rtrim($facetValue, '/'); $translatedValue = translate(array('prefix' => $translationPrefix, 'text' => $facetValue)); if ($translatedValue == $facetValue) { // Didn't find a translation, so let's just clean up the display string a bit $translatedValue = end(explode('/', $facetValue)); } $currentSettings['value'] = $translatedValue; } else { $currentSettings['value'] = translate(array('prefix' => $translationPrefix, 'text' => $facet[0])); } } else { $currentSettings['value'] = $facet[0]; } $currentSettings['untranslated'] = $facet[0]; $currentSettings['count'] = $facet[1]; $currentSettings['isApplied'] = false; $currentSettings['url'] = $this->renderLinkWithFilter("{$field}:" . $facet[0]); // If we want to have expanding links (all values matching the // facet) in addition to limiting links (filter current search // with facet), do some extra work: if ($expandingLinks) { $currentSettings['expandUrl'] = $this->getExpandingFacetLink($field, $facet[0]); } // Is this field a current filter? // preg_replace removes the filter exclude if any $rawField = preg_replace('/{!ex=.+}/', '', $field); if (in_array($rawField, array_keys($this->filterList))) { // and is this value a selected filter? if (in_array($facet[0], $this->filterList[$rawField])) { $currentSettings['isApplied'] = true; $list[$field]['isApplied'] = true; } } // Store the collected values: $list[$field]['list'][] = $currentSettings; } } foreach ($this->indexResult['facet_counts']['facet_queries'] as $key => $count) { list($field, $query) = explode(':', $key, 2); if (!in_array($field, $validFields)) { continue; } // Initialize the settings for the current field if (!isset($list[$field])) { $list[$field] = array(); // Add the on-screen label $list[$field]['label'] = $this->pseudoFacets[$field]; // Build our array of values for this field $list[$field]['list'] = array(); } // Initialize the array of data about the current facet: $currentSettings = array(); $range = VuFindSolrUtils::parseRange($query); $currentSettings['value'] = $currentSettings['untranslated'] = $query; $currentSettings['count'] = $count; $currentSettings['isApplied'] = false; $filter = $this->buildDateRangeFilter($field, $range['from'], $range['to']); $currentSettings['url'] = $this->renderLinkWithFilter($filter); // If we want to have expanding links (all values matching the // facet) in addition to limiting links (filter current search // with facet), do some extra work: if ($expandingLinks) { $currentSettings['expandUrl'] = $this->getExpandingFacetLink($field, $facet[0]); } // Is this field a current filter? if (in_array($field, array_keys($this->filterList))) { // and is this value a selected filter? if (in_array($facet[0], $this->filterList[$field])) { $currentSettings['isApplied'] = true; } } // Store the collected values: $list[$field]['list'][] = $currentSettings; } // Sort configured facets alphabetically $alphaSorted = $this->getFacetSetting('Results_Settings', 'hierarchicalFacetSortOptions'); if (is_array($alphaSorted)) { foreach ($alphaSorted as $alphaFacet => $mode) { if (isset($list[$alphaFacet])) { if ($mode == 'all' || $mode == 'top' && isset($list[$alphaFacet]['list'][0]['untranslated'][0]) && $list[$alphaFacet]['list'][0]['untranslated'][0] == '0') { usort($list[$alphaFacet]['list'], function ($a, $b) { return strtolower($a['value']) > strtolower($b['value']); }); } } } } return $list; }
/** * Support function to get publication date range. Return string in the form * "YYYY-YYYY" * * @param string $field Name of filter field to check for date limits. * * @return string * @access protected */ protected function getPublishedDates($field) { // Try to extract range details from request parameters or SearchObject: if (isset($_REQUEST[$field . 'from']) && isset($_REQUEST[$field . 'to'])) { $range = array('from' => $_REQUEST[$field . 'from'], 'to' => $_REQUEST[$field . 'to']); } else { if (is_object($this->_searchObject)) { $currentFilters = $this->_searchObject->getFilters(); if (isset($currentFilters[$field][0])) { $range = VuFindSolrUtils::parseRange($currentFilters[$field][0]); } } } // Normalize range if we found one: if (isset($range)) { if (empty($range['from']) || $range['from'] == '*') { $range['from'] = 0; } if (empty($range['to']) || $range['to'] == '*') { $range['to'] = date('Y') + 1; } return $range['from'] . '-' . $range['to']; } // No range found? Return empty string: return ''; }
/** * Execute a search. * * @param array $query The search terms from the Search Object * @param array $filterList 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 string $sortBy The value to be used by for sorting * @param array $facets The facets to include (null for defaults) * @param bool $returnErr On fatal error, should we fail outright (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 query($query, $filterList = null, $start = 1, $limit = 20, $sortBy = null, $facets = null, $returnErr = false) { // Query String Parameters $options = array('s.q' => $this->_buildQuery($query)); // TODO: add configurable authentication mechanisms to identify authorized // users and switch this to "authenticated" when appropriate (VUFIND-475): $options['s.role'] = 'none'; // Which facets should we include in results? Set defaults if not provided. if (!$facets) { $facets = array_keys($this->_config['Facets']); } // Default to "holdings only" unless a different setting is found in the // filters: $options['s.ho'] = 'true'; // Which filters should be applied to our query? $options['s.fvf'] = array(); $options['s.rf'] = array(); if (!empty($filterList)) { // Loop through all filters and add appropriate values to request: foreach ($filterList as $filterArray) { foreach ($filterArray as $filter) { $safeValue = $this->_escapeParam($filter['value']); // Special case -- "holdings only" is a separate parameter from // other facets. if ($filter['field'] == 'holdingsOnly') { $options['s.ho'] = $safeValue; } else { if ($filter['field'] == 'excludeNewspapers') { // Special case -- support a checkbox for excluding // newspapers: $options['s.fvf'][] = "ContentType,Newspaper Article,true"; } else { if ($range = VuFindSolrUtils::parseRange($filter['value'])) { // Special case -- range query (translate [x TO y] syntax): $from = $this->_escapeParam($range['from']); $to = $this->_escapeParam($range['to']); $options['s.rf'][] = "{$filter['field']},{$from}:{$to}"; } else { // Standard case: $options['s.fvf'][] = "{$filter['field']},{$safeValue}"; } } } } } } // Special case -- if user filtered down to newspapers AND excluded them, // we can't possibly have any results: if (in_array('ContentType,Newspaper Article,true', $options['s.fvf']) && in_array('ContentType,Newspaper Article', $options['s.fvf'])) { return array('recordCount' => 0, 'documents' => array()); } if (is_array($facets)) { $options['s.ff'] = array(); foreach ($facets as $facet) { // See if parameters are included as part of the facet name; // if not, override them with defaults. $parts = explode(',', $facet); $facetName = $parts[0]; $facetMode = isset($parts[1]) ? $parts[1] : 'and'; $facetPage = isset($parts[2]) ? $parts[2] : 1; if (isset($parts[3])) { $facetLimit = $parts[3]; } else { $facetLimit = isset($this->_config['Facet_Settings']['facet_limit']) ? $this->_config['Facet_Settings']['facet_limit'] : 30; } $facetParams = "{$facetMode},{$facetPage},{$facetLimit}"; // Special case -- we can't actually facet on PublicationDate, // but we need it in the results to display range controls. If // we encounter this field, set a flag indicating that we need // to inject it into the results for proper display later: if ($facetName == 'PublicationDate') { $injectPubDate = true; } else { $options['s.ff'][] = "{$facetName},{$facetParams}"; } } } if (isset($sortBy)) { $options['s.sort'] = $sortBy; } $options['s.ps'] = $limit; $options['s.pn'] = $start; // Define Highlighting if ($this->_highlight) { $options['s.hl'] = 'true'; $options['s.hs'] = '{{{{START_HILITE}}}}'; $options['s.he'] = '{{{{END_HILITE}}}}'; } else { $options['s.hl'] = 'false'; $options['s.hs'] = $options['s.he'] = ''; } if ($this->debug) { echo '<pre>Query: '; print_r($options); echo "</pre>\n"; } $result = $this->_call($options); if (PEAR::isError($result)) { if ($returnErr) { return array('recordCount' => 0, 'documents' => array(), 'errors' => $result->getMessage()); } else { PEAR::raiseError($result); } } // Add a fake "PublicationDate" facet if flagged earlier; this is necessary // in order to display the date range facet control in the interface. if (isset($injectPubDate) && $injectPubDate) { $result['facetFields'][] = array('fieldName' => 'PublicationDate', 'displayName' => 'PublicationDate', 'counts' => array()); } return $result; }