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