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