MediaWiki  REL1_22
ApiQuerySearch.php
Go to the documentation of this file.
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 }