*
* Created on Sep 24, 2006
*
- * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ * Copyright © 2006, 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the second instance for all their work.
*
* @ingroup API
+ * @since 1.21 derives from ApiBase instead of ApiQueryBase
*/
-class ApiPageSet extends ApiQueryBase {
+class ApiPageSet extends ApiBase {
- private $mAllPages; // [ns][dbkey] => page_id or negative when missing
- private $mTitles, $mGoodTitles, $mMissingTitles, $mInvalidTitles;
- private $mMissingPageIDs, $mRedirectTitles, $mSpecialTitles;
- private $mNormalizedTitles, $mInterwikiTitles;
- private $mResolveRedirects, $mPendingRedirectIDs;
- private $mConvertTitles, $mConvertedTitles;
- private $mGoodRevIDs, $mMissingRevIDs;
- private $mFakePageId;
-
- private $mRequestedPageFields;
+ /**
+ * Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter
+ * @since 1.21
+ */
+ const DISABLE_GENERATORS = 1;
+
+ private $mDbSource, $mParams;
+ private $mResolveRedirects, $mConvertTitles, $mAllowGenerator;
+
+ private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing
+ private $mTitles = array();
+ private $mGoodTitles = array();
+ private $mMissingTitles = array();
+ private $mInvalidTitles = array();
+ private $mMissingPageIDs = array();
+ private $mRedirectTitles = array();
+ private $mSpecialTitles = array();
+ private $mNormalizedTitles = array();
+ private $mInterwikiTitles = array();
+ private $mPendingRedirectIDs = array();
+ private $mConvertedTitles = array();
+ private $mGoodRevIDs = array();
+ private $mMissingRevIDs = array();
+ private $mFakePageId = -1;
+ private $mCacheMode = 'public';
+ private $mRequestedPageFields = array();
/**
* Constructor
- * @param $query ApiBase
- * @param $resolveRedirects bool Whether redirects should be resolved
- * @param $convertTitles bool
- */
- public function __construct( $query, $resolveRedirects = false, $convertTitles = false ) {
- parent::__construct( $query, 'query' );
-
- $this->mAllPages = array();
- $this->mTitles = array();
- $this->mGoodTitles = array();
- $this->mMissingTitles = array();
- $this->mInvalidTitles = array();
- $this->mMissingPageIDs = array();
- $this->mRedirectTitles = array();
- $this->mNormalizedTitles = array();
- $this->mInterwikiTitles = array();
- $this->mGoodRevIDs = array();
- $this->mMissingRevIDs = array();
- $this->mSpecialTitles = array();
-
- $this->mRequestedPageFields = array();
- $this->mResolveRedirects = $resolveRedirects;
- if ( $resolveRedirects ) {
- $this->mPendingRedirectIDs = array();
- }
+ * @param $dbSource ApiBase Module implementing getDB().
+ * Allows PageSet to reuse existing db connection from the shared state like ApiQuery.
+ * @param $flags int Zero or more flags like DISABLE_GENERATORS
+ * @since 1.21 accepts $flags instead of two boolean values
+ */
+ public function __construct( ApiBase $dbSource, $flags = 0 ) {
+ parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() );
+ $this->mDbSource = $dbSource;
+ $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0;
+
+ $this->profileIn();
+ $this->mParams = $this->extractRequestParams();
+ $this->mResolveRedirects = $this->mParams['redirects'];
+ $this->mConvertTitles = $this->mParams['converttitles'];
+ $this->profileOut();
+ }
- $this->mConvertTitles = $convertTitles;
- $this->mConvertedTitles = array();
+ /**
+ * Populate the PageSet from the request parameters.
+ */
+ public function execute() {
+ $this->profileIn();
- $this->mFakePageId = - 1;
+ $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null;
+ if ( isset( $generatorName ) ) {
+ $dbSource = $this->mDbSource;
+ $isQuery = $dbSource instanceof ApiQuery;
+ if ( !$isQuery ) {
+ // 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 ) {
+ $this->dieUsage( 'Unknown generator=' . $generatorName, 'badgenerator' );
+ }
+ if ( !$generator instanceof ApiQueryGeneratorBase ) {
+ $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
+ }
+ // Create a temporary pageset to store generator's output,
+ // add any additional fields generator may need, and execute pageset to populate titles/pageids
+ $tmpPageSet = new ApiPageSet( $dbSource, ApiPageSet::DISABLE_GENERATORS );
+ $generator->setGeneratorMode( $tmpPageSet );
+ $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() );
+ $generator->requestExtraData( $tmpPageSet );
+ $tmpPageSet->execute();
+
+ // populate this pageset with the generator output
+ $this->profileOut();
+ $generator->profileIn();
+ $generator->executeGenerator( $this );
+ wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) );
+ $this->resolvePendingRedirects();
+ $generator->profileOut();
+ $this->profileIn();
+
+ 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;
+ if ( isset( $this->mParams['titles'] ) ) {
+ $dataSource = 'titles';
+ }
+ if ( isset( $this->mParams['pageids'] ) ) {
+ if ( isset( $dataSource ) ) {
+ $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
+ }
+ $dataSource = 'pageids';
+ }
+ if ( isset( $this->mParams['revids'] ) ) {
+ if ( isset( $dataSource ) ) {
+ $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
+ }
+ $dataSource = 'revids';
+ }
+ // Populate page information with the original user input
+ switch( $dataSource ) {
+ case 'titles':
+ $this->initFromTitles( $this->mParams['titles'] );
+ break;
+ case 'pageids':
+ $this->initFromPageIds( $this->mParams['pageids'] );
+ break;
+ case 'revids':
+ if ( $this->mResolveRedirects ) {
+ $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
+ 'Any redirects the revids= point to have not been resolved.' );
+ }
+ $this->mResolveRedirects = false;
+ $this->initFromRevIDs( $this->mParams['revids'] );
+ break;
+ default:
+ // Do nothing - some queries do not need any of the data sources.
+ break;
+ }
+ }
+ $this->profileOut();
}
/**
}
/**
- * Request an additional field from the page table. Must be called
- * before execute()
+ * Request an additional field from the page table.
+ * Must be called before execute()
* @param $fieldName string Field name
*/
public function requestField( $fieldName ) {
/**
* Get a list of redirect resolutions - maps a title to its redirect
- * target.
- * @return array prefixed_title (string) => Title object
+ * target, as an array of output-ready arrays
+ * @return array
*/
public function getRedirectTitles() {
return $this->mRedirectTitles;
}
+ /**
+ * Get a list of redirect resolutions - maps a title to its redirect
+ * target.
+ * @param $result ApiResult
+ * @return array of prefixed_title (string) => Title object
+ * @since 1.21
+ */
+ public function getRedirectTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) {
+ $r = array(
+ 'from' => strval( $titleStrFrom ),
+ 'to' => $titleTo->getPrefixedText(),
+ );
+ if ( $titleTo->getFragment() !== '' ) {
+ $r['tofragment'] = $titleTo->getFragment();
+ }
+ $values[] = $r;
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'r' );
+ }
+ return $values;
+ }
+
/**
* Get a list of title normalizations - maps a title to its normalized
* version.
return $this->mNormalizedTitles;
}
+ /**
+ * Get a list of title normalizations - maps a title to its normalized
+ * version in the form of result array.
+ * @param $result ApiResult
+ * @return array of raw_prefixed_title (string) => prefixed_title (string)
+ * @since 1.21
+ */
+ public function getNormalizedTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
+ $values[] = array(
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'n' );
+ }
+ return $values;
+ }
+
/**
* Get a list of title conversions - maps a title to its converted
* version.
return $this->mConvertedTitles;
}
+ /**
+ * Get a list of title conversions - maps a title to its converted
+ * version as a result array.
+ * @param $result ApiResult
+ * @return array of (from, to) strings
+ * @since 1.21
+ */
+ public function getConvertedTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) {
+ $values[] = array(
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'c' );
+ }
+ return $values;
+ }
+
/**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix.
return $this->mInterwikiTitles;
}
+ /**
+ * Get a list of interwiki titles - maps a title to its interwiki
+ * prefix as result.
+ * @param $result ApiResult
+ * @param $iwUrl boolean
+ * @return array raw_prefixed_title (string) => interwiki_prefix (string)
+ * @since 1.21
+ */
+ public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
+ $values = array();
+ foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
+ $item = array(
+ 'title' => $rawTitleStr,
+ 'iw' => $interwikiStr,
+ );
+ if ( $iwUrl ) {
+ $title = Title::newFromText( $rawTitleStr );
+ $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT );
+ }
+ $values[] = $item;
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'i' );
+ }
+ return $values;
+ }
+
/**
* Get the list of revision IDs (requested with the revids= parameter)
* @return array revID (int) => pageID (int)
return $this->mMissingRevIDs;
}
+ /**
+ * Revision IDs that were not found in the database as result array.
+ * @param $result ApiResult
+ * @return array of revision IDs
+ * @since 1.21
+ */
+ public function getMissingRevisionIDsAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getMissingRevisionIDs() as $revid ) {
+ $values[$revid] = array(
+ 'revid' => $revid
+ );
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'rev' );
+ }
+ return $values;
+ }
+
/**
* Get the list of titles with negative namespace
* @return array Title
return count( $this->getRevisionIDs() );
}
- /**
- * Populate the PageSet from the request parameters.
- */
- public function execute() {
- $this->profileIn();
- $params = $this->extractRequestParams();
-
- // Only one of the titles/pageids/revids is allowed at the same time
- $dataSource = null;
- if ( isset( $params['titles'] ) ) {
- $dataSource = 'titles';
- }
- if ( isset( $params['pageids'] ) ) {
- if ( isset( $dataSource ) ) {
- $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
- }
- $dataSource = 'pageids';
- }
- if ( isset( $params['revids'] ) ) {
- if ( isset( $dataSource ) ) {
- $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
- }
- $dataSource = 'revids';
- }
-
- switch ( $dataSource ) {
- case 'titles':
- $this->initFromTitles( $params['titles'] );
- break;
- case 'pageids':
- $this->initFromPageIds( $params['pageids'] );
- break;
- case 'revids':
- if ( $this->mResolveRedirects ) {
- $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
- 'Any redirects the revids= point to have not been resolved.' );
- }
- $this->mResolveRedirects = false;
- $this->initFromRevIDs( $params['revids'] );
- break;
- default:
- // Do nothing - some queries do not need any of the data sources.
- break;
- }
- $this->profileOut();
- }
-
/**
* Populate this PageSet from a list of Titles
* @param $titles array of Title objects
}
/**
- * Resolve redirects, if applicable
+ * Do not use, does nothing, will be removed
+ * @deprecated 1.21
*/
public function finishPageSetGeneration() {
- $this->profileIn();
- $this->resolvePendingRedirects();
- $this->profileOut();
+ wfDeprecated( __METHOD__, '1.21' );
}
/**
* @param $pageids array of page IDs
*/
private function initFromPageIds( $pageids ) {
- if ( !count( $pageids ) ) {
+ if ( !$pageids ) {
return;
}
$pageids = self::getPositiveIntegers( $pageids );
$res = null;
- if ( count( $pageids ) ) {
+ if ( !empty( $pageids ) ) {
$set = array(
'page_id' => $pageids
);
$this->processDbRow( $row );
// Need gender information
- if( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) {
+ if ( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) {
$usernames[] = $row->page_title;
}
}
$this->mTitles[] = $title;
// need gender information
- if( MWNamespace::hasGenderDistinction( $ns ) ) {
+ if ( MWNamespace::hasGenderDistinction( $ns ) ) {
$usernames[] = $dbkey;
}
}
* @param $revids array of revision IDs
*/
private function initFromRevIDs( $revids ) {
- if ( !count( $revids ) ) {
+ if ( !$revids ) {
return;
}
$revids = self::getPositiveIntegers( $revids );
- if ( count( $revids ) ) {
+ if ( !empty( $revids ) ) {
$tables = array( 'revision', 'page' );
$fields = array( 'rev_id', 'rev_page' );
$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__ );
+ $res = $db->select( $tables, $fields, $where, __METHOD__ );
foreach ( $res as $row ) {
$revid = intval( $row->rev_id );
$pageid = intval( $row->rev_page );
* @return LinkBatch
*/
private function getRedirectTargets() {
- $redirectTitles = $this->resolveIdsToRedirectTargets( array_keys( $this->mPendingRedirectIDs ), 'profileDB' );
-
$lb = new LinkBatch();
- foreach( $this->mPendingRedirectIDs as $rdfrom => $from ) {
- if( !isset( $redirectTitles[$rdfrom] ) ) {
- continue;
- }
- $to = $redirectTitles[$rdfrom];
- if ( $to && !isset( $this->mAllPages[$to->getNamespace()][$to->getText()] ) ) {
- $lb->addObj( $to );
- }
- $this->mRedirectTitles[$from->getPrefixedText()] = $to;
- }
- return $lb;
- }
-
- /**
- * Get the targets of redirects from the database
- *
- * Also creates entries in the redirect table for redirects that don't
- * have one.
- *
- * @param $redirectIDs array The array of pageids to resolve
- * @param $profileDB string if profileDBIn should called
- * @return array id => redirect target as title
- * @since 1.21
- */
- public function resolveIdsToRedirectTargets( $redirectIDs, $profileDB = '' ) {
- if( !$redirectIDs ) {
- return array();
- }
-
$db = $this->getDB();
- if( $profileDB ) {
- $this->profileDBIn();
- }
+ $this->profileDBIn();
$res = $db->select(
'redirect',
array(
'rd_fragment',
'rd_interwiki',
'rd_title'
- ), array( 'rd_from' => $redirectIDs ),
+ ), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ),
__METHOD__
);
- if( $profileDB ) {
- $this->profileDBOut();
- }
-
- $redirectTitles = array();
+ $this->profileDBOut();
foreach ( $res as $row ) {
$rdfrom = intval( $row->rd_from );
+ $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
$to = Title::makeTitle( $row->rd_namespace, $row->rd_title, $row->rd_fragment, $row->rd_interwiki );
- $redirectTitles[$rdfrom] = $to;
+ unset( $this->mPendingRedirectIDs[$rdfrom] );
+ if ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
+ $lb->add( $row->rd_namespace, $row->rd_title );
+ }
+ $this->mRedirectTitles[$from] = $to;
}
- $unresolvedRedirectIDs = array_diff( $redirectIDs, array_keys( $redirectTitles ) );
- if ( $unresolvedRedirectIDs ) {
+ if ( $this->mPendingRedirectIDs ) {
// We found pages that aren't in the redirect table
// Add them
- foreach ( $unresolvedRedirectIDs as $id ) {
- $page = WikiPage::newFromID( $id );
- if ( !$page ) {
- continue;
- }
+ foreach ( $this->mPendingRedirectIDs as $id => $title ) {
+ $page = WikiPage::factory( $title );
$rt = $page->insertRedirect();
if ( !$rt ) {
// What the hell. Let's just ignore this
continue;
}
- $redirectTitles[$id] = $rt;
+ $lb->addObj( $rt );
+ $this->mRedirectTitles[$title->getPrefixedText()] = $rt;
+ unset( $this->mPendingRedirectIDs[$id] );
}
}
- return $redirectTitles;
+ return $lb;
+ }
+
+ /**
+ * Get the cache mode for the data generated by this module.
+ * All PageSet users should take into account whether this returns a more-restrictive
+ * cache mode than the using module itself. For possible return values and other
+ * details about cache modes, see ApiMain::setCacheMode()
+ *
+ * Public caching will only be allowed if *all* the modules that supply
+ * data for a given request return a cache mode of public.
+ *
+ * @param $params
+ * @return string
+ * @since 1.21
+ */
+ public function getCacheMode( $params = null ) {
+ return $this->mCacheMode;
}
/**
if ( $this->mConvertTitles &&
count( $wgContLang->getVariants() ) > 1 &&
!$titleObj->exists() ) {
- // Language::findVariantLink will modify titleObj into
+ // Language::findVariantLink will modify titleText and titleObj into
// the canonical variant if possible
- $wgContLang->findVariantLink( $title, $titleObj );
+ $titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
+ $wgContLang->findVariantLink( $titleText, $titleObj );
$titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
}
// namespace is localized or the capitalization is
// different
if ( $titleWasConverted ) {
- $this->mConvertedTitles[$title] = $titleObj->getPrefixedText();
+ $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
+ // In this case the page can't be Special.
+ if ( is_string( $title ) && $title !== $unconvertedTitle ) {
+ $this->mNormalizedTitles[$title] = $unconvertedTitle;
+ }
} elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
$this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
}
return $linkBatch;
}
+ /**
+ * Get the database connection (read-only)
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ return $this->mDbSource->getDB();
+ }
+
/**
* Returns the input array of integers with all values < 0 removed
*
// bug 25734 API: possible issue with revids validation
// It seems with a load of revision rows, MySQL gets upset
// Remove any < 0 integers, as they can't be valid
- foreach( $array as $i => $int ) {
+ foreach ( $array as $i => $int ) {
if ( $int < 0 ) {
unset( $array[$i] );
}
return $array;
}
- public function getAllowedParams() {
- return array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'titles' => array(
ApiBase::PARAM_ISMULTI => true
),
'revids' => array(
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_ISMULTI => true
- )
+ ),
+ 'redirects' => false,
+ 'converttitles' => false,
);
+ if ( $this->mAllowGenerator ) {
+ $result['generator'] = array(
+ ApiBase::PARAM_TYPE => $this->getGenerators() );
+ }
+ return $result;
+ }
+
+ private static $generators = null;
+
+ /**
+ * Get an array of all available generators
+ * @return array
+ */
+ private function getGenerators() {
+ if ( self::$generators === null ) {
+ $query = $this->mDbSource;
+ if ( !( $query instanceof ApiQuery ) ) {
+ // If the parent container of this pageset is not ApiQuery,
+ // we must create it to get module manager
+ $query = $this->getMain()->getModuleManager()->getModule( 'query' );
+ }
+ $gens = array_keys( $query->getGenerators() );
+ sort( $gens );
+ self::$generators = $gens;
+ }
+ 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'
+ '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 ) ),
);
}
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'multisource', 'info' => "Cannot use 'pageids' at the same time as 'dataSource'" ),
array( 'code' => 'multisource', 'info' => "Cannot use 'revids' at the same time as 'dataSource'" ),
+ array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
) );
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}