47 private function parseQuery( $filteredText, $fulltext ) {
50 $this->searchTerms = [];
52 # @todo FIXME: This doesn't handle parenthetical expressions.
54 if ( preg_match_all(
'/([-+<>~]?)(([' . $lc .
']+)(\*?)|"[^"]*")/',
55 $filteredText, $m, PREG_SET_ORDER ) ) {
56 foreach ( $m as $bits ) {
57 Wikimedia\suppressWarnings();
58 list( , $modifier, $term, $nonQuoted, $wildcard ) = $bits;
59 Wikimedia\restoreWarnings();
61 if ( $nonQuoted !=
'' ) {
65 $term = str_replace(
'"',
'', $term );
69 if ( $searchon !==
'' ) {
72 if ( $this->strictMatching && ( $modifier ==
'' ) ) {
79 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
80 $convertedVariants = $contLang->autoConvertToAllVariants( $term );
81 if ( is_array( $convertedVariants ) ) {
82 $variants = array_unique( array_values( $convertedVariants ) );
84 $variants = [ $term ];
91 $strippedVariants = array_map( [ $contLang,
'normalizeForSearch' ], $variants );
96 $strippedVariants = array_unique( $strippedVariants );
98 $searchon .= $modifier;
99 if ( count( $strippedVariants ) > 1 ) {
102 foreach ( $strippedVariants as $stripped ) {
104 if ( $nonQuoted && strpos( $stripped,
' ' ) !==
false ) {
108 $stripped =
'"' . trim( $stripped ) .
'"';
110 $searchon .=
"$quote$stripped$quote$wildcard ";
112 if ( count( $strippedVariants ) > 1 ) {
118 $regexp = $this->
regexTerm( $term, $wildcard );
119 $this->searchTerms[] = $regexp;
121 wfDebug( __METHOD__ .
": Would search with '$searchon'\n" );
122 wfDebug( __METHOD__ .
': Match with /' . implode(
'|', $this->searchTerms ) .
"/\n" );
124 wfDebug( __METHOD__ .
": Can't understand search query '{$filteredText}'\n" );
128 $searchon =
$dbr->addQuotes( $searchon );
131 " MATCH($field) AGAINST($searchon IN BOOLEAN MODE) ",
132 " MATCH($field) AGAINST($searchon IN NATURAL LANGUAGE MODE) DESC "
137 $regex = preg_quote( $string,
'/' );
138 if ( MediaWikiServices::getInstance()->getContentLanguage()->hasWordBreaks() ) {
143 $regex =
"\b$regex\b";
154 $searchChars = parent::legalSearchChars(
$type );
155 if (
$type === self::CHARS_ALL ) {
157 $searchChars =
"\"*" . $searchChars;
184 if ( trim( $term ) ===
'' ) {
188 $filteredTerm = $this->
filter( $term );
189 $query = $this->
getQuery( $filteredTerm, $fulltext );
191 $resultSet =
$dbr->select(
192 $query[
'tables'], $query[
'fields'], $query[
'conds'],
193 __METHOD__, $query[
'options'], $query[
'joins']
198 $totalResult =
$dbr->select(
199 $query[
'tables'], $query[
'fields'], $query[
'conds'],
200 __METHOD__, $query[
'options'], $query[
'joins']
203 $row = $totalResult->fetchObject();
205 $total = intval( $row->c );
207 $totalResult->free();
213 switch ( $feature ) {
214 case 'title-suffix-filter':
217 return parent::supports( $feature );
227 foreach ( $this->features as $feature => $value ) {
228 if ( $feature ===
'title-suffix-filter' && $value ) {
230 $query[
'conds'][] =
'page_title' .
$dbr->buildLike(
$dbr->anyString(), $value );
241 if ( is_array( $this->namespaces ) ) {
242 if ( count( $this->namespaces ) === 0 ) {
243 $this->namespaces[] =
'0';
267 private function getQuery( $filteredTerm, $fulltext ) {
276 $this->
queryMain( $query, $filteredTerm, $fulltext );
290 return $fulltext ?
'si_text' :
'si_title';
301 private function queryMain( &$query, $filteredTerm, $fulltext ) {
302 $match = $this->
parseQuery( $filteredTerm, $fulltext );
303 $query[
'tables'][] =
'page';
304 $query[
'tables'][] =
'searchindex';
305 $query[
'fields'][] =
'page_id';
306 $query[
'fields'][] =
'page_namespace';
307 $query[
'fields'][] =
'page_title';
308 $query[
'conds'][] =
'page_id=si_page';
309 $query[
'conds'][] = $match[0];
310 $query[
'options'][
'ORDER BY'] = $match[1];
320 $match = $this->
parseQuery( $filteredTerm, $fulltext );
323 'tables' => [
'page',
'searchindex' ],
324 'fields' => [
'COUNT(*) as c' ],
325 'conds' => [
'page_id=si_page', $match[0] ],
345 $dbw = $this->lb->getConnectionRef(
DB_MASTER );
346 $dbw->replace(
'searchindex',
363 $dbw = $this->lb->getConnectionRef(
DB_MASTER );
364 $dbw->update(
'searchindex',
366 [
'si_page' => $id ],
379 $dbw = $this->lb->getConnectionRef(
DB_MASTER );
380 $dbw->delete(
'searchindex', [
'si_page' => $id ], __METHOD__ );
390 $out = parent::normalizeText( $string );
394 $out = preg_replace_callback(
395 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
396 [ $this,
'stripForSearchCallback' ],
397 MediaWikiServices::getInstance()->getContentLanguage()->lc( $out ) );
403 if ( $minLength > 1 ) {
432 return 'u8' . bin2hex(
$matches[1] );
442 if ( is_null( self::$mMinSearchLength ) ) {
443 $sql =
"SHOW GLOBAL VARIABLES LIKE 'ft\\_min\\_word\\_len'";
446 $result =
$dbr->query( $sql, __METHOD__ );
447 $row = $result->fetchObject();
450 if ( $row && $row->Variable_name ==
'ft_min_word_len' ) {
451 self::$mMinSearchLength = intval( $row->Value );
453 self::$mMinSearchLength = 0;