From: Brad Jorsch Date: Sun, 27 Sep 2015 19:43:05 +0000 (-0400) Subject: API: Add ApiQueryAllRevisions X-Git-Tag: 1.31.0-rc.0~9505^2 X-Git-Url: http://git.cyclocoop.org/ecrire?a=commitdiff_plain;h=2516ca03eda623ea4db358e665970f2c4ac26f3b;p=lhc%2Fweb%2Fwiklou.git API: Add ApiQueryAllRevisions Bug: T113885 Change-Id: I43bdc1e33945dab27466fc047d78af5e65df1740 --- diff --git a/RELEASE-NOTES-1.27 b/RELEASE-NOTES-1.27 index 65e07992b3..5d26ee995a 100644 --- a/RELEASE-NOTES-1.27 +++ b/RELEASE-NOTES-1.27 @@ -27,6 +27,7 @@ production. === Bug fixes in 1.27 === === Action API changes in 1.27 === +* Added list=allrevisions. === Action API internal changes in 1.27 === diff --git a/autoload.php b/autoload.php index 6820fc740d..dee87c0bca 100644 --- a/autoload.php +++ b/autoload.php @@ -74,6 +74,7 @@ $wgAutoloadLocalClasses = array( 'ApiQueryAllLinks' => __DIR__ . '/includes/api/ApiQueryAllLinks.php', 'ApiQueryAllMessages' => __DIR__ . '/includes/api/ApiQueryAllMessages.php', 'ApiQueryAllPages' => __DIR__ . '/includes/api/ApiQueryAllPages.php', + 'ApiQueryAllRevisions' => __DIR__ . '/includes/api/ApiQueryAllRevisions.php', 'ApiQueryAllUsers' => __DIR__ . '/includes/api/ApiQueryAllUsers.php', 'ApiQueryBacklinks' => __DIR__ . '/includes/api/ApiQueryBacklinks.php', 'ApiQueryBacklinksprop' => __DIR__ . '/includes/api/ApiQueryBacklinksprop.php', diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 3609a11909..902bca7c5f 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -76,6 +76,7 @@ class ApiQuery extends ApiBase { 'alllinks' => 'ApiQueryAllLinks', 'allpages' => 'ApiQueryAllPages', 'allredirects' => 'ApiQueryAllLinks', + 'allrevisions' => 'ApiQueryAllRevisions', 'alltransclusions' => 'ApiQueryAllLinks', 'allusers' => 'ApiQueryAllUsers', 'backlinks' => 'ApiQueryBacklinks', diff --git a/includes/api/ApiQueryAllRevisions.php b/includes/api/ApiQueryAllRevisions.php new file mode 100644 index 0000000000..e853cdc3e3 --- /dev/null +++ b/includes/api/ApiQueryAllRevisions.php @@ -0,0 +1,286 @@ +getDB(); + $params = $this->extractRequestParams( false ); + + $result = $this->getResult(); + + $this->requireMaxOneParameter( $params, 'user', 'excludeuser' ); + + // Namespace check is likely to be desired, but can't be done + // efficiently in SQL. + $miser_ns = null; + $needPageTable = false; + if ( $params['namespace'] !== null ) { + $params['namespace'] = array_unique( $params['namespace'] ); + sort( $params['namespace'] ); + if ( $params['namespace'] != MWNamespace::getValidNamespaces() ) { + $needPageTable = true; + if ( $this->getConfig()->get( 'MiserMode' ) ) { + $miser_ns = $params['namespace']; + } else { + $this->addWhere( array( 'page_namespace' => $params['namespace'] ) ); + } + } + } + + $this->addTables( 'revision' ); + if ( $resultPageSet === null ) { + $this->parseParameters( $params ); + $this->addTables( 'page' ); + $this->addJoinConds( + array( 'page' => array( 'INNER JOIN', array( 'rev_page = page_id' ) ) ) + ); + $this->addFields( Revision::selectFields() ); + $this->addFields( Revision::selectPageFields() ); + + // Review this depeneding on the outcome of T113901 + $this->addOption( 'STRAIGHT_JOIN' ); + } else { + $this->limit = $this->getParameter( 'limit' ) ?: 10; + $this->addFields( array( 'rev_timestamp', 'rev_id' ) ); + if ( $params['generatetitles'] ) { + $this->addFields( array( 'rev_page' ) ); + } + + if ( $needPageTable ) { + $this->addTables( 'page' ); + $this->addJoinConds( + array( 'page' => array( 'INNER JOIN', array( 'rev_page = page_id' ) ) ) + ); + $this->addFieldsIf( array( 'page_namespace' ), (bool)$miser_ns ); + + // Review this depeneding on the outcome of T113901 + $this->addOption( 'STRAIGHT_JOIN' ); + } + } + + if ( $this->fld_tags ) { + $this->addTables( 'tag_summary' ); + $this->addJoinConds( + array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) + ); + $this->addFields( 'ts_tags' ); + } + + if ( $this->fetchContent ) { + $this->addTables( 'text' ); + $this->addJoinConds( + array( 'text' => array( 'INNER JOIN', array( 'rev_text_id=old_id' ) ) ) + ); + $this->addFields( 'old_id' ); + $this->addFields( Revision::selectTextFields() ); + } + + if ( $params['user'] !== null ) { + $id = User::idFromName( $params['user'] ); + if ( $id ) { + $this->addWhereFld( 'rev_user', $id ); + } else { + $this->addWhereFld( 'rev_user_text', $params['user'] ); + } + } elseif ( $params['excludeuser'] !== null ) { + $id = User::idFromName( $params['excludeuser'] ); + if ( $id ) { + $this->addWhere( 'rev_user != ' . $id ); + } else { + $this->addWhere( 'rev_user_text != ' . $db->addQuotes( $params['excludeuser'] ) ); + } + } + + if ( $params['user'] !== null || $params['excludeuser'] !== null ) { + // Paranoia: avoid brute force searches (bug 17342) + if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) { + $bitmask = Revision::DELETED_USER; + } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) { + $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; + } else { + $bitmask = 0; + } + if ( $bitmask ) { + $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" ); + } + } + + $dir = $params['dir']; + + if ( $params['continue'] !== null ) { + $op = ( $dir == 'newer' ? '>' : '<' ); + $cont = explode( '|', $params['continue'] ); + $this->dieContinueUsageIf( count( $cont ) != 2 ); + $ts = $db->addQuotes( $db->timestamp( $cont[0] ) ); + $rev_id = (int)$cont[1]; + $this->dieContinueUsageIf( strval( $rev_id ) !== $cont[1] ); + $this->addWhere( "rev_timestamp $op $ts OR " . + "(rev_timestamp = $ts AND " . + "rev_id $op= $rev_id)" ); + } + + $this->addOption( 'LIMIT', $this->limit + 1 ); + + $sort = ( $dir == 'newer' ? '' : ' DESC' ); + $orderby = array(); + // Targeting index rev_timestamp, user_timestamp, or usertext_timestamp + // But 'user' is always constant for the latter two, so it doesn't matter here. + $orderby[] = "rev_timestamp $sort"; + $orderby[] = "rev_id $sort"; + $this->addOption( 'ORDER BY', $orderby ); + + $res = $this->select( __METHOD__ ); + $pageMap = array(); // Maps rev_page to array index + $count = 0; + $nextIndex = 0; + $generated = array(); + foreach ( $res as $row ) { + if ( ++$count > $this->limit ) { + // We've had enough + $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" ); + break; + } + + // Miser mode namespace check + if ( $miser_ns !== null && !in_array( $row->page_namespace, $miser_ns ) ) { + continue; + } + + if ( $resultPageSet !== null ) { + if ( $params['generatetitles'] ) { + $generated[$row->rev_page] = $row->rev_page; + } else { + $generated[] = $row->rev_id; + } + } else { + $revision = Revision::newFromRow( $row ); + $rev = $this->extractRevisionInfo( $revision, $row ); + + if ( !isset( $pageMap[$row->rev_page] ) ) { + $index = $nextIndex++; + $pageMap[$row->rev_page] = $index; + $title = $revision->getTitle(); + $a = array( + 'pageid' => $title->getArticleID(), + 'revisions' => array( $rev ), + ); + ApiResult::setIndexedTagName( $a['revisions'], 'rev' ); + ApiQueryBase::addTitleInfo( $a, $title ); + $fit = $result->addValue( array( 'query', $this->getModuleName() ), $index, $a ); + } else { + $index = $pageMap[$row->rev_page]; + $fit = $result->addValue( + array( 'query', $this->getModuleName(), $index, 'revisions' ), + null, $rev ); + } + if ( !$fit ) { + $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" ); + break; + } + } + } + + if ( $resultPageSet !== null ) { + if ( $params['generatetitles'] ) { + $resultPageSet->populateFromPageIDs( $generated ); + } else { + $resultPageSet->populateFromRevisionIDs( $generated ); + } + } else { + $result->addIndexedTagName( 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', + ), + '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', + ), + 'excludeuser' => array( + ApiBase::PARAM_TYPE => 'user', + ), + 'continue' => array( + ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', + ), + 'generatetitles' => array( + ApiBase::PARAM_DFLT => false, + ), + ); + + if ( $this->getConfig()->get( 'MiserMode' ) ) { + $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = array( + 'api-help-param-limited-in-miser-mode', + ); + } + + return $ret; + } + + protected function getExamplesMessages() { + return array( + 'action=query&list=allrevisions&arvuser=Example&arvlimit=50' + => 'apihelp-query+allrevisions-example-user', + 'action=query&list=allrevisions&arvdir=newer&arvlimit=50' + => 'apihelp-query+allrevisions-example-ns-main', + ); + } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Allrevisions'; + } +} diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 30182c1805..64b56f02e2 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -499,6 +499,16 @@ "apihelp-query+allredirects-example-unique-generator": "Gets all target pages, marking the missing ones.", "apihelp-query+allredirects-example-generator": "Gets pages containing the redirects.", + "apihelp-query+allrevisions-description": "List all revisions.", + "apihelp-query+allrevisions-param-start": "The timestamp to start enumerating from.", + "apihelp-query+allrevisions-param-end": "The timestamp to stop enumerating at.", + "apihelp-query+allrevisions-param-user": "Only list revisions by this user.", + "apihelp-query+allrevisions-param-excludeuser": "Don't list revisions by this user.", + "apihelp-query+allrevisions-param-namespace": "Only list pages in this namespace.", + "apihelp-query+allrevisions-param-generatetitles": "When being used as a generator, generate titles rather than revision IDs.", + "apihelp-query+allrevisions-example-user": "List the last 50 contributions by user Example.", + "apihelp-query+allrevisions-example-ns-main": "List the first 50 revisions in the main namespace.", + "apihelp-query+alltransclusions-description": "List all transclusions (pages embedded using {{x}}), including non-existing.", "apihelp-query+alltransclusions-param-from": "The title of the transclusion to start enumerating from.", "apihelp-query+alltransclusions-param-to": "The title of the transclusion to stop enumerating at.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index b55faf4cc4..0d0930db65 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -469,6 +469,15 @@ "apihelp-query+allredirects-example-unique": "{{doc-apihelp-example|query+allredirects}}", "apihelp-query+allredirects-example-unique-generator": "{{doc-apihelp-example|query+allredirects}}", "apihelp-query+allredirects-example-generator": "{{doc-apihelp-example|query+allredirects}}", + "apihelp-query+allrevisions-description": "{{apihelp-description|query+allrevisions}}", + "apihelp-query+allrevisions-param-end": "{{apihelp-param|query+allrevisions|end}}", + "apihelp-query+allrevisions-param-excludeuser": "{{apihelp-param|query+allrevisions|excludeuser}}", + "apihelp-query+allrevisions-param-generatetitles": "{{apihelp-param|query+allrevisions|generatetitles}}", + "apihelp-query+allrevisions-param-namespace": "{{apihelp-param|query+allrevisions|namespace}}", + "apihelp-query+allrevisions-param-start": "{{apihelp-param|query+allrevisions|start}}", + "apihelp-query+allrevisions-param-user": "{{apihelp-param|query+allrevisions|user}}", + "apihelp-query+allrevisions-example-ns-main": "{{apihelp-example|query+allrevisions}}", + "apihelp-query+allrevisions-example-user": "{{apihelp-example|query+allrevisions}}", "apihelp-query+alltransclusions-description": "{{doc-apihelp-description|query+alltransclusions}}", "apihelp-query+alltransclusions-param-from": "{{doc-apihelp-param|query+alltransclusions|from}}", "apihelp-query+alltransclusions-param-to": "{{doc-apihelp-param|query+alltransclusions|to}}",