code == '200') { $data = json_decode($response->data); } } return $data->fields; } /** * This is copied from search module. The search module implementation doesn't * handle quoted terms correctly (bug) and this function is copied here until * I have the bugfix perfected, at which point a patch will be submitted to search * module with the goal of removing the function here. * * Extract a module-specific search option from a search query. e.g. 'type:book' */ static function query_extract($keys, $option) { $pattern = '/(^| )'. $option .':(\"([^\"]*)\")/i'; preg_match_all($pattern, $keys, $matches); if (!empty($matches[2])) { // The preg_replace removes beginning and trailing quotations. return preg_replace('/^"|"$/', '', $matches[2]); } $pattern = '/(^| )'. $option .':([^ ]*)/i'; if (preg_match_all($pattern, $keys, $matches)) { if (!empty($matches[2])) { return $matches[2]; } } } /** * Replaces all occurances of $option in $keys. */ static function query_replace($keys, $option) { $matches = Solr_Base_Query::query_extract($keys, $option); if (count($matches) > 0) { foreach ($matches as $match) { // TODO: Make some sort of name->value container object. $found = Solr_Base_Query::make_field(array('#name' => $option, '#value' => $match)); $keys = str_replace($found, '', $keys); } } return $keys; } /** * Takes an array $values and combines the #name and #value in a way * suitable for use in a Solr query. */ static function make_field(array $values) { if (empty($values['#name'])) { return implode(' ', array_filter(explode(' ', $values['#value']), 'trim')); } else { // if the field value has spaces in it, wrap it in double quotes. if (count(explode(' ', $values['#value'])) > 1) { $values['#value'] = '"'. $values['#value']. '"'; } return $values['#name']. ':'. $values['#value']; } } /** * A keyed array where the key is a position integer and the value * is an array with #name and #value properties. */ private $_fields; /** * An array of subqueries. */ private $_subqueries = array(); /** * The query string. */ private $_query; /** * Should fields be AND'd or OR'd together? */ private $_field_operator; /** * @param $querystring * The string that a user would type into the search box. Suitable input * may come from search_get_keys() * @param $field_operator * An object level operator. AND is the implicit default. All segments will * be joined with this operator. */ function __construct($querystring, $field_operator = "AND") { $this->_field_operator = $field_operator; $this->_query = trim($querystring); $this->parse_query(); } function add_field($field, $value) { // microtime guarantees that added fields come at the end of the query, // in order. $this->_fields[microtime()] = array('#name' => $field, '#value' => trim($value)); $this->rebuild_query(); } function get_fields() { return $this->_fields; } function remove_field($name, $value = NULL) { // We can only remove named fields. if (empty($name)) { return; } if (empty($value)) { foreach ($this->_fields as $pos => $values) { if ($values['#name'] == $name) { unset($this->_fields[$pos]); } } } else { foreach ($this->_fields as $pos => $values) { if ($values['#name'] == $name && $values['#value'] == $value) { unset($this->_fields[$pos]); } } } $this->rebuild_query(); } function has_field($name, $value) { foreach ($this->_fields as $pos => $values) { if (!empty($values['#name']) && !empty($values['#value']) && $values['#name'] == $name && $values['#value'] == $value) { return TRUE; } } return FALSE; } /** * A subquery is another instance of a Solr_Base_Query that should be joined * to the query. The operator determines whether it will be joined with AND or * OR. * * @param $query * An instance of Solr_Base_Query. * * @param $operator * 'AND' or 'OR' */ function add_subquery(Solr_Base_Query $query, $operator = 'AND') { $this->_subqueries[$query->get_query_basic()] = array('#query' => $query, '#operator' => $operator); } function remove_subquery(Solr_Base_Query $query) { unset($this->_subqueries[$query->get_query_basic()]); } function remove_subqueries() { $this->_subqueries = array(); } function get_query() { $this->rebuild_query(); return $this->_query; } /** * A function to get just the keyword components of the query, * omitting any field:value portions. */ function get_query_basic() { $nonames = array_filter($this->_fields, create_function('$a', 'return empty($a[\'#name\']);')); $result = array(); foreach ($nonames as $pos => $field) { $result[] = $field['#value']; } return implode(' ', $result); } function get_breadcrumb() { // This encodes an assumption that the breadcrumb is always building off // of the current page. Could be a problem. $breadcrumb = menu_get_active_breadcrumb(); // double check that the fields are ordered by position. ksort($this->_fields); $progressive_crumb = array(); // TODO: Don't know if hardcoding this is going to come back to bite. $base = 'search/'. arg(1). '/'; foreach ($this->_fields as $field) { $progressive_crumb[] = Solr_Base_Query::make_field($field); if (empty($field['#name'])) { $breadcrumb[] = l($field['#value'], $base. implode(' ', $progressive_crumb)); } else if ($themed = theme("apachesolr_breadcrumb_{$field['#name']}", $field['#value'])) { $breadcrumb[] = l($themed, $base. implode(' ', $progressive_crumb)); } else { $breadcrumb[] = l($field['#value'], $base. implode(' ', $progressive_crumb)); } } // the last breadcrumb is the current page, so it shouldn't be a link. $last = count($breadcrumb) - 1; $breadcrumb[$last] = strip_tags($breadcrumb[$last]); drupal_set_breadcrumb($breadcrumb); return $breadcrumb; } private function parse_query() { $this->_fields = array(); $_keys = $this->_query; // Gets information about the fields already in solr index. $index_fields = Solr_Base_Query::get_fields_in_index(); $rows = array(); foreach ((array) $index_fields as $name => $field) { do { // save the strlen so we can detect if it has changed at the bottom // of the do loop $a = (int)strlen($_keys); // Get the values for $name $values = Solr_Base_Query::query_extract($_keys, $name); if (count($values) > 0) { foreach ($values as $value) { $found = Solr_Base_Query::make_field(array('#name' => $name, '#value' => $value)); $pos = strpos($this->_query, $found); // $solr_keys and $solr_crumbs are keyed on $pos so that query order // is maintained. This is important for breadcrumbs. $this->_fields[$pos] = array('#name' => $name, '#value' => trim($value)); } // Update the local copy of $_keys by removing the key that was just found. $_keys = trim(Solr_Base_Query::query_replace($_keys, $name)); } // Take new strlen to compare with $a. $b = (int)strlen($_keys); } while ($a !== $b); // Clean up by adding remaining keywords. if (!empty($_keys)) { $pos = strpos($this->_query, $_keys); $this->_fields[$pos] = array('#name' => '', '#value' => trim($_keys)); } } // Even though the array has the right keys they are likely in the wrong // order. ksort() sorts the array by key while maintaining the key. ksort($this->_fields); } private function rebuild_query() { $fields = array(); foreach ($this->_fields as $pos => $values) { $fields[] = Solr_Base_Query::make_field($values); } $join_delim = $this->_field_operator == 'AND' ? ' ' : ' OR '; $this->_query = trim(implode($join_delim, array_filter($fields, 'trim'))); foreach ($this->_subqueries as $id => $data) { $operator = $data['#operator']; $subquery = $data['#query']->get_query(); $this->_query .= " {$operator} ({$subquery})"; } } }