[TikiWiki-commits] [Git][tikiwiki/tiki][27.x] [FIX] Manticore: handling wildcards in regex
"Victor Emanouilov \(@kroky\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <69f30a885564_38132f9f0937d@gitlab-sidekiq-low-urgency-cpu-bound-v2-5599fc94bd-tpf6x.mail> |
Victor Emanouilov pushed to branch 27.x at Tiki Wiki CMS Groupware / Tiki
Commits:
0a7dbf2f by Victor Emanouilov at 2026-04-30T10:53:36+03:00
[FIX] Manticore: handling wildcards in regex
---
* [FIX] Manticore: handling wildcards in regex resulted in parse error or incorrect regex syntax when fulltext was scaled down to regex due to missing fulltext field index
See merge request tikiwiki/tiki!10112
- - - - -
2 changed files:
- lib/core/Search/Manticore/QueryBuilder.php
- + lib/test/core/Search/Manticore/WildcardTest.php
Changes:
=====================================
lib/core/Search/Manticore/QueryBuilder.php
=====================================
@@ -226,7 +226,7 @@ class QueryBuilder
} elseif ($node instanceof Initial) {
$field = $this->getField($node);
Index::addSearchedField($node->getField(), 'others');
- $value = $this->getQuoted($node, '(?i)^');
+ $value = $this->quoteRegex($node, '(?i)^');
$key = 'tf_' . uniqid();
$this->select[$key] = "REGEX(TO_STRING({$field}), $value)";
return "$key = 1";
@@ -295,7 +295,7 @@ class QueryBuilder
$this->select[$key] = "TO_STRING({$field}) = $value";
return "$key = 1";
} else {
- $value = $this->getQuoted($node, '(?i)');
+ $value = $this->quoteRegex($node, '(?i)');
$key = 'tf_' . uniqid();
$this->select[$key] = "REGEX(TO_STRING({$field}), $value)";
return "$key = 1";
@@ -310,17 +310,18 @@ class QueryBuilder
$value = $this->getQuoted($node);
return "{$field} = $value";
} else {
- $value = $this->getQuoted($node, '(?i)');
- if (is_array($value)) {
+ $rawValue = $this->getRaw($node);
+ if (is_array($rawValue)) {
return '(' . implode(' OR ', array_filter(array_map(function ($v) use ($field) {
if (is_scalar($v)) {
- $v = $this->pdo_client->quote('(?i)' . strval($v));
+ $v = $this->pdo_client->quote('(?i)' . $this->wildcardToRe2(strval($v)));
} else {
return null;
}
return "REGEX({$field}, $v)";
- }, $value))) . ')';
+ }, $rawValue))) . ')';
} else {
+ $value = $this->quoteRegex($node, '(?i)');
return "REGEX({$field}, $value)";
}
}
@@ -393,6 +394,20 @@ class QueryBuilder
}
}
+ private function quoteRegex($node, $prefix)
+ {
+ return $this->pdo_client->quote($prefix . $this->wildcardToRe2(strval($this->getRaw($node))));
+ }
+
+ private function wildcardToRe2($value)
+ {
+ if ($value === '') {
+ return $value;
+ }
+ $escaped = preg_quote($value, '/');
+ return str_replace('\\*', '.*', $escaped);
+ }
+
private function getRaw($node, $forceType = null)
{
$value = $node->getValue($this->factory);
=====================================
lib/test/core/Search/Manticore/WildcardTest.php
=====================================
@@ -0,0 +1,172 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+namespace Search\Manticore;
+
+use ReflectionMethod;
+use Search_Expr_Token;
+use Search_Query;
+
+/**
+ * Verifies how the Manticore QueryBuilder turns wildcard ("*foo*") and
+ * regex-special user input into RE2 patterns inside REGEX() calls.
+ *
+ * The object selector (and similar callers) wrap user input with "*" to
+ * emulate substring search. Because Manticore evaluates these patterns
+ * with RE2, a leading "*" is invalid and previously caused a
+ * "no argument for repetition operator: *" parse error.
+ */
+class WildcardTest extends \PHPUnit\Framework\TestCase
+{
+ use IndexBuilder;
+
+ public $index;
+ private $old_prefs;
+
+ protected function setUp(): void
+ {
+ global $prefs;
+ $this->old_prefs = $prefs;
+
+ $this->index = $this->createIndex('_wildcard');
+ $this->index->destroy();
+
+ $typeFactory = $this->index->getTypeFactory();
+ $this->index->addDocument(
+ [
+ 'object_type' => $typeFactory->identifier('trackeritem'),
+ 'object_id' => $typeFactory->identifier('1'),
+ 'title' => $typeFactory->plaintext('Michaela Smith'),
+ 'tracker_field_name' => $typeFactory->sortable('Michaela Smith'),
+ ]
+ );
+ $this->index->addDocument(
+ [
+ 'object_type' => $typeFactory->identifier('trackeritem'),
+ 'object_id' => $typeFactory->identifier('2'),
+ 'title' => $typeFactory->plaintext('John Doe'),
+ 'tracker_field_name' => $typeFactory->sortable('John Doe'),
+ ]
+ );
+ $this->index->addDocument(
+ [
+ 'object_type' => $typeFactory->identifier('trackeritem'),
+ 'object_id' => $typeFactory->identifier('3'),
+ 'title' => $typeFactory->plaintext('first.middle last'),
+ 'tracker_field_name' => $typeFactory->sortable('first.middle last'),
+ ]
+ );
+ $this->index->addDocument(
+ [
+ 'object_type' => $typeFactory->identifier('trackeritem'),
+ 'object_id' => $typeFactory->identifier('4'),
+ 'title' => $typeFactory->plaintext('firstXmiddle last'),
+ 'tracker_field_name' => $typeFactory->sortable('firstXmiddle last'),
+ ]
+ );
+ }
+
+ protected function tearDown(): void
+ {
+ global $prefs;
+ $prefs = $this->old_prefs;
+
+ if ($this->index) {
+ $this->index->destroy();
+ }
+ }
+
+ public function testLeadingAndTrailingWildcardsAreAccepted()
+ {
+ $query = $this->newQuery();
+ $query->filterContent('*michaela*', 'tracker_field_name');
+ $this->assertCount(1, $query->search($this->index));
+ }
+
+ public function testWildcardOnlyMatchesAnything()
+ {
+ $query = $this->newQuery();
+ $query->filterContent('*', 'tracker_field_name');
+ $this->assertCount(4, $query->search($this->index));
+ }
+
+ public function testInnerWildcardActsAsGlob()
+ {
+ $query = $this->newQuery();
+ $query->filterContent('first*last', 'tracker_field_name');
+ // Should match both "first.middle last" and "firstXmiddle last".
+ $this->assertCount(2, $query->search($this->index));
+ }
+
+ public function testRegexMetacharactersAreTreatedLiterally()
+ {
+ $query = $this->newQuery();
+ $query->filterContent('first.middle', 'tracker_field_name');
+ $this->assertCount(1, $query->search($this->index));
+ }
+
+ public function testWildcardToRe2EscapesMetacharacters()
+ {
+ $convert = new ReflectionMethod(QueryBuilder::class, 'wildcardToRe2');
+ $convert->setAccessible(true);
+
+ $builder = new QueryBuilder($this->index);
+
+ $cases = [
+ // input => expected RE2 body
+ '' => '',
+ 'michaela' => 'michaela',
+ '*michaela*' => '.*michaela.*',
+ 'foo*bar' => 'foo.*bar',
+ '*' => '.*',
+ 'foo.bar' => 'foo\\.bar',
+ 'a+b' => 'a\\+b',
+ 'a(b)c' => 'a\\(b\\)c',
+ ];
+
+ foreach ($cases as $input => $expected) {
+ $this->assertSame(
+ $expected,
+ $convert->invoke($builder, $input),
+ 'wildcardToRe2 for input ' . var_export($input, true)
+ );
+ }
+ }
+
+ public function testQuoteRegexBuildsExpectedPattern()
+ {
+ $quote = new ReflectionMethod(QueryBuilder::class, 'quoteRegex');
+ $quote->setAccessible(true);
+
+ $builder = new QueryBuilder($this->index);
+
+ $token = new Search_Expr_Token('*michaela*');
+ $token->setType('plaintext');
+ $token->setField('tracker_field_name');
+
+ $this->assertSame(
+ "'(?i).*michaela.*'",
+ $quote->invoke($builder, $token, '(?i)')
+ );
+
+ $token = new Search_Expr_Token('mich');
+ $token->setType('plaintext');
+ $token->setField('tracker_field_name');
+
+ $this->assertSame(
+ "'(?i)^mich'",
+ $quote->invoke($builder, $token, '(?i)^')
+ );
+ }
+
+ private function newQuery(): Search_Query
+ {
+ $query = new Search_Query();
+ \TikiLib::lib('unifiedsearch')->initQuery($query);
+ $query->filterType('trackeritem');
+ return $query;
+ }
+}
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/0a7dbf2feb6d1fcc207682a8195f929390791c9a
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/0a7dbf2feb6d1fcc207682a8195f929390791c9a
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs