![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/mirasvit/module-search-ultimate/src/SearchSphinx/SphinxQL/ |
<?php /** * Mirasvit * * This source file is subject to the Mirasvit Software License, which is available at https://mirasvit.com/license/. * Do not edit or add to this file if you wish to upgrade the to newer versions in the future. * If you wish to customize this module for your needs. * Please refer to http://www.magentocommerce.com for more information. * * @category Mirasvit * @package mirasvit/module-search-ultimate * @version 2.2.35 * @copyright Copyright (C) 2024 Mirasvit (https://mirasvit.com/) */ namespace Mirasvit\SearchSphinx\SphinxQL; /** * Query Builder class for MatchQuery statements. * @SuppressWarnings(PHPMD) * @codingStandardsIgnoreFile */ class MatchQuery { /** * The last compiled query. * * @var string */ protected $last_compiled = null; /** * List of match operations. * * @var array */ protected $tokens = array(); /** * The owning SphinxQL object; used for escaping text. * * @var SphinxQL */ protected $sphinxql = null; /** * MatchQuery constructor. * @param SphinxQL $sphinxql */ public function __construct(SphinxQL $sphinxql) { $this->sphinxql = $sphinxql; } /** * @param SphinxQL $sphinxql * * @return MatchQuery */ public static function create(SphinxQL $sphinxql) { return new MatchQuery($sphinxql); } /** * MatchQuery text or sub expression. * * Examples: * $match->match('test'); * // test * * $match->match('test case'); * // (test case) * * $match->match(function ($m) { * $m->match('a')->orMatch('b'); * }); * // (a | b) * * $sub = new MatchQuery($sphinxql); * $sub->match('a')->orMatch('b'); * $match->match($sub); * // (a | b) * * @param string|Match|callable $keywords The text or expression to match. * @return MatchQuery */ public function match($keywords = null) { if ($keywords !== null) { $this->tokens[] = array('MATCH' => $keywords); } return $this; } /** * Provide an alternation match. * * Examples: * $match->match('test')->orMatch(); * // test | * * $match->match('test')->orMatch('case'); * // test | case * * @param string|Match|callable $keywords The text or expression to alternatively match. * @return MatchQuery */ public function orMatch($keywords = null) { $this->tokens[] = array('OPERATOR' => '| '); $this->match($keywords); return $this; } /** * Provide an optional match. * * Examples: * $match->match('test')->maybe(); * // test MAYBE * * $match->match('test')->maybe('case'); * // test MAYBE case * * @param string|Match|callable $keywords The text or expression to optionally match. * @return MatchQuery */ public function maybe($keywords = null) { $this->tokens[] = array('OPERATOR' => 'MAYBE '); $this->match($keywords); return $this; } /** * Do not match a keyword. * * Examples: * $match->not()->match('test'); * // -test * * $match->not('test'); * // -test * * @param string $keyword The word not to match. * @return MatchQuery */ public function not($keyword = null) { $this->tokens[] = array('OPERATOR' => '-'); $this->match($keyword); return $this; } /** * Specify which field(s) to search. * * Examples: * $match->field('*')->match('test'); * // @* test * * $match->field('title')->match('test'); * // @title test * * $match->field('body', 50)->match('test'); * // @body[50] test * * $match->field('title', 'body')->match('test'); * // @(title,body) test * * $match->field(['title', 'body'])->match('test'); * // @(title,body) test * * $match->field('@relaxed')->field('nosuchfield')->match('test'); * // @@relaxed @nosuchfield test * * @param string|array $fields Field or fields to search. * @param int $limit Maximum position limit in field a match is allowed at. * @return MatchQuery */ public function field($fields, $limit = null) { if (is_string($fields)) { $fields = func_get_args(); $limit = null; } if (!is_string(end($fields))) { $limit = array_pop($fields); } $this->tokens[] = array( 'FIELD' => '@', 'fields' => $fields, 'limit' => $limit, ); return $this; } /** * Specify which field(s) not to search. * * Examples: * $match->ignoreField('title')->match('test'); * // @!title test * * $match->ignoreField('title', 'body')->match('test'); * // @!(title,body) test * * $match->ignoreField(['title', 'body'])->match('test'); * // @!(title,body) test * * @param string|array $fields Field or fields to ignore during search. * @return MatchQuery */ public function ignoreField($fields) { if (is_string($fields)) { $fields = func_get_args(); } $this->tokens[] = array( 'FIELD' => '@!', 'fields' => $fields, 'limit' => null, ); return $this; } /** * MatchQuery an exact phrase. * * Example: * $match->phrase('test case'); * // "test case" * * @param string $keywords The phrase to match. * @return MatchQuery */ public function phrase($keywords) { $this->tokens[] = array('PHRASE' => $keywords); return $this; } /** * MatchQuery if keywords are close enough. * * Example: * $match->proximity('test case', 5); * // "test case"~5 * * @param string $keywords The words to match. * @param int $distance The upper limit on separation between words. * @return MatchQuery */ public function proximity($keywords, $distance) { $this->tokens[] = array( 'PROXIMITY' => $distance, 'keywords' => $keywords, ); return $this; } /** * MatchQuery if enough keywords are present. * * Examples: * $match->quorum('this is a test case', 3); * // "this is a test case"/3 * * $match->quorum('this is a test case', 0.5); * // "this is a test case"/0.5 * * @param string $keywords The words to match. * @param int|float $threshold The minimum number or percent of words that must match. * @return MatchQuery */ public function quorum($keywords, $threshold) { $this->tokens[] = array( 'QUORUM' => $threshold, 'keywords' => $keywords, ); return $this; } /** * Assert keywords or expressions must be matched in order. * * Examples: * $match->match('test')->before(); * // test << * * $match->match('test')->before('case'); * // test << case * * @param string|Match|callable $keywords The text or expression that must come after. * @return MatchQuery */ public function before($keywords = null) { $this->tokens[] = array('OPERATOR' => '<< '); $this->match($keywords); return $this; } /** * Assert a keyword must be matched exactly as written. * * Examples: * $match->match('test')->exact('cases'); * // test =cases * * $match->match('test')->exact()->phrase('specific cases'); * // test ="specific cases" * * @param string $keyword The word that must be matched exactly. * @return MatchQuery */ public function exact($keyword = null) { $this->tokens[] = array('OPERATOR' => '='); $this->match($keyword); return $this; } /** * Boost the IDF score of a keyword. * * Examples: * $match->match('test')->boost(1.2); * // test^1.2 * * $match->match('test')->boost('case', 1.2); * // test case^1.2 * * @param string $keyword The word to modify the score of. * @param float $amount The amount to boost the score. * @return MatchQuery */ public function boost($keyword, $amount = null) { if ($amount === null) { $amount = $keyword; } else { $this->match($keyword); } $this->tokens[] = array('BOOST' => $amount); return $this; } /** * Assert keywords or expressions must be matched close to each other. * * Examples: * $match->match('test')->near(3); * // test NEAR/3 * * $match->match('test')->near('case', 3); * // test NEAR/3 case * * @param string|Match|callable $keywords The text or expression to match nearby. * @param int $distance Maximum distance to the match. * @return MatchQuery */ public function near($keywords, $distance = null) { $this->tokens[] = array('NEAR' => $distance ?: $keywords); if ($distance !== null) { $this->match($keywords); } return $this; } /** * Assert matches must be in the same sentence. * * Examples: * $match->match('test')->sentence(); * // test SENTENCE * * $match->match('test')->sentence('case'); * // test SENTENCE case * * @param string|Match|callable $keywords The text or expression that must be in the sentence. * @return MatchQuery */ public function sentence($keywords = null) { $this->tokens[] = array('OPERATOR' => 'SENTENCE '); $this->match($keywords); return $this; } /** * Assert matches must be in the same paragraph. * * Examples: * $match->match('test')->paragraph(); * // test PARAGRAPH * * $match->match('test')->paragraph('case'); * // test PARAGRAPH case * * @param string|Match|callable $keywords The text or expression that must be in the paragraph. * @return MatchQuery */ public function paragraph($keywords = null) { $this->tokens[] = array('OPERATOR' => 'PARAGRAPH '); $this->match($keywords); return $this; } /** * Assert matches must be in the specified zone(s). * * Examples: * $match->zone('th'); * // ZONE:(th) * * $match->zone(['h3', 'h4']); * // ZONE:(h3,h4) * * $match->zone('th', 'test'); * // ZONE:(th) test * * @param string|array $zones The zone or zones to search. * @param string|Match|callable $keywords The text or expression that must be in these zones. * @return MatchQuery */ public function zone($zones, $keywords = null) { if (is_string($zones)) { $zones = array($zones); } $this->tokens[] = array('ZONE' => $zones); $this->match($keywords); return $this; } /** * Assert matches must be in the same instance of the specified zone. * * Examples: * $match->zonespan('th'); * // ZONESPAN:(th) * * $match->zonespan('th', 'test'); * // ZONESPAN:(th) test * * @param string $zone The zone to search. * @param string|Match|callable $keywords The text or expression that must be in this zone. * @return MatchQuery */ public function zonespan($zone, $keywords = null) { $this->tokens[] = array('ZONESPAN' => $zone); $this->match($keywords); return $this; } /** * Build the match expression. * @return MatchQuery */ public function compile() { $query = ''; foreach ($this->tokens as $token) { if (key($token) == 'MATCH') { if ($token['MATCH'] instanceof Expression) { $query .= $token['MATCH']->value() . ' '; } elseif ($token['MATCH'] instanceof MatchQuery) { $query .= '(' . $token['MATCH']->compile()->getCompiled() . ') '; } elseif (is_callable($token['MATCH'])) { $sub = new static($this->sphinxql); call_user_func($token['MATCH'], $sub); $query .= '(' . $sub->compile()->getCompiled() . ') '; } elseif (strpos($token['MATCH'], ' ') === false) { $query .= $this->sphinxql->escapeMatch($token['MATCH']) . ' '; } else { $query .= '(' . $this->sphinxql->escapeMatch($token['MATCH']) . ') '; } } elseif (key($token) == 'OPERATOR') { $query .= $token['OPERATOR']; } elseif (key($token) == 'FIELD') { $query .= $token['FIELD']; if (count($token['fields']) == 1) { $query .= $token['fields'][0]; } else { $query .= '(' . implode(',', $token['fields']) . ')'; } if ($token['limit']) { $query .= '[' . $token['limit'] . ']'; } $query .= ' '; } elseif (key($token) == 'PHRASE') { $query .= '"' . $this->sphinxql->escapeMatch($token['PHRASE']) . '" '; } elseif (key($token) == 'PROXIMITY') { $query .= '"' . $this->sphinxql->escapeMatch($token['keywords']) . '"~'; $query .= $token['PROXIMITY'] . ' '; } elseif (key($token) == 'QUORUM') { $query .= '"' . $this->sphinxql->escapeMatch($token['keywords']) . '"/'; $query .= $token['QUORUM'] . ' '; } elseif (key($token) == 'BOOST') { $query = rtrim($query) . '^' . $token['BOOST'] . ' '; } elseif (key($token) == 'NEAR') { $query .= 'NEAR/' . $token['NEAR'] . ' '; } elseif (key($token) == 'ZONE') { $query .= 'ZONE:(' . implode(',', $token['ZONE']) . ') '; } elseif (key($token) == 'ZONESPAN') { $query .= 'ZONESPAN:(' . $token['ZONESPAN'] . ') '; } } $this->last_compiled = trim($query); return $this; } /** * Returns the latest compiled match expression. * * @return string The last compiled match expression. */ public function getCompiled() { return $this->last_compiled; } }