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