MediaWiki master
ApiQueryAllRevisions.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
28
36
37 private RevisionStore $revisionStore;
38 private ActorMigration $actorMigration;
39 private NamespaceInfo $namespaceInfo;
40 private ChangeTagsStore $changeTagsStore;
41
42 public function __construct(
43 ApiQuery $query,
44 string $moduleName,
45 RevisionStore $revisionStore,
46 IContentHandlerFactory $contentHandlerFactory,
47 ParserFactory $parserFactory,
48 SlotRoleRegistry $slotRoleRegistry,
49 ActorMigration $actorMigration,
50 NamespaceInfo $namespaceInfo,
51 ChangeTagsStore $changeTagsStore,
52 ContentRenderer $contentRenderer,
53 ContentTransformer $contentTransformer,
54 CommentFormatter $commentFormatter,
55 TempUserCreator $tempUserCreator,
56 UserFactory $userFactory
57 ) {
58 parent::__construct(
59 $query,
60 $moduleName,
61 'arv',
62 $revisionStore,
63 $contentHandlerFactory,
64 $parserFactory,
65 $slotRoleRegistry,
66 $contentRenderer,
67 $contentTransformer,
68 $commentFormatter,
69 $tempUserCreator,
70 $userFactory
71 );
72 $this->revisionStore = $revisionStore;
73 $this->actorMigration = $actorMigration;
74 $this->namespaceInfo = $namespaceInfo;
75 $this->changeTagsStore = $changeTagsStore;
76 }
77
82 protected function run( ?ApiPageSet $resultPageSet = null ) {
83 $db = $this->getDB();
84 $params = $this->extractRequestParams( false );
85
86 $result = $this->getResult();
87
88 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
89
90 $tsField = 'rev_timestamp';
91 $idField = 'rev_id';
92 $pageField = 'rev_page';
93
94 // Namespace check is likely to be desired, but can't be done
95 // efficiently in SQL.
96 $miser_ns = null;
97 $needPageTable = false;
98 if ( $params['namespace'] !== null ) {
99 $params['namespace'] = array_unique( $params['namespace'] );
100 sort( $params['namespace'] );
101 if ( $params['namespace'] != $this->namespaceInfo->getValidNamespaces() ) {
102 $needPageTable = true;
103 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
104 $miser_ns = $params['namespace'];
105 } else {
106 $this->addWhere( [ 'page_namespace' => $params['namespace'] ] );
107 }
108 }
109 }
110
111 if ( $resultPageSet === null ) {
112 $this->parseParameters( $params );
113 $queryBuilder = $this->revisionStore->newSelectQueryBuilder( $db )
114 ->joinComment()
115 ->joinPage();
116 $this->getQueryBuilder()->merge( $queryBuilder );
117 } else {
118 $this->limit = $this->getParameter( 'limit' ) ?: 10;
119 $this->addTables( [ 'revision' ] );
120 $this->addFields( [ 'rev_timestamp', 'rev_id' ] );
121
122 if ( $params['generatetitles'] ) {
123 $this->addFields( [ 'rev_page' ] );
124 }
125
126 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
127 $this->getQueryBuilder()->join( 'actor', 'actor_rev_user', 'actor_rev_user.actor_id = rev_actor' );
128 }
129
130 if ( $needPageTable ) {
131 $this->getQueryBuilder()->join( 'page', null, [ "$pageField = page_id" ] );
132 if ( (bool)$miser_ns ) {
133 $this->addFields( [ 'page_namespace' ] );
134 }
135 }
136 }
137
138 // Seems to be needed to avoid a planner bug (T113901)
139 $this->addOption( 'STRAIGHT_JOIN' );
140
141 $dir = $params['dir'];
142 $this->addTimestampWhereRange( $tsField, $dir, $params['start'], $params['end'] );
143
144 if ( $this->fld_tags ) {
145 $this->addFields( [
146 'ts_tags' => $this->changeTagsStore->makeTagSummarySubquery( 'revision' )
147 ] );
148 }
149
150 if ( $params['user'] !== null ) {
151 $actorQuery = $this->actorMigration->getWhere( $db, 'rev_user', $params['user'] );
152 $this->addWhere( $actorQuery['conds'] );
153 } elseif ( $params['excludeuser'] !== null ) {
154 $actorQuery = $this->actorMigration->getWhere( $db, 'rev_user', $params['excludeuser'] );
155 $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
156 }
157
158 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
159 // Paranoia: avoid brute force searches (T19342)
160 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
161 $bitmask = RevisionRecord::DELETED_USER;
162 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
163 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
164 } else {
165 $bitmask = 0;
166 }
167 if ( $bitmask ) {
168 $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
169 }
170 }
171
172 if ( $params['continue'] !== null ) {
173 $op = ( $dir == 'newer' ? '>=' : '<=' );
174 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
175 $this->addWhere( $db->buildComparison( $op, [
176 $tsField => $db->timestamp( $cont[0] ),
177 $idField => $cont[1],
178 ] ) );
179 }
180
181 $this->addOption( 'LIMIT', $this->limit + 1 );
182
183 $sort = ( $dir == 'newer' ? '' : ' DESC' );
184 $orderby = [];
185 // Targeting index rev_timestamp, user_timestamp, usertext_timestamp, or actor_timestamp.
186 // But 'user' is always constant for the latter three, so it doesn't matter here.
187 $orderby[] = "rev_timestamp $sort";
188 $orderby[] = "rev_id $sort";
189 $this->addOption( 'ORDER BY', $orderby );
190
191 $hookData = [];
192 $res = $this->select( __METHOD__, [], $hookData );
193
194 if ( $resultPageSet === null ) {
195 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__ );
196 }
197
198 $pageMap = []; // Maps rev_page to array index
199 $count = 0;
200 $nextIndex = 0;
201 $generated = [];
202 foreach ( $res as $row ) {
203 if ( $count === 0 && $resultPageSet !== null ) {
204 // Set the non-continue since the list of all revisions is
205 // prone to having entries added at the start frequently.
206 $this->getContinuationManager()->addGeneratorNonContinueParam(
207 $this, 'continue', "$row->rev_timestamp|$row->rev_id"
208 );
209 }
210 if ( ++$count > $this->limit ) {
211 // We've had enough
212 $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" );
213 break;
214 }
215
216 // Miser mode namespace check
217 if ( $miser_ns !== null && !in_array( $row->page_namespace, $miser_ns ) ) {
218 continue;
219 }
220
221 if ( $resultPageSet !== null ) {
222 if ( $params['generatetitles'] ) {
223 $generated[$row->rev_page] = $row->rev_page;
224 } else {
225 $generated[] = $row->rev_id;
226 }
227 } else {
228 $revision = $this->revisionStore->newRevisionFromRow( $row, 0, Title::newFromRow( $row ) );
229 $rev = $this->extractRevisionInfo( $revision, $row );
230
231 if ( !isset( $pageMap[$row->rev_page] ) ) {
232 $index = $nextIndex++;
233 $pageMap[$row->rev_page] = $index;
234 $title = Title::newFromPageIdentity( $revision->getPage() );
235 $a = [
236 'pageid' => $title->getArticleID(),
237 'revisions' => [ $rev ],
238 ];
239 ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
240 ApiQueryBase::addTitleInfo( $a, $title );
241 $fit = $this->processRow( $row, $a['revisions'][0], $hookData ) &&
242 $result->addValue( [ 'query', $this->getModuleName() ], $index, $a );
243 } else {
244 $index = $pageMap[$row->rev_page];
245 $fit = $this->processRow( $row, $rev, $hookData ) &&
246 $result->addValue( [ 'query', $this->getModuleName(), $index, 'revisions' ], null, $rev );
247 }
248 if ( !$fit ) {
249 $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" );
250 break;
251 }
252 }
253 }
254
255 if ( $resultPageSet !== null ) {
256 if ( $params['generatetitles'] ) {
257 $resultPageSet->populateFromPageIDs( $generated );
258 } else {
259 $resultPageSet->populateFromRevisionIDs( $generated );
260 }
261 } else {
262 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'page' );
263 }
264 }
265
267 public function getAllowedParams() {
268 $ret = parent::getAllowedParams() + [
269 'user' => [
270 ParamValidator::PARAM_TYPE => 'user',
271 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
272 UserDef::PARAM_RETURN_OBJECT => true,
273 ],
274 'namespace' => [
275 ParamValidator::PARAM_ISMULTI => true,
276 ParamValidator::PARAM_TYPE => 'namespace',
277 ParamValidator::PARAM_DEFAULT => null,
278 ],
279 'start' => [
280 ParamValidator::PARAM_TYPE => 'timestamp',
281 ],
282 'end' => [
283 ParamValidator::PARAM_TYPE => 'timestamp',
284 ],
285 'dir' => [
286 ParamValidator::PARAM_TYPE => [
287 'newer',
288 'older'
289 ],
290 ParamValidator::PARAM_DEFAULT => 'older',
291 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
293 'newer' => 'api-help-paramvalue-direction-newer',
294 'older' => 'api-help-paramvalue-direction-older',
295 ],
296 ],
297 'excludeuser' => [
298 ParamValidator::PARAM_TYPE => 'user',
299 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
300 UserDef::PARAM_RETURN_OBJECT => true,
301 ],
302 'continue' => [
303 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
304 ],
305 'generatetitles' => [
306 ParamValidator::PARAM_DEFAULT => false,
307 ],
308 ];
309
310 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
311 $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = [
312 'api-help-param-limited-in-miser-mode',
313 ];
314 }
315
316 return $ret;
317 }
318
320 protected function getExamplesMessages() {
321 return [
322 'action=query&list=allrevisions&arvuser=Example&arvlimit=50'
323 => 'apihelp-query+allrevisions-example-user',
324 'action=query&list=allrevisions&arvdir=newer&arvlimit=50'
325 => 'apihelp-query+allrevisions-example-ns-any',
326 ];
327 }
328
330 public function getHelpUrls() {
331 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allrevisions';
332 }
333}
334
336class_alias( ApiQueryAllRevisions::class, 'ApiQueryAllRevisions' );
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:543
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1696
getResult()
Get the result object.
Definition ApiBase.php:682
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:207
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:998
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition ApiBase.php:175
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:167
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:944
This class contains a list of pages that the client has requested.
Query module to enumerate all revisions.
run(?ApiPageSet $resultPageSet=null)
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
__construct(ApiQuery $query, string $moduleName, RevisionStore $revisionStore, IContentHandlerFactory $contentHandlerFactory, ParserFactory $parserFactory, SlotRoleRegistry $slotRoleRegistry, ActorMigration $actorMigration, NamespaceInfo $namespaceInfo, ChangeTagsStore $changeTagsStore, ContentRenderer $contentRenderer, ContentTransformer $contentTransformer, CommentFormatter $commentFormatter, TempUserCreator $tempUserCreator, UserFactory $userFactory)
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
select( $method, $extraQuery=[], ?array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
A base class for functions common to producing a list of revisions.
extractRevisionInfo(RevisionRecord $revision, $row)
Extract information from the RevisionRecord.
parseParameters( $params)
Parse the parameters into the various instance fields.
This is the main query class.
Definition ApiQuery.php:36
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Read-write access to the change_tags table.
This is the main service interface for converting single-line comments from various DB comment fields...
A class containing constants representing the names of configuration variables.
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
Type definition for user types.
Definition UserDef.php:27
Page revision base class.
Service for looking up page revisions.
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:69
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
Service for temporary user creation.
Create User objects.
Service for formatting and validating API parameters.
addTables( $tables, $alias=null)
addWhere( $conds)
addFields( $fields)