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 );
159 if (
$type === self::CHARS_ALL ) {
161 $searchChars =
"\"*" . $searchChars;
188 if ( trim( $term ) ===
'' ) {
192 $filteredTerm = $this->
filter( $term );
193 $query = $this->getQuery( $filteredTerm, $fulltext );
195 $resultSet =
$dbr->select(
196 $query[
'tables'], $query[
'fields'], $query[
'conds'],
197 __METHOD__, $query[
'options'], $query[
'joins']
201 $query = $this->getCountQuery( $filteredTerm, $fulltext );
202 $totalResult =
$dbr->select(
203 $query[
'tables'], $query[
'fields'], $query[
'conds'],
204 __METHOD__, $query[
'options'], $query[
'joins']
207 $row = $totalResult->fetchObject();
209 $total = intval( $row->c );
211 $totalResult->free();
217 switch ( $feature ) {
218 case 'title-suffix-filter':
221 return parent::supports( $feature );
231 foreach ( $this->features as $feature => $value ) {
232 if ( $feature ===
'title-suffix-filter' && $value ) {
234 $query[
'conds'][] =
'page_title' .
$dbr->buildLike(
$dbr->anyString(), $value );
244 private function queryNamespaces( &$query ) {
245 if ( is_array( $this->namespaces ) ) {
246 if ( count( $this->namespaces ) === 0 ) {
271 private function getQuery( $filteredTerm, $fulltext ) {
280 $this->queryMain( $query, $filteredTerm, $fulltext );
282 $this->queryNamespaces( $query );
293 private function getIndexField( $fulltext ) {
294 return $fulltext ?
'si_text' :
'si_title';
305 private function queryMain( &$query, $filteredTerm, $fulltext ) {
306 $match = $this->parseQuery( $filteredTerm, $fulltext );
307 $query[
'tables'][] =
'page';
308 $query[
'tables'][] =
'searchindex';
309 $query[
'fields'][] =
'page_id';
310 $query[
'fields'][] =
'page_namespace';
311 $query[
'fields'][] =
'page_title';
312 $query[
'conds'][] =
'page_id=si_page';
313 $query[
'conds'][] = $match[0];
314 $query[
'options'][
'ORDER BY'] = $match[1];
323 private function getCountQuery( $filteredTerm, $fulltext ) {
324 $match = $this->parseQuery( $filteredTerm, $fulltext );
327 'tables' => [
'page',
'searchindex' ],
328 'fields' => [
'COUNT(*) as c' ],
329 'conds' => [
'page_id=si_page', $match[0] ],
335 $this->queryNamespaces( $query );
349 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
370 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
371 $dbw->update(
'searchindex',
373 [
'si_page' => $id ],
386 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
387 $dbw->delete(
'searchindex', [
'si_page' => $id ], __METHOD__ );
397 $out = parent::normalizeText( $string );
401 $out = preg_replace_callback(
402 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
403 [ $this,
'stripForSearchCallback' ],
404 MediaWikiServices::getInstance()->getContentLanguage()->lc( $out ) );
410 if ( $minLength > 1 ) {
438 return 'u8' . bin2hex(
$matches[1] );
448 if ( self::$mMinSearchLength ===
null ) {
449 $sql =
"SHOW GLOBAL VARIABLES LIKE 'ft\\_min\\_word\\_len'";
452 $result =
$dbr->query( $sql, __METHOD__ );
453 $row = $result->fetchObject();
456 if ( $row && $row->Variable_name ==
'ft_min_word_len' ) {
457 self::$mMinSearchLength = intval( $row->Value );
459 self::$mMinSearchLength = 0;
462 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.