Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 408
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryDeletedrevs
0.00% covered (danger)
0.00%
0 / 407
0.00% covered (danger)
0.00%
0 / 6
8742
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 293
0.00% covered (danger)
0.00%
0 / 1
7482
 isDeprecated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 1
6
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com>
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use MediaWiki\ChangeTags\ChangeTagsStore;
12use MediaWiki\CommentFormatter\RowCommentFormatter;
13use MediaWiki\CommentStore\CommentStore;
14use MediaWiki\Page\LinkBatchFactory;
15use MediaWiki\ParamValidator\TypeDef\UserDef;
16use MediaWiki\Revision\RevisionRecord;
17use MediaWiki\Revision\RevisionStore;
18use MediaWiki\Revision\SlotRecord;
19use MediaWiki\Storage\NameTableAccessException;
20use MediaWiki\Storage\NameTableStore;
21use MediaWiki\Title\Title;
22use Wikimedia\ParamValidator\ParamValidator;
23use Wikimedia\ParamValidator\TypeDef\EnumDef;
24use Wikimedia\ParamValidator\TypeDef\IntegerDef;
25use Wikimedia\Rdbms\IExpression;
26use Wikimedia\Rdbms\LikeValue;
27use Wikimedia\Rdbms\Subquery;
28use Wikimedia\Timestamp\TimestampFormat as TS;
29
30/**
31 * Query module to enumerate all deleted revisions.
32 *
33 * @ingroup API
34 * @deprecated since 1.25
35 */
36class ApiQueryDeletedrevs extends ApiQueryBase {
37
38    private CommentStore $commentStore;
39    private RowCommentFormatter $commentFormatter;
40    private RevisionStore $revisionStore;
41    private NameTableStore $changeTagDefStore;
42    private ChangeTagsStore $changeTagsStore;
43    private LinkBatchFactory $linkBatchFactory;
44
45    public function __construct(
46        ApiQuery $query,
47        string $moduleName,
48        CommentStore $commentStore,
49        RowCommentFormatter $commentFormatter,
50        RevisionStore $revisionStore,
51        NameTableStore $changeTagDefStore,
52        ChangeTagsStore $changeTagsStore,
53        LinkBatchFactory $linkBatchFactory
54    ) {
55        parent::__construct( $query, $moduleName, 'dr' );
56        $this->commentStore = $commentStore;
57        $this->commentFormatter = $commentFormatter;
58        $this->revisionStore = $revisionStore;
59        $this->changeTagDefStore = $changeTagDefStore;
60        $this->changeTagsStore = $changeTagsStore;
61        $this->linkBatchFactory = $linkBatchFactory;
62    }
63
64    public function execute() {
65        // Before doing anything at all, let's check permissions
66        $this->checkUserRightsAny( 'deletedhistory' );
67
68        $this->addDeprecation( 'apiwarn-deprecation-deletedrevs', 'action=query&list=deletedrevs' );
69
70        $user = $this->getUser();
71        $db = $this->getDB();
72        $params = $this->extractRequestParams( false );
73        $prop = array_fill_keys( $params['prop'], true );
74        $fld_parentid = isset( $prop['parentid'] );
75        $fld_revid = isset( $prop['revid'] );
76        $fld_user = isset( $prop['user'] );
77        $fld_userid = isset( $prop['userid'] );
78        $fld_comment = isset( $prop['comment'] );
79        $fld_parsedcomment = isset( $prop['parsedcomment'] );
80        $fld_minor = isset( $prop['minor'] );
81        $fld_len = isset( $prop['len'] );
82        $fld_sha1 = isset( $prop['sha1'] );
83        $fld_content = isset( $prop['content'] );
84        $fld_token = isset( $prop['token'] );
85        $fld_tags = isset( $prop['tags'] );
86
87        // If we're in a mode that breaks the same-origin policy, no tokens can
88        // be obtained
89        if ( $this->lacksSameOriginSecurity() ||
90            // If user can't undelete, no tokens
91            !$this->getAuthority()->isAllowed( 'undelete' )
92        ) {
93            $fld_token = false;
94        }
95
96        $result = $this->getResult();
97        $pageSet = $this->getPageSet();
98        $titles = $pageSet->getPages();
99
100        // This module operates in three modes:
101        // 'revs': List deleted revs for certain titles (1)
102        // 'user': List deleted revs by a certain user (2)
103        // 'all': List all deleted revs in NS (3)
104        $mode = 'all';
105        if ( count( $titles ) > 0 ) {
106            $mode = 'revs';
107        } elseif ( $params['user'] !== null ) {
108            $mode = 'user';
109        }
110
111        if ( $mode == 'revs' || $mode == 'user' ) {
112            // Ignore namespace and unique due to inability to know whether they were purposely set
113            foreach ( [ 'from', 'to', 'prefix', /*'namespace', 'unique'*/ ] as $p ) {
114                if ( $params[$p] !== null ) {
115                    $this->dieWithError( [ 'apierror-deletedrevs-param-not-1-2', $p ], 'badparams' );
116                }
117            }
118        } else {
119            foreach ( [ 'start', 'end' ] as $p ) {
120                if ( $params[$p] !== null ) {
121                    $this->dieWithError( [ 'apierror-deletedrevs-param-not-3', $p ], 'badparams' );
122                }
123            }
124        }
125
126        if ( $params['user'] !== null && $params['excludeuser'] !== null ) {
127            $this->dieWithError( 'user and excludeuser cannot be used together', 'badparams' );
128        }
129
130        $arQuery = $this->revisionStore->getArchiveQueryInfo();
131        $this->addTables( $arQuery['tables'] );
132        $this->addFields( $arQuery['fields'] );
133        $this->addJoinConds( $arQuery['joins'] );
134        $this->addFields( [ 'ar_title', 'ar_namespace' ] );
135
136        if ( $fld_tags ) {
137            $this->addFields( [
138                'ts_tags' => $this->changeTagsStore->makeTagSummarySubquery( 'archive' )
139            ] );
140        }
141
142        if ( $fld_sha1 ) {
143            $pairExpr = $db->buildGroupConcat(
144                $db->buildConcat( [ 'sr.role_name', $db->addQuotes( ':' ), 'c.content_sha1' ] ),
145                ','
146            );
147            $sha1Subquery = $db->newSelectQueryBuilder()
148                ->select( [
149                    'ar_rev_id',
150                    'ar_deleted',
151                    'ar_slot_pairs' => $pairExpr,
152                ] )
153                ->from( 'archive' )
154                ->join( 'slots', 's', [ 'ar_rev_id = s.slot_revision_id' ] )
155                ->join( 'content', 'c', [ 's.slot_content_id = c.content_id' ] )
156                ->join( 'slot_roles', 'sr', [ 's.slot_role_id = sr.role_id' ] )
157                ->groupBy( [ 'ar_rev_id', 'ar_deleted' ] )
158                ->caller( __METHOD__ )
159                ->getSQL();
160
161            $this->addTables( [ 'arsha1' => new Subquery( $sha1Subquery ) ] );
162            $this->addFields( [
163                'ar_deleted' => 'arsha1.ar_deleted',
164                'ar_slot_pairs' => 'arsha1.ar_slot_pairs'
165            ] );
166            $this->addJoinConds( [ 'arsha1' => [ 'LEFT JOIN', [ 'ar_rev_id = arsha1.ar_rev_id' ] ] ] );
167        }
168
169        if ( $params['tag'] !== null ) {
170            $this->addTables( 'change_tag' );
171            $this->addJoinConds(
172                [ 'change_tag' => [ 'JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ]
173            );
174            try {
175                $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $params['tag'] ) );
176            } catch ( NameTableAccessException ) {
177                // Return nothing.
178                $this->addWhere( '1=0' );
179            }
180        }
181
182        // This means stricter restrictions
183        if ( $fld_content ) {
184            $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
185        }
186        // Check limits
187        $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1;
188        $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
189
190        $limit = $params['limit'];
191
192        if ( $limit == 'max' ) {
193            $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
194            $this->getResult()->addParsedLimit( $this->getModuleName(), $limit );
195        }
196
197        $limit = $this->getMain()->getParamValidator()->validateValue(
198            $this, 'limit', $limit, [
199                ParamValidator::PARAM_TYPE => 'limit',
200                IntegerDef::PARAM_MIN => 1,
201                IntegerDef::PARAM_MAX => $userMax,
202                IntegerDef::PARAM_MAX2 => $botMax,
203                IntegerDef::PARAM_IGNORE_RANGE => true,
204            ]
205        );
206
207        if ( $fld_token ) {
208            // Undelete tokens are identical for all pages, so we cache one here
209            $token = $user->getEditToken( '', $this->getMain()->getRequest() );
210        }
211
212        $dir = $params['dir'];
213
214        // We need a custom WHERE clause that matches all titles.
215        if ( $mode == 'revs' ) {
216            $lb = $this->linkBatchFactory->newLinkBatch( $titles );
217            $where = $lb->constructSet( 'ar', $db );
218            $this->addWhere( $where );
219        } elseif ( $mode == 'all' ) {
220            $this->addWhereFld( 'ar_namespace', $params['namespace'] );
221
222            $from = $params['from'] === null
223                ? null
224                : $this->titlePartToKey( $params['from'], $params['namespace'] );
225            $to = $params['to'] === null
226                ? null
227                : $this->titlePartToKey( $params['to'], $params['namespace'] );
228            $this->addWhereRange( 'ar_title', $dir, $from, $to );
229
230            if ( isset( $params['prefix'] ) ) {
231                $this->addWhere(
232                    $db->expr(
233                        'ar_title',
234                        IExpression::LIKE,
235                        new LikeValue(
236                            $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
237                            $db->anyString()
238                        )
239                    )
240                );
241            }
242        }
243
244        if ( $params['user'] !== null ) {
245            // We already join on actor due to getArchiveQueryInfo()
246            $this->addWhereFld( 'actor_name', $params['user'] );
247        } elseif ( $params['excludeuser'] !== null ) {
248            $this->addWhere( $db->expr( 'actor_name', '!=', $params['excludeuser'] ) );
249        }
250
251        if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
252            // Paranoia: avoid brute force searches (T19342)
253            // (shouldn't be able to get here without 'deletedhistory', but
254            // check it again just in case)
255            if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
256                $bitmask = RevisionRecord::DELETED_USER;
257            } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
258                $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
259            } else {
260                $bitmask = 0;
261            }
262            if ( $bitmask ) {
263                $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
264            }
265        }
266
267        if ( $params['continue'] !== null ) {
268            $op = ( $dir == 'newer' ? '>=' : '<=' );
269            if ( $mode == 'all' || $mode == 'revs' ) {
270                $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'string', 'timestamp', 'int' ] );
271                $this->addWhere( $db->buildComparison( $op, [
272                    'ar_namespace' => $cont[0],
273                    'ar_title' => $cont[1],
274                    'ar_timestamp' => $db->timestamp( $cont[2] ),
275                    'ar_id' => $cont[3],
276                ] ) );
277            } else {
278                $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
279                $this->addWhere( $db->buildComparison( $op, [
280                    'ar_timestamp' => $db->timestamp( $cont[0] ),
281                    'ar_id' => $cont[1],
282                ] ) );
283            }
284        }
285
286        $this->addOption( 'LIMIT', $limit + 1 );
287        if ( $mode == 'all' ) {
288            if ( $params['unique'] ) {
289                // @todo Does this work on non-MySQL?
290                $this->addOption( 'GROUP BY', 'ar_title' );
291            } else {
292                $sort = ( $dir == 'newer' ? '' : ' DESC' );
293                $this->addOption( 'ORDER BY', [
294                    'ar_title' . $sort,
295                    'ar_timestamp' . $sort,
296                    'ar_id' . $sort,
297                ] );
298            }
299        } else {
300            if ( $mode == 'revs' ) {
301                // Sort by ns and title in the same order as timestamp for efficiency
302                $this->addWhereRange( 'ar_namespace', $dir, null, null );
303                $this->addWhereRange( 'ar_title', $dir, null, null );
304            }
305            $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
306            // Include in ORDER BY for uniqueness
307            $this->addWhereRange( 'ar_id', $dir, null, null );
308        }
309        $res = $this->select( __METHOD__ );
310
311        $formattedComments = [];
312        if ( $fld_parsedcomment ) {
313            $formattedComments = $this->commentFormatter->formatItems(
314                $this->commentFormatter->rows( $res )
315                    ->indexField( 'ar_id' )
316                    ->commentKey( 'ar_comment' )
317                    ->namespaceField( 'ar_namespace' )
318                    ->titleField( 'ar_title' )
319            );
320        }
321
322        $pageMap = []; // Maps ns&title to (fake) pageid
323        $count = 0;
324        $newPageID = 0;
325        foreach ( $res as $row ) {
326            if ( ++$count > $limit ) {
327                // We've had enough
328                if ( $mode == 'all' || $mode == 'revs' ) {
329                    $this->setContinueEnumParameter( 'continue',
330                        "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
331                    );
332                } else {
333                    $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
334                }
335                break;
336            }
337
338            $rev = [];
339            $anyHidden = false;
340
341            $rev['timestamp'] = wfTimestamp( TS::ISO_8601, $row->ar_timestamp );
342            if ( $fld_revid ) {
343                $rev['revid'] = (int)$row->ar_rev_id;
344            }
345            if ( $fld_parentid && $row->ar_parent_id !== null ) {
346                $rev['parentid'] = (int)$row->ar_parent_id;
347            }
348            if ( $fld_user || $fld_userid ) {
349                if ( $row->ar_deleted & RevisionRecord::DELETED_USER ) {
350                    $rev['userhidden'] = true;
351                    $anyHidden = true;
352                }
353                if ( RevisionRecord::userCanBitfield(
354                    $row->ar_deleted,
355                    RevisionRecord::DELETED_USER,
356                    $user
357                ) ) {
358                    if ( $fld_user ) {
359                        $rev['user'] = $row->ar_user_text;
360                    }
361                    if ( $fld_userid ) {
362                        $rev['userid'] = (int)$row->ar_user;
363                    }
364                }
365            }
366
367            if ( $fld_comment || $fld_parsedcomment ) {
368                if ( $row->ar_deleted & RevisionRecord::DELETED_COMMENT ) {
369                    $rev['commenthidden'] = true;
370                    $anyHidden = true;
371                }
372                if ( RevisionRecord::userCanBitfield(
373                    $row->ar_deleted,
374                    RevisionRecord::DELETED_COMMENT,
375                    $user
376                ) ) {
377                    $comment = $this->commentStore->getComment( 'ar_comment', $row )->text;
378                    if ( $fld_comment ) {
379                        $rev['comment'] = $comment;
380                    }
381                    if ( $fld_parsedcomment ) {
382                        $rev['parsedcomment'] = $formattedComments[$row->ar_id];
383                    }
384                }
385            }
386
387            if ( $fld_minor ) {
388                $rev['minor'] = $row->ar_minor_edit == 1;
389            }
390            if ( $fld_len ) {
391                $rev['len'] = $row->ar_len;
392            }
393            if ( $fld_sha1 ) {
394                if ( $row->ar_deleted & RevisionRecord::DELETED_TEXT ) {
395                    $rev['sha1hidden'] = true;
396                    $anyHidden = true;
397                }
398                if ( RevisionRecord::userCanBitfield(
399                    $row->ar_deleted,
400                    RevisionRecord::DELETED_TEXT,
401                    $user
402                ) ) {
403                    if ( $row->ar_slot_pairs !== null ) {
404                        $combinedBase36 = '';
405                        if ( $row->ar_slot_pairs !== '' ) {
406                            $items = explode( ',', $row->ar_slot_pairs );
407                            $slotHashes = [];
408                            foreach ( $items as $item ) {
409                                $parts = explode( ':', $item );
410                                $slotHashes[$parts[0]] = $parts[1];
411                            }
412                            ksort( $slotHashes );
413
414                            $accu = null;
415                            foreach ( $slotHashes as $slotHash ) {
416                                $accu = $accu === null
417                                    ? $slotHash
418                                    : SlotRecord::base36Sha1( $accu . $slotHash );
419                            }
420                            $combinedBase36 = $accu ?? SlotRecord::base36Sha1( '' );
421                        }
422
423                        $rev['sha1'] = $combinedBase36 !== ''
424                            ? \Wikimedia\base_convert( $combinedBase36, 36, 16, 40 )
425                            : '';
426                    }
427                }
428            }
429            if ( $fld_content ) {
430                if ( $row->ar_deleted & RevisionRecord::DELETED_TEXT ) {
431                    $rev['texthidden'] = true;
432                    $anyHidden = true;
433                }
434                if ( RevisionRecord::userCanBitfield(
435                    $row->ar_deleted,
436                    RevisionRecord::DELETED_TEXT,
437                    $user
438                ) ) {
439                    ApiResult::setContentValue( $rev, 'text',
440                        $this->revisionStore->newRevisionFromArchiveRow( $row )
441                            ->getContent( SlotRecord::MAIN )->serialize() );
442                }
443            }
444
445            if ( $fld_tags ) {
446                if ( $row->ts_tags ) {
447                    $tags = explode( ',', $row->ts_tags );
448                    ApiResult::setIndexedTagName( $tags, 'tag' );
449                    $rev['tags'] = $tags;
450                } else {
451                    $rev['tags'] = [];
452                }
453            }
454
455            if ( $anyHidden && ( $row->ar_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
456                $rev['suppressed'] = true;
457            }
458
459            if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
460                $pageID = $newPageID++;
461                $pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
462                $a = [ 'revisions' => [ $rev ] ];
463                ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
464                $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
465                ApiQueryBase::addTitleInfo( $a, $title );
466                if ( $fld_token ) {
467                    // @phan-suppress-next-line PhanPossiblyUndeclaredVariable token is set when used
468                    $a['token'] = $token;
469                }
470                $fit = $result->addValue( [ 'query', $this->getModuleName() ], $pageID, $a );
471            } else {
472                $pageID = $pageMap[$row->ar_namespace][$row->ar_title];
473                $fit = $result->addValue(
474                    [ 'query', $this->getModuleName(), $pageID, 'revisions' ],
475                    null, $rev );
476            }
477            if ( !$fit ) {
478                if ( $mode == 'all' || $mode == 'revs' ) {
479                    $this->setContinueEnumParameter( 'continue',
480                        "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
481                    );
482                } else {
483                    $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
484                }
485                break;
486            }
487        }
488        $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'page' );
489    }
490
491    /** @inheritDoc */
492    public function isDeprecated() {
493        return true;
494    }
495
496    /** @inheritDoc */
497    public function getAllowedParams() {
498        $smallLimit = $this->getMain()->canApiHighLimits() ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_SML1;
499        return [
500            'start' => [
501                ParamValidator::PARAM_TYPE => 'timestamp',
502                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 1, 2 ] ],
503            ],
504            'end' => [
505                ParamValidator::PARAM_TYPE => 'timestamp',
506                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 1, 2 ] ],
507            ],
508            'dir' => [
509                ParamValidator::PARAM_TYPE => [
510                    'newer',
511                    'older'
512                ],
513                ParamValidator::PARAM_DEFAULT => 'older',
514                ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
515                ApiBase::PARAM_HELP_MSG_PER_VALUE => [
516                    'newer' => 'api-help-paramvalue-direction-newer',
517                    'older' => 'api-help-paramvalue-direction-older',
518                ],
519                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 1, 3 ] ],
520            ],
521            'from' => [
522                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
523            ],
524            'to' => [
525                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
526            ],
527            'prefix' => [
528                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
529            ],
530            'unique' => [
531                ParamValidator::PARAM_DEFAULT => false,
532                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
533            ],
534            'namespace' => [
535                ParamValidator::PARAM_TYPE => 'namespace',
536                ParamValidator::PARAM_DEFAULT => NS_MAIN,
537                ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
538            ],
539            'tag' => null,
540            'user' => [
541                ParamValidator::PARAM_TYPE => 'user',
542                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
543            ],
544            'excludeuser' => [
545                ParamValidator::PARAM_TYPE => 'user',
546                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
547            ],
548            'prop' => [
549                ParamValidator::PARAM_DEFAULT => 'user|comment',
550                ParamValidator::PARAM_TYPE => [
551                    'revid',
552                    'parentid',
553                    'user',
554                    'userid',
555                    'comment',
556                    'parsedcomment',
557                    'minor',
558                    'len',
559                    'sha1',
560                    'content',
561                    'token',
562                    'tags'
563                ],
564                ParamValidator::PARAM_ISMULTI => true,
565                ApiBase::PARAM_HELP_MSG_PER_VALUE => [
566                    'content' => [ 'apihelp-query+deletedrevs-paramvalue-prop-content', $smallLimit ],
567                ],
568                EnumDef::PARAM_DEPRECATED_VALUES => [
569                    'token' => true,
570                ],
571            ],
572            'limit' => [
573                ParamValidator::PARAM_DEFAULT => 10,
574                ParamValidator::PARAM_TYPE => 'limit',
575                IntegerDef::PARAM_MIN => 1,
576                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
577                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
578                ApiBase::PARAM_HELP_MSG => [ 'apihelp-query+deletedrevs-param-limit', $smallLimit ],
579            ],
580            'continue' => [
581                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
582            ],
583        ];
584    }
585
586    /** @inheritDoc */
587    protected function getExamplesMessages() {
588        $title = Title::newMainPage();
589        $talkTitle = $title->getTalkPageIfDefined();
590        $examples = [];
591
592        if ( $talkTitle ) {
593            $title = rawurlencode( $title->getPrefixedText() );
594            $talkTitle = rawurlencode( $talkTitle->getPrefixedText() );
595            $examples = [
596                "action=query&list=deletedrevs&titles={$title}|{$talkTitle}&" .
597                    'drprop=user|comment|content'
598                    => 'apihelp-query+deletedrevs-example-mode1',
599            ];
600        }
601
602        return array_merge( $examples, [
603            'action=query&list=deletedrevs&druser=Bob&drlimit=50'
604                => 'apihelp-query+deletedrevs-example-mode2',
605            'action=query&list=deletedrevs&drdir=newer&drlimit=50'
606                => 'apihelp-query+deletedrevs-example-mode3-main',
607            'action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique='
608                => 'apihelp-query+deletedrevs-example-mode3-talk',
609        ] );
610    }
611
612    /** @inheritDoc */
613    public function getHelpUrls() {
614        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Deletedrevs';
615    }
616}
617
618/** @deprecated class alias since 1.43 */
619class_alias( ApiQueryDeletedrevs::class, 'ApiQueryDeletedrevs' );