MediaWiki  REL1_24
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( 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 }