MediaWiki  REL1_24
ApiPageSet.php
Go to the documentation of this file.
00001 <?php
00041 class ApiPageSet extends ApiBase {
00046     const DISABLE_GENERATORS = 1;
00047 
00048     private $mDbSource;
00049     private $mParams;
00050     private $mResolveRedirects;
00051     private $mConvertTitles;
00052     private $mAllowGenerator;
00053 
00054     private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing
00055     private $mTitles = array();
00056     private $mGoodTitles = array();
00057     private $mMissingTitles = array();
00058     private $mInvalidTitles = array();
00059     private $mMissingPageIDs = array();
00060     private $mRedirectTitles = array();
00061     private $mSpecialTitles = array();
00062     private $mNormalizedTitles = array();
00063     private $mInterwikiTitles = array();
00065     private $mPendingRedirectIDs = array();
00066     private $mConvertedTitles = array();
00067     private $mGoodRevIDs = array();
00068     private $mMissingRevIDs = array();
00069     private $mFakePageId = -1;
00070     private $mCacheMode = 'public';
00071     private $mRequestedPageFields = array();
00073     private $mDefaultNamespace = NS_MAIN;
00074 
00082     private static function addValues( array &$result, $values, $flag = null, $name = null ) {
00083         foreach ( $values as $val ) {
00084             if ( $val instanceof Title ) {
00085                 $v = array();
00086                 ApiQueryBase::addTitleInfo( $v, $val );
00087             } elseif ( $name !== null ) {
00088                 $v = array( $name => $val );
00089             } else {
00090                 $v = $val;
00091             }
00092             if ( $flag !== null ) {
00093                 $v[$flag] = '';
00094             }
00095             $result[] = $v;
00096         }
00097     }
00098 
00106     public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
00107         parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() );
00108         $this->mDbSource = $dbSource;
00109         $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0;
00110         $this->mDefaultNamespace = $defaultNamespace;
00111 
00112         $this->profileIn();
00113         $this->mParams = $this->extractRequestParams();
00114         $this->mResolveRedirects = $this->mParams['redirects'];
00115         $this->mConvertTitles = $this->mParams['converttitles'];
00116         $this->profileOut();
00117     }
00118 
00123     public function executeDryRun() {
00124         $this->executeInternal( true );
00125     }
00126 
00130     public function execute() {
00131         $this->executeInternal( false );
00132     }
00133 
00139     private function executeInternal( $isDryRun ) {
00140         $this->profileIn();
00141 
00142         $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null;
00143         if ( isset( $generatorName ) ) {
00144             $dbSource = $this->mDbSource;
00145             $isQuery = $dbSource instanceof ApiQuery;
00146             if ( !$isQuery ) {
00147                 // If the parent container of this pageset is not ApiQuery, we must create it to run generator
00148                 $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' );
00149                 // Enable profiling for query module because it will be used for db sql profiling
00150                 $dbSource->profileIn();
00151             }
00152             $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true );
00153             if ( $generator === null ) {
00154                 $this->dieUsage( 'Unknown generator=' . $generatorName, 'badgenerator' );
00155             }
00156             if ( !$generator instanceof ApiQueryGeneratorBase ) {
00157                 $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
00158             }
00159             // Create a temporary pageset to store generator's output,
00160             // add any additional fields generator may need, and execute pageset to populate titles/pageids
00161             $tmpPageSet = new ApiPageSet( $dbSource, ApiPageSet::DISABLE_GENERATORS );
00162             $generator->setGeneratorMode( $tmpPageSet );
00163             $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() );
00164 
00165             if ( !$isDryRun ) {
00166                 $generator->requestExtraData( $tmpPageSet );
00167             }
00168             $tmpPageSet->executeInternal( $isDryRun );
00169 
00170             // populate this pageset with the generator output
00171             $this->profileOut();
00172             $generator->profileIn();
00173 
00174             if ( !$isDryRun ) {
00175                 $generator->executeGenerator( $this );
00176                 wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) );
00177             } else {
00178                 // Prevent warnings from being reported on these parameters
00179                 $main = $this->getMain();
00180                 foreach ( $generator->extractRequestParams() as $paramName => $param ) {
00181                     $main->getVal( $generator->encodeParamName( $paramName ) );
00182                 }
00183             }
00184             $generator->profileOut();
00185             $this->profileIn();
00186 
00187             if ( !$isDryRun ) {
00188                 $this->resolvePendingRedirects();
00189             }
00190 
00191             if ( !$isQuery ) {
00192                 // If this pageset is not part of the query, we called profileIn() above
00193                 $dbSource->profileOut();
00194             }
00195         } else {
00196             // Only one of the titles/pageids/revids is allowed at the same time
00197             $dataSource = null;
00198             if ( isset( $this->mParams['titles'] ) ) {
00199                 $dataSource = 'titles';
00200             }
00201             if ( isset( $this->mParams['pageids'] ) ) {
00202                 if ( isset( $dataSource ) ) {
00203                     $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
00204                 }
00205                 $dataSource = 'pageids';
00206             }
00207             if ( isset( $this->mParams['revids'] ) ) {
00208                 if ( isset( $dataSource ) ) {
00209                     $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
00210                 }
00211                 $dataSource = 'revids';
00212             }
00213 
00214             if ( !$isDryRun ) {
00215                 // Populate page information with the original user input
00216                 switch ( $dataSource ) {
00217                     case 'titles':
00218                         $this->initFromTitles( $this->mParams['titles'] );
00219                         break;
00220                     case 'pageids':
00221                         $this->initFromPageIds( $this->mParams['pageids'] );
00222                         break;
00223                     case 'revids':
00224                         if ( $this->mResolveRedirects ) {
00225                             $this->setWarning( 'Redirect resolution cannot be used ' .
00226                                 'together with the revids= parameter. Any redirects ' .
00227                                 'the revids= point to have not been resolved.' );
00228                         }
00229                         $this->mResolveRedirects = false;
00230                         $this->initFromRevIDs( $this->mParams['revids'] );
00231                         break;
00232                     default:
00233                         // Do nothing - some queries do not need any of the data sources.
00234                         break;
00235                 }
00236             }
00237         }
00238         $this->profileOut();
00239     }
00240 
00245     public function isResolvingRedirects() {
00246         return $this->mResolveRedirects;
00247     }
00248 
00257     public function getDataSource() {
00258         if ( $this->mAllowGenerator && isset( $this->mParams['generator'] ) ) {
00259             return 'generator';
00260         }
00261         if ( isset( $this->mParams['titles'] ) ) {
00262             return 'titles';
00263         }
00264         if ( isset( $this->mParams['pageids'] ) ) {
00265             return 'pageids';
00266         }
00267         if ( isset( $this->mParams['revids'] ) ) {
00268             return 'revids';
00269         }
00270 
00271         return null;
00272     }
00273 
00279     public function requestField( $fieldName ) {
00280         $this->mRequestedPageFields[$fieldName] = null;
00281     }
00282 
00289     public function getCustomField( $fieldName ) {
00290         return $this->mRequestedPageFields[$fieldName];
00291     }
00292 
00299     public function getPageTableFields() {
00300         // Ensure we get minimum required fields
00301         // DON'T change this order
00302         $pageFlds = array(
00303             'page_namespace' => null,
00304             'page_title' => null,
00305             'page_id' => null,
00306         );
00307 
00308         if ( $this->mResolveRedirects ) {
00309             $pageFlds['page_is_redirect'] = null;
00310         }
00311 
00312         // only store non-default fields
00313         $this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds );
00314 
00315         $pageFlds = array_merge( $pageFlds, $this->mRequestedPageFields );
00316 
00317         return array_keys( $pageFlds );
00318     }
00319 
00326     public function getAllTitlesByNamespace() {
00327         return $this->mAllPages;
00328     }
00329 
00334     public function getTitles() {
00335         return $this->mTitles;
00336     }
00337 
00342     public function getTitleCount() {
00343         return count( $this->mTitles );
00344     }
00345 
00350     public function getGoodTitles() {
00351         return $this->mGoodTitles;
00352     }
00353 
00358     public function getGoodTitleCount() {
00359         return count( $this->mGoodTitles );
00360     }
00361 
00367     public function getMissingTitles() {
00368         return $this->mMissingTitles;
00369     }
00370 
00376     public function getInvalidTitles() {
00377         return $this->mInvalidTitles;
00378     }
00379 
00384     public function getMissingPageIDs() {
00385         return $this->mMissingPageIDs;
00386     }
00387 
00393     public function getRedirectTitles() {
00394         return $this->mRedirectTitles;
00395     }
00396 
00404     public function getRedirectTitlesAsResult( $result = null ) {
00405         $values = array();
00406         foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) {
00407             $r = array(
00408                 'from' => strval( $titleStrFrom ),
00409                 'to' => $titleTo->getPrefixedText(),
00410             );
00411             if ( $titleTo->hasFragment() ) {
00412                 $r['tofragment'] = $titleTo->getFragment();
00413             }
00414             $values[] = $r;
00415         }
00416         if ( !empty( $values ) && $result ) {
00417             $result->setIndexedTagName( $values, 'r' );
00418         }
00419 
00420         return $values;
00421     }
00422 
00428     public function getNormalizedTitles() {
00429         return $this->mNormalizedTitles;
00430     }
00431 
00439     public function getNormalizedTitlesAsResult( $result = null ) {
00440         $values = array();
00441         foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
00442             $values[] = array(
00443                 'from' => $rawTitleStr,
00444                 'to' => $titleStr
00445             );
00446         }
00447         if ( !empty( $values ) && $result ) {
00448             $result->setIndexedTagName( $values, 'n' );
00449         }
00450 
00451         return $values;
00452     }
00453 
00459     public function getConvertedTitles() {
00460         return $this->mConvertedTitles;
00461     }
00462 
00470     public function getConvertedTitlesAsResult( $result = null ) {
00471         $values = array();
00472         foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) {
00473             $values[] = array(
00474                 'from' => $rawTitleStr,
00475                 'to' => $titleStr
00476             );
00477         }
00478         if ( !empty( $values ) && $result ) {
00479             $result->setIndexedTagName( $values, 'c' );
00480         }
00481 
00482         return $values;
00483     }
00484 
00490     public function getInterwikiTitles() {
00491         return $this->mInterwikiTitles;
00492     }
00493 
00502     public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
00503         $values = array();
00504         foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
00505             $item = array(
00506                 'title' => $rawTitleStr,
00507                 'iw' => $interwikiStr,
00508             );
00509             if ( $iwUrl ) {
00510                 $title = Title::newFromText( $rawTitleStr );
00511                 $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT );
00512             }
00513             $values[] = $item;
00514         }
00515         if ( !empty( $values ) && $result ) {
00516             $result->setIndexedTagName( $values, 'i' );
00517         }
00518 
00519         return $values;
00520     }
00521 
00536     public function getInvalidTitlesAndRevisions( $invalidChecks = array( 'invalidTitles',
00537         'special', 'missingIds', 'missingRevIds', 'missingTitles', 'interwikiTitles' )
00538     ) {
00539         $result = array();
00540         if ( in_array( "invalidTitles", $invalidChecks ) ) {
00541             self::addValues( $result, $this->getInvalidTitles(), 'invalid', 'title' );
00542         }
00543         if ( in_array( "special", $invalidChecks ) ) {
00544             self::addValues( $result, $this->getSpecialTitles(), 'special', 'title' );
00545         }
00546         if ( in_array( "missingIds", $invalidChecks ) ) {
00547             self::addValues( $result, $this->getMissingPageIDs(), 'missing', 'pageid' );
00548         }
00549         if ( in_array( "missingRevIds", $invalidChecks ) ) {
00550             self::addValues( $result, $this->getMissingRevisionIDs(), 'missing', 'revid' );
00551         }
00552         if ( in_array( "missingTitles", $invalidChecks ) ) {
00553             self::addValues( $result, $this->getMissingTitles(), 'missing' );
00554         }
00555         if ( in_array( "interwikiTitles", $invalidChecks ) ) {
00556             self::addValues( $result, $this->getInterwikiTitlesAsResult() );
00557         }
00558 
00559         return $result;
00560     }
00561 
00566     public function getRevisionIDs() {
00567         return $this->mGoodRevIDs;
00568     }
00569 
00574     public function getMissingRevisionIDs() {
00575         return $this->mMissingRevIDs;
00576     }
00577 
00584     public function getMissingRevisionIDsAsResult( $result = null ) {
00585         $values = array();
00586         foreach ( $this->getMissingRevisionIDs() as $revid ) {
00587             $values[$revid] = array(
00588                 'revid' => $revid
00589             );
00590         }
00591         if ( !empty( $values ) && $result ) {
00592             $result->setIndexedTagName( $values, 'rev' );
00593         }
00594 
00595         return $values;
00596     }
00597 
00602     public function getSpecialTitles() {
00603         return $this->mSpecialTitles;
00604     }
00605 
00610     public function getRevisionCount() {
00611         return count( $this->getRevisionIDs() );
00612     }
00613 
00618     public function populateFromTitles( $titles ) {
00619         $this->profileIn();
00620         $this->initFromTitles( $titles );
00621         $this->profileOut();
00622     }
00623 
00628     public function populateFromPageIDs( $pageIDs ) {
00629         $this->profileIn();
00630         $this->initFromPageIds( $pageIDs );
00631         $this->profileOut();
00632     }
00633 
00639     public function populateFromQueryResult( $db, $queryResult ) {
00640         $this->profileIn();
00641         $this->initFromQueryResult( $queryResult );
00642         $this->profileOut();
00643     }
00644 
00649     public function populateFromRevisionIDs( $revIDs ) {
00650         $this->profileIn();
00651         $this->initFromRevIDs( $revIDs );
00652         $this->profileOut();
00653     }
00654 
00659     public function processDbRow( $row ) {
00660         // Store Title object in various data structures
00661         $title = Title::newFromRow( $row );
00662 
00663         $pageId = intval( $row->page_id );
00664         $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
00665         $this->mTitles[] = $title;
00666 
00667         if ( $this->mResolveRedirects && $row->page_is_redirect == '1' ) {
00668             $this->mPendingRedirectIDs[$pageId] = $title;
00669         } else {
00670             $this->mGoodTitles[$pageId] = $title;
00671         }
00672 
00673         foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) {
00674             $fieldValues[$pageId] = $row->$fieldName;
00675         }
00676     }
00677 
00682     public function finishPageSetGeneration() {
00683         wfDeprecated( __METHOD__, '1.21' );
00684     }
00685 
00702     private function initFromTitles( $titles ) {
00703         // Get validated and normalized title objects
00704         $linkBatch = $this->processTitlesArray( $titles );
00705         if ( $linkBatch->isEmpty() ) {
00706             return;
00707         }
00708 
00709         $db = $this->getDB();
00710         $set = $linkBatch->constructSet( 'page', $db );
00711 
00712         // Get pageIDs data from the `page` table
00713         $this->profileDBIn();
00714         $res = $db->select( 'page', $this->getPageTableFields(), $set,
00715             __METHOD__ );
00716         $this->profileDBOut();
00717 
00718         // Hack: get the ns:titles stored in array(ns => array(titles)) format
00719         $this->initFromQueryResult( $res, $linkBatch->data, true ); // process Titles
00720 
00721         // Resolve any found redirects
00722         $this->resolvePendingRedirects();
00723     }
00724 
00729     private function initFromPageIds( $pageids ) {
00730         if ( !$pageids ) {
00731             return;
00732         }
00733 
00734         $pageids = array_map( 'intval', $pageids ); // paranoia
00735         $remaining = array_flip( $pageids );
00736 
00737         $pageids = self::getPositiveIntegers( $pageids );
00738 
00739         $res = null;
00740         if ( !empty( $pageids ) ) {
00741             $set = array(
00742                 'page_id' => $pageids
00743             );
00744             $db = $this->getDB();
00745 
00746             // Get pageIDs data from the `page` table
00747             $this->profileDBIn();
00748             $res = $db->select( 'page', $this->getPageTableFields(), $set,
00749                 __METHOD__ );
00750             $this->profileDBOut();
00751         }
00752 
00753         $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
00754 
00755         // Resolve any found redirects
00756         $this->resolvePendingRedirects();
00757     }
00758 
00769     private function initFromQueryResult( $res, &$remaining = null, $processTitles = null ) {
00770         if ( !is_null( $remaining ) && is_null( $processTitles ) ) {
00771             ApiBase::dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' );
00772         }
00773 
00774         $usernames = array();
00775         if ( $res ) {
00776             foreach ( $res as $row ) {
00777                 $pageId = intval( $row->page_id );
00778 
00779                 // Remove found page from the list of remaining items
00780                 if ( isset( $remaining ) ) {
00781                     if ( $processTitles ) {
00782                         unset( $remaining[$row->page_namespace][$row->page_title] );
00783                     } else {
00784                         unset( $remaining[$pageId] );
00785                     }
00786                 }
00787 
00788                 // Store any extra fields requested by modules
00789                 $this->processDbRow( $row );
00790 
00791                 // Need gender information
00792                 if ( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) {
00793                     $usernames[] = $row->page_title;
00794                 }
00795             }
00796         }
00797 
00798         if ( isset( $remaining ) ) {
00799             // Any items left in the $remaining list are added as missing
00800             if ( $processTitles ) {
00801                 // The remaining titles in $remaining are non-existent pages
00802                 foreach ( $remaining as $ns => $dbkeys ) {
00803                     foreach ( array_keys( $dbkeys ) as $dbkey ) {
00804                         $title = Title::makeTitle( $ns, $dbkey );
00805                         $this->mAllPages[$ns][$dbkey] = $this->mFakePageId;
00806                         $this->mMissingTitles[$this->mFakePageId] = $title;
00807                         $this->mFakePageId--;
00808                         $this->mTitles[] = $title;
00809 
00810                         // need gender information
00811                         if ( MWNamespace::hasGenderDistinction( $ns ) ) {
00812                             $usernames[] = $dbkey;
00813                         }
00814                     }
00815                 }
00816             } else {
00817                 // The remaining pageids do not exist
00818                 if ( !$this->mMissingPageIDs ) {
00819                     $this->mMissingPageIDs = array_keys( $remaining );
00820                 } else {
00821                     $this->mMissingPageIDs = array_merge( $this->mMissingPageIDs, array_keys( $remaining ) );
00822                 }
00823             }
00824         }
00825 
00826         // Get gender information
00827         $genderCache = GenderCache::singleton();
00828         $genderCache->doQuery( $usernames, __METHOD__ );
00829     }
00830 
00836     private function initFromRevIDs( $revids ) {
00837         if ( !$revids ) {
00838             return;
00839         }
00840 
00841         $revids = array_map( 'intval', $revids ); // paranoia
00842         $db = $this->getDB();
00843         $pageids = array();
00844         $remaining = array_flip( $revids );
00845 
00846         $revids = self::getPositiveIntegers( $revids );
00847 
00848         if ( !empty( $revids ) ) {
00849             $tables = array( 'revision', 'page' );
00850             $fields = array( 'rev_id', 'rev_page' );
00851             $where = array( 'rev_id' => $revids, 'rev_page = page_id' );
00852 
00853             // Get pageIDs data from the `page` table
00854             $this->profileDBIn();
00855             $res = $db->select( $tables, $fields, $where, __METHOD__ );
00856             foreach ( $res as $row ) {
00857                 $revid = intval( $row->rev_id );
00858                 $pageid = intval( $row->rev_page );
00859                 $this->mGoodRevIDs[$revid] = $pageid;
00860                 $pageids[$pageid] = '';
00861                 unset( $remaining[$revid] );
00862             }
00863             $this->profileDBOut();
00864         }
00865 
00866         $this->mMissingRevIDs = array_keys( $remaining );
00867 
00868         // Populate all the page information
00869         $this->initFromPageIds( array_keys( $pageids ) );
00870     }
00871 
00877     private function resolvePendingRedirects() {
00878         if ( $this->mResolveRedirects ) {
00879             $db = $this->getDB();
00880             $pageFlds = $this->getPageTableFields();
00881 
00882             // Repeat until all redirects have been resolved
00883             // The infinite loop is prevented by keeping all known pages in $this->mAllPages
00884             while ( $this->mPendingRedirectIDs ) {
00885                 // Resolve redirects by querying the pagelinks table, and repeat the process
00886                 // Create a new linkBatch object for the next pass
00887                 $linkBatch = $this->getRedirectTargets();
00888 
00889                 if ( $linkBatch->isEmpty() ) {
00890                     break;
00891                 }
00892 
00893                 $set = $linkBatch->constructSet( 'page', $db );
00894                 if ( $set === false ) {
00895                     break;
00896                 }
00897 
00898                 // Get pageIDs data from the `page` table
00899                 $this->profileDBIn();
00900                 $res = $db->select( 'page', $pageFlds, $set, __METHOD__ );
00901                 $this->profileDBOut();
00902 
00903                 // Hack: get the ns:titles stored in array(ns => array(titles)) format
00904                 $this->initFromQueryResult( $res, $linkBatch->data, true );
00905             }
00906         }
00907     }
00908 
00916     private function getRedirectTargets() {
00917         $lb = new LinkBatch();
00918         $db = $this->getDB();
00919 
00920         $this->profileDBIn();
00921         $res = $db->select(
00922             'redirect',
00923             array(
00924                 'rd_from',
00925                 'rd_namespace',
00926                 'rd_fragment',
00927                 'rd_interwiki',
00928                 'rd_title'
00929             ), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ),
00930             __METHOD__
00931         );
00932         $this->profileDBOut();
00933         foreach ( $res as $row ) {
00934             $rdfrom = intval( $row->rd_from );
00935             $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
00936             $to = Title::makeTitle(
00937                 $row->rd_namespace,
00938                 $row->rd_title,
00939                 $row->rd_fragment,
00940                 $row->rd_interwiki
00941             );
00942             unset( $this->mPendingRedirectIDs[$rdfrom] );
00943             if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
00944                 $lb->add( $row->rd_namespace, $row->rd_title );
00945             }
00946             $this->mRedirectTitles[$from] = $to;
00947         }
00948 
00949         if ( $this->mPendingRedirectIDs ) {
00950             // We found pages that aren't in the redirect table
00951             // Add them
00952             foreach ( $this->mPendingRedirectIDs as $id => $title ) {
00953                 $page = WikiPage::factory( $title );
00954                 $rt = $page->insertRedirect();
00955                 if ( !$rt ) {
00956                     // What the hell. Let's just ignore this
00957                     continue;
00958                 }
00959                 $lb->addObj( $rt );
00960                 $this->mRedirectTitles[$title->getPrefixedText()] = $rt;
00961                 unset( $this->mPendingRedirectIDs[$id] );
00962             }
00963         }
00964 
00965         return $lb;
00966     }
00967 
00981     public function getCacheMode( $params = null ) {
00982         return $this->mCacheMode;
00983     }
00984 
00994     private function processTitlesArray( $titles ) {
00995         $usernames = array();
00996         $linkBatch = new LinkBatch();
00997 
00998         foreach ( $titles as $title ) {
00999             if ( is_string( $title ) ) {
01000                 $titleObj = Title::newFromText( $title, $this->mDefaultNamespace );
01001             } else {
01002                 $titleObj = $title;
01003             }
01004             if ( !$titleObj ) {
01005                 // Handle invalid titles gracefully
01006                 $this->mAllPages[0][$title] = $this->mFakePageId;
01007                 $this->mInvalidTitles[$this->mFakePageId] = $title;
01008                 $this->mFakePageId--;
01009                 continue; // There's nothing else we can do
01010             }
01011             $unconvertedTitle = $titleObj->getPrefixedText();
01012             $titleWasConverted = false;
01013             if ( $titleObj->isExternal() ) {
01014                 // This title is an interwiki link.
01015                 $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki();
01016             } else {
01017                 // Variants checking
01018                 global $wgContLang;
01019                 if ( $this->mConvertTitles &&
01020                     count( $wgContLang->getVariants() ) > 1 &&
01021                     !$titleObj->exists()
01022                 ) {
01023                     // Language::findVariantLink will modify titleText and titleObj into
01024                     // the canonical variant if possible
01025                     $titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
01026                     $wgContLang->findVariantLink( $titleText, $titleObj );
01027                     $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
01028                 }
01029 
01030                 if ( $titleObj->getNamespace() < 0 ) {
01031                     // Handle Special and Media pages
01032                     $titleObj = $titleObj->fixSpecialName();
01033                     $this->mSpecialTitles[$this->mFakePageId] = $titleObj;
01034                     $this->mFakePageId--;
01035                 } else {
01036                     // Regular page
01037                     $linkBatch->addObj( $titleObj );
01038                 }
01039             }
01040 
01041             // Make sure we remember the original title that was
01042             // given to us. This way the caller can correlate new
01043             // titles with the originally requested when e.g. the
01044             // namespace is localized or the capitalization is
01045             // different
01046             if ( $titleWasConverted ) {
01047                 $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
01048                 // In this case the page can't be Special.
01049                 if ( is_string( $title ) && $title !== $unconvertedTitle ) {
01050                     $this->mNormalizedTitles[$title] = $unconvertedTitle;
01051                 }
01052             } elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
01053                 $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
01054             }
01055 
01056             // Need gender information
01057             if ( MWNamespace::hasGenderDistinction( $titleObj->getNamespace() ) ) {
01058                 $usernames[] = $titleObj->getText();
01059             }
01060         }
01061         // Get gender information
01062         $genderCache = GenderCache::singleton();
01063         $genderCache->doQuery( $usernames, __METHOD__ );
01064 
01065         return $linkBatch;
01066     }
01067 
01072     protected function getDB() {
01073         return $this->mDbSource->getDB();
01074     }
01075 
01082     private static function getPositiveIntegers( $array ) {
01083         // bug 25734 API: possible issue with revids validation
01084         // It seems with a load of revision rows, MySQL gets upset
01085         // Remove any < 0 integers, as they can't be valid
01086         foreach ( $array as $i => $int ) {
01087             if ( $int < 0 ) {
01088                 unset( $array[$i] );
01089             }
01090         }
01091 
01092         return $array;
01093     }
01094 
01095     public function getAllowedParams( $flags = 0 ) {
01096         $result = array(
01097             'titles' => array(
01098                 ApiBase::PARAM_ISMULTI => true
01099             ),
01100             'pageids' => array(
01101                 ApiBase::PARAM_TYPE => 'integer',
01102                 ApiBase::PARAM_ISMULTI => true
01103             ),
01104             'revids' => array(
01105                 ApiBase::PARAM_TYPE => 'integer',
01106                 ApiBase::PARAM_ISMULTI => true
01107             ),
01108             'redirects' => false,
01109             'converttitles' => false,
01110         );
01111         if ( $this->mAllowGenerator ) {
01112             if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
01113                 $result['generator'] = array(
01114                     ApiBase::PARAM_TYPE => $this->getGenerators()
01115                 );
01116             } else {
01117                 $result['generator'] = null;
01118             }
01119         }
01120 
01121         return $result;
01122     }
01123 
01124     private static $generators = null;
01125 
01130     private function getGenerators() {
01131         if ( self::$generators === null ) {
01132             $query = $this->mDbSource;
01133             if ( !( $query instanceof ApiQuery ) ) {
01134                 // If the parent container of this pageset is not ApiQuery,
01135                 // we must create it to get module manager
01136                 $query = $this->getMain()->getModuleManager()->getModule( 'query' );
01137             }
01138             $gens = array();
01139             $mgr = $query->getModuleManager();
01140             foreach ( $mgr->getNamesWithClasses() as $name => $class ) {
01141                 if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) {
01142                     $gens[] = $name;
01143                 }
01144             }
01145             sort( $gens );
01146             self::$generators = $gens;
01147         }
01148 
01149         return self::$generators;
01150     }
01151 
01152     public function getParamDescription() {
01153         return array(
01154             'titles' => 'A list of titles to work on',
01155             'pageids' => 'A list of page IDs to work on',
01156             'revids' => 'A list of revision IDs to work on',
01157             'generator' => array(
01158                 'Get the list of pages to work on by executing the specified query module.',
01159                 'NOTE: generator parameter names must be prefixed with a \'g\', see examples'
01160             ),
01161             'redirects' => 'Automatically resolve redirects',
01162             'converttitles' => array(
01163                 'Convert titles to other variants if necessary. Only works if ' .
01164                     'the wiki\'s content language supports variant conversion.',
01165                 'Languages that support variant conversion include ' .
01166                     implode( ', ', LanguageConverter::$languagesWithVariants )
01167             ),
01168         );
01169     }
01170 }