function SearchQuery::parseSearchExpression

Same name in other branches
  1. 7.x modules/search/search.extender.inc \SearchQuery::parseSearchExpression()
  2. 9 core/modules/search/src/SearchQuery.php \Drupal\search\SearchQuery::parseSearchExpression()
  3. 10 core/modules/search/src/SearchQuery.php \Drupal\search\SearchQuery::parseSearchExpression()
  4. 11.x core/modules/search/src/SearchQuery.php \Drupal\search\SearchQuery::parseSearchExpression()

Parses the search query into SQL conditions.

Sets up the following variables:

  • $this->keys
  • $this->words
  • $this->conditions
  • $this->simple
  • $this->matches
2 calls to SearchQuery::parseSearchExpression()
SearchQuery::prepareAndNormalize in core/modules/search/src/SearchQuery.php
Prepares the query and calculates the normalization factor.
ViewsSearchQuery::publicParseSearchExpression in core/modules/search/src/ViewsSearchQuery.php
Executes and returns the protected parseSearchExpression method.

File

core/modules/search/src/SearchQuery.php, line 224

Class

SearchQuery
Search query extender and helper functions.

Namespace

Drupal\search

Code

protected function parseSearchExpression() {
    // Matches words optionally prefixed by a - sign. A word in this case is
    // something between two spaces, optionally quoted.
    preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->searchExpression, $keywords, PREG_SET_ORDER);
    if (count($keywords) == 0) {
        return;
    }
    // Classify tokens.
    $in_or = FALSE;
    $limit_combinations = \Drupal::config('search.settings')->get('and_or_limit');
    // The first search expression does not count as AND.
    $and_count = -1;
    $or_count = 0;
    foreach ($keywords as $match) {
        if ($or_count && $and_count + $or_count >= $limit_combinations) {
            // Ignore all further search expressions to prevent Denial-of-Service
            // attacks using a high number of AND/OR combinations.
            $this->status |= SearchQuery::EXPRESSIONS_IGNORED;
            break;
        }
        // Strip off phrase quotes.
        $phrase = FALSE;
        if ($match[2][0] == '"') {
            $match[2] = substr($match[2], 1, -1);
            $phrase = TRUE;
            $this->simple = FALSE;
        }
        // Simplify keyword according to indexing rules and external
        // preprocessors. Use same process as during search indexing, so it
        // will match search index.
        $words = search_simplify($match[2]);
        // Re-explode in case simplification added more words, except when
        // matching a phrase.
        $words = $phrase ? [
            $words,
        ] : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
        // Negative matches.
        if ($match[1] == '-') {
            $this->keys['negative'] = array_merge($this->keys['negative'], $words);
        }
        elseif ($match[2] == 'OR' && count($this->keys['positive'])) {
            $last = array_pop($this->keys['positive']);
            // Starting a new OR?
            if (!is_array($last)) {
                $last = [
                    $last,
                ];
            }
            $this->keys['positive'][] = $last;
            $in_or = TRUE;
            $or_count++;
            continue;
        }
        elseif ($match[2] == 'AND' || $match[2] == 'and') {
            continue;
        }
        else {
            if ($match[2] == 'or') {
                // Lower-case "or" instead of "OR" is a warning condition.
                $this->status |= SearchQuery::LOWER_CASE_OR;
            }
            if ($in_or) {
                // Add to last element (which is an array).
                $this->keys['positive'][count($this->keys['positive']) - 1] = array_merge($this->keys['positive'][count($this->keys['positive']) - 1], $words);
            }
            else {
                $this->keys['positive'] = array_merge($this->keys['positive'], $words);
                $and_count++;
            }
        }
        $in_or = FALSE;
    }
    // Convert keywords into SQL statements.
    $has_and = FALSE;
    $has_or = FALSE;
    // Positive matches.
    foreach ($this->keys['positive'] as $key) {
        // Group of ORed terms.
        if (is_array($key) && count($key)) {
            // If we had already found one OR, this is another one AND-ed with the
            // first, meaning it is not a simple query.
            if ($has_or) {
                $this->simple = FALSE;
            }
            $has_or = TRUE;
            $has_new_scores = FALSE;
            $queryor = new Condition('OR');
            foreach ($key as $or) {
                list($num_new_scores) = $this->parseWord($or);
                $has_new_scores |= $num_new_scores;
                $queryor->condition('d.data', "% {$or} %", 'LIKE');
            }
            if (count($queryor)) {
                $this->conditions
                    ->condition($queryor);
                // A group of OR keywords only needs to match once.
                $this->matches += $has_new_scores > 0;
            }
        }
        else {
            $has_and = TRUE;
            list($num_new_scores, $num_valid_words) = $this->parseWord($key);
            $this->conditions
                ->condition('d.data', "% {$key} %", 'LIKE');
            if (!$num_valid_words) {
                $this->simple = FALSE;
            }
            // Each AND keyword needs to match at least once.
            $this->matches += $num_new_scores;
        }
    }
    if ($has_and && $has_or) {
        $this->simple = FALSE;
    }
    // Negative matches.
    foreach ($this->keys['negative'] as $key) {
        $this->conditions
            ->condition('d.data', "% {$key} %", 'NOT LIKE');
        $this->simple = FALSE;
    }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.