You are here

class SolrFilterSubQuery in Apache Solr Search 7

Same name and namespace in other branches
  1. 8 Solr_Base_Query.php \SolrFilterSubQuery
  2. 6.3 Solr_Base_Query.php \SolrFilterSubQuery

This class allows you to make operations on a query that will be sent to Apache Solr. methods such as adding and removing sorts, remove and replace parameters, adding and removing filters, getters and setters for various parameters and more @file Class that defines the base query for the Apache Solr Drupal module.

Hierarchy

Expanded class hierarchy of SolrFilterSubQuery

1 string reference to 'SolrFilterSubQuery'
apachesolr_drupal_subquery in ./apachesolr.module
Factory function for query objects.

File

./Solr_Base_Query.php, line 11
This class allows you to make operations on a query that will be sent to Apache Solr. methods such as adding and removing sorts, remove and replace parameters, adding and removing filters, getters and setters for various parameters and more

View source
class SolrFilterSubQuery {

  /**
   * Static shared by all instances, used to increment ID numbers.
   */
  protected static $idCount = 0;

  /**
   * Each query/subquery will have a unique ID.
   */
  public $id;
  public $operator;
  public $exclude;

  /**
   * A keyed array where the key is a position integer and the value
   * is an array with #name and #value properties.  Each value is a
   * used for filter queries, e.g. array('#name' => 'is_uid', '#value' => 0)
   * for anonymous content.
   */
  protected $fields = array();

  /**
   * An array of subqueries.
   */
  protected $subqueries = array();
  function __construct($operator = 'OR', $exclude = FALSE) {
    $this->operator = $operator;
    $this->exclude = $exclude;
    $this->id = ++SolrFilterSubQuery::$idCount;
  }
  function __clone() {
    $this->id = ++SolrFilterSubQuery::$idCount;
  }
  public function getFilters($name = NULL) {
    if (empty($name)) {
      return $this->fields;
    }
    reset($this->fields);
    $matches = array();
    foreach ($this->fields as $filter) {
      if ($filter['#name'] == $name) {
        $matches[] = $filter;
      }
    }
    return $matches;
  }
  public function hasFilter($name, $value, $exclude = FALSE) {
    foreach ($this->fields as $pos => $values) {
      if ($values['#name'] == $name && $values['#value'] == $value && $values['#exclude'] == $exclude) {
        return TRUE;
      }
    }
    return FALSE;
  }
  public function addFilter($name, $value, $exclude = FALSE, $local = '') {

    // @todo - escape the value if it has spaces in it and is not a range query or parenthesized.
    $filter = array(
      '#exclude' => (bool) $exclude,
      '#name' => trim($name),
      '#value' => trim($value),
      '#local' => trim($local),
    );
    $this->fields[] = $filter;
    return $this;
  }
  public function removeFilter($name, $value = NULL, $exclude = FALSE) {

    // Remove from the public list of filters.
    $this
      ->unsetFilter($this->fields, $name, $value, $exclude);
    return $this;
  }
  protected function unsetFilter(&$fields, $name, $value, $exclude) {
    if (!isset($value)) {
      foreach ($fields as $pos => $values) {
        if ($values['#name'] == $name) {
          unset($fields[$pos]);
        }
      }
    }
    else {
      foreach ($fields as $pos => $values) {
        if ($values['#name'] == $name && $values['#value'] == $value && $values['#exclude'] == $exclude) {
          unset($fields[$pos]);
        }
      }
    }
  }
  public function getFilterSubQueries() {
    return $this->subqueries;
  }
  public function addFilterSubQuery(SolrFilterSubQuery $query) {
    $this->subqueries[$query->id] = $query;
    return $this;
  }
  public function removeFilterSubQuery(SolrFilterSubQuery $query) {
    unset($this->subqueries[$query->id]);
    return $this;
  }
  public function removeFilterSubQueries() {
    $this->subqueries = array();
    return $this;
  }
  public function makeFilterQuery(array $filter) {
    $prefix = empty($filter['#exclude']) ? '' : '-';
    if ($filter['#local']) {
      $prefix = '{!' . $filter['#local'] . '}' . $prefix;
    }

    // If the field value contains a colon or a space, wrap it in double quotes,
    // unless it is a range query or is already wrapped in double quotes or
    // parentheses.
    if (preg_match('/[ :]/', $filter['#value']) && !preg_match('/^[\\[\\{]\\S+ TO \\S+[\\]\\}]$/', $filter['#value']) && !preg_match('/^["\\(].*["\\)]$/', $filter['#value'])) {
      $filter['#value'] = '"' . $filter['#value'] . '"';
    }
    return $prefix . $filter['#name'] . ':' . $filter['#value'];
  }

  /**
   * Make sure our query matches the pattern name:value or name:"value"
   * Make sure that if we are ranges we use name:[ AND ]
   * allowed inputs :
   * a. bundle:article
   * b. date:[1970-12-31T23:59:59Z TO NOW]
   * Split the text in 4 different parts
   * 1. name, eg.: bundle or date
   * 2. The first opening bracket (or nothing), eg.: [
   * 3. The value of the field, eg. article or 1970-12-31T23:59:59Z TO NOW
   * 4. The last closing bracket, eg.: ]
   * @param string $filter
   *   The filter to validate
   * @return boolean
   */
  public static function validFilterValue($filter) {
    $name = NULL;
    $value = NULL;
    $matches = array();
    $datefields = array();
    $datefield_match = array();
    if (preg_match('/(?P<name>[^:]+):(?P<value>.+)?$/', $filter, $matches)) {
      foreach ($matches as $match_id => $match) {
        switch ($match_id) {
          case 'name':
            $name = $match;
            break;
          case 'value':
            $value = $match;
            break;
        }
      }

      // For the name we allow any character that fits between the A-Z0-9 range and
      // any alternative for this in other languages. No special characters allowed.
      // Negative filters may have a leading "-".
      if (!preg_match('/^-?[a-zA-Z0-9_\\x7f-\\xff]+$/', $name)) {
        return FALSE;
      }

      // For the value we allow anything that is UTF8
      if (!drupal_validate_utf8($value)) {
        return FALSE;
      }

      // Check our bracket count. If it does not match it is also not valid
      $valid_brackets = TRUE;
      $brackets['opening']['{'] = substr_count($value, '{');
      $brackets['closing']['}'] = substr_count($value, '}');
      $valid_brackets = $valid_brackets && $brackets['opening']['{'] == $brackets['closing']['}'];
      $brackets['opening']['['] = substr_count($value, '[');
      $brackets['closing'][']'] = substr_count($value, ']');
      $valid_brackets = $valid_brackets && $brackets['opening']['['] == $brackets['closing'][']'];
      $brackets['opening']['('] = substr_count($value, '(');
      $brackets['closing'][')'] = substr_count($value, ')');
      $valid_brackets = $valid_brackets && $brackets['opening']['('] == $brackets['closing'][')'];
      if (!$valid_brackets) {
        return FALSE;
      }

      // Check the date field inputs
      if (preg_match('/\\[(.+) TO (.+)\\]$/', $value, $datefields)) {

        // Only Allow a value in the form of
        // http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
        // http://lucene.apache.org/solr/api/org/apache/solr/util/DateMathParser.html
        // http://wiki.apache.org/solr/SolrQuerySyntax
        // 1976-03-06T23:59:59.999Z (valid)
        // * (valid)
        // 1995-12-31T23:59:59.999Z (valid)
        // 2007-03-06T00:00:00Z (valid)
        // NOW-1YEAR/DAY (valid)
        // NOW/DAY+1DAY (valid)
        // 1976-03-06T23:59:59.999Z (valid)
        // 1976-03-06T23:59:59.999Z+1YEAR (valid)
        // 1976-03-06T23:59:59.999Z/YEAR (valid)
        // 1976-03-06T23:59:59.999Z (valid)
        // 1976-03-06T23::59::59.999Z (invalid)
        if (!empty($datefields[1]) && !empty($datefields[2])) {

          // Do not check to full value, only the splitted ones
          unset($datefields[0]);

          // Check if both matches are valid datefields
          foreach ($datefields as $datefield) {
            if (!preg_match('/(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:[\\d\\.]{2,6}Z(\\S)*)|(^([A-Z\\*]+)(\\A-Z0-9\\+\\-\\/)*)/', $datefield, $datefield_match)) {
              return FALSE;
            }
          }
        }
      }
    }
    return TRUE;
  }

  /**
   * Builds a set of filter queries from $this->fields and all subqueries.
   *
   * Returns an array of strings that can be combined into
   * a URL query parameter or passed to Solr as fq paramters.
   */
  protected function rebuildFq() {
    $fq = array();
    foreach ($this->fields as $pos => $field) {
      $fq[] = $this
        ->makeFilterQuery($field);
    }
    foreach ($this->subqueries as $subquery) {
      $subfq = $subquery
        ->rebuildFq();
      if ($subfq) {
        $operator = $subquery->operator;
        $prefix = $subquery->exclude ? '-' : '';
        $fq[] = "{$prefix}(" . implode(" {$operator} ", $subfq) . ")";
      }
    }
    return $fq;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SolrFilterSubQuery::$exclude public property
SolrFilterSubQuery::$fields protected property A keyed array where the key is a position integer and the value is an array with #name and #value properties. Each value is a used for filter queries, e.g. array('#name' => 'is_uid', '#value' => 0) for anonymous…
SolrFilterSubQuery::$id public property Each query/subquery will have a unique ID.
SolrFilterSubQuery::$idCount protected static property Static shared by all instances, used to increment ID numbers.
SolrFilterSubQuery::$operator public property
SolrFilterSubQuery::$subqueries protected property An array of subqueries.
SolrFilterSubQuery::addFilter public function
SolrFilterSubQuery::addFilterSubQuery public function
SolrFilterSubQuery::getFilters public function
SolrFilterSubQuery::getFilterSubQueries public function
SolrFilterSubQuery::hasFilter public function
SolrFilterSubQuery::makeFilterQuery public function
SolrFilterSubQuery::rebuildFq protected function Builds a set of filter queries from $this->fields and all subqueries.
SolrFilterSubQuery::removeFilter public function
SolrFilterSubQuery::removeFilterSubQueries public function
SolrFilterSubQuery::removeFilterSubQuery public function
SolrFilterSubQuery::unsetFilter protected function
SolrFilterSubQuery::validFilterValue public static function Make sure our query matches the pattern name:value or name:"value" Make sure that if we are ranges we use name:[ AND ] allowed inputs : a. bundle:article b. date:[1970-12-31T23:59:59Z TO NOW] Split the text in 4 different parts 1. name, eg.:…
SolrFilterSubQuery::__clone function
SolrFilterSubQuery::__construct function 1