37 private static $mMinSearchLength;
48 private function parseQuery( $filteredText, $fulltext ) {
51 $this->searchTerms = [];
53 # @todo FIXME: This doesn't handle parenthetical expressions.
55 if ( preg_match_all(
'/([-+<>~]?)(([' . $lc .
']+)(\*?)|"[^"]*")/',
56 $filteredText, $m, PREG_SET_ORDER )
58 $services = MediaWikiServices::getInstance();
59 $contLang = $services->getContentLanguage();
60 $langConverter = $services->getLanguageConverterFactory()->getLanguageConverter( $contLang );
61 foreach ( $m as $bits ) {
62 AtEase::suppressWarnings();
63 list( , $modifier, $term, $nonQuoted, $wildcard ) = $bits;
64 AtEase::restoreWarnings();
66 if ( $nonQuoted !=
'' ) {
70 $term = str_replace(
'"',
'', $term );
74 if ( $searchon !==
'' ) {
77 if ( $this->strictMatching && ( $modifier ==
'' ) ) {
84 $convertedVariants = $langConverter->autoConvertToAllVariants( $term );
85 if ( is_array( $convertedVariants ) ) {
86 $variants = array_unique( array_values( $convertedVariants ) );
88 $variants = [ $term ];
95 $strippedVariants = array_map( [ $contLang,
'normalizeForSearch' ], $variants );
100 $strippedVariants = array_unique( $strippedVariants );
102 $searchon .= $modifier;
103 if ( count( $strippedVariants ) > 1 ) {
106 foreach ( $strippedVariants as $stripped ) {
108 if ( $nonQuoted && strpos( $stripped,
' ' ) !==
false ) {
112 $stripped =
'"' . trim( $stripped ) .
'"';
114 $searchon .=
"$quote$stripped$quote$wildcard ";
116 if ( count( $strippedVariants ) > 1 ) {
122 $regexp = $this->regexTerm( $term, $wildcard );
123 $this->searchTerms[] = $regexp;
125 wfDebug( __METHOD__ .
": Would search with '$searchon'" );
126 wfDebug( __METHOD__ .
': Match with /' . implode(
'|', $this->searchTerms ) .
"/" );
128 wfDebug( __METHOD__ .
": Can't understand search query '{$filteredText}'" );
132 $searchon =
$dbr->addQuotes( $searchon );
133 $field = $this->getIndexField( $fulltext );
135 " MATCH($field) AGAINST($searchon IN BOOLEAN MODE) ",
136 " MATCH($field) AGAINST($searchon IN NATURAL LANGUAGE MODE) DESC "
140 private function regexTerm( $string, $wildcard ) {
141 $regex = preg_quote( $string,
'/' );
142 if ( MediaWikiServices::getInstance()->getContentLanguage()->hasWordBreaks() ) {
147 $regex =
"\b$regex\b";
158 $searchChars = parent::legalSearchChars(
$type );
168 $searchChars = preg_replace(
'/\\\\-/',
'', $searchChars );
170 if (
$type === self::CHARS_ALL ) {
172 $searchChars =
"\"*" . $searchChars;
199 if ( trim( $term ) ===
'' ) {
203 $filteredTerm = $this->
filter( $term );
204 $query = $this->getQuery( $filteredTerm, $fulltext );
206 $resultSet =
$dbr->select(
207 $query[
'tables'], $query[
'fields'], $query[
'conds'],
208 __METHOD__, $query[
'options'], $query[
'joins']
212 $query = $this->getCountQuery( $filteredTerm, $fulltext );
213 $totalResult =
$dbr->select(
214 $query[
'tables'], $query[
'fields'], $query[
'conds'],
215 __METHOD__, $query[
'options'], $query[
'joins']
218 $row = $totalResult->fetchObject();
220 $total = intval( $row->c );
222 $totalResult->free();
228 switch ( $feature ) {
229 case 'title-suffix-filter':
232 return parent::supports( $feature );
242 foreach ( $this->features as $feature => $value ) {
243 if ( $feature ===
'title-suffix-filter' && $value ) {
245 $query[
'conds'][] =
'page_title' .
$dbr->buildLike(
$dbr->anyString(), $value );
255 private function queryNamespaces( &$query ) {
256 if ( is_array( $this->namespaces ) ) {
257 if ( count( $this->namespaces ) === 0 ) {
282 private function getQuery( $filteredTerm, $fulltext ) {
291 $this->queryMain( $query, $filteredTerm, $fulltext );
293 $this->queryNamespaces( $query );
304 private function getIndexField( $fulltext ) {
305 return $fulltext ?
'si_text' :
'si_title';
316 private function queryMain( &$query, $filteredTerm, $fulltext ) {
317 $match = $this->parseQuery( $filteredTerm, $fulltext );
318 $query[
'tables'][] =
'page';
319 $query[
'tables'][] =
'searchindex';
320 $query[
'fields'][] =
'page_id';
321 $query[
'fields'][] =
'page_namespace';
322 $query[
'fields'][] =
'page_title';
323 $query[
'conds'][] =
'page_id=si_page';
324 $query[
'conds'][] = $match[0];
325 $query[
'options'][
'ORDER BY'] = $match[1];
334 private function getCountQuery( $filteredTerm, $fulltext ) {
335 $match = $this->parseQuery( $filteredTerm, $fulltext );
338 'tables' => [
'page',
'searchindex' ],
339 'fields' => [
'COUNT(*) as c' ],
340 'conds' => [
'page_id=si_page', $match[0] ],
346 $this->queryNamespaces( $query );
360 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
381 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
382 $dbw->update(
'searchindex',
384 [
'si_page' => $id ],
397 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
398 $dbw->delete(
'searchindex', [
'si_page' => $id ], __METHOD__ );
408 $out = parent::normalizeText( $string );
412 $out = preg_replace_callback(
413 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
414 [ $this,
'stripForSearchCallback' ],
415 MediaWikiServices::getInstance()->getContentLanguage()->lc( $out ) );
421 if ( $minLength > 1 ) {
449 return 'u8' . bin2hex(
$matches[1] );
459 if ( self::$mMinSearchLength ===
null ) {
460 $sql =
"SHOW GLOBAL VARIABLES LIKE 'ft\\_min\\_word\\_len'";
463 $result =
$dbr->query( $sql, __METHOD__ );
464 $row = $result->fetchObject();
467 if ( $row && $row->Variable_name ==
'ft_min_word_len' ) {
468 self::$mMinSearchLength = intval( $row->Value );
470 self::$mMinSearchLength = 0;
473 return self::$mMinSearchLength;
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.