diff options
Diffstat (limited to 'includes/api/ApiPageSet.php')
-rw-r--r-- | includes/api/ApiPageSet.php | 337 |
1 files changed, 260 insertions, 77 deletions
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 0f264675..e6f218d6 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -53,7 +53,10 @@ class ApiPageSet extends ApiBase { private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing private $mTitles = array(); + private $mGoodAndMissingPages = array(); // [ns][dbkey] => page_id or negative when missing + private $mGoodPages = array(); // [ns][dbkey] => page_id private $mGoodTitles = array(); + private $mMissingPages = array(); // [ns][dbkey] => fake page_id private $mMissingTitles = array(); private $mInvalidTitles = array(); private $mMissingPageIDs = array(); @@ -65,7 +68,10 @@ class ApiPageSet extends ApiBase { private $mPendingRedirectIDs = array(); private $mConvertedTitles = array(); private $mGoodRevIDs = array(); + private $mLiveRevIDs = array(); + private $mDeletedRevIDs = array(); private $mMissingRevIDs = array(); + private $mGeneratorData = array(); // [ns][dbkey] => data array private $mFakePageId = -1; private $mCacheMode = 'public'; private $mRequestedPageFields = array(); @@ -90,7 +96,7 @@ class ApiPageSet extends ApiBase { $v = $val; } if ( $flag !== null ) { - $v[$flag] = ''; + $v[$flag] = true; } $result[] = $v; } @@ -109,11 +115,9 @@ class ApiPageSet extends ApiBase { $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0; $this->mDefaultNamespace = $defaultNamespace; - $this->profileIn(); $this->mParams = $this->extractRequestParams(); $this->mResolveRedirects = $this->mParams['redirects']; $this->mConvertTitles = $this->mParams['converttitles']; - $this->profileOut(); } /** @@ -137,17 +141,12 @@ class ApiPageSet extends ApiBase { * relevant parameters as used */ private function executeInternal( $isDryRun ) { - $this->profileIn(); - $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null; if ( isset( $generatorName ) ) { $dbSource = $this->mDbSource; - $isQuery = $dbSource instanceof ApiQuery; - if ( !$isQuery ) { + if ( !$dbSource instanceof ApiQuery ) { // If the parent container of this pageset is not ApiQuery, we must create it to run generator $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' ); - // Enable profiling for query module because it will be used for db sql profiling - $dbSource->profileIn(); } $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true ); if ( $generator === null ) { @@ -168,12 +167,9 @@ class ApiPageSet extends ApiBase { $tmpPageSet->executeInternal( $isDryRun ); // populate this pageset with the generator output - $this->profileOut(); - $generator->profileIn(); - if ( !$isDryRun ) { $generator->executeGenerator( $this ); - wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) ); + Hooks::run( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) ); } else { // Prevent warnings from being reported on these parameters $main = $this->getMain(); @@ -181,17 +177,10 @@ class ApiPageSet extends ApiBase { $main->getVal( $generator->encodeParamName( $paramName ) ); } } - $generator->profileOut(); - $this->profileIn(); if ( !$isDryRun ) { $this->resolvePendingRedirects(); } - - if ( !$isQuery ) { - // If this pageset is not part of the query, we called profileIn() above - $dbSource->profileOut(); - } } else { // Only one of the titles/pageids/revids is allowed at the same time $dataSource = null; @@ -235,7 +224,6 @@ class ApiPageSet extends ApiBase { } } } - $this->profileOut(); } /** @@ -309,6 +297,10 @@ class ApiPageSet extends ApiBase { $pageFlds['page_is_redirect'] = null; } + if ( $this->getConfig()->get( 'ContentHandlerUseDB' ) ) { + $pageFlds['page_content_model'] = null; + } + // only store non-default fields $this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds ); @@ -344,6 +336,14 @@ class ApiPageSet extends ApiBase { } /** + * Returns an array [ns][dbkey] => page_id for all good titles. + * @return array + */ + public function getGoodTitlesByNamespace() { + return $this->mGoodPages; + } + + /** * Title objects that were found in the database. * @return Title[] Array page_id (int) => Title (obj) */ @@ -360,6 +360,15 @@ class ApiPageSet extends ApiBase { } /** + * Returns an array [ns][dbkey] => fake_page_id for all missing titles. + * fake_page_id is a unique negative number. + * @return array + */ + public function getMissingTitlesByNamespace() { + return $this->mMissingPages; + } + + /** * Title objects that were NOT found in the database. * The array's index will be negative for each item * @return Title[] @@ -369,6 +378,22 @@ class ApiPageSet extends ApiBase { } /** + * Returns an array [ns][dbkey] => page_id for all good and missing titles. + * @return array + */ + public function getGoodAndMissingTitlesByNamespace() { + return $this->mGoodAndMissingPages; + } + + /** + * Title objects for good and missing titles. + * @return array + */ + public function getGoodAndMissingTitles() { + return $this->mGoodTitles + $this->mMissingTitles; + } + + /** * Titles that were deemed invalid by Title::newFromText() * The array's index will be unique and negative for each item * @return string[] Array of strings (not Title objects) @@ -411,10 +436,13 @@ class ApiPageSet extends ApiBase { if ( $titleTo->hasFragment() ) { $r['tofragment'] = $titleTo->getFragment(); } + if ( $titleTo->isExternal() ) { + $r['tointerwiki'] = $titleTo->getInterwiki(); + } $values[] = $r; } if ( !empty( $values ) && $result ) { - $result->setIndexedTagName( $values, 'r' ); + ApiResult::setIndexedTagName( $values, 'r' ); } return $values; @@ -445,7 +473,7 @@ class ApiPageSet extends ApiBase { ); } if ( !empty( $values ) && $result ) { - $result->setIndexedTagName( $values, 'n' ); + ApiResult::setIndexedTagName( $values, 'n' ); } return $values; @@ -476,7 +504,7 @@ class ApiPageSet extends ApiBase { ); } if ( !empty( $values ) && $result ) { - $result->setIndexedTagName( $values, 'c' ); + ApiResult::setIndexedTagName( $values, 'c' ); } return $values; @@ -513,7 +541,7 @@ class ApiPageSet extends ApiBase { $values[] = $item; } if ( !empty( $values ) && $result ) { - $result->setIndexedTagName( $values, 'i' ); + ApiResult::setIndexedTagName( $values, 'i' ); } return $values; @@ -560,7 +588,7 @@ class ApiPageSet extends ApiBase { } /** - * Get the list of revision IDs (requested with the revids= parameter) + * Get the list of valid revision IDs (requested with the revids= parameter) * @return array Array of revID (int) => pageID (int) */ public function getRevisionIDs() { @@ -568,6 +596,22 @@ class ApiPageSet extends ApiBase { } /** + * Get the list of non-deleted revision IDs (requested with the revids= parameter) + * @return array Array of revID (int) => pageID (int) + */ + public function getLiveRevisionIDs() { + return $this->mLiveRevIDs; + } + + /** + * Get the list of revision IDs that were associated with deleted titles. + * @return array Array of revID (int) => pageID (int) + */ + public function getDeletedRevisionIDs() { + return $this->mDeletedRevIDs; + } + + /** * Revision IDs that were not found in the database * @return array Array of revision IDs */ @@ -589,7 +633,7 @@ class ApiPageSet extends ApiBase { ); } if ( !empty( $values ) && $result ) { - $result->setIndexedTagName( $values, 'rev' ); + ApiResult::setIndexedTagName( $values, 'rev' ); } return $values; @@ -616,9 +660,7 @@ class ApiPageSet extends ApiBase { * @param array $titles Array of Title objects */ public function populateFromTitles( $titles ) { - $this->profileIn(); $this->initFromTitles( $titles ); - $this->profileOut(); } /** @@ -626,20 +668,20 @@ class ApiPageSet extends ApiBase { * @param array $pageIDs Array of page IDs */ public function populateFromPageIDs( $pageIDs ) { - $this->profileIn(); $this->initFromPageIds( $pageIDs ); - $this->profileOut(); } /** * Populate this PageSet from a rowset returned from the database + * + * Note that the query result must include the columns returned by + * $this->getPageTableFields(). + * * @param DatabaseBase $db * @param ResultWrapper $queryResult Query result object */ public function populateFromQueryResult( $db, $queryResult ) { - $this->profileIn(); $this->initFromQueryResult( $queryResult ); - $this->profileOut(); } /** @@ -647,9 +689,7 @@ class ApiPageSet extends ApiBase { * @param array $revIDs Array of revision IDs */ public function populateFromRevisionIDs( $revIDs ) { - $this->profileIn(); $this->initFromRevIDs( $revIDs ); - $this->profileOut(); } /** @@ -667,6 +707,8 @@ class ApiPageSet extends ApiBase { if ( $this->mResolveRedirects && $row->page_is_redirect == '1' ) { $this->mPendingRedirectIDs[$pageId] = $title; } else { + $this->mGoodPages[$row->page_namespace][$row->page_title] = $pageId; + $this->mGoodAndMissingPages[$row->page_namespace][$row->page_title] = $pageId; $this->mGoodTitles[$pageId] = $title; } @@ -710,10 +752,8 @@ class ApiPageSet extends ApiBase { $set = $linkBatch->constructSet( 'page', $db ); // Get pageIDs data from the `page` table - $this->profileDBIn(); $res = $db->select( 'page', $this->getPageTableFields(), $set, __METHOD__ ); - $this->profileDBOut(); // Hack: get the ns:titles stored in array(ns => array(titles)) format $this->initFromQueryResult( $res, $linkBatch->data, true ); // process Titles @@ -744,10 +784,8 @@ class ApiPageSet extends ApiBase { $db = $this->getDB(); // Get pageIDs data from the `page` table - $this->profileDBIn(); $res = $db->select( 'page', $this->getPageTableFields(), $set, __METHOD__ ); - $this->profileDBOut(); } $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs @@ -803,6 +841,8 @@ class ApiPageSet extends ApiBase { foreach ( array_keys( $dbkeys ) as $dbkey ) { $title = Title::makeTitle( $ns, $dbkey ); $this->mAllPages[$ns][$dbkey] = $this->mFakePageId; + $this->mMissingPages[$ns][$dbkey] = $this->mFakePageId; + $this->mGoodAndMissingPages[$ns][$dbkey] = $this->mFakePageId; $this->mMissingTitles[$this->mFakePageId] = $title; $this->mFakePageId--; $this->mTitles[] = $title; @@ -851,22 +891,64 @@ class ApiPageSet extends ApiBase { $where = array( 'rev_id' => $revids, 'rev_page = page_id' ); // Get pageIDs data from the `page` table - $this->profileDBIn(); $res = $db->select( $tables, $fields, $where, __METHOD__ ); foreach ( $res as $row ) { $revid = intval( $row->rev_id ); $pageid = intval( $row->rev_page ); $this->mGoodRevIDs[$revid] = $pageid; + $this->mLiveRevIDs[$revid] = $pageid; $pageids[$pageid] = ''; unset( $remaining[$revid] ); } - $this->profileDBOut(); } $this->mMissingRevIDs = array_keys( $remaining ); // Populate all the page information $this->initFromPageIds( array_keys( $pageids ) ); + + // If the user can see deleted revisions, pull out the corresponding + // titles from the archive table and include them too. We ignore + // ar_page_id because deleted revisions are tied by title, not page_id. + if ( !empty( $this->mMissingRevIDs ) && $this->getUser()->isAllowed( 'deletedhistory' ) ) { + $remaining = array_flip( $this->mMissingRevIDs ); + $tables = array( 'archive' ); + $fields = array( 'ar_rev_id', 'ar_namespace', 'ar_title' ); + $where = array( 'ar_rev_id' => $this->mMissingRevIDs ); + + $res = $db->select( $tables, $fields, $where, __METHOD__ ); + $titles = array(); + foreach ( $res as $row ) { + $revid = intval( $row->ar_rev_id ); + $titles[$revid] = Title::makeTitle( $row->ar_namespace, $row->ar_title ); + unset( $remaining[$revid] ); + } + + $this->initFromTitles( $titles ); + + foreach ( $titles as $revid => $title ) { + $ns = $title->getNamespace(); + $dbkey = $title->getDBkey(); + + // Handle converted titles + if ( !isset( $this->mAllPages[$ns][$dbkey] ) && + isset( $this->mConvertedTitles[$title->getPrefixedText()] ) + ) { + $title = Title::newFromText( $this->mConvertedTitles[$title->getPrefixedText()] ); + $ns = $title->getNamespace(); + $dbkey = $title->getDBkey(); + } + + if ( isset( $this->mAllPages[$ns][$dbkey] ) ) { + $this->mGoodRevIDs[$revid] = $this->mAllPages[$ns][$dbkey]; + $this->mDeletedRevIDs[$revid] = $this->mAllPages[$ns][$dbkey]; + } else { + $remaining[$revid] = true; + } + } + + $this->mMissingRevIDs = array_keys( $remaining ); + } } /** @@ -896,9 +978,7 @@ class ApiPageSet extends ApiBase { } // Get pageIDs data from the `page` table - $this->profileDBIn(); $res = $db->select( 'page', $pageFlds, $set, __METHOD__ ); - $this->profileDBOut(); // Hack: get the ns:titles stored in array(ns => array(titles)) format $this->initFromQueryResult( $res, $linkBatch->data, true ); @@ -917,7 +997,6 @@ class ApiPageSet extends ApiBase { $lb = new LinkBatch(); $db = $this->getDB(); - $this->profileDBIn(); $res = $db->select( 'redirect', array( @@ -929,7 +1008,6 @@ class ApiPageSet extends ApiBase { ), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ), __METHOD__ ); - $this->profileDBOut(); foreach ( $res as $row ) { $rdfrom = intval( $row->rd_from ); $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText(); @@ -940,7 +1018,9 @@ class ApiPageSet extends ApiBase { $row->rd_interwiki ); unset( $this->mPendingRedirectIDs[$rdfrom] ); - if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) { + if ( $to->isExternal() ) { + $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki(); + } elseif ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) { $lb->add( $row->rd_namespace, $row->rd_title ); } $this->mRedirectTitles[$from] = $to; @@ -1066,6 +1146,103 @@ class ApiPageSet extends ApiBase { } /** + * Set data for a title. + * + * This data may be extracted into an ApiResult using + * self::populateGeneratorData. This should generally be limited to + * data that is likely to be particularly useful to end users rather than + * just being a dump of everything returned in non-generator mode. + * + * Redirects here will *not* be followed, even if 'redirects' was + * specified, since in the case of multiple redirects we can't know which + * source's data to use on the target. + * + * @param Title $title + * @param array $data + */ + public function setGeneratorData( Title $title, array $data ) { + $ns = $title->getNamespace(); + $dbkey = $title->getDBkey(); + $this->mGeneratorData[$ns][$dbkey] = $data; + } + + /** + * Populate the generator data for all titles in the result + * + * The page data may be inserted into an ApiResult object or into an + * associative array. The $path parameter specifies the path within the + * ApiResult or array to find the "pages" node. + * + * The "pages" node itself must be an associative array mapping the page ID + * or fake page ID values returned by this pageset (see + * self::getAllTitlesByNamespace() and self::getSpecialTitles()) to + * associative arrays of page data. Each of those subarrays will have the + * data from self::setGeneratorData() merged in. + * + * Data that was set by self::setGeneratorData() for pages not in the + * "pages" node will be ignored. + * + * @param ApiResult|array &$result + * @param array $path + * @return bool Whether the data fit + */ + public function populateGeneratorData( &$result, array $path = array() ) { + if ( $result instanceof ApiResult ) { + $data = $result->getResultData( $path ); + if ( $data === null ) { + return true; + } + } else { + $data = &$result; + foreach ( $path as $key ) { + if ( !isset( $data[$key] ) ) { + // Path isn't in $result, so nothing to add, so everything + // "fits" + return true; + } + $data = &$data[$key]; + } + } + foreach ( $this->mGeneratorData as $ns => $dbkeys ) { + if ( $ns === -1 ) { + $pages = array(); + foreach ( $this->mSpecialTitles as $id => $title ) { + $pages[$title->getDBkey()] = $id; + } + } else { + if ( !isset( $this->mAllPages[$ns] ) ) { + // No known titles in the whole namespace. Skip it. + continue; + } + $pages = $this->mAllPages[$ns]; + } + foreach ( $dbkeys as $dbkey => $genData ) { + if ( !isset( $pages[$dbkey] ) ) { + // Unknown title. Forget it. + continue; + } + $pageId = $pages[$dbkey]; + if ( !isset( $data[$pageId] ) ) { + // $pageId didn't make it into the result. Ignore it. + continue; + } + + if ( $result instanceof ApiResult ) { + $path2 = array_merge( $path, array( $pageId ) ); + foreach ( $genData as $key => $value ) { + if ( !$result->addValue( $path2, $key, $value ) ) { + return false; + } + } + } else { + $data[$pageId] = array_merge( $data[$pageId], $genData ); + } + } + } + return true; + } + + /** * Get the database connection (read-only) * @return DatabaseBase */ @@ -1095,26 +1272,51 @@ class ApiPageSet extends ApiBase { public function getAllowedParams( $flags = 0 ) { $result = array( 'titles' => array( - ApiBase::PARAM_ISMULTI => true + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_HELP_MSG => 'api-pageset-param-titles', ), 'pageids' => array( ApiBase::PARAM_TYPE => 'integer', - ApiBase::PARAM_ISMULTI => true + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_HELP_MSG => 'api-pageset-param-pageids', ), 'revids' => array( ApiBase::PARAM_TYPE => 'integer', - ApiBase::PARAM_ISMULTI => true + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_HELP_MSG => 'api-pageset-param-revids', + ), + 'generator' => array( + ApiBase::PARAM_TYPE => null, + ApiBase::PARAM_VALUE_LINKS => array(), + ApiBase::PARAM_HELP_MSG => 'api-pageset-param-generator', + ), + 'redirects' => array( + ApiBase::PARAM_DFLT => false, + ApiBase::PARAM_HELP_MSG => $this->mAllowGenerator + ? 'api-pageset-param-redirects-generator' + : 'api-pageset-param-redirects-nogenerator', + ), + 'converttitles' => array( + ApiBase::PARAM_DFLT => false, + ApiBase::PARAM_HELP_MSG => array( + 'api-pageset-param-converttitles', + new DeferredStringifier( + function ( IContextSource $context ) { + return $context->getLanguage() + ->commaList( LanguageConverter::$languagesWithVariants ); + }, + $this + ) + ), ), - 'redirects' => false, - 'converttitles' => false, ); - if ( $this->mAllowGenerator ) { - if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) { - $result['generator'] = array( - ApiBase::PARAM_TYPE => $this->getGenerators() - ); - } else { - $result['generator'] = null; + + if ( !$this->mAllowGenerator ) { + unset( $result['generator'] ); + } elseif ( $flags & ApiBase::GET_VALUES_FOR_HELP ) { + foreach ( $this->getGenerators() as $g ) { + $result['generator'][ApiBase::PARAM_TYPE][] = $g; + $result['generator'][ApiBase::PARAM_VALUE_LINKS][$g] = "Special:ApiHelp/query+$g"; } } @@ -1148,23 +1350,4 @@ class ApiPageSet extends ApiBase { return self::$generators; } - - public function getParamDescription() { - return array( - 'titles' => 'A list of titles to work on', - 'pageids' => 'A list of page IDs to work on', - 'revids' => 'A list of revision IDs to work on', - 'generator' => array( - 'Get the list of pages to work on by executing the specified query module.', - 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' - ), - 'redirects' => 'Automatically resolve redirects', - 'converttitles' => array( - 'Convert titles to other variants if necessary. Only works if ' . - 'the wiki\'s content language supports variant conversion.', - 'Languages that support variant conversion include ' . - implode( ', ', LanguageConverter::$languagesWithVariants ) - ), - ); - } } |