MediaWiki 1.43.1
ApiQueryDeletedRevisions.php
Go to the documentation of this file.
1<?php
26namespace MediaWiki\Api;
27
45
52
53 private RevisionStore $revisionStore;
54 private NameTableStore $changeTagDefStore;
55 private LinkBatchFactory $linkBatchFactory;
56
57 public function __construct(
58 ApiQuery $query,
59 string $moduleName,
60 RevisionStore $revisionStore,
61 IContentHandlerFactory $contentHandlerFactory,
62 ParserFactory $parserFactory,
63 SlotRoleRegistry $slotRoleRegistry,
64 NameTableStore $changeTagDefStore,
65 LinkBatchFactory $linkBatchFactory,
66 ContentRenderer $contentRenderer,
67 ContentTransformer $contentTransformer,
68 CommentFormatter $commentFormatter,
69 TempUserCreator $tempUserCreator,
70 UserFactory $userFactory
71 ) {
72 parent::__construct(
73 $query,
74 $moduleName,
75 'drv',
76 $revisionStore,
77 $contentHandlerFactory,
78 $parserFactory,
79 $slotRoleRegistry,
80 $contentRenderer,
81 $contentTransformer,
82 $commentFormatter,
83 $tempUserCreator,
84 $userFactory
85 );
86 $this->revisionStore = $revisionStore;
87 $this->changeTagDefStore = $changeTagDefStore;
88 $this->linkBatchFactory = $linkBatchFactory;
89 }
90
91 protected function run( ?ApiPageSet $resultPageSet = null ) {
92 $pageSet = $this->getPageSet();
93 $pageMap = $pageSet->getGoodAndMissingTitlesByNamespace();
94 $pageCount = count( $pageSet->getGoodAndMissingPages() );
95 $revCount = $pageSet->getRevisionCount();
96 if ( $revCount === 0 && $pageCount === 0 ) {
97 // Nothing to do
98 return;
99 }
100 if ( $revCount !== 0 && count( $pageSet->getDeletedRevisionIDs() ) === 0 ) {
101 // Nothing to do, revisions were supplied but none are deleted
102 return;
103 }
104
105 $params = $this->extractRequestParams( false );
106
107 $db = $this->getDB();
108
109 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
110
111 if ( $resultPageSet === null ) {
112 $this->parseParameters( $params );
113 $arQuery = $this->revisionStore->getArchiveQueryInfo();
114 $this->addTables( $arQuery['tables'] );
115 $this->addFields( $arQuery['fields'] );
116 $this->addJoinConds( $arQuery['joins'] );
117 $this->addFields( [ 'ar_title', 'ar_namespace' ] );
118 } else {
119 $this->limit = $this->getParameter( 'limit' ) ?: 10;
120 $this->addTables( 'archive' );
121 $this->addFields( [ 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id' ] );
122 }
123
124 if ( $this->fld_tags ) {
125 $this->addFields( [
126 'ts_tags' => MediaWikiServices::getInstance()->getChangeTagsStore()
127 ->makeTagSummarySubquery( 'archive' )
128 ] );
129 }
130
131 if ( $params['tag'] !== null ) {
132 $this->addTables( 'change_tag' );
133 $this->addJoinConds(
134 [ 'change_tag' => [ 'JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ]
135 );
136 try {
137 $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $params['tag'] ) );
138 } catch ( NameTableAccessException $exception ) {
139 // Return nothing.
140 $this->addWhere( '1=0' );
141 }
142 }
143
144 // This means stricter restrictions
145 if ( ( $this->fld_comment || $this->fld_parsedcomment ) &&
146 !$this->getAuthority()->isAllowed( 'deletedhistory' )
147 ) {
148 $this->dieWithError( 'apierror-cantview-deleted-comment', 'permissiondenied' );
149 }
150 if ( $this->fetchContent && !$this->getAuthority()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
151 $this->dieWithError( 'apierror-cantview-deleted-revision-content', 'permissiondenied' );
152 }
153
154 $dir = $params['dir'];
155
156 if ( $revCount !== 0 ) {
157 $this->addWhere( [
158 'ar_rev_id' => array_keys( $pageSet->getDeletedRevisionIDs() )
159 ] );
160 } else {
161 // We need a custom WHERE clause that matches all titles.
162 $lb = $this->linkBatchFactory->newLinkBatch( $pageSet->getGoodAndMissingPages() );
163 $where = $lb->constructSet( 'ar', $db );
164 $this->addWhere( $where );
165 }
166
167 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
168 // In the non-generator case, the actor join will already be present.
169 if ( $resultPageSet !== null ) {
170 $this->addTables( 'actor' );
171 $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=ar_actor' ] ] );
172 }
173 if ( $params['user'] !== null ) {
174 $this->addWhereFld( 'actor_name', $params['user'] );
175 } elseif ( $params['excludeuser'] !== null ) {
176 $this->addWhere( $db->expr( 'actor_name', '!=', $params['excludeuser'] ) );
177 }
178 }
179
180 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
181 // Paranoia: avoid brute force searches (T19342)
182 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
183 $bitmask = RevisionRecord::DELETED_USER;
184 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
185 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
186 } else {
187 $bitmask = 0;
188 }
189 if ( $bitmask ) {
190 $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
191 }
192 }
193
194 if ( $params['continue'] !== null ) {
195 $op = ( $dir == 'newer' ? '>=' : '<=' );
196 if ( $revCount !== 0 ) {
197 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'int' ] );
198 $this->addWhere( $db->buildComparison( $op, [
199 'ar_rev_id' => $cont[0],
200 'ar_id' => $cont[1],
201 ] ) );
202 } else {
203 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'string', 'timestamp', 'int' ] );
204 $this->addWhere( $db->buildComparison( $op, [
205 'ar_namespace' => $cont[0],
206 'ar_title' => $cont[1],
207 'ar_timestamp' => $db->timestamp( $cont[2] ),
208 'ar_id' => $cont[3],
209 ] ) );
210 }
211 }
212
213 $this->addOption( 'LIMIT', $this->limit + 1 );
214
215 if ( $revCount !== 0 ) {
216 // Sort by ar_rev_id when querying by ar_rev_id
217 $this->addWhereRange( 'ar_rev_id', $dir, null, null );
218 } else {
219 // Sort by ns and title in the same order as timestamp for efficiency
220 // But only when not already unique in the query
221 if ( count( $pageMap ) > 1 ) {
222 $this->addWhereRange( 'ar_namespace', $dir, null, null );
223 }
224 $oneTitle = key( reset( $pageMap ) );
225 foreach ( $pageMap as $pages ) {
226 if ( count( $pages ) > 1 || key( $pages ) !== $oneTitle ) {
227 $this->addWhereRange( 'ar_title', $dir, null, null );
228 break;
229 }
230 }
231 $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
232 }
233 // Include in ORDER BY for uniqueness
234 $this->addWhereRange( 'ar_id', $dir, null, null );
235
236 $res = $this->select( __METHOD__ );
237 $count = 0;
238 $generated = [];
239 foreach ( $res as $row ) {
240 if ( ++$count > $this->limit ) {
241 // We've had enough
242 $this->setContinueEnumParameter( 'continue',
243 $revCount
244 ? "$row->ar_rev_id|$row->ar_id"
245 : "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
246 );
247 break;
248 }
249
250 if ( $resultPageSet !== null ) {
251 $generated[] = $row->ar_rev_id;
252 } else {
253 if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
254 // Was it converted?
255 $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
256 $converted = $pageSet->getConvertedTitles();
257 if ( $title && isset( $converted[$title->getPrefixedText()] ) ) {
258 $title = Title::newFromText( $converted[$title->getPrefixedText()] );
259 if ( $title && isset( $pageMap[$title->getNamespace()][$title->getDBkey()] ) ) {
260 $pageMap[$row->ar_namespace][$row->ar_title] =
261 $pageMap[$title->getNamespace()][$title->getDBkey()];
262 }
263 }
264 }
265 if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
267 __METHOD__,
268 "Found row in archive (ar_id={$row->ar_id}) that didn't get processed by ApiPageSet"
269 );
270 }
271
272 $fit = $this->addPageSubItem(
273 $pageMap[$row->ar_namespace][$row->ar_title],
274 $this->extractRevisionInfo( $this->revisionStore->newRevisionFromArchiveRow( $row ), $row ),
275 'rev'
276 );
277 if ( !$fit ) {
278 $this->setContinueEnumParameter( 'continue',
279 $revCount
280 ? "$row->ar_rev_id|$row->ar_id"
281 : "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
282 );
283 break;
284 }
285 }
286 }
287
288 if ( $resultPageSet !== null ) {
289 $resultPageSet->populateFromRevisionIDs( $generated );
290 }
291 }
292
293 public function getAllowedParams() {
294 return parent::getAllowedParams() + [
295 'start' => [
296 ParamValidator::PARAM_TYPE => 'timestamp',
297 ],
298 'end' => [
299 ParamValidator::PARAM_TYPE => 'timestamp',
300 ],
301 'dir' => [
302 ParamValidator::PARAM_TYPE => [
303 'newer',
304 'older'
305 ],
306 ParamValidator::PARAM_DEFAULT => 'older',
307 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
309 'newer' => 'api-help-paramvalue-direction-newer',
310 'older' => 'api-help-paramvalue-direction-older',
311 ],
312 ],
313 'tag' => null,
314 'user' => [
315 ParamValidator::PARAM_TYPE => 'user',
316 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
317 ],
318 'excludeuser' => [
319 ParamValidator::PARAM_TYPE => 'user',
320 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
321 ],
322 'continue' => [
323 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
324 ],
325 ];
326 }
327
328 protected function getExamplesMessages() {
329 $title = Title::newMainPage();
330 $talkTitle = $title->getTalkPageIfDefined();
331 $examples = [
332 'action=query&prop=deletedrevisions&revids=123456'
333 => 'apihelp-query+deletedrevisions-example-revids',
334 ];
335
336 if ( $talkTitle ) {
337 $title = rawurlencode( $title->getPrefixedText() );
338 $talkTitle = rawurlencode( $talkTitle->getPrefixedText() );
339 $examples["action=query&prop=deletedrevisions&titles={$title}|{$talkTitle}&" .
340 'drvslots=*&drvprop=user|comment|content'] = 'apihelp-query+deletedrevisions-example-titles';
341 }
342
343 return $examples;
344 }
345
346 public function getHelpUrls() {
347 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Deletedrevisions';
348 }
349}
350
352class_alias( ApiQueryDeletedRevisions::class, 'ApiQueryDeletedRevisions' );
array $params
The job parameters.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1577
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1768
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:224
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:1025
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1820
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:184
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:973
This class contains a list of pages that the client has requested.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addPageSubItem( $pageId, $item, $elemname=null)
Same as addPageSubItems(), but one element of $data at a time.
select( $method, $extraQuery=[], ?array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, and an ORDER BY clause to sort in the right direction.
Query module to enumerate deleted revisions for pages.
getExamplesMessages()
Returns usage examples for this module.
__construct(ApiQuery $query, string $moduleName, RevisionStore $revisionStore, IContentHandlerFactory $contentHandlerFactory, ParserFactory $parserFactory, SlotRoleRegistry $slotRoleRegistry, NameTableStore $changeTagDefStore, LinkBatchFactory $linkBatchFactory, ContentRenderer $contentRenderer, ContentTransformer $contentTransformer, CommentFormatter $commentFormatter, TempUserCreator $tempUserCreator, UserFactory $userFactory)
getHelpUrls()
Return links to more detailed help pages about the module.
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
getPageSet()
Get the PageSet object to work on.
A base class for functions common to producing a list of revisions.
parseParameters( $params)
Parse the parameters into the various instance fields.
This is the main query class.
Definition ApiQuery.php:48
This is the main service interface for converting single-line comments from various DB comment fields...
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
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.
Exception representing a failure to look up a row from a name table.
Represents a title within MediaWiki.
Definition Title.php:78
Service for temporary user creation.
Creates User objects.
Service for formatting and validating API parameters.
addTables( $tables, $alias=null)
addWhere( $conds)
addJoinConds( $conds)
addFields( $fields)