when a batch of pages is complete.
* Pretty-printed HTML output now has nicer formatting and (if available)
better syntax highlighting.
+* Deprecated list=deletedrevs in favor of newly-added prop=deletedrevisions and
+ list=alldeletedrevisions.
+* prop=revisions will gracefully continue when given too many revids or titles,
+ rather than just ignoring the extras.
+* prop=revisions will no longer die if rvcontentformat doesn't match a
+ revision's content model; it will instead warn and omit the content.
+* If the user has the 'deletedhistory' right, action=query's revids parameter
+ will now recognize deleted revids.
+* prop=revisions may be used as a generator, generating revids.
=== Action API internal changes in 1.25 ===
* ApiHelp has been rewritten to support i18n and paginated HTML output.
has been removed.
* ApiFormatBase now always buffers. Output is done when
ApiFormatBase::closePrinter is called.
+* Much of the logic in ApiQueryRevisions has been split into ApiQueryRevisionsBase.
+* The 'revids' parameter supplied by ApiPageSet will now count deleted
+ revisions as "good" if the user has the 'deletedhistory' right. New methods
+ ApiPageSet::getLiveRevisionIDs() and ApiPageSet::getDeletedRevisionIDs() are
+ provided to access just the live or just the deleted revids.
* The following methods have been deprecated and may be removed in a future
release:
* ApiBase::getDescription
* ApiMain::reallyMakeHelpMsg
* ApiMain::makeHelpMsgHeader
* ApiQueryImageInfo::getPropertyDescriptions
+* The following classes have been deprecated and may be removed in a future
+ release:
+ * ApiQueryDeletedrevs
=== Languages updated in 1.25 ===
'ApiPurge' => 'includes/api/ApiPurge.php',
'ApiQuery' => 'includes/api/ApiQuery.php',
'ApiQueryAllCategories' => 'includes/api/ApiQueryAllCategories.php',
+ 'ApiQueryAllDeletedRevisions' => 'includes/api/ApiQueryAllDeletedRevisions.php',
'ApiQueryAllImages' => 'includes/api/ApiQueryAllImages.php',
'ApiQueryAllLinks' => 'includes/api/ApiQueryAllLinks.php',
'ApiQueryAllMessages' => 'includes/api/ApiQueryAllMessages.php',
'ApiQueryCategoryMembers' => 'includes/api/ApiQueryCategoryMembers.php',
'ApiQueryContributions' => 'includes/api/ApiQueryUserContributions.php',
'ApiQueryContributors' => 'includes/api/ApiQueryContributors.php',
+ 'ApiQueryDeletedRevisions' => 'includes/api/ApiQueryDeletedRevisions.php',
'ApiQueryDeletedrevs' => 'includes/api/ApiQueryDeletedrevs.php',
'ApiQueryDisabled' => 'includes/api/ApiQueryDisabled.php',
'ApiQueryDuplicateFiles' => 'includes/api/ApiQueryDuplicateFiles.php',
'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php',
'ApiQueryFileRepoInfo' => 'includes/api/ApiQueryFileRepoInfo.php',
'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
+ 'ApiQueryRevisionsBase' => 'includes/api/ApiQueryRevisionsBase.php',
'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php',
'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
'ApiQueryStashImageInfo' => 'includes/api/ApiQueryStashImageInfo.php',
// of arrays, with the first member being the 'tag' for the info and the
// remaining members being the values. In the help, this is formatted using
// apihelp-{$path}-paraminfo-{$tag}, which is passed $1 = count, $2 =
- // comma-joined list of values.
+ // comma-joined list of values, $3 = module prefix.
const PARAM_HELP_MSG_INFO = 12;
/// @since 1.25
// When PARAM_DFLT is an array, this may be an array mapping those values
$info[] = $context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
->numParams( count( $i ) )
->params( $context->getLanguage()->commaList( $i ) )
+ ->params( $module->getModulePrefix() )
->parse();
}
}
private $mPendingRedirectIDs = array();
private $mConvertedTitles = array();
private $mGoodRevIDs = array();
+ private $mLiveRevIDs = array();
+ private $mDeletedRevIDs = array();
private $mMissingRevIDs = array();
private $mFakePageId = -1;
private $mCacheMode = 'public';
}
/**
- * 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() {
return $this->mGoodRevIDs;
}
+ /**
+ * 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
$revid = intval( $row->rev_id );
$pageid = intval( $row->rev_page );
$this->mGoodRevIDs[$revid] = $pageid;
+ $this->mLiveRevIDs[$revid] = $pageid;
$pageids[$pageid] = '';
unset( $remaining[$revid] );
}
// 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 );
+
+ $this->profileDBIn();
+ $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->profileDBOut();
+
+ $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 );
+ }
}
/**
$this->context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
->numParams( count( $i ) )
->params( $this->context->getLanguage()->commaList( $i ) )
+ ->params( $module->getModulePrefix() )
) );
$result->setSubelements( $info, 'text' );
$item['info'][] = $info;
'categories' => 'ApiQueryCategories',
'categoryinfo' => 'ApiQueryCategoryInfo',
'contributors' => 'ApiQueryContributors',
+ 'deletedrevisions' => 'ApiQueryDeletedRevisions',
'duplicatefiles' => 'ApiQueryDuplicateFiles',
'extlinks' => 'ApiQueryExternalLinks',
'fileusage' => 'ApiQueryBacklinksprop',
*/
private static $QueryListModules = array(
'allcategories' => 'ApiQueryAllCategories',
+ 'alldeletedrevisions' => 'ApiQueryAllDeletedRevisions',
'allfileusages' => 'ApiQueryAllLinks',
'allimages' => 'ApiQueryAllImages',
'alllinks' => 'ApiQueryAllLinks',
--- /dev/null
+<?php
+/**
+ * Created on Oct 3, 2014
+ *
+ * Copyright © 2014 Brad Jorsch "bjorsch@wikimedia.org"
+ *
+ * Heavily based on ApiQueryDeletedrevs,
+ * Copyright © 2007 Roan Kattouw "<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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Query module to enumerate all deleted revisions.
+ *
+ * @ingroup API
+ */
+class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'adr' );
+ }
+
+ /**
+ * @param ApiPageSet $resultPageSet
+ * @return void
+ */
+ protected function run( ApiPageSet $resultPageSet = null ) {
+ $user = $this->getUser();
+ // Before doing anything at all, let's check permissions
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision information',
+ 'permissiondenied'
+ );
+ }
+
+ $db = $this->getDB();
+ $params = $this->extractRequestParams( false );
+
+ $result = $this->getResult();
+ $pageSet = $this->getPageSet();
+ $titles = $pageSet->getTitles();
+
+ // This module operates in two modes:
+ // 'user': List deleted revs by a certain user
+ // 'all': List all deleted revs in NS
+ $mode = 'all';
+ if ( !is_null( $params['user'] ) ) {
+ $mode = 'user';
+ }
+
+ if ( $mode == 'user' ) {
+ foreach ( array( 'from', 'to', 'prefix', 'excludeuser' ) as $param ) {
+ if ( !is_null( $params[$param] ) ) {
+ $p = $this->getModulePrefix();
+ $this->dieUsage( "The '{$p}{$param}' parameter cannot be used with '{$p}user'",
+ 'badparams' );
+ }
+ }
+ } else {
+ foreach ( array( 'start', 'end' ) as $param ) {
+ if ( !is_null( $params[$param] ) ) {
+ $p = $this->getModulePrefix();
+ $this->dieUsage( "The '{$p}{$param}' parameter may only be used with '{$p}user'",
+ 'badparams' );
+ }
+ }
+ }
+
+ $this->addTables( 'archive' );
+ if ( $resultPageSet === null ) {
+ $this->parseParameters( $params );
+ $this->addFields( Revision::selectArchiveFields() );
+ $this->addFields( array( 'ar_title', 'ar_namespace' ) );
+ } else {
+ $this->limit = $this->getParameter( 'limit' ) ?: 10;
+ $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id' ) );
+ }
+
+ if ( $this->fld_tags ) {
+ $this->addTables( 'tag_summary' );
+ $this->addJoinConds(
+ array( 'tag_summary' => array( 'LEFT JOIN', array( 'ar_rev_id=ts_rev_id' ) ) )
+ );
+ $this->addFields( 'ts_tags' );
+ }
+
+ if ( !is_null( $params['tag'] ) ) {
+ $this->addTables( 'change_tag' );
+ $this->addJoinConds(
+ array( 'change_tag' => array( 'INNER JOIN', array( 'ar_rev_id=ct_rev_id' ) ) )
+ );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
+ }
+
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ // Modern MediaWiki has the content for deleted revs in the 'text'
+ // table using fields old_text and old_flags. But revisions deleted
+ // pre-1.5 store the content in the 'archive' table directly using
+ // fields ar_text and ar_flags, and no corresponding 'text' row. So
+ // we have to LEFT JOIN and fetch all four fields.
+ $this->addTables( 'text' );
+ $this->addJoinConds(
+ array( 'text' => array( 'LEFT JOIN', array( 'ar_text_id=old_id' ) ) )
+ );
+ $this->addFields( array( 'ar_text', 'ar_flags', 'old_text', 'old_flags' ) );
+
+ // This also means stricter restrictions
+ if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision content',
+ 'permissiondenied'
+ );
+ }
+ }
+
+ $dir = $params['dir'];
+ $miser_ns = null;
+
+ if ( $mode == 'all' ) {
+ if ( $params['namespace'] !== null ) {
+ $this->addWhereFld( 'ar_namespace', $params['namespace'] );
+ }
+
+ $from = $params['from'] === null
+ ? null
+ : $this->titlePartToKey( $params['from'], $params['namespace'] );
+ $to = $params['to'] === null
+ ? null
+ : $this->titlePartToKey( $params['to'], $params['namespace'] );
+ $this->addWhereRange( 'ar_title', $dir, $from, $to );
+
+ if ( isset( $params['prefix'] ) ) {
+ $this->addWhere( 'ar_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
+ $db->anyString() ) );
+ }
+ } else {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $miser_ns = $params['namespace'];
+ } else {
+ $this->addWhereFld( 'ar_namespace', $params['namespace'] );
+ }
+ $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
+ }
+
+ if ( !is_null( $params['user'] ) ) {
+ $this->addWhereFld( 'ar_user_text', $params['user'] );
+ } elseif ( !is_null( $params['excludeuser'] ) ) {
+ $this->addWhere( 'ar_user_text != ' .
+ $db->addQuotes( $params['excludeuser'] ) );
+ }
+
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ // Paranoia: avoid brute force searches (bug 17342)
+ // (shouldn't be able to get here without 'deletedhistory', but
+ // check it again just in case)
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $op = ( $dir == 'newer' ? '>' : '<' );
+ if ( $mode == 'all' ) {
+ $this->dieContinueUsageIf( count( $cont ) != 4 );
+ $ns = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
+ $title = $db->addQuotes( $cont[1] );
+ $ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
+ $ar_id = (int)$cont[3];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[3] );
+ $this->addWhere( "ar_namespace $op $ns OR " .
+ "(ar_namespace = $ns AND " .
+ "(ar_title $op $title OR " .
+ "(ar_title = $title AND " .
+ "(ar_timestamp $op $ts OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)))))" );
+ } else {
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $ts = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $ar_id = (int)$cont[1];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[1] );
+ $this->addWhere( "ar_timestamp $op $ts OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)" );
+ }
+ }
+
+ $this->addOption( 'LIMIT', $this->limit + 1 );
+
+ $sort = ( $dir == 'newer' ? '' : ' DESC' );
+ $orderby = array();
+ if ( $mode == 'all' ) {
+ // Targeting index name_title_timestamp
+ if ( $params['namespace'] === null || count( array_unique( $params['namespace'] ) ) > 1 ) {
+ $orderby[] = "ar_namespace $sort";
+ }
+ $orderby[] = "ar_title $sort";
+ $orderby[] = "ar_timestamp $sort";
+ $orderby[] = "ar_id $sort";
+ } else {
+ // Targeting index usertext_timestamp
+ // 'user' is always constant.
+ $orderby[] = "ar_timestamp $sort";
+ $orderby[] = "ar_id $sort";
+ }
+ $this->addOption( 'ORDER BY', $orderby );
+
+ $res = $this->select( __METHOD__ );
+ $pageMap = array(); // Maps ns&title to array index
+ $count = 0;
+ $nextIndex = 0;
+ $generated = array();
+ foreach ( $res as $row ) {
+ if ( ++$count > $this->limit ) {
+ // We've had enough
+ if ( $mode == 'all' ) {
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ } else {
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
+ }
+ break;
+ }
+
+ // Miser mode namespace check
+ if ( $miser_ns !== null && !in_array( $row->ar_namespace, $miser_ns ) ) {
+ continue;
+ }
+
+ if ( $resultPageSet !== null ) {
+ if ( $params['generatetitles'] ) {
+ $key = "{$row->ar_namespace}:{$row->ar_title}";
+ if ( !isset( $generated[$key] ) ) {
+ $generated[$key] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ }
+ } else {
+ $generated[] = $row->ar_rev_id;
+ }
+ } else {
+ $revision = Revision::newFromArchiveRow( $row );
+ $rev = $this->extractRevisionInfo( $revision, $row );
+
+ if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
+ $index = $nextIndex++;
+ $pageMap[$row->ar_namespace][$row->ar_title] = $index;
+ $title = $revision->getTitle();
+ $a = array(
+ 'pageid' => $title->getArticleID(),
+ 'revisions' => array( $rev ),
+ );
+ $result->setIndexedTagName( $a['revisions'], 'rev' );
+ ApiQueryBase::addTitleInfo( $a, $title );
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), $index, $a );
+ } else {
+ $index = $pageMap[$row->ar_namespace][$row->ar_title];
+ $fit = $result->addValue(
+ array( 'query', $this->getModuleName(), $index, 'revisions' ),
+ null, $rev );
+ }
+ if ( !$fit ) {
+ if ( $mode == 'all' ) {
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ } else {
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
+ }
+ break;
+ }
+ }
+ }
+
+ if ( $resultPageSet !== null ) {
+ if ( $params['generatetitles'] ) {
+ $resultPageSet->populateFromTitles( $generated );
+ } else {
+ $resultPageSet->populateFromRevisionIDs( $generated );
+ }
+ } else {
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ }
+ }
+
+ public function getAllowedParams() {
+ $ret = parent::getAllowedParams() + array(
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_DFLT => null,
+ ),
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'useronly' ) ),
+ ),
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'useronly' ) ),
+ ),
+ 'dir' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'newer',
+ 'older'
+ ),
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
+ ),
+ 'from' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'to' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'prefix' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'excludeuser' => array(
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'tag' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
+ 'generatetitles' => array(
+ ApiBase::PARAM_DFLT => false
+ ),
+ );
+
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['user'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+ 'apihelp-query+alldeletedrevisions-param-miser-user-namespace',
+ );
+ $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+ 'apihelp-query+alldeletedrevisions-param-miser-user-namespace',
+ );
+ }
+
+ return $ret;
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=query&list=alldeletedrevisions&adruser=Example&adrlimit=50'
+ => 'apihelp-query+alldeletedrevisions-example-user',
+ 'action=query&list=alldeletedrevisions&adrdir=newer&adrlimit=50'
+ => 'apihelp-query+alldeletedrevisions-example-ns-main',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Alldeletedrevisions';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Created on Oct 3, 2014
+ *
+ * Copyright © 2014 Brad Jorsch "bjorsch@wikimedia.org"
+ *
+ * Heavily based on ApiQueryDeletedrevs,
+ * Copyright © 2007 Roan Kattouw "<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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Query module to enumerate deleted revisions for pages.
+ *
+ * @ingroup API
+ */
+class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'drv' );
+ }
+
+ protected function run( ApiPageSet $resultPageSet = null ) {
+ $user = $this->getUser();
+ // Before doing anything at all, let's check permissions
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision information',
+ 'permissiondenied'
+ );
+ }
+
+ $result = $this->getResult();
+ $pageSet = $this->getPageSet();
+ $pageMap = $pageSet->getGoodAndMissingTitlesByNamespace();
+ $pageCount = count( $pageSet->getGoodAndMissingTitles() );
+ $revCount = $pageSet->getRevisionCount();
+ if ( $revCount === 0 && $pageCount === 0 ) {
+ // Nothing to do
+ return;
+ }
+ if ( $revCount !== 0 && count( $pageSet->getDeletedRevisionIDs() ) === 0 ) {
+ // Nothing to do, revisions were supplied but none are deleted
+ return;
+ }
+
+ $params = $this->extractRequestParams( false );
+
+ $db = $this->getDB();
+
+ if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
+ $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
+ }
+
+ $this->addTables( 'archive' );
+ if ( $resultPageSet === null ) {
+ $this->parseParameters( $params );
+ $this->addFields( Revision::selectArchiveFields() );
+ $this->addFields( array( 'ar_title', 'ar_namespace' ) );
+ } else {
+ $this->limit = $this->getParameter( 'limit' ) ?: 10;
+ $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id' ) );
+ }
+
+ if ( $this->fld_tags ) {
+ $this->addTables( 'tag_summary' );
+ $this->addJoinConds(
+ array( 'tag_summary' => array( 'LEFT JOIN', array( 'ar_rev_id=ts_rev_id' ) ) )
+ );
+ $this->addFields( 'ts_tags' );
+ }
+
+ if ( !is_null( $params['tag'] ) ) {
+ $this->addTables( 'change_tag' );
+ $this->addJoinConds(
+ array( 'change_tag' => array( 'INNER JOIN', array( 'ar_rev_id=ct_rev_id' ) ) )
+ );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
+ }
+
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ // Modern MediaWiki has the content for deleted revs in the 'text'
+ // table using fields old_text and old_flags. But revisions deleted
+ // pre-1.5 store the content in the 'archive' table directly using
+ // fields ar_text and ar_flags, and no corresponding 'text' row. So
+ // we have to LEFT JOIN and fetch all four fields.
+ $this->addTables( 'text' );
+ $this->addJoinConds(
+ array( 'text' => array( 'LEFT JOIN', array( 'ar_text_id=old_id' ) ) )
+ );
+ $this->addFields( array( 'ar_text', 'ar_flags', 'old_text', 'old_flags' ) );
+
+ // This also means stricter restrictions
+ if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision content',
+ 'permissiondenied'
+ );
+ }
+ }
+
+ $dir = $params['dir'];
+
+ if ( $revCount !== 0 ) {
+ $this->addWhere( array(
+ 'ar_rev_id' => array_keys( $pageSet->getDeletedRevisionIDs() )
+ ) );
+ } else {
+ // We need a custom WHERE clause that matches all titles.
+ $lb = new LinkBatch( $pageSet->getGoodAndMissingTitles() );
+ $where = $lb->constructSet( 'ar', $db );
+ $this->addWhere( $where );
+ }
+
+ if ( !is_null( $params['user'] ) ) {
+ $this->addWhereFld( 'ar_user_text', $params['user'] );
+ } elseif ( !is_null( $params['excludeuser'] ) ) {
+ $this->addWhere( 'ar_user_text != ' .
+ $db->addQuotes( $params['excludeuser'] ) );
+ }
+
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ // Paranoia: avoid brute force searches (bug 17342)
+ // (shouldn't be able to get here without 'deletedhistory', but
+ // check it again just in case)
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $op = ( $dir == 'newer' ? '>' : '<' );
+ if ( $revCount !== 0 ) {
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $rev = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $rev ) !== $cont[0] );
+ $ar_id = (int)$cont[1];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[1] );
+ $this->addWhere( "ar_rev_id $op $rev OR " .
+ "(ar_rev_id = $rev AND " .
+ "ar_id $op= $ar_id)" );
+ } else {
+ $this->dieContinueUsageIf( count( $cont ) != 4 );
+ $ns = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
+ $title = $db->addQuotes( $cont[1] );
+ $ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
+ $ar_id = (int)$cont[3];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[3] );
+ $this->addWhere( "ar_namespace $op $ns OR " .
+ "(ar_namespace = $ns AND " .
+ "(ar_title $op $title OR " .
+ "(ar_title = $title AND " .
+ "(ar_timestamp $op $ts OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)))))" );
+ }
+ }
+
+ $this->addOption( 'LIMIT', $this->limit + 1 );
+
+ if ( $revCount !== 0 ) {
+ // Sort by ar_rev_id when querying by ar_rev_id
+ $this->addWhereRange( 'ar_rev_id', $dir, null, null );
+ } else {
+ // Sort by ns and title in the same order as timestamp for efficiency
+ // But only when not already unique in the query
+ if ( count( $pageMap ) > 1 ) {
+ $this->addWhereRange( 'ar_namespace', $dir, null, null );
+ }
+ $oneTitle = key( reset( $pageMap ) );
+ foreach ( $pageMap as $pages ) {
+ if ( count( $pages ) > 1 || key( $pages ) !== $oneTitle ) {
+ $this->addWhereRange( 'ar_title', $dir, null, null );
+ break;
+ }
+ }
+ $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
+ }
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'ar_id', $dir, null, null );
+
+ $res = $this->select( __METHOD__ );
+ $count = 0;
+ $generated = array();
+ foreach ( $res as $row ) {
+ if ( ++$count > $this->limit ) {
+ // We've had enough
+ $this->setContinueEnumParameter( 'continue',
+ $revCount
+ ? "$row->ar_rev_id|$row->ar_id"
+ : "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ break;
+ }
+
+ if ( $resultPageSet !== null ) {
+ $generated[] = $row->ar_rev_id;
+ } else {
+ if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
+ // Was it converted?
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ $converted = $pageSet->getConvertedTitles();
+ if ( $title && isset( $converted[$title->getPrefixedText()] ) ) {
+ $title = Title::newFromText( $converted[$title->getPrefixedText()] );
+ if ( $title && isset( $pageMap[$title->getNamespace()][$title->getDBkey()] ) ) {
+ $pageMap[$row->ar_namespace][$row->ar_title] =
+ $pageMap[$title->getNamespace()][$title->getDBkey()];
+ }
+ }
+ }
+ if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
+ ApiBase::dieDebug( "Found row in archive (ar_id={$row->ar_id}) that didn't " .
+ "get processed by ApiPageSet" );
+ }
+
+ $fit = $this->addPageSubItem(
+ $pageMap[$row->ar_namespace][$row->ar_title],
+ $this->extractRevisionInfo( Revision::newFromArchiveRow( $row ), $row ),
+ 'rev'
+ );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue',
+ $revCount
+ ? "$row->ar_rev_id|$row->ar_id"
+ : "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ break;
+ }
+ }
+ }
+
+ if ( $resultPageSet !== null ) {
+ $resultPageSet->populateFromRevisionIDs( $generated );
+ }
+ }
+
+ public function getAllowedParams() {
+ return parent::getAllowedParams() + array(
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ),
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ),
+ 'dir' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'newer',
+ 'older'
+ ),
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
+ ),
+ 'tag' => null,
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'excludeuser' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
+ );
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=query&prop=deletedrevisions&titles=Main%20Page|Talk:Main%20Page&' .
+ 'drvprop=user|comment|content'
+ => 'apihelp-query+deletedrevisions-example-titles',
+ 'action=query&prop=deletedrevisions&revids=123456'
+ => 'apihelp-query+deletedrevisions-example-revids',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#deletedrevisions_.2F_drv';
+ }
+}
* Query module to enumerate all deleted revisions.
*
* @ingroup API
+ * @deprecated since 1.25
*/
class ApiQueryDeletedrevs extends ApiQueryBase {
);
}
+ $this->setWarning(
+ 'list=deletedrevs has been deprecated. Please use prop=deletedrevisions or ' .
+ 'list=alldeletedrevisions instead.'
+ );
+ $this->logFeatureUsage( 'action=query&list=deletedrevs' );
+
$db = $this->getDB();
$params = $this->extractRequestParams( false );
$prop = array_flip( $params['prop'] );
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
}
+ public function isDeprecated() {
+ return true;
+ }
+
public function getAllowedParams() {
return array(
'start' => array(
*
* @ingroup API
*/
-class ApiQueryRevisions extends ApiQueryBase {
+class ApiQueryRevisions extends ApiQueryRevisionsBase {
- private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
- $token, $parseContent, $contentFormat;
+ private $token = null;
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rv' );
}
- private $fld_ids = false, $fld_flags = false, $fld_timestamp = false,
- $fld_size = false, $fld_sha1 = false, $fld_comment = false,
- $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
- $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
-
private $tokenFunctions;
/** @deprecated since 1.24 */
array( $title->getPrefixedText(), $rev->getUserText() ) );
}
- public function execute() {
+ protected function run( ApiPageSet $resultPageSet = null ) {
$params = $this->extractRequestParams( false );
// If any of those parameters are used, work in 'enumeration' mode.
// Optimization -- nothing to do
if ( $revCount === 0 && $pageCount === 0 ) {
+ // Nothing to do
+ return;
+ }
+ if ( $revCount > 0 && count( $pageSet->getLiveRevisionIDs() ) === 0 ) {
+ // We're in revisions mode but all given revisions are deleted
return;
}
);
}
- if ( !is_null( $params['difftotext'] ) ) {
- $this->difftotext = $params['difftotext'];
- } elseif ( !is_null( $params['diffto'] ) ) {
- if ( $params['diffto'] == 'cur' ) {
- $params['diffto'] = 0;
- }
- if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
- && $params['diffto'] != 'prev' && $params['diffto'] != 'next'
- ) {
- $this->dieUsage(
- 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"',
- 'diffto'
- );
- }
- // Check whether the revision exists and is readable,
- // DifferenceEngine returns a rather ambiguous empty
- // string if that's not the case
- if ( $params['diffto'] != 0 ) {
- $difftoRev = Revision::newFromID( $params['diffto'] );
- if ( !$difftoRev ) {
- $this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
- }
- if ( !$difftoRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
- $this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
- $params['diffto'] = null;
- }
- }
- $this->diffto = $params['diffto'];
+ // In non-enum mode, rvlimit can't be directly used. Use the maximum
+ // allowed value.
+ if ( !$enumRevMode ) {
+ $this->setParsedLimit = false;
+ $params['limit'] = 'max';
}
$db = $this->getDB();
- $this->addTables( 'page' );
- $this->addFields( Revision::selectFields() );
- $this->addWhere( 'page_id = rev_page' );
-
- $prop = array_flip( $params['prop'] );
-
- // Optional fields
- $this->fld_ids = isset( $prop['ids'] );
- // $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed?
- $this->fld_flags = isset( $prop['flags'] );
- $this->fld_timestamp = isset( $prop['timestamp'] );
- $this->fld_comment = isset( $prop['comment'] );
- $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
- $this->fld_size = isset( $prop['size'] );
- $this->fld_sha1 = isset( $prop['sha1'] );
- $this->fld_contentmodel = isset( $prop['contentmodel'] );
- $this->fld_userid = isset( $prop['userid'] );
- $this->fld_user = isset( $prop['user'] );
- $this->token = $params['token'];
-
- if ( !empty( $params['contentformat'] ) ) {
- $this->contentFormat = $params['contentformat'];
- }
-
- $userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
- $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
- $limit = $params['limit'];
- if ( $limit == 'max' ) {
- $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
- }
+ $this->addTables( array( 'revision', 'page' ) );
+ $this->addJoinConds(
+ array( 'page' => array( 'INNER JOIN', array( 'page_id = rev_page' ) ) )
+ );
- if ( !is_null( $this->token ) || $pageCount > 0 ) {
- $this->addFields( Revision::selectPageFields() );
+ if ( $resultPageSet === null ) {
+ $this->parseParameters( $params );
+ $this->token = $params['token'];
+ $this->addFields( Revision::selectFields() );
+ if ( $this->token !== null || $pageCount > 0 ) {
+ $this->addFields( Revision::selectPageFields() );
+ }
+ } else {
+ $this->limit = $this->getParameter( 'limit' ) ?: 10;
+ $this->addFields( array( 'rev_id', 'rev_page' ) );
}
- if ( isset( $prop['tags'] ) ) {
- $this->fld_tags = true;
+ if ( $this->fld_tags ) {
$this->addTables( 'tag_summary' );
$this->addJoinConds(
array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) )
$this->addWhereFld( 'ct_tag', $params['tag'] );
}
- if ( isset( $prop['content'] ) || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
// For each page we will request, the user must have read rights for that page
$user = $this->getUser();
/** @var $title Title */
}
$this->addTables( 'text' );
- $this->addWhere( 'rev_text_id=old_id' );
+ $this->addJoinConds(
+ array( 'text' => array( 'INNER JOIN', array( 'rev_text_id=old_id' ) ) )
+ );
$this->addFields( 'old_id' );
$this->addFields( Revision::selectTextFields() );
-
- $this->fld_content = isset( $prop['content'] );
-
- $this->expandTemplates = $params['expandtemplates'];
- $this->generateXML = $params['generatexml'];
- $this->parseContent = $params['parse'];
- if ( $this->parseContent ) {
- // Must manually initialize unset limit
- if ( is_null( $limit ) ) {
- $limit = 1;
- }
- // We are only going to parse 1 revision per request
- $this->validateLimit( 'limit', $limit, 1, 1, 1 );
- }
- if ( isset( $params['section'] ) ) {
- $this->section = $params['section'];
- } else {
- $this->section = false;
- }
}
// add user name, if needed
$this->addFields( Revision::selectUserFields() );
}
- // Bug 24166 - API error when using rvprop=tags
- $this->addTables( 'revision' );
-
if ( $enumRevMode ) {
// This is mostly to prevent parameter errors (and optimize SQL?)
if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) ) {
$params['start'], $params['end'], false );
}
- // must manually initialize unset limit
- if ( is_null( $limit ) ) {
- $limit = 10;
- }
- $this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
-
// There is only one ID, use it
$ids = array_keys( $pageSet->getGoodTitles() );
$this->addWhereFld( 'rev_page', reset( $ids ) );
}
}
} elseif ( $revCount > 0 ) {
- $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $revs = $pageSet->getRevisionIDs();
- if ( self::truncateArray( $revs, $max ) ) {
- $this->setWarning( "Too many values supplied for parameter 'revids': the limit is $max" );
- }
+ $revs = $pageSet->getLiveRevisionIDs();
// Get all revision IDs
$this->addWhereFld( 'rev_id', array_keys( $revs ) );
$this->addWhere( 'rev_id >= ' . intval( $params['continue'] ) );
}
$this->addOption( 'ORDER BY', 'rev_id' );
-
- // assumption testing -- we should never get more then $revCount rows.
- $limit = $revCount;
} elseif ( $pageCount > 0 ) {
- $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
$titles = $pageSet->getGoodTitles();
- if ( self::truncateArray( $titles, $max ) ) {
- $this->setWarning( "Too many values supplied for parameter 'titles': the limit is $max" );
- }
// When working in multi-page non-enumeration mode,
// limit to the latest revision only
- $this->addWhere( 'page_id=rev_page' );
$this->addWhere( 'page_latest=rev_id' );
// Get all page IDs
'rev_page',
'rev_id'
) );
-
- // assumption testing -- we should never get more then $pageCount rows.
- $limit = $pageCount;
} else {
ApiBase::dieDebug( __METHOD__, 'param validation?' );
}
- $this->addOption( 'LIMIT', $limit + 1 );
+ $this->addOption( 'LIMIT', $this->limit + 1 );
$count = 0;
+ $generated = array();
$res = $this->select( __METHOD__ );
foreach ( $res as $row ) {
- if ( ++$count > $limit ) {
+ if ( ++$count > $this->limit ) {
// We've reached the one extra which shows that there are
// additional pages to be had. Stop here...
- if ( !$enumRevMode ) {
- ApiBase::dieDebug( __METHOD__, 'Got more rows then expected' ); // bug report
- }
- $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
- break;
- }
-
- $fit = $this->addPageSubItem( $row->rev_page, $this->extractRowInfo( $row ), 'rev' );
- if ( !$fit ) {
if ( $enumRevMode ) {
$this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
} elseif ( $revCount > 0 ) {
}
break;
}
- }
- }
-
- private function extractRowInfo( $row ) {
- $revision = new Revision( $row );
- $title = $revision->getTitle();
- $user = $this->getUser();
- $vals = array();
- $anyHidden = false;
-
- if ( $this->fld_ids ) {
- $vals['revid'] = intval( $revision->getId() );
- // $vals['oldid'] = intval( $row->rev_text_id ); // todo: should this be exposed?
- if ( !is_null( $revision->getParentId() ) ) {
- $vals['parentid'] = intval( $revision->getParentId() );
- }
- }
-
- if ( $this->fld_flags && $revision->isMinor() ) {
- $vals['minor'] = '';
- }
-
- if ( $this->fld_user || $this->fld_userid ) {
- if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
- $vals['userhidden'] = '';
- $anyHidden = true;
- }
- if ( $revision->userCan( Revision::DELETED_USER, $user ) ) {
- if ( $this->fld_user ) {
- $vals['user'] = $revision->getRawUserText();
- }
- $userid = $revision->getRawUser();
- if ( !$userid ) {
- $vals['anon'] = '';
- }
-
- if ( $this->fld_userid ) {
- $vals['userid'] = $userid;
- }
- }
- }
-
- if ( $this->fld_timestamp ) {
- $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
- }
-
- if ( $this->fld_size ) {
- if ( !is_null( $revision->getSize() ) ) {
- $vals['size'] = intval( $revision->getSize() );
- } else {
- $vals['size'] = 0;
- }
- }
-
- if ( $this->fld_sha1 ) {
- if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
- $vals['sha1hidden'] = '';
- $anyHidden = true;
- }
- if ( $revision->userCan( Revision::DELETED_TEXT, $user ) ) {
- if ( $revision->getSha1() != '' ) {
- $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
- } else {
- $vals['sha1'] = '';
- }
- }
- }
-
- if ( $this->fld_contentmodel ) {
- $vals['contentmodel'] = $revision->getContentModel();
- }
-
- if ( $this->fld_comment || $this->fld_parsedcomment ) {
- if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
- $vals['commenthidden'] = '';
- $anyHidden = true;
- }
- if ( $revision->userCan( Revision::DELETED_COMMENT, $user ) ) {
- $comment = $revision->getRawComment();
- if ( $this->fld_comment ) {
- $vals['comment'] = $comment;
- }
-
- if ( $this->fld_parsedcomment ) {
- $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
- }
- }
- }
-
- if ( $this->fld_tags ) {
- if ( $row->ts_tags ) {
- $tags = explode( ',', $row->ts_tags );
- $this->getResult()->setIndexedTagName( $tags, 'tag' );
- $vals['tags'] = $tags;
+ if ( $resultPageSet !== null ) {
+ $generated[] = $row->rev_id;
} else {
- $vals['tags'] = array();
- }
- }
-
- if ( !is_null( $this->token ) ) {
- $tokenFunctions = $this->getTokenFunctions();
- foreach ( $this->token as $t ) {
- $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
- if ( $val === false ) {
- $this->setWarning( "Action '$t' is not allowed for the current user" );
- } else {
- $vals[$t . 'token'] = $val;
- }
- }
- }
-
- $content = null;
- global $wgParser;
- if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
- $content = $revision->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- // Expand templates after getting section content because
- // template-added sections don't count and Parser::preprocess()
- // will have less input
- if ( $content && $this->section !== false ) {
- $content = $content->getSection( $this->section, false );
- if ( !$content ) {
- $this->dieUsage(
- "There is no section {$this->section} in r" . $revision->getId(),
- 'nosuchsection'
- );
- }
- }
- if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
- $vals['texthidden'] = '';
- $anyHidden = true;
- } elseif ( !$content ) {
- $vals['textmissing'] = '';
- }
- }
- if ( $this->fld_content && $content ) {
- $text = null;
-
- if ( $this->generateXML ) {
- if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
- $t = $content->getNativeData(); # note: don't set $text
-
- $wgParser->startExternalParse(
- $title,
- ParserOptions::newFromContext( $this->getContext() ),
- Parser::OT_PREPROCESS
- );
- $dom = $wgParser->preprocessToDom( $t );
- if ( is_callable( array( $dom, 'saveXML' ) ) ) {
- $xml = $dom->saveXML();
- } else {
- $xml = $dom->__toString();
+ $revision = new Revision( $row );
+ $rev = $this->extractRevisionInfo( $revision, $row );
+
+ if ( $this->token !== null ) {
+ $title = $revision->getTitle();
+ $tokenFunctions = $this->getTokenFunctions();
+ foreach ( $this->token as $t ) {
+ $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
+ if ( $val === false ) {
+ $this->setWarning( "Action '$t' is not allowed for the current user" );
+ } else {
+ $rev[$t . 'token'] = $val;
+ }
}
- $vals['parsetree'] = $xml;
- } else {
- $this->setWarning( "Conversion to XML is supported for wikitext only, " .
- $title->getPrefixedDBkey() .
- " uses content model " . $content->getModel() );
}
- }
-
- if ( $this->expandTemplates && !$this->parseContent ) {
- #XXX: implement template expansion for all content types in ContentHandler?
- if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
- $text = $content->getNativeData();
-
- $text = $wgParser->preprocess(
- $text,
- $title,
- ParserOptions::newFromContext( $this->getContext() )
- );
- } else {
- $this->setWarning( "Template expansion is supported for wikitext only, " .
- $title->getPrefixedDBkey() .
- " uses content model " . $content->getModel() );
-
- $text = false;
- }
- }
- if ( $this->parseContent ) {
- $po = $content->getParserOutput(
- $title,
- $revision->getId(),
- ParserOptions::newFromContext( $this->getContext() )
- );
- $text = $po->getText();
- }
-
- if ( $text === null ) {
- $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
- $model = $content->getModel();
-
- if ( !$content->isSupportedFormat( $format ) ) {
- $name = $title->getPrefixedDBkey();
- $this->dieUsage( "The requested format {$this->contentFormat} is not supported " .
- "for content model $model used by $name", 'badformat' );
- }
-
- $text = $content->serialize( $format );
-
- // always include format and model.
- // Format is needed to deserialize, model is needed to interpret.
- $vals['contentformat'] = $format;
- $vals['contentmodel'] = $model;
- }
-
- if ( $text !== false ) {
- ApiResult::setContent( $vals, $text );
- }
- }
-
- if ( $content && ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) ) {
- static $n = 0; // Number of uncached diffs we've had
-
- if ( $n < $this->getConfig()->get( 'APIMaxUncachedDiffs' ) ) {
- $vals['diff'] = array();
- $context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $title );
- $handler = $revision->getContentHandler();
-
- if ( !is_null( $this->difftotext ) ) {
- $model = $title->getContentModel();
-
- if ( $this->contentFormat
- && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat )
- ) {
-
- $name = $title->getPrefixedDBkey();
-
- $this->dieUsage( "The requested format {$this->contentFormat} is not supported for " .
- "content model $model used by $name", 'badformat' );
+ $fit = $this->addPageSubItem( $row->rev_page, $rev, 'rev' );
+ if ( !$fit ) {
+ if ( $enumRevMode ) {
+ $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
+ } elseif ( $revCount > 0 ) {
+ $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
+ } else {
+ $this->setContinueEnumParameter( 'continue', intval( $row->rev_page ) .
+ '|' . intval( $row->rev_id ) );
}
-
- $difftocontent = ContentHandler::makeContent(
- $this->difftotext,
- $title,
- $model,
- $this->contentFormat
- );
-
- $engine = $handler->createDifferenceEngine( $context );
- $engine->setContent( $content, $difftocontent );
- } else {
- $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
- $vals['diff']['from'] = $engine->getOldid();
- $vals['diff']['to'] = $engine->getNewid();
+ break;
}
- $difftext = $engine->getDiffBody();
- ApiResult::setContent( $vals['diff'], $difftext );
- if ( !$engine->wasCacheHit() ) {
- $n++;
- }
- } else {
- $vals['diff']['notcached'] = '';
}
}
- if ( $anyHidden && $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) {
- $vals['suppressed'] = '';
+ if ( $resultPageSet !== null ) {
+ $resultPageSet->populateFromRevisionIDs( $generated );
}
-
- return $vals;
}
public function getCacheMode( $params ) {
if ( isset( $params['token'] ) ) {
return 'private';
}
- if ( $this->userCanSeeRevDel() ) {
- return 'private';
- }
- if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
- // formatComment() calls wfMessage() among other things
- return 'anon-public-user-private';
- }
-
- return 'public';
+ return parent::getCacheMode( $params );
}
public function getAllowedParams() {
- return array(
- 'prop' => array(
- ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_DFLT => 'ids|timestamp|flags|comment|user',
- ApiBase::PARAM_TYPE => array(
- 'ids',
- 'flags',
- 'timestamp',
- 'user',
- 'userid',
- 'size',
- 'sha1',
- 'contentmodel',
- 'comment',
- 'parsedcomment',
- 'content',
- 'tags'
- )
- ),
- 'limit' => array(
- ApiBase::PARAM_TYPE => 'limit',
- ApiBase::PARAM_MIN => 1,
- ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
- ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
- ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
- ),
+ $ret = parent::getAllowedParams() + array(
'startid' => array(
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
'newer',
'older'
),
- ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'user' => array(
ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'tag' => null,
- 'expandtemplates' => false,
- 'generatexml' => false,
- 'parse' => false,
- 'section' => null,
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
'continue' => array(
ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- 'diffto' => null,
- 'difftotext' => null,
- 'contentformat' => array(
- ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
- ApiBase::PARAM_DFLT => null
- ),
);
+
+ $ret['limit'][ApiBase::PARAM_HELP_MSG_INFO] = array( array( 'singlepageonly' ) );
+
+ return $ret;
}
protected function getExamplesMessages() {
--- /dev/null
+<?php
+/**
+ *
+ *
+ * Created on Oct 3, 2014 as a split from ApiQueryRevisions
+ *
+ * Copyright © 2006 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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * A base class for functions common to producing a list of revisions.
+ *
+ * @ingroup API
+ */
+abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
+
+ protected $limit, $diffto, $difftotext, $expandTemplates, $generateXML, $section,
+ $parseContent, $contentFormat, $setParsedLimit = true;
+
+ protected $fld_ids = false, $fld_flags = false, $fld_timestamp = false,
+ $fld_size = false, $fld_sha1 = false, $fld_comment = false,
+ $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
+ $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param ApiPageSet $resultPageSet
+ * @return void
+ */
+ abstract protected function run( ApiPageSet $resultPageSet = null );
+
+ /**
+ * Parse the parameters into the various instance fields.
+ *
+ * @param array $params
+ */
+ protected function parseParameters( $params ) {
+ if ( !is_null( $params['difftotext'] ) ) {
+ $this->difftotext = $params['difftotext'];
+ } elseif ( !is_null( $params['diffto'] ) ) {
+ if ( $params['diffto'] == 'cur' ) {
+ $params['diffto'] = 0;
+ }
+ if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
+ && $params['diffto'] != 'prev' && $params['diffto'] != 'next'
+ ) {
+ $p = $this->getModulePrefix();
+ $this->dieUsage(
+ "{$p}diffto must be set to a non-negative number, \"prev\", \"next\" or \"cur\"",
+ 'diffto'
+ );
+ }
+ // Check whether the revision exists and is readable,
+ // DifferenceEngine returns a rather ambiguous empty
+ // string if that's not the case
+ if ( $params['diffto'] != 0 ) {
+ $difftoRev = Revision::newFromID( $params['diffto'] );
+ if ( !$difftoRev ) {
+ $this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
+ }
+ if ( !$difftoRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
+ $params['diffto'] = null;
+ }
+ }
+ $this->diffto = $params['diffto'];
+ }
+
+ $prop = array_flip( $params['prop'] );
+
+ $this->fld_ids = isset( $prop['ids'] );
+ $this->fld_flags = isset( $prop['flags'] );
+ $this->fld_timestamp = isset( $prop['timestamp'] );
+ $this->fld_comment = isset( $prop['comment'] );
+ $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
+ $this->fld_size = isset( $prop['size'] );
+ $this->fld_sha1 = isset( $prop['sha1'] );
+ $this->fld_content = isset( $prop['content'] );
+ $this->fld_contentmodel = isset( $prop['contentmodel'] );
+ $this->fld_userid = isset( $prop['userid'] );
+ $this->fld_user = isset( $prop['user'] );
+ $this->fld_tags = isset( $prop['tags'] );
+
+ if ( !empty( $params['contentformat'] ) ) {
+ $this->contentFormat = $params['contentformat'];
+ }
+
+ $this->limit = $params['limit'];
+
+ $smallLimit = false;
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ $smallLimit = true;
+ $this->expandTemplates = $params['expandtemplates'];
+ $this->generateXML = $params['generatexml'];
+ $this->parseContent = $params['parse'];
+ if ( $this->parseContent ) {
+ // Must manually initialize unset limit
+ if ( is_null( $this->limit ) ) {
+ $this->limit = 1;
+ }
+ }
+ if ( isset( $params['section'] ) ) {
+ $this->section = $params['section'];
+ } else {
+ $this->section = false;
+ }
+ }
+
+ $userMax = $this->parseContent ? 1 : ( $smallLimit ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
+ $botMax = $this->parseContent ? 1 : ( $smallLimit ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
+ if ( $this->limit == 'max' ) {
+ $this->limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
+ if ( $this->setParsedLimit ) {
+ $this->getResult()->setParsedLimit( $this->getModuleName(), $this->limit );
+ }
+ }
+
+ if ( is_null( $this->limit ) ) {
+ $this->limit = 10;
+ }
+ $this->validateLimit( 'limit', $this->limit, 1, $userMax, $botMax );
+ }
+
+ /**
+ * Extract information from the Revision
+ *
+ * @param Revision $revision
+ * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set
+ * @return array
+ */
+ protected function extractRevisionInfo( Revision $revision, $row ) {
+ $title = $revision->getTitle();
+ $user = $this->getUser();
+ $vals = array();
+ $anyHidden = false;
+
+ if ( $this->fld_ids ) {
+ $vals['revid'] = intval( $revision->getId() );
+ if ( !is_null( $revision->getParentId() ) ) {
+ $vals['parentid'] = intval( $revision->getParentId() );
+ }
+ }
+
+ if ( $this->fld_flags && $revision->isMinor() ) {
+ $vals['minor'] = '';
+ }
+
+ if ( $this->fld_user || $this->fld_userid ) {
+ if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
+ $vals['userhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_USER, $user ) ) {
+ if ( $this->fld_user ) {
+ $vals['user'] = $revision->getRawUserText();
+ }
+ $userid = $revision->getRawUser();
+ if ( !$userid ) {
+ $vals['anon'] = '';
+ }
+
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $userid;
+ }
+ }
+ }
+
+ if ( $this->fld_timestamp ) {
+ $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
+ }
+
+ if ( $this->fld_size ) {
+ if ( !is_null( $revision->getSize() ) ) {
+ $vals['size'] = intval( $revision->getSize() );
+ } else {
+ $vals['size'] = 0;
+ }
+ }
+
+ if ( $this->fld_sha1 ) {
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['sha1hidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_TEXT, $user ) ) {
+ if ( $revision->getSha1() != '' ) {
+ $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
+ }
+ }
+
+ if ( $this->fld_contentmodel ) {
+ $vals['contentmodel'] = $revision->getContentModel();
+ }
+
+ if ( $this->fld_comment || $this->fld_parsedcomment ) {
+ if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
+ $vals['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $comment = $revision->getRawComment();
+
+ if ( $this->fld_comment ) {
+ $vals['comment'] = $comment;
+ }
+
+ if ( $this->fld_parsedcomment ) {
+ $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
+ }
+ }
+ }
+
+ if ( $this->fld_tags ) {
+ if ( $row->ts_tags ) {
+ $tags = explode( ',', $row->ts_tags );
+ $this->getResult()->setIndexedTagName( $tags, 'tag' );
+ $vals['tags'] = $tags;
+ } else {
+ $vals['tags'] = array();
+ }
+ }
+
+ $content = null;
+ global $wgParser;
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ $content = $revision->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ // Expand templates after getting section content because
+ // template-added sections don't count and Parser::preprocess()
+ // will have less input
+ if ( $content && $this->section !== false ) {
+ $content = $content->getSection( $this->section, false );
+ if ( !$content ) {
+ $this->dieUsage(
+ "There is no section {$this->section} in r" . $revision->getId(),
+ 'nosuchsection'
+ );
+ }
+ }
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['texthidden'] = '';
+ $anyHidden = true;
+ } elseif ( !$content ) {
+ $vals['textmissing'] = '';
+ }
+ }
+ if ( $this->fld_content && $content ) {
+ $text = null;
+
+ if ( $this->generateXML ) {
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $t = $content->getNativeData(); # note: don't set $text
+
+ $wgParser->startExternalParse(
+ $title,
+ ParserOptions::newFromContext( $this->getContext() ),
+ Parser::OT_PREPROCESS
+ );
+ $dom = $wgParser->preprocessToDom( $t );
+ if ( is_callable( array( $dom, 'saveXML' ) ) ) {
+ $xml = $dom->saveXML();
+ } else {
+ $xml = $dom->__toString();
+ }
+ $vals['parsetree'] = $xml;
+ } else {
+ $vals['badcontentformatforparsetree'] = '';
+ $this->setWarning( "Conversion to XML is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() );
+ }
+ }
+
+ if ( $this->expandTemplates && !$this->parseContent ) {
+ #XXX: implement template expansion for all content types in ContentHandler?
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $text = $content->getNativeData();
+
+ $text = $wgParser->preprocess(
+ $text,
+ $title,
+ ParserOptions::newFromContext( $this->getContext() )
+ );
+ } else {
+ $this->setWarning( "Template expansion is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() );
+ $vals['badcontentformat'] = '';
+ $text = false;
+ }
+ }
+ if ( $this->parseContent ) {
+ $po = $content->getParserOutput(
+ $title,
+ $revision->getId(),
+ ParserOptions::newFromContext( $this->getContext() )
+ );
+ $text = $po->getText();
+ }
+
+ if ( $text === null ) {
+ $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
+ $model = $content->getModel();
+
+ if ( !$content->isSupportedFormat( $format ) ) {
+ $name = $title->getPrefixedDBkey();
+ $this->setWarning( "The requested format {$this->contentFormat} is not " .
+ "supported for content model $model used by $name" );
+ $vals['badcontentformat'] = '';
+ $text = false;
+ } else {
+ $text = $content->serialize( $format );
+ // always include format and model.
+ // Format is needed to deserialize, model is needed to interpret.
+ $vals['contentformat'] = $format;
+ $vals['contentmodel'] = $model;
+ }
+ }
+
+ if ( $text !== false ) {
+ ApiResult::setContent( $vals, $text );
+ }
+ }
+
+ if ( $content && ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) ) {
+ static $n = 0; // Number of uncached diffs we've had
+
+ if ( $n < $this->getConfig()->get( 'APIMaxUncachedDiffs' ) ) {
+ $vals['diff'] = array();
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $title );
+ $handler = $revision->getContentHandler();
+
+ if ( !is_null( $this->difftotext ) ) {
+ $model = $title->getContentModel();
+
+ if ( $this->contentFormat
+ && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat )
+ ) {
+ $name = $title->getPrefixedDBkey();
+ $this->setWarning( "The requested format {$this->contentFormat} is not " .
+ "supported for content model $model used by $name" );
+ $vals['diff']['badcontentformat'] = '';
+ $engine = null;
+ } else {
+ $difftocontent = ContentHandler::makeContent(
+ $this->difftotext,
+ $title,
+ $model,
+ $this->contentFormat
+ );
+
+ $engine = $handler->createDifferenceEngine( $context );
+ $engine->setContent( $content, $difftocontent );
+ }
+ } else {
+ $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
+ $vals['diff']['from'] = $engine->getOldid();
+ $vals['diff']['to'] = $engine->getNewid();
+ }
+ if ( $engine ) {
+ $difftext = $engine->getDiffBody();
+ ApiResult::setContent( $vals['diff'], $difftext );
+ if ( !$engine->wasCacheHit() ) {
+ $n++;
+ }
+ }
+ } else {
+ $vals['diff']['notcached'] = '';
+ }
+ }
+
+ if ( $anyHidden && $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
+ }
+
+ return $vals;
+ }
+
+ public function getCacheMode( $params ) {
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
+
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'ids|timestamp|flags|comment|user',
+ ApiBase::PARAM_TYPE => array(
+ 'ids',
+ 'flags',
+ 'timestamp',
+ 'user',
+ 'userid',
+ 'size',
+ 'sha1',
+ 'contentmodel',
+ 'comment',
+ 'parsedcomment',
+ 'content',
+ 'tags'
+ ),
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-prop',
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-limit',
+ ),
+ 'expandtemplates' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-expandtemplates',
+ ),
+ 'generatexml' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-generatexml',
+ ),
+ 'parse' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-parse',
+ ),
+ 'section' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-section',
+ ),
+ 'diffto' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-diffto',
+ ),
+ 'difftotext' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotext',
+ ),
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-contentformat',
+ ),
+ );
+ }
+
+}
"apihelp-query+allcategories-example-size": "List categories with information on the number of pages in each",
"apihelp-query+allcategories-example-generator": "Retrieve info about the category page itself for categories beginning \"List\"",
+ "apihelp-query+alldeletedrevisions-description": "List all deleted revisions by a user or in a namespace.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "May only be used with $3user.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Cannot be used with $3user.",
+ "apihelp-query+alldeletedrevisions-param-start": "The timestamp to start enumerating from.",
+ "apihelp-query+alldeletedrevisions-param-end": "The timestamp to stop enumerating at.",
+ "apihelp-query+alldeletedrevisions-param-from": "Start listing at this title.",
+ "apihelp-query+alldeletedrevisions-param-to": "Stop listing at this title.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Search for all page titles that begin with this value.",
+ "apihelp-query+alldeletedrevisions-param-tag": "Only list revisions tagged with this tag.",
+ "apihelp-query+alldeletedrevisions-param-user": "Only list revisions by this user.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Don't list revisions by this user.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Only list pages in this namespace.",
+ "apihelp-query+alldeletedrevisions-param-miser-user-namespace": "'''NOTE:''' Due to [https://www.mediawiki.org/wiki/Manual:$wgMiserMode miser mode], using $1user and $1namespace together may result in fewer than \"$1limit\" results returned before continuing; in extreme cases, zero results may be returned.",
+ "apihelp-query+alldeletedrevisions-param-generatetitles": "When being used as a generator, generate titles rather than revision IDs.",
+ "apihelp-query+alldeletedrevisions-example-user": "List the last 50 deleted contributions by User:Example",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "List the first 50 deleted revisions in the main namespace",
+
"apihelp-query+allfileusages-description": "List all file usages, including non-existing.",
"apihelp-query+allfileusages-param-from": "The title of the file to start enumerating from.",
"apihelp-query+allfileusages-param-to": "The title of the file to stop enumerating at.",
"apihelp-query+contributors-param-limit": "How many contributors to return.",
"apihelp-query+contributors-example-simple": "Show contributors to the [[Main Page]]",
+ "apihelp-query+deletedrevisions-description": "Get deleted revision information.\n\nMay be used in several ways:\n# Get deleted revisions for a set of pages, by setting titles or pageids. Ordered by title and timestamp.\n# Get data about a set of deleted revisions by setting their IDs with revids. Ordered by revision ID.",
+ "apihelp-query+deletedrevisions-param-start": "The timestamp to start enumerating from. Ignored when processing a list of revision IDs.",
+ "apihelp-query+deletedrevisions-param-end": "The timestamp to stop enumerating at. Ignored when processing a list of revision IDs.",
+ "apihelp-query+deletedrevisions-param-tag": "Only list revisions tagged with this tag.",
+ "apihelp-query+deletedrevisions-param-user": "Only list revisions by this user.",
+ "apihelp-query+deletedrevisions-param-excludeuser": "Don't list revisions by this user.",
+ "apihelp-query+deletedrevisions-param-limit": "The maximum amount of revisions to list.",
+ "apihelp-query+deletedrevisions-param-prop": "Which properties to get:\n;revid:Adds the revision ID of the deleted revision.\n;parentid:Adds the revision ID of the previous revision to the page.\n;user:Adds the user who made the revision.\n;userid:Adds the user ID who made the revision.\n;comment:Adds the comment of the revision.\n;parsedcomment:Adds the parsed comment of the revision.\n;minor:Tags if the revision is minor.\n;len:Adds the length (bytes) of the revision.\n;sha1:Adds the SHA-1 (base 16) of the revision.\n;content:Adds the content of the revision.\n;tags:Tags for the revision.",
+ "apihelp-query+deletedrevisions-example-titles": "List the deleted revisions of [[Main Page]] and [[Talk:Main Page]], with content",
+ "apihelp-query+deletedrevisions-example-revids": "List the information for deleted revision 123456",
+
"apihelp-query+deletedrevs-description": "List deleted revisions.\n\nOperates in three modes:\n# List deleted revisions for the given titles, sorted by timestamp.\n# List deleted contributions for the given user, sorted by timestamp (no titles specified).\n# List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, $1user not set).\n\nCertain parameters only apply to some modes and are ignored in others.",
"apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Mode|Modes}}: $2",
"apihelp-query+deletedrevs-param-start": "The timestamp to start enumerating from.",
"apihelp-query+revisions-description": "Get revision information.\n\nMay be used in several ways:\n# Get data about a set of pages (last revision), by setting titles or pageids.\n# Get revisions for one given page, by using titles or pageids with start, end, or limit.\n# Get data about a set of revisions by setting their IDs with revids.",
"apihelp-query+revisions-paraminfo-singlepageonly": "May only be used with a single page (mode #2).",
- "apihelp-query+revisions-param-prop": "Which properties to get for each revision:\n;ids:The ID of the revision.\n;flags:Revision flags (minor).\n;timestamp:The timestamp of the revision.\n;user:User that made the revision.\n;userid:User ID of revision creator.\n;size:Length (bytes) of the revision.\n;sha1:SHA-1 (base 16) of the revision.\n;contentmodel:Content model ID.\n;comment:Comment by the user for revision.\n;parsedcomment:Parsed comment by the user for the revision.\n;content:Text of the revision.\n;tags:Tags for the revision.",
- "apihelp-query+revisions-param-limit": "Limit how many revisions will be returned.",
"apihelp-query+revisions-param-startid": "From which revision ID to start enumeration.",
"apihelp-query+revisions-param-endid": "Stop revision enumeration on this revision ID.",
"apihelp-query+revisions-param-start": "From which revision timestamp to start enumeration.",
"apihelp-query+revisions-param-user": "Only include revisions made by user.",
"apihelp-query+revisions-param-excludeuser": "Exclude revisions made by user.",
"apihelp-query+revisions-param-tag": "Only list revisions tagged with this tag.",
- "apihelp-query+revisions-param-expandtemplates": "Expand templates in revision content (requires $1prop=content).",
- "apihelp-query+revisions-param-generatexml": "Generate XML parse tree for revision content (requires $1prop=content).",
- "apihelp-query+revisions-param-parse": "Parse revision content (requires $1prop=content). For performance reasons, if this option is used, $1limit is enforced to 1.",
- "apihelp-query+revisions-param-section": "Only retrieve the content of this section number.",
"apihelp-query+revisions-param-token": "Which tokens to obtain for each revision.",
- "apihelp-query+revisions-param-diffto": "Revision ID to diff each revision to. Use \"prev\", \"next\" and \"cur\" for the previous, next and current revision respectively.",
- "apihelp-query+revisions-param-difftotext": "Text to diff each revision to. Only diffs a limited number of revisions. Overrides $1diffto. If $1section is set, only that section will be diffed against this text.",
- "apihelp-query+revisions-param-contentformat": "Serialization format used for $1difftotext and expected for output of content.",
"apihelp-query+revisions-example-content": "Get data with content for the last revision of titles \"API\" and \"Main Page\"",
"apihelp-query+revisions-example-last5": "Get last 5 revisions of the \"Main Page\"",
"apihelp-query+revisions-example-first5": "Get first 5 revisions of the \"Main Page\"",
"apihelp-query+revisions-example-first5-not-localhost": "Get first 5 revisions of the \"Main Page\" that were not made made by anonymous user \"127.0.0.1\"",
"apihelp-query+revisions-example-first5-user": "Get first 5 revisions of the \"Main Page\" that were made by the user \"MediaWiki default\"",
+ "apihelp-query+revisions+base-param-prop": "Which properties to get for each revision:\n;ids:The ID of the revision.\n;flags:Revision flags (minor).\n;timestamp:The timestamp of the revision.\n;user:User that made the revision.\n;userid:User ID of the revision creator.\n;size:Length (bytes) of the revision.\n;sha1:SHA-1 (base 16) of the revision.\n;contentmodel:Content model ID of the revision.\n;comment:Comment by the user for the revision.\n;parsedcomment:Parsed comment by the user for the revision.\n;content:Text of the revision.\n;tags:Tags for the revision.",
+ "apihelp-query+revisions+base-param-limit": "Limit how many revisions will be returned.",
+ "apihelp-query+revisions+base-param-expandtemplates": "Expand templates in revision content (requires $1prop=content).",
+ "apihelp-query+revisions+base-param-generatexml": "Generate XML parse tree for revision content (requires $1prop=content).",
+ "apihelp-query+revisions+base-param-parse": "Parse revision content (requires $1prop=content). For performance reasons, if this option is used, $1limit is enforced to 1.",
+ "apihelp-query+revisions+base-param-section": "Only retrieve the content of this section number.",
+ "apihelp-query+revisions+base-param-diffto": "Revision ID to diff each revision to. Use \"prev\", \"next\" and \"cur\" for the previous, next and current revision respectively.",
+ "apihelp-query+revisions+base-param-difftotext": "Text to diff each revision to. Only diffs a limited number of revisions. Overrides $1diffto. If $1section is set, only that section will be diffed against this text",
+ "apihelp-query+revisions+base-param-contentformat": "Serialization format used for $1difftotext and expected for output of content.",
+
"apihelp-query+search-description": "Perform a full text search.",
"apihelp-query+search-param-search": "Search for all page titles (or content) that have this value.",
"apihelp-query+search-param-namespace": "Search only within these namespaces.",
"apihelp-query+allcategories-param-prop": "{{doc-apihelp-param|query+allcategories|prop}}",
"apihelp-query+allcategories-example-size": "{{doc-apihelp-example|query+allcategories}}",
"apihelp-query+allcategories-example-generator": "{{doc-apihelp-example|query+allcategories}}",
+
+ "apihelp-query+alldeletedrevisions-description": "{{doc-apihelp-description|query+alldeletedrevisions}}",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "{{doc-apihelp-paraminfo|query+alldeletedrevisions|nonuseronly}}",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "{{doc-apihelp-paraminfo|query+alldeletedrevisions|useronly}}",
+ "apihelp-query+alldeletedrevisions-param-end": "{{doc-apihelp-param|query+alldeletedrevisions|end}}",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "{{doc-apihelp-param|query+alldeletedrevisions|excludeuser}}",
+ "apihelp-query+alldeletedrevisions-param-from": "{{doc-apihelp-param|query+alldeletedrevisions|from}}",
+ "apihelp-query+alldeletedrevisions-param-generatetitles": "{{doc-apihelp-param|query+alldeletedrevisions|generatetitles}}",
+ "apihelp-query+alldeletedrevisions-param-miser-user-namespace": "{{doc-apihelp-param|query+alldeletedrevisions|miser-user-namespace}}",
+ "apihelp-query+alldeletedrevisions-param-namespace": "{{doc-apihelp-param|query+alldeletedrevisions|namespace}}",
+ "apihelp-query+alldeletedrevisions-param-prefix": "{{doc-apihelp-param|query+alldeletedrevisions|prefix}}",
+ "apihelp-query+alldeletedrevisions-param-start": "{{doc-apihelp-param|query+alldeletedrevisions|start}}",
+ "apihelp-query+alldeletedrevisions-param-tag": "{{doc-apihelp-param|query+alldeletedrevisions|tag}}",
+ "apihelp-query+alldeletedrevisions-param-to": "{{doc-apihelp-param|query+alldeletedrevisions|to}}",
+ "apihelp-query+alldeletedrevisions-param-user": "{{doc-apihelp-param|query+alldeletedrevisions|user}}",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "{{doc-apihelp-example|query+alldeletedrevisions}}",
+ "apihelp-query+alldeletedrevisions-example-user": "{{doc-apihelp-example|query+alldeletedrevisions}}",
+
"apihelp-query+allfileusages-description": "{{doc-apihelp-description|query+allfileusages}}",
"apihelp-query+allfileusages-param-from": "{{doc-apihelp-param|query+allfileusages|from}}",
"apihelp-query+allfileusages-param-to": "{{doc-apihelp-param|query+allfileusages|to}}",
"apihelp-query+contributors-param-excluderights": "{{doc-apihelp-param|query+contributors|excluderights}}",
"apihelp-query+contributors-param-limit": "{{doc-apihelp-param|query+contributors|limit}}",
"apihelp-query+contributors-example-simple": "{{doc-apihelp-example|query+contributors}}",
+
+ "apihelp-query+deletedrevisions-description": "{{doc-apihelp-description|query+deletedrevisions}}",
+ "apihelp-query+deletedrevisions-param-end": "{{doc-apihelp-param|query+deletedrevisions|end}}",
+ "apihelp-query+deletedrevisions-param-excludeuser": "{{doc-apihelp-param|query+deletedrevisions|excludeuser}}",
+ "apihelp-query+deletedrevisions-param-limit": "{{doc-apihelp-param|query+deletedrevisions|limit}}",
+ "apihelp-query+deletedrevisions-param-prop": "{{doc-apihelp-param|query+deletedrevisions|prop}}",
+ "apihelp-query+deletedrevisions-param-start": "{{doc-apihelp-param|query+deletedrevisions|start}}",
+ "apihelp-query+deletedrevisions-param-tag": "{{doc-apihelp-param|query+deletedrevisions|tag}}",
+ "apihelp-query+deletedrevisions-param-user": "{{doc-apihelp-param|query+deletedrevisions|user}}",
+ "apihelp-query+deletedrevisions-example-revids": "{{doc-apihelp-example|query+deletedrevisions}}",
+ "apihelp-query+deletedrevisions-example-titles": "{{doc-apihelp-example|query+deletedrevisions}}",
+
"apihelp-query+deletedrevs-description": "{{doc-apihelp-description|query+deletedrevs}}",
"apihelp-query+deletedrevs-paraminfo-modes": "{{doc-apihelp-paraminfo|query+deletedrevs|modes}}\n{{Identical|Mode}}",
"apihelp-query+deletedrevs-param-start": "{{doc-apihelp-param|query+deletedrevs|start}}",
"apihelp-query+redirects-example-generator": "{{doc-apihelp-example|query+redirects}}",
"apihelp-query+revisions-description": "{{doc-apihelp-description|query+revisions}}",
"apihelp-query+revisions-paraminfo-singlepageonly": "{{doc-apihelp-paraminfo|query+revisions|singlepageonly}}",
- "apihelp-query+revisions-param-prop": "{{doc-apihelp-param|query+revisions|prop}}",
- "apihelp-query+revisions-param-limit": "{{doc-apihelp-param|query+revisions|limit}}",
"apihelp-query+revisions-param-startid": "{{doc-apihelp-param|query+revisions|startid}}",
"apihelp-query+revisions-param-endid": "{{doc-apihelp-param|query+revisions|endid}}",
"apihelp-query+revisions-param-start": "{{doc-apihelp-param|query+revisions|start}}",
"apihelp-query+revisions-param-user": "{{doc-apihelp-param|query+revisions|user}}",
"apihelp-query+revisions-param-excludeuser": "{{doc-apihelp-param|query+revisions|excludeuser}}",
"apihelp-query+revisions-param-tag": "{{doc-apihelp-param|query+revisions|tag}}",
- "apihelp-query+revisions-param-expandtemplates": "{{doc-apihelp-param|query+revisions|expandtemplates}}",
- "apihelp-query+revisions-param-generatexml": "{{doc-apihelp-param|query+revisions|generatexml}}",
- "apihelp-query+revisions-param-parse": "{{doc-apihelp-param|query+revisions|parse}}",
- "apihelp-query+revisions-param-section": "{{doc-apihelp-param|query+revisions|section}}",
"apihelp-query+revisions-param-token": "{{doc-apihelp-param|query+revisions|token}}",
- "apihelp-query+revisions-param-diffto": "{{doc-apihelp-param|query+revisions|diffto}}",
- "apihelp-query+revisions-param-difftotext": "{{doc-apihelp-param|query+revisions|difftotext}}",
- "apihelp-query+revisions-param-contentformat": "{{doc-apihelp-param|query+revisions|contentformat}}",
"apihelp-query+revisions-example-content": "{{doc-apihelp-example|query+revisions}}",
"apihelp-query+revisions-example-last5": "{{doc-apihelp-example|query+revisions}}",
"apihelp-query+revisions-example-first5": "{{doc-apihelp-example|query+revisions}}",
"apihelp-query+revisions-example-first5-after": "{{doc-apihelp-example|query+revisions}}",
"apihelp-query+revisions-example-first5-not-localhost": "{{doc-apihelp-example|query+revisions}}",
"apihelp-query+revisions-example-first5-user": "{{doc-apihelp-example|query+revisions}}",
+
+ "apihelp-query+revisions+base-param-contentformat": "{{doc-apihelp-param|query+revisions+base|contentformat|description=the \"contentformat\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-diffto": "{{doc-apihelp-param|query+revisions+base|diffto|description=the \"diffto\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-difftotext": "{{doc-apihelp-param|query+revisions+base|difftotext|description=the \"difftotext\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-expandtemplates": "{{doc-apihelp-param|query+revisions+base|expandtemplates|description=the \"expandtemplates\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-generatexml": "{{doc-apihelp-param|query+revisions+base|generatexml|description=the \"generatexml\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-limit": "{{doc-apihelp-param|query+revisions+base|limit|description=the \"limit\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-parse": "{{doc-apihelp-param|query+revisions+base|parse|description=the \"parse\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-prop": "{{doc-apihelp-param|query+revisions+base|prop|description=the \"prop\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-section": "{{doc-apihelp-param|query+revisions+base|section|description=the \"section\" parameter to revision querying modules|noseealso=1}}",
+
"apihelp-query+search-description": "{{doc-apihelp-description|query+search}}",
"apihelp-query+search-param-search": "{{doc-apihelp-param|query+search|search}}",
"apihelp-query+search-param-namespace": "{{doc-apihelp-param|query+search|namespace}}",