MediaWiki
REL1_23
|
00001 <?php 00032 class ApiQuerySearch extends ApiQueryGeneratorBase { 00033 00040 const BACKEND_NULL_PARAM = 'database-backed'; 00041 00042 public function __construct( $query, $moduleName ) { 00043 parent::__construct( $query, $moduleName, 'sr' ); 00044 } 00045 00046 public function execute() { 00047 $this->run(); 00048 } 00049 00050 public function executeGenerator( $resultPageSet ) { 00051 $this->run( $resultPageSet ); 00052 } 00053 00058 private function run( $resultPageSet = null ) { 00059 global $wgContLang; 00060 $params = $this->extractRequestParams(); 00061 00062 // Extract parameters 00063 $limit = $params['limit']; 00064 $query = $params['search']; 00065 $what = $params['what']; 00066 $interwiki = $params['interwiki']; 00067 $searchInfo = array_flip( $params['info'] ); 00068 $prop = array_flip( $params['prop'] ); 00069 00070 // Create search engine instance and set options 00071 $search = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ? 00072 SearchEngine::create( $params['backend'] ) : SearchEngine::create(); 00073 $search->setLimitOffset( $limit + 1, $params['offset'] ); 00074 $search->setNamespaces( $params['namespace'] ); 00075 00076 $query = $search->transformSearchTerm( $query ); 00077 $query = $search->replacePrefixes( $query ); 00078 00079 // Perform the actual search 00080 if ( $what == 'text' ) { 00081 $matches = $search->searchText( $query ); 00082 } elseif ( $what == 'title' ) { 00083 $matches = $search->searchTitle( $query ); 00084 } elseif ( $what == 'nearmatch' ) { 00085 $matches = SearchEngine::getNearMatchResultSet( $query ); 00086 } else { 00087 // We default to title searches; this is a terrible legacy 00088 // of the way we initially set up the MySQL fulltext-based 00089 // search engine with separate title and text fields. 00090 // In the future, the default should be for a combined index. 00091 $what = 'title'; 00092 $matches = $search->searchTitle( $query ); 00093 00094 // Not all search engines support a separate title search, 00095 // for instance the Lucene-based engine we use on Wikipedia. 00096 // In this case, fall back to full-text search (which will 00097 // include titles in it!) 00098 if ( is_null( $matches ) ) { 00099 $what = 'text'; 00100 $matches = $search->searchText( $query ); 00101 } 00102 } 00103 if ( is_null( $matches ) ) { 00104 $this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" ); 00105 } elseif ( $matches instanceof Status && !$matches->isGood() ) { 00106 $this->dieUsage( $matches->getWikiText(), 'search-error' ); 00107 } 00108 00109 $apiResult = $this->getResult(); 00110 // Add search meta data to result 00111 if ( isset( $searchInfo['totalhits'] ) ) { 00112 $totalhits = $matches->getTotalHits(); 00113 if ( $totalhits !== null ) { 00114 $apiResult->addValue( array( 'query', 'searchinfo' ), 00115 'totalhits', $totalhits ); 00116 } 00117 } 00118 if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) { 00119 $apiResult->addValue( array( 'query', 'searchinfo' ), 00120 'suggestion', $matches->getSuggestionQuery() ); 00121 } 00122 00123 // Add the search results to the result 00124 $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); 00125 $titles = array(); 00126 $count = 0; 00127 $result = $matches->next(); 00128 00129 while ( $result ) { 00130 if ( ++$count > $limit ) { 00131 // We've reached the one extra which shows that there are 00132 // additional items to be had. Stop here... 00133 $this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] ); 00134 break; 00135 } 00136 00137 // Silently skip broken and missing titles 00138 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) { 00139 $result = $matches->next(); 00140 continue; 00141 } 00142 00143 $title = $result->getTitle(); 00144 if ( is_null( $resultPageSet ) ) { 00145 $vals = array(); 00146 ApiQueryBase::addTitleInfo( $vals, $title ); 00147 00148 if ( isset( $prop['snippet'] ) ) { 00149 $vals['snippet'] = $result->getTextSnippet( $terms ); 00150 } 00151 if ( isset( $prop['size'] ) ) { 00152 $vals['size'] = $result->getByteSize(); 00153 } 00154 if ( isset( $prop['wordcount'] ) ) { 00155 $vals['wordcount'] = $result->getWordCount(); 00156 } 00157 if ( isset( $prop['timestamp'] ) ) { 00158 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() ); 00159 } 00160 if ( !is_null( $result->getScore() ) && isset( $prop['score'] ) ) { 00161 $vals['score'] = $result->getScore(); 00162 } 00163 if ( isset( $prop['titlesnippet'] ) ) { 00164 $vals['titlesnippet'] = $result->getTitleSnippet( $terms ); 00165 } 00166 if ( !is_null( $result->getRedirectTitle() ) ) { 00167 if ( isset( $prop['redirecttitle'] ) ) { 00168 $vals['redirecttitle'] = $result->getRedirectTitle(); 00169 } 00170 if ( isset( $prop['redirectsnippet'] ) ) { 00171 $vals['redirectsnippet'] = $result->getRedirectSnippet( $terms ); 00172 } 00173 } 00174 if ( !is_null( $result->getSectionTitle() ) ) { 00175 if ( isset( $prop['sectiontitle'] ) ) { 00176 $vals['sectiontitle'] = $result->getSectionTitle()->getFragment(); 00177 } 00178 if ( isset( $prop['sectionsnippet'] ) ) { 00179 $vals['sectionsnippet'] = $result->getSectionSnippet(); 00180 } 00181 } 00182 if ( isset( $prop['hasrelated'] ) && $result->hasRelated() ) { 00183 $vals['hasrelated'] = ''; 00184 } 00185 00186 // Add item to results and see whether it fits 00187 $fit = $apiResult->addValue( array( 'query', $this->getModuleName() ), 00188 null, $vals ); 00189 if ( !$fit ) { 00190 $this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 ); 00191 break; 00192 } 00193 } else { 00194 $titles[] = $title; 00195 } 00196 00197 $result = $matches->next(); 00198 } 00199 00200 $hasInterwikiResults = false; 00201 if ( $interwiki && $resultPageSet === null && $matches->hasInterwikiResults() ) { 00202 $matches = $matches->getInterwikiResults(); 00203 $iwprefixes = array(); 00204 $hasInterwikiResults = true; 00205 00206 // Include number of results if requested 00207 if ( isset( $searchInfo['totalhits'] ) ) { 00208 $totalhits = $matches->getTotalHits(); 00209 if ( $totalhits !== null ) { 00210 $apiResult->addValue( array( 'query', 'interwikisearchinfo' ), 00211 'totalhits', $totalhits ); 00212 } 00213 } 00214 00215 $result = $matches->next(); 00216 while ( $result ) { 00217 $title = $result->getTitle(); 00218 $vals = array( 00219 'namespace' => $result->getInterwikiNamespaceText(), 00220 'title' => $title->getText(), 00221 'url' => $title->getFullUrl(), 00222 ); 00223 00224 // Add item to results and see whether it fits 00225 $fit = $apiResult->addValue( array( 'query', 'interwiki' . $this->getModuleName(), $result->getInterwikiPrefix() ), 00226 null, $vals ); 00227 if ( !$fit ) { 00228 // We hit the limit. We can't really provide any meaningful 00229 // pagination info so just bail out 00230 break; 00231 } 00232 00233 $result = $matches->next(); 00234 } 00235 } 00236 00237 if ( is_null( $resultPageSet ) ) { 00238 $apiResult->setIndexedTagName_internal( array( 00239 'query', $this->getModuleName() 00240 ), 'p' ); 00241 if ( $hasInterwikiResults ) { 00242 $apiResult->setIndexedTagName_internal( array( 00243 'query', 'interwiki' . $this->getModuleName() 00244 ), 'p' ); 00245 } 00246 } else { 00247 $resultPageSet->populateFromTitles( $titles ); 00248 } 00249 } 00250 00251 public function getCacheMode( $params ) { 00252 return 'public'; 00253 } 00254 00255 public function getAllowedParams() { 00256 global $wgSearchType; 00257 00258 $params = array( 00259 'search' => array( 00260 ApiBase::PARAM_TYPE => 'string', 00261 ApiBase::PARAM_REQUIRED => true 00262 ), 00263 'namespace' => array( 00264 ApiBase::PARAM_DFLT => NS_MAIN, 00265 ApiBase::PARAM_TYPE => 'namespace', 00266 ApiBase::PARAM_ISMULTI => true, 00267 ), 00268 'what' => array( 00269 ApiBase::PARAM_DFLT => null, 00270 ApiBase::PARAM_TYPE => array( 00271 'title', 00272 'text', 00273 'nearmatch', 00274 ) 00275 ), 00276 'info' => array( 00277 ApiBase::PARAM_DFLT => 'totalhits|suggestion', 00278 ApiBase::PARAM_TYPE => array( 00279 'totalhits', 00280 'suggestion', 00281 ), 00282 ApiBase::PARAM_ISMULTI => true, 00283 ), 00284 'prop' => array( 00285 ApiBase::PARAM_DFLT => 'size|wordcount|timestamp|snippet', 00286 ApiBase::PARAM_TYPE => array( 00287 'size', 00288 'wordcount', 00289 'timestamp', 00290 'score', 00291 'snippet', 00292 'titlesnippet', 00293 'redirecttitle', 00294 'redirectsnippet', 00295 'sectiontitle', 00296 'sectionsnippet', 00297 'hasrelated', 00298 ), 00299 ApiBase::PARAM_ISMULTI => true, 00300 ), 00301 'offset' => 0, 00302 'limit' => array( 00303 ApiBase::PARAM_DFLT => 10, 00304 ApiBase::PARAM_TYPE => 'limit', 00305 ApiBase::PARAM_MIN => 1, 00306 ApiBase::PARAM_MAX => ApiBase::LIMIT_SML1, 00307 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_SML2 00308 ), 00309 'interwiki' => false, 00310 ); 00311 00312 $alternatives = SearchEngine::getSearchTypes(); 00313 if ( count( $alternatives ) > 1 ) { 00314 if ( $alternatives[0] === null ) { 00315 $alternatives[0] = self::BACKEND_NULL_PARAM; 00316 } 00317 $params['backend'] = array( 00318 ApiBase::PARAM_DFLT => $wgSearchType, 00319 ApiBase::PARAM_TYPE => $alternatives, 00320 ); 00321 } 00322 00323 return $params; 00324 } 00325 00326 public function getParamDescription() { 00327 $descriptions = array( 00328 'search' => 'Search for all page titles (or content) that has this value', 00329 'namespace' => 'The namespace(s) to enumerate', 00330 'what' => 'Search inside the text or titles', 00331 'info' => 'What metadata to return', 00332 'prop' => array( 00333 'What properties to return', 00334 ' size - Adds the size of the page in bytes', 00335 ' wordcount - Adds the word count of the page', 00336 ' timestamp - Adds the timestamp of when the page was last edited', 00337 ' score - Adds the score (if any) from the search engine', 00338 ' snippet - Adds a parsed snippet of the page', 00339 ' titlesnippet - Adds a parsed snippet of the page title', 00340 ' redirectsnippet - Adds a parsed snippet of the redirect title', 00341 ' redirecttitle - Adds the title of the matching redirect', 00342 ' sectionsnippet - Adds a parsed snippet of the matching section title', 00343 ' sectiontitle - Adds the title of the matching section', 00344 ' hasrelated - Indicates whether a related search is available', 00345 ), 00346 'offset' => 'Use this value to continue paging (return by query)', 00347 'limit' => 'How many total pages to return', 00348 'interwiki' => 'Include interwiki results in the search, if available' 00349 ); 00350 00351 if ( count( SearchEngine::getSearchTypes() ) > 1 ) { 00352 $descriptions['backend'] = 'Which search backend to use, if not the default'; 00353 } 00354 00355 return $descriptions; 00356 } 00357 00358 public function getResultProperties() { 00359 return array( 00360 '' => array( 00361 'ns' => 'namespace', 00362 'title' => 'string' 00363 ), 00364 'snippet' => array( 00365 'snippet' => 'string' 00366 ), 00367 'size' => array( 00368 'size' => 'integer' 00369 ), 00370 'wordcount' => array( 00371 'wordcount' => 'integer' 00372 ), 00373 'timestamp' => array( 00374 'timestamp' => 'timestamp' 00375 ), 00376 'score' => array( 00377 'score' => array( 00378 ApiBase::PROP_TYPE => 'string', 00379 ApiBase::PROP_NULLABLE => true 00380 ) 00381 ), 00382 'titlesnippet' => array( 00383 'titlesnippet' => 'string' 00384 ), 00385 'redirecttitle' => array( 00386 'redirecttitle' => array( 00387 ApiBase::PROP_TYPE => 'string', 00388 ApiBase::PROP_NULLABLE => true 00389 ) 00390 ), 00391 'redirectsnippet' => array( 00392 'redirectsnippet' => array( 00393 ApiBase::PROP_TYPE => 'string', 00394 ApiBase::PROP_NULLABLE => true 00395 ) 00396 ), 00397 'sectiontitle' => array( 00398 'sectiontitle' => array( 00399 ApiBase::PROP_TYPE => 'string', 00400 ApiBase::PROP_NULLABLE => true 00401 ) 00402 ), 00403 'sectionsnippet' => array( 00404 'sectionsnippet' => array( 00405 ApiBase::PROP_TYPE => 'string', 00406 ApiBase::PROP_NULLABLE => true 00407 ) 00408 ), 00409 'hasrelated' => array( 00410 'hasrelated' => 'boolean' 00411 ) 00412 ); 00413 } 00414 00415 public function getDescription() { 00416 return 'Perform a full text search.'; 00417 } 00418 00419 public function getPossibleErrors() { 00420 return array_merge( parent::getPossibleErrors(), array( 00421 array( 'code' => 'search-text-disabled', 'info' => 'text search is disabled' ), 00422 array( 'code' => 'search-title-disabled', 'info' => 'title search is disabled' ), 00423 array( 'code' => 'search-error', 'info' => 'search error has occurred' ), 00424 ) ); 00425 } 00426 00427 public function getExamples() { 00428 return array( 00429 'api.php?action=query&list=search&srsearch=meaning', 00430 'api.php?action=query&list=search&srwhat=text&srsearch=meaning', 00431 'api.php?action=query&generator=search&gsrsearch=meaning&prop=info', 00432 ); 00433 } 00434 00435 public function getHelpUrls() { 00436 return 'https://www.mediawiki.org/wiki/API:Search'; 00437 } 00438 }