[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Basic search engine 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup Search 22 */ 23 24 /** 25 * @defgroup Search Search 26 */ 27 28 /** 29 * Contain a class for special pages 30 * @ingroup Search 31 */ 32 class SearchEngine { 33 /** @var string */ 34 public $prefix = ''; 35 36 /** @var int[] */ 37 public $namespaces = array( NS_MAIN ); 38 39 /** @var int */ 40 protected $limit = 10; 41 42 /** @var int */ 43 protected $offset = 0; 44 45 /** @var array|string */ 46 protected $searchTerms = array(); 47 48 /** @var bool */ 49 protected $showSuggestion = true; 50 51 /** @var array Feature values */ 52 protected $features = array(); 53 54 /** 55 * Perform a full text search query and return a result set. 56 * If title searches are not supported or disabled, return null. 57 * STUB 58 * 59 * @param string $term Raw search term 60 * @return SearchResultSet|Status|null 61 */ 62 function searchText( $term ) { 63 return null; 64 } 65 66 /** 67 * Perform a title-only search query and return a result set. 68 * If title searches are not supported or disabled, return null. 69 * STUB 70 * 71 * @param string $term Raw search term 72 * @return SearchResultSet|null 73 */ 74 function searchTitle( $term ) { 75 return null; 76 } 77 78 /** 79 * @since 1.18 80 * @param string $feature 81 * @return bool 82 */ 83 public function supports( $feature ) { 84 switch ( $feature ) { 85 case 'search-update': 86 return true; 87 case 'title-suffix-filter': 88 default: 89 return false; 90 } 91 } 92 93 /** 94 * Way to pass custom data for engines 95 * @since 1.18 96 * @param string $feature 97 * @param mixed $data 98 * @return bool 99 */ 100 public function setFeatureData( $feature, $data ) { 101 $this->features[$feature] = $data; 102 } 103 104 /** 105 * When overridden in derived class, performs database-specific conversions 106 * on text to be used for searching or updating search index. 107 * Default implementation does nothing (simply returns $string). 108 * 109 * @param string $string String to process 110 * @return string 111 */ 112 public function normalizeText( $string ) { 113 global $wgContLang; 114 115 // Some languages such as Chinese require word segmentation 116 return $wgContLang->segmentByWord( $string ); 117 } 118 119 /** 120 * Transform search term in cases when parts of the query came as different 121 * GET params (when supported), e.g. for prefix queries: 122 * search=test&prefix=Main_Page/Archive -> test prefix:Main Page/Archive 123 * @param string $term 124 * @return string 125 */ 126 function transformSearchTerm( $term ) { 127 return $term; 128 } 129 130 /** 131 * If an exact title match can be found, or a very slightly close match, 132 * return the title. If no match, returns NULL. 133 * 134 * @param string $searchterm 135 * @return Title 136 */ 137 public static function getNearMatch( $searchterm ) { 138 $title = self::getNearMatchInternal( $searchterm ); 139 140 wfRunHooks( 'SearchGetNearMatchComplete', array( $searchterm, &$title ) ); 141 return $title; 142 } 143 144 /** 145 * Do a near match (see SearchEngine::getNearMatch) and wrap it into a 146 * SearchResultSet. 147 * 148 * @param string $searchterm 149 * @return SearchResultSet 150 */ 151 public static function getNearMatchResultSet( $searchterm ) { 152 return new SearchNearMatchResultSet( self::getNearMatch( $searchterm ) ); 153 } 154 155 /** 156 * Really find the title match. 157 * @param string $searchterm 158 * @return null|Title 159 */ 160 private static function getNearMatchInternal( $searchterm ) { 161 global $wgContLang, $wgEnableSearchContributorsByIP; 162 163 $allSearchTerms = array( $searchterm ); 164 165 if ( $wgContLang->hasVariants() ) { 166 $allSearchTerms = array_merge( 167 $allSearchTerms, 168 $wgContLang->autoConvertToAllVariants( $searchterm ) 169 ); 170 } 171 172 $titleResult = null; 173 if ( !wfRunHooks( 'SearchGetNearMatchBefore', array( $allSearchTerms, &$titleResult ) ) ) { 174 return $titleResult; 175 } 176 177 foreach ( $allSearchTerms as $term ) { 178 179 # Exact match? No need to look further. 180 $title = Title::newFromText( $term ); 181 if ( is_null( $title ) ) { 182 return null; 183 } 184 185 # Try files if searching in the Media: namespace 186 if ( $title->getNamespace() == NS_MEDIA ) { 187 $title = Title::makeTitle( NS_FILE, $title->getText() ); 188 } 189 190 if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) { 191 return $title; 192 } 193 194 # See if it still otherwise has content is some sane sense 195 $page = WikiPage::factory( $title ); 196 if ( $page->hasViewableContent() ) { 197 return $title; 198 } 199 200 if ( !wfRunHooks( 'SearchAfterNoDirectMatch', array( $term, &$title ) ) ) { 201 return $title; 202 } 203 204 # Now try all lower case (i.e. first letter capitalized) 205 $title = Title::newFromText( $wgContLang->lc( $term ) ); 206 if ( $title && $title->exists() ) { 207 return $title; 208 } 209 210 # Now try capitalized string 211 $title = Title::newFromText( $wgContLang->ucwords( $term ) ); 212 if ( $title && $title->exists() ) { 213 return $title; 214 } 215 216 # Now try all upper case 217 $title = Title::newFromText( $wgContLang->uc( $term ) ); 218 if ( $title && $title->exists() ) { 219 return $title; 220 } 221 222 # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc 223 $title = Title::newFromText( $wgContLang->ucwordbreaks( $term ) ); 224 if ( $title && $title->exists() ) { 225 return $title; 226 } 227 228 // Give hooks a chance at better match variants 229 $title = null; 230 if ( !wfRunHooks( 'SearchGetNearMatch', array( $term, &$title ) ) ) { 231 return $title; 232 } 233 } 234 235 $title = Title::newFromText( $searchterm ); 236 237 # Entering an IP address goes to the contributions page 238 if ( $wgEnableSearchContributorsByIP ) { 239 if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) ) 240 || User::isIP( trim( $searchterm ) ) ) { 241 return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() ); 242 } 243 } 244 245 # Entering a user goes to the user page whether it's there or not 246 if ( $title->getNamespace() == NS_USER ) { 247 return $title; 248 } 249 250 # Go to images that exist even if there's no local page. 251 # There may have been a funny upload, or it may be on a shared 252 # file repository such as Wikimedia Commons. 253 if ( $title->getNamespace() == NS_FILE ) { 254 $image = wfFindFile( $title ); 255 if ( $image ) { 256 return $title; 257 } 258 } 259 260 # MediaWiki namespace? Page may be "implied" if not customized. 261 # Just return it, with caps forced as the message system likes it. 262 if ( $title->getNamespace() == NS_MEDIAWIKI ) { 263 return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( $title->getText() ) ); 264 } 265 266 # Quoted term? Try without the quotes... 267 $matches = array(); 268 if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) { 269 return SearchEngine::getNearMatch( $matches[1] ); 270 } 271 272 return null; 273 } 274 275 public static function legalSearchChars() { 276 return "A-Za-z_'.0-9\\x80-\\xFF\\-"; 277 } 278 279 /** 280 * Set the maximum number of results to return 281 * and how many to skip before returning the first. 282 * 283 * @param int $limit 284 * @param int $offset 285 */ 286 function setLimitOffset( $limit, $offset = 0 ) { 287 $this->limit = intval( $limit ); 288 $this->offset = intval( $offset ); 289 } 290 291 /** 292 * Set which namespaces the search should include. 293 * Give an array of namespace index numbers. 294 * 295 * @param array $namespaces 296 */ 297 function setNamespaces( $namespaces ) { 298 $this->namespaces = $namespaces; 299 } 300 301 /** 302 * Set whether the searcher should try to build a suggestion. Note: some searchers 303 * don't support building a suggestion in the first place and others don't respect 304 * this flag. 305 * 306 * @param bool $showSuggestion Should the searcher try to build suggestions 307 */ 308 function setShowSuggestion( $showSuggestion ) { 309 $this->showSuggestion = $showSuggestion; 310 } 311 312 /** 313 * Parse some common prefixes: all (search everything) 314 * or namespace names 315 * 316 * @param string $query 317 * @return string 318 */ 319 function replacePrefixes( $query ) { 320 global $wgContLang; 321 322 $parsed = $query; 323 if ( strpos( $query, ':' ) === false ) { // nothing to do 324 return $parsed; 325 } 326 327 $allkeyword = wfMessage( 'searchall' )->inContentLanguage()->text() . ":"; 328 if ( strncmp( $query, $allkeyword, strlen( $allkeyword ) ) == 0 ) { 329 $this->namespaces = null; 330 $parsed = substr( $query, strlen( $allkeyword ) ); 331 } elseif ( strpos( $query, ':' ) !== false ) { 332 $prefix = str_replace( ' ', '_', substr( $query, 0, strpos( $query, ':' ) ) ); 333 $index = $wgContLang->getNsIndex( $prefix ); 334 if ( $index !== false ) { 335 $this->namespaces = array( $index ); 336 $parsed = substr( $query, strlen( $prefix ) + 1 ); 337 } 338 } 339 if ( trim( $parsed ) == '' ) { 340 $parsed = $query; // prefix was the whole query 341 } 342 343 return $parsed; 344 } 345 346 /** 347 * Make a list of searchable namespaces and their canonical names. 348 * @return array 349 */ 350 public static function searchableNamespaces() { 351 global $wgContLang; 352 $arr = array(); 353 foreach ( $wgContLang->getNamespaces() as $ns => $name ) { 354 if ( $ns >= NS_MAIN ) { 355 $arr[$ns] = $name; 356 } 357 } 358 359 wfRunHooks( 'SearchableNamespaces', array( &$arr ) ); 360 return $arr; 361 } 362 363 /** 364 * Extract default namespaces to search from the given user's 365 * settings, returning a list of index numbers. 366 * 367 * @param user $user 368 * @return array 369 */ 370 public static function userNamespaces( $user ) { 371 $arr = array(); 372 foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) { 373 if ( $user->getOption( 'searchNs' . $ns ) ) { 374 $arr[] = $ns; 375 } 376 } 377 378 return $arr; 379 } 380 381 /** 382 * Find snippet highlight settings for all users 383 * 384 * @return array Contextlines, contextchars 385 */ 386 public static function userHighlightPrefs() { 387 $contextlines = 2; // Hardcode this. Old defaults sucked. :) 388 $contextchars = 75; // same as above.... :P 389 return array( $contextlines, $contextchars ); 390 } 391 392 /** 393 * An array of namespaces indexes to be searched by default 394 * 395 * @return array 396 */ 397 public static function defaultNamespaces() { 398 global $wgNamespacesToBeSearchedDefault; 399 400 return array_keys( $wgNamespacesToBeSearchedDefault, true ); 401 } 402 403 /** 404 * Get a list of namespace names useful for showing in tooltips 405 * and preferences 406 * 407 * @param array $namespaces 408 * @return array 409 */ 410 public static function namespacesAsText( $namespaces ) { 411 global $wgContLang; 412 413 $formatted = array_map( array( $wgContLang, 'getFormattedNsText' ), $namespaces ); 414 foreach ( $formatted as $key => $ns ) { 415 if ( empty( $ns ) ) { 416 $formatted[$key] = wfMessage( 'blanknamespace' )->text(); 417 } 418 } 419 return $formatted; 420 } 421 422 /** 423 * Load up the appropriate search engine class for the currently 424 * active database backend, and return a configured instance. 425 * 426 * @param string $type Type of search backend, if not the default 427 * @return SearchEngine 428 */ 429 public static function create( $type = null ) { 430 global $wgSearchType; 431 $dbr = null; 432 433 $alternatives = self::getSearchTypes(); 434 435 if ( $type && in_array( $type, $alternatives ) ) { 436 $class = $type; 437 } elseif ( $wgSearchType !== null ) { 438 $class = $wgSearchType; 439 } else { 440 $dbr = wfGetDB( DB_SLAVE ); 441 $class = $dbr->getSearchEngine(); 442 } 443 444 $search = new $class( $dbr ); 445 return $search; 446 } 447 448 /** 449 * Return the search engines we support. If only $wgSearchType 450 * is set, it'll be an array of just that one item. 451 * 452 * @return array 453 */ 454 public static function getSearchTypes() { 455 global $wgSearchType, $wgSearchTypeAlternatives; 456 457 $alternatives = $wgSearchTypeAlternatives ?: array(); 458 array_unshift( $alternatives, $wgSearchType ); 459 460 return $alternatives; 461 } 462 463 /** 464 * Create or update the search index record for the given page. 465 * Title and text should be pre-processed. 466 * STUB 467 * 468 * @param int $id 469 * @param string $title 470 * @param string $text 471 */ 472 function update( $id, $title, $text ) { 473 // no-op 474 } 475 476 /** 477 * Update a search index record's title only. 478 * Title should be pre-processed. 479 * STUB 480 * 481 * @param int $id 482 * @param string $title 483 */ 484 function updateTitle( $id, $title ) { 485 // no-op 486 } 487 488 /** 489 * Delete an indexed page 490 * Title should be pre-processed. 491 * STUB 492 * 493 * @param int $id Page id that was deleted 494 * @param string $title Title of page that was deleted 495 */ 496 function delete( $id, $title ) { 497 // no-op 498 } 499 500 /** 501 * Get OpenSearch suggestion template 502 * 503 * @return string 504 */ 505 public static function getOpenSearchTemplate() { 506 global $wgOpenSearchTemplate, $wgCanonicalServer; 507 508 if ( $wgOpenSearchTemplate ) { 509 return $wgOpenSearchTemplate; 510 } else { 511 $ns = implode( '|', SearchEngine::defaultNamespaces() ); 512 if ( !$ns ) { 513 $ns = "0"; 514 } 515 516 return $wgCanonicalServer . wfScript( 'api' ) 517 . '?action=opensearch&search={searchTerms}&namespace=' . $ns; 518 } 519 } 520 521 /** 522 * Get the raw text for updating the index from a content object 523 * Nicer search backends could possibly do something cooler than 524 * just returning raw text 525 * 526 * @todo This isn't ideal, we'd really like to have content-specific handling here 527 * @param Title $t Title we're indexing 528 * @param Content $c Content of the page to index 529 * @return string 530 */ 531 public function getTextFromContent( Title $t, Content $c = null ) { 532 return $c ? $c->getTextForSearchIndex() : ''; 533 } 534 535 /** 536 * If an implementation of SearchEngine handles all of its own text processing 537 * in getTextFromContent() and doesn't require SearchUpdate::updateText()'s 538 * rather silly handling, it should return true here instead. 539 * 540 * @return bool 541 */ 542 public function textAlreadyUpdatedForIndex() { 543 return false; 544 } 545 } 546 547 /** 548 * Dummy class to be used when non-supported Database engine is present. 549 * @todo FIXME: Dummy class should probably try something at least mildly useful, 550 * such as a LIKE search through titles. 551 * @ingroup Search 552 */ 553 class SearchEngineDummy extends SearchEngine { 554 // no-op 555 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |