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