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