MediaWiki
REL1_22
|
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 $searchInfo = array_flip( $params['info'] ); 00067 $prop = array_flip( $params['prop'] ); 00068 00069 // Create search engine instance and set options 00070 $search = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ? 00071 SearchEngine::create( $params['backend'] ) : SearchEngine::create(); 00072 $search->setLimitOffset( $limit + 1, $params['offset'] ); 00073 $search->setNamespaces( $params['namespace'] ); 00074 $search->showRedirects = $params['redirects']; 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 additional items to be had. Stop here... 00132 $this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] ); 00133 break; 00134 } 00135 00136 // Silently skip broken and missing titles 00137 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) { 00138 $result = $matches->next(); 00139 continue; 00140 } 00141 00142 $title = $result->getTitle(); 00143 if ( is_null( $resultPageSet ) ) { 00144 $vals = array(); 00145 ApiQueryBase::addTitleInfo( $vals, $title ); 00146 00147 if ( isset( $prop['snippet'] ) ) { 00148 $vals['snippet'] = $result->getTextSnippet( $terms ); 00149 } 00150 if ( isset( $prop['size'] ) ) { 00151 $vals['size'] = $result->getByteSize(); 00152 } 00153 if ( isset( $prop['wordcount'] ) ) { 00154 $vals['wordcount'] = $result->getWordCount(); 00155 } 00156 if ( isset( $prop['timestamp'] ) ) { 00157 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() ); 00158 } 00159 if ( !is_null( $result->getScore() ) && isset( $prop['score'] ) ) { 00160 $vals['score'] = $result->getScore(); 00161 } 00162 if ( isset( $prop['titlesnippet'] ) ) { 00163 $vals['titlesnippet'] = $result->getTitleSnippet( $terms ); 00164 } 00165 if ( !is_null( $result->getRedirectTitle() ) ) { 00166 if ( isset( $prop['redirecttitle'] ) ) { 00167 $vals['redirecttitle'] = $result->getRedirectTitle(); 00168 } 00169 if ( isset( $prop['redirectsnippet'] ) ) { 00170 $vals['redirectsnippet'] = $result->getRedirectSnippet( $terms ); 00171 } 00172 } 00173 if ( !is_null( $result->getSectionTitle() ) ) { 00174 if ( isset( $prop['sectiontitle'] ) ) { 00175 $vals['sectiontitle'] = $result->getSectionTitle()->getFragment(); 00176 } 00177 if ( isset( $prop['sectionsnippet'] ) ) { 00178 $vals['sectionsnippet'] = $result->getSectionSnippet(); 00179 } 00180 } 00181 if ( isset( $prop['hasrelated'] ) && $result->hasRelated() ) { 00182 $vals['hasrelated'] = ''; 00183 } 00184 00185 // Add item to results and see whether it fits 00186 $fit = $apiResult->addValue( array( 'query', $this->getModuleName() ), 00187 null, $vals ); 00188 if ( !$fit ) { 00189 $this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 ); 00190 break; 00191 } 00192 } else { 00193 $titles[] = $title; 00194 } 00195 00196 $result = $matches->next(); 00197 } 00198 00199 if ( is_null( $resultPageSet ) ) { 00200 $apiResult->setIndexedTagName_internal( array( 00201 'query', $this->getModuleName() 00202 ), 'p' ); 00203 } else { 00204 $resultPageSet->populateFromTitles( $titles ); 00205 } 00206 } 00207 00208 public function getCacheMode( $params ) { 00209 return 'public'; 00210 } 00211 00212 public function getAllowedParams() { 00213 global $wgSearchType; 00214 00215 $params = array( 00216 'search' => array( 00217 ApiBase::PARAM_TYPE => 'string', 00218 ApiBase::PARAM_REQUIRED => true 00219 ), 00220 'namespace' => array( 00221 ApiBase::PARAM_DFLT => NS_MAIN, 00222 ApiBase::PARAM_TYPE => 'namespace', 00223 ApiBase::PARAM_ISMULTI => true, 00224 ), 00225 'what' => array( 00226 ApiBase::PARAM_DFLT => null, 00227 ApiBase::PARAM_TYPE => array( 00228 'title', 00229 'text', 00230 'nearmatch', 00231 ) 00232 ), 00233 'info' => array( 00234 ApiBase::PARAM_DFLT => 'totalhits|suggestion', 00235 ApiBase::PARAM_TYPE => array( 00236 'totalhits', 00237 'suggestion', 00238 ), 00239 ApiBase::PARAM_ISMULTI => true, 00240 ), 00241 'prop' => array( 00242 ApiBase::PARAM_DFLT => 'size|wordcount|timestamp|snippet', 00243 ApiBase::PARAM_TYPE => array( 00244 'size', 00245 'wordcount', 00246 'timestamp', 00247 'score', 00248 'snippet', 00249 'titlesnippet', 00250 'redirecttitle', 00251 'redirectsnippet', 00252 'sectiontitle', 00253 'sectionsnippet', 00254 'hasrelated', 00255 ), 00256 ApiBase::PARAM_ISMULTI => true, 00257 ), 00258 'redirects' => false, 00259 'offset' => 0, 00260 'limit' => array( 00261 ApiBase::PARAM_DFLT => 10, 00262 ApiBase::PARAM_TYPE => 'limit', 00263 ApiBase::PARAM_MIN => 1, 00264 ApiBase::PARAM_MAX => ApiBase::LIMIT_SML1, 00265 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_SML2 00266 ) 00267 ); 00268 00269 $alternatives = SearchEngine::getSearchTypes(); 00270 if ( count( $alternatives ) > 1 ) { 00271 if ( $alternatives[0] === null ) { 00272 $alternatives[0] = self::BACKEND_NULL_PARAM; 00273 } 00274 $params['backend'] = array( 00275 ApiBase::PARAM_DFLT => $wgSearchType, 00276 ApiBase::PARAM_TYPE => $alternatives, 00277 ); 00278 } 00279 00280 return $params; 00281 } 00282 00283 public function getParamDescription() { 00284 $descriptions = array( 00285 'search' => 'Search for all page titles (or content) that has this value', 00286 'namespace' => 'The namespace(s) to enumerate', 00287 'what' => 'Search inside the text or titles', 00288 'info' => 'What metadata to return', 00289 'prop' => array( 00290 'What properties to return', 00291 ' size - Adds the size of the page in bytes', 00292 ' wordcount - Adds the word count of the page', 00293 ' timestamp - Adds the timestamp of when the page was last edited', 00294 ' score - Adds the score (if any) from the search engine', 00295 ' snippet - Adds a parsed snippet of the page', 00296 ' titlesnippet - Adds a parsed snippet of the page title', 00297 ' redirectsnippet - Adds a parsed snippet of the redirect title', 00298 ' redirecttitle - Adds the title of the matching redirect', 00299 ' sectionsnippet - Adds a parsed snippet of the matching section title', 00300 ' sectiontitle - Adds the title of the matching section', 00301 ' hasrelated - Indicates whether a related search is available', 00302 ), 00303 'redirects' => 'Include redirect pages in the search', 00304 'offset' => 'Use this value to continue paging (return by query)', 00305 'limit' => 'How many total pages to return' 00306 ); 00307 00308 if ( count( SearchEngine::getSearchTypes() ) > 1 ) { 00309 $descriptions['backend'] = 'Which search backend to use, if not the default'; 00310 } 00311 00312 return $descriptions; 00313 } 00314 00315 public function getResultProperties() { 00316 return array( 00317 '' => array( 00318 'ns' => 'namespace', 00319 'title' => 'string' 00320 ), 00321 'snippet' => array( 00322 'snippet' => 'string' 00323 ), 00324 'size' => array( 00325 'size' => 'integer' 00326 ), 00327 'wordcount' => array( 00328 'wordcount' => 'integer' 00329 ), 00330 'timestamp' => array( 00331 'timestamp' => 'timestamp' 00332 ), 00333 'score' => array( 00334 'score' => array( 00335 ApiBase::PROP_TYPE => 'string', 00336 ApiBase::PROP_NULLABLE => true 00337 ) 00338 ), 00339 'titlesnippet' => array( 00340 'titlesnippet' => 'string' 00341 ), 00342 'redirecttitle' => array( 00343 'redirecttitle' => array( 00344 ApiBase::PROP_TYPE => 'string', 00345 ApiBase::PROP_NULLABLE => true 00346 ) 00347 ), 00348 'redirectsnippet' => array( 00349 'redirectsnippet' => array( 00350 ApiBase::PROP_TYPE => 'string', 00351 ApiBase::PROP_NULLABLE => true 00352 ) 00353 ), 00354 'sectiontitle' => array( 00355 'sectiontitle' => array( 00356 ApiBase::PROP_TYPE => 'string', 00357 ApiBase::PROP_NULLABLE => true 00358 ) 00359 ), 00360 'sectionsnippet' => array( 00361 'sectionsnippet' => array( 00362 ApiBase::PROP_TYPE => 'string', 00363 ApiBase::PROP_NULLABLE => true 00364 ) 00365 ), 00366 'hasrelated' => array( 00367 'hasrelated' => 'boolean' 00368 ) 00369 ); 00370 } 00371 00372 public function getDescription() { 00373 return 'Perform a full text search'; 00374 } 00375 00376 public function getPossibleErrors() { 00377 return array_merge( parent::getPossibleErrors(), array( 00378 array( 'code' => 'search-text-disabled', 'info' => 'text search is disabled' ), 00379 array( 'code' => 'search-title-disabled', 'info' => 'title search is disabled' ), 00380 array( 'code' => 'search-error', 'info' => 'search error has occurred' ), 00381 ) ); 00382 } 00383 00384 public function getExamples() { 00385 return array( 00386 'api.php?action=query&list=search&srsearch=meaning', 00387 'api.php?action=query&list=search&srwhat=text&srsearch=meaning', 00388 'api.php?action=query&generator=search&gsrsearch=meaning&prop=info', 00389 ); 00390 } 00391 00392 public function getHelpUrls() { 00393 return 'https://www.mediawiki.org/wiki/API:Search'; 00394 } 00395 }