3 * Copyright © 2015 Wikimedia Foundation and contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 namespace MediaWiki\Api
;
25 use MediaWiki\ChangeTags\ChangeTagsStore
;
26 use MediaWiki\CommentFormatter\CommentFormatter
;
27 use MediaWiki\Content\IContentHandlerFactory
;
28 use MediaWiki\Content\Renderer\ContentRenderer
;
29 use MediaWiki\Content\Transform\ContentTransformer
;
30 use MediaWiki\MainConfigNames
;
31 use MediaWiki\ParamValidator\TypeDef\UserDef
;
32 use MediaWiki\Parser\ParserFactory
;
33 use MediaWiki\Revision\RevisionRecord
;
34 use MediaWiki\Revision\RevisionStore
;
35 use MediaWiki\Revision\SlotRoleRegistry
;
36 use MediaWiki\Title\NamespaceInfo
;
37 use MediaWiki\Title\Title
;
38 use MediaWiki\User\ActorMigration
;
39 use MediaWiki\User\TempUser\TempUserCreator
;
40 use MediaWiki\User\UserFactory
;
41 use Wikimedia\ParamValidator\ParamValidator
;
44 * Query module to enumerate all revisions.
49 class ApiQueryAllRevisions
extends ApiQueryRevisionsBase
{
51 private RevisionStore
$revisionStore;
52 private ActorMigration
$actorMigration;
53 private NamespaceInfo
$namespaceInfo;
54 private ChangeTagsStore
$changeTagsStore;
56 public function __construct(
59 RevisionStore
$revisionStore,
60 IContentHandlerFactory
$contentHandlerFactory,
61 ParserFactory
$parserFactory,
62 SlotRoleRegistry
$slotRoleRegistry,
63 ActorMigration
$actorMigration,
64 NamespaceInfo
$namespaceInfo,
65 ChangeTagsStore
$changeTagsStore,
66 ContentRenderer
$contentRenderer,
67 ContentTransformer
$contentTransformer,
68 CommentFormatter
$commentFormatter,
69 TempUserCreator
$tempUserCreator,
70 UserFactory
$userFactory
77 $contentHandlerFactory,
86 $this->revisionStore
= $revisionStore;
87 $this->actorMigration
= $actorMigration;
88 $this->namespaceInfo
= $namespaceInfo;
89 $this->changeTagsStore
= $changeTagsStore;
93 * @param ApiPageSet|null $resultPageSet
96 protected function run( ?ApiPageSet
$resultPageSet = null ) {
98 $params = $this->extractRequestParams( false );
100 $result = $this->getResult();
102 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
104 $tsField = 'rev_timestamp';
106 $pageField = 'rev_page';
108 // Namespace check is likely to be desired, but can't be done
109 // efficiently in SQL.
111 $needPageTable = false;
112 if ( $params['namespace'] !== null ) {
113 $params['namespace'] = array_unique( $params['namespace'] );
114 sort( $params['namespace'] );
115 if ( $params['namespace'] != $this->namespaceInfo
->getValidNamespaces() ) {
116 $needPageTable = true;
117 if ( $this->getConfig()->get( MainConfigNames
::MiserMode
) ) {
118 $miser_ns = $params['namespace'];
120 $this->addWhere( [ 'page_namespace' => $params['namespace'] ] );
125 if ( $resultPageSet === null ) {
126 $this->parseParameters( $params );
127 $queryBuilder = $this->revisionStore
->newSelectQueryBuilder( $db )
130 $this->getQueryBuilder()->merge( $queryBuilder );
132 $this->limit
= $this->getParameter( 'limit' ) ?
: 10;
133 $this->addTables( [ 'revision' ] );
134 $this->addFields( [ 'rev_timestamp', 'rev_id' ] );
136 if ( $params['generatetitles'] ) {
137 $this->addFields( [ 'rev_page' ] );
140 if ( $params['user'] !== null ||
$params['excludeuser'] !== null ) {
141 $this->getQueryBuilder()->join( 'actor', 'actor_rev_user', 'actor_rev_user.actor_id = rev_actor' );
144 if ( $needPageTable ) {
145 $this->getQueryBuilder()->join( 'page', null, [ "$pageField = page_id" ] );
146 if ( (bool)$miser_ns ) {
147 $this->addFields( [ 'page_namespace' ] );
152 // Seems to be needed to avoid a planner bug (T113901)
153 $this->addOption( 'STRAIGHT_JOIN' );
155 $dir = $params['dir'];
156 $this->addTimestampWhereRange( $tsField, $dir, $params['start'], $params['end'] );
158 if ( $this->fld_tags
) {
160 'ts_tags' => $this->changeTagsStore
->makeTagSummarySubquery( 'revision' )
164 if ( $params['user'] !== null ) {
165 $actorQuery = $this->actorMigration
->getWhere( $db, 'rev_user', $params['user'] );
166 $this->addWhere( $actorQuery['conds'] );
167 } elseif ( $params['excludeuser'] !== null ) {
168 $actorQuery = $this->actorMigration
->getWhere( $db, 'rev_user', $params['excludeuser'] );
169 $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
172 if ( $params['user'] !== null ||
$params['excludeuser'] !== null ) {
173 // Paranoia: avoid brute force searches (T19342)
174 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
175 $bitmask = RevisionRecord
::DELETED_USER
;
176 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
177 $bitmask = RevisionRecord
::DELETED_USER | RevisionRecord
::DELETED_RESTRICTED
;
182 $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
186 if ( $params['continue'] !== null ) {
187 $op = ( $dir == 'newer' ?
'>=' : '<=' );
188 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
189 $this->addWhere( $db->buildComparison( $op, [
190 $tsField => $db->timestamp( $cont[0] ),
191 $idField => $cont[1],
195 $this->addOption( 'LIMIT', $this->limit +
1 );
197 $sort = ( $dir == 'newer' ?
'' : ' DESC' );
199 // Targeting index rev_timestamp, user_timestamp, usertext_timestamp, or actor_timestamp.
200 // But 'user' is always constant for the latter three, so it doesn't matter here.
201 $orderby[] = "rev_timestamp $sort";
202 $orderby[] = "rev_id $sort";
203 $this->addOption( 'ORDER BY', $orderby );
206 $res = $this->select( __METHOD__
, [], $hookData );
208 if ( $resultPageSet === null ) {
209 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__
);
212 $pageMap = []; // Maps rev_page to array index
216 foreach ( $res as $row ) {
217 if ( $count === 0 && $resultPageSet !== null ) {
218 // Set the non-continue since the list of all revisions is
219 // prone to having entries added at the start frequently.
220 $this->getContinuationManager()->addGeneratorNonContinueParam(
221 $this, 'continue', "$row->rev_timestamp|$row->rev_id"
224 if ( ++
$count > $this->limit
) {
226 $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" );
230 // Miser mode namespace check
231 if ( $miser_ns !== null && !in_array( $row->page_namespace
, $miser_ns ) ) {
235 if ( $resultPageSet !== null ) {
236 if ( $params['generatetitles'] ) {
237 $generated[$row->rev_page
] = $row->rev_page
;
239 $generated[] = $row->rev_id
;
242 $revision = $this->revisionStore
->newRevisionFromRow( $row, 0, Title
::newFromRow( $row ) );
243 $rev = $this->extractRevisionInfo( $revision, $row );
245 if ( !isset( $pageMap[$row->rev_page
] ) ) {
246 $index = $nextIndex++
;
247 $pageMap[$row->rev_page
] = $index;
248 $title = Title
::newFromLinkTarget( $revision->getPageAsLinkTarget() );
250 'pageid' => $title->getArticleID(),
251 'revisions' => [ $rev ],
253 ApiResult
::setIndexedTagName( $a['revisions'], 'rev' );
254 ApiQueryBase
::addTitleInfo( $a, $title );
255 $fit = $this->processRow( $row, $a['revisions'][0], $hookData ) &&
256 $result->addValue( [ 'query', $this->getModuleName() ], $index, $a );
258 $index = $pageMap[$row->rev_page
];
259 $fit = $this->processRow( $row, $rev, $hookData ) &&
260 $result->addValue( [ 'query', $this->getModuleName(), $index, 'revisions' ], null, $rev );
263 $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" );
269 if ( $resultPageSet !== null ) {
270 if ( $params['generatetitles'] ) {
271 $resultPageSet->populateFromPageIDs( $generated );
273 $resultPageSet->populateFromRevisionIDs( $generated );
276 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'page' );
280 public function getAllowedParams() {
281 $ret = parent
::getAllowedParams() +
[
283 ParamValidator
::PARAM_TYPE
=> 'user',
284 UserDef
::PARAM_ALLOWED_USER_TYPES
=> [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
285 UserDef
::PARAM_RETURN_OBJECT
=> true,
288 ParamValidator
::PARAM_ISMULTI
=> true,
289 ParamValidator
::PARAM_TYPE
=> 'namespace',
290 ParamValidator
::PARAM_DEFAULT
=> null,
293 ParamValidator
::PARAM_TYPE
=> 'timestamp',
296 ParamValidator
::PARAM_TYPE
=> 'timestamp',
299 ParamValidator
::PARAM_TYPE
=> [
303 ParamValidator
::PARAM_DEFAULT
=> 'older',
304 ApiBase
::PARAM_HELP_MSG
=> 'api-help-param-direction',
305 ApiBase
::PARAM_HELP_MSG_PER_VALUE
=> [
306 'newer' => 'api-help-paramvalue-direction-newer',
307 'older' => 'api-help-paramvalue-direction-older',
311 ParamValidator
::PARAM_TYPE
=> 'user',
312 UserDef
::PARAM_ALLOWED_USER_TYPES
=> [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
313 UserDef
::PARAM_RETURN_OBJECT
=> true,
316 ApiBase
::PARAM_HELP_MSG
=> 'api-help-param-continue',
318 'generatetitles' => [
319 ParamValidator
::PARAM_DEFAULT
=> false,
323 if ( $this->getConfig()->get( MainConfigNames
::MiserMode
) ) {
324 $ret['namespace'][ApiBase
::PARAM_HELP_MSG_APPEND
] = [
325 'api-help-param-limited-in-miser-mode',
332 protected function getExamplesMessages() {
334 'action=query&list=allrevisions&arvuser=Example&arvlimit=50'
335 => 'apihelp-query+allrevisions-example-user',
336 'action=query&list=allrevisions&arvdir=newer&arvlimit=50'
337 => 'apihelp-query+allrevisions-example-ns-any',
341 public function getHelpUrls() {
342 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allrevisions';
346 /** @deprecated class alias since 1.43 */
347 class_alias( ApiQueryAllRevisions
::class, 'ApiQueryAllRevisions' );