Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.93% covered (warning)
86.93%
306 / 352
50.00% covered (danger)
50.00%
5 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryWatchlist
86.93% covered (warning)
86.93%
306 / 352
50.00% covered (danger)
50.00%
5 / 10
134.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 executeGenerator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
88.30% covered (warning)
88.30%
83 / 94
0.00% covered (danger)
0.00%
0 / 1
40.31
 getFieldsToInclude
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
13.02
 showParamsConflicting
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
14
 extractOutputData
83.50% covered (warning)
83.50%
86 / 103
0.00% covered (danger)
0.00%
0 / 1
43.16
 getAllowedParams
100.00% covered (success)
100.00%
95 / 95
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23use MediaWiki\Cache\GenderCache;
24use MediaWiki\CommentFormatter\CommentFormatter;
25use MediaWiki\CommentStore\CommentStore;
26use MediaWiki\Linker\LinkTarget;
27use MediaWiki\ParamValidator\TypeDef\UserDef;
28use MediaWiki\Revision\RevisionRecord;
29use MediaWiki\Title\NamespaceInfo;
30use MediaWiki\Title\Title;
31use MediaWiki\User\TempUser\TempUserConfig;
32use Wikimedia\ParamValidator\ParamValidator;
33use Wikimedia\ParamValidator\TypeDef\IntegerDef;
34
35/**
36 * This query action allows clients to retrieve a list of recently modified pages
37 * that are part of the logged-in user's watchlist.
38 *
39 * @ingroup API
40 */
41class ApiQueryWatchlist extends ApiQueryGeneratorBase {
42
43    private CommentStore $commentStore;
44    private WatchedItemQueryService $watchedItemQueryService;
45    private Language $contentLanguage;
46    private NamespaceInfo $namespaceInfo;
47    private GenderCache $genderCache;
48    private CommentFormatter $commentFormatter;
49    private TempUserConfig $tempUserConfig;
50
51    /**
52     * @param ApiQuery $query
53     * @param string $moduleName
54     * @param CommentStore $commentStore
55     * @param WatchedItemQueryService $watchedItemQueryService
56     * @param Language $contentLanguage
57     * @param NamespaceInfo $namespaceInfo
58     * @param GenderCache $genderCache
59     * @param CommentFormatter $commentFormatter
60     * @param TempUserConfig $tempUserConfig
61     */
62    public function __construct(
63        ApiQuery $query,
64        $moduleName,
65        CommentStore $commentStore,
66        WatchedItemQueryService $watchedItemQueryService,
67        Language $contentLanguage,
68        NamespaceInfo $namespaceInfo,
69        GenderCache $genderCache,
70        CommentFormatter $commentFormatter,
71        TempUserConfig $tempUserConfig
72    ) {
73        parent::__construct( $query, $moduleName, 'wl' );
74        $this->commentStore = $commentStore;
75        $this->watchedItemQueryService = $watchedItemQueryService;
76        $this->contentLanguage = $contentLanguage;
77        $this->namespaceInfo = $namespaceInfo;
78        $this->genderCache = $genderCache;
79        $this->commentFormatter = $commentFormatter;
80        $this->tempUserConfig = $tempUserConfig;
81    }
82
83    public function execute() {
84        $this->run();
85    }
86
87    public function executeGenerator( $resultPageSet ) {
88        $this->run( $resultPageSet );
89    }
90
91    private bool $fld_ids = false;
92    private bool $fld_title = false;
93    private bool $fld_patrol = false;
94    private bool $fld_flags = false;
95    private bool $fld_timestamp = false;
96    private bool $fld_user = false;
97    private bool $fld_comment = false;
98    private bool $fld_parsedcomment = false;
99    private bool $fld_sizes = false;
100    private bool $fld_notificationtimestamp = false;
101    private bool $fld_userid = false;
102    private bool $fld_loginfo = false;
103    private bool $fld_tags = false;
104    private bool $fld_expiry = false;
105
106    /**
107     * @param ApiPageSet|null $resultPageSet
108     * @return void
109     */
110    private function run( $resultPageSet = null ) {
111        $params = $this->extractRequestParams();
112
113        $user = $this->getUser();
114        $wlowner = $this->getWatchlistUser( $params );
115
116        if ( $params['prop'] !== null && $resultPageSet === null ) {
117            $prop = array_fill_keys( $params['prop'], true );
118
119            $this->fld_ids = isset( $prop['ids'] );
120            $this->fld_title = isset( $prop['title'] );
121            $this->fld_flags = isset( $prop['flags'] );
122            $this->fld_user = isset( $prop['user'] );
123            $this->fld_userid = isset( $prop['userid'] );
124            $this->fld_comment = isset( $prop['comment'] );
125            $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
126            $this->fld_timestamp = isset( $prop['timestamp'] );
127            $this->fld_sizes = isset( $prop['sizes'] );
128            $this->fld_patrol = isset( $prop['patrol'] );
129            $this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
130            $this->fld_loginfo = isset( $prop['loginfo'] );
131            $this->fld_tags = isset( $prop['tags'] );
132            $this->fld_expiry = isset( $prop['expiry'] );
133
134            if ( $this->fld_patrol && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
135                $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'patrol' );
136            }
137        }
138
139        $options = [
140            'dir' => $params['dir'] === 'older'
141                ? WatchedItemQueryService::DIR_OLDER
142                : WatchedItemQueryService::DIR_NEWER,
143        ];
144
145        if ( $resultPageSet === null ) {
146            $options['includeFields'] = $this->getFieldsToInclude();
147        } else {
148            $options['usedInGenerator'] = true;
149        }
150
151        if ( $params['start'] ) {
152            $options['start'] = $params['start'];
153        }
154        if ( $params['end'] ) {
155            $options['end'] = $params['end'];
156        }
157
158        $startFrom = null;
159        if ( $params['continue'] !== null ) {
160            $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'string', 'int' ] );
161            $startFrom = $cont;
162        }
163
164        if ( $wlowner !== $user ) {
165            $options['watchlistOwner'] = $wlowner;
166            $options['watchlistOwnerToken'] = $params['token'];
167        }
168
169        if ( $params['namespace'] !== null ) {
170            $options['namespaceIds'] = $params['namespace'];
171        }
172
173        if ( $params['allrev'] ) {
174            $options['allRevisions'] = true;
175        }
176
177        if ( $params['show'] !== null ) {
178            $show = array_fill_keys( $params['show'], true );
179
180            /* Check for conflicting parameters. */
181            if ( $this->showParamsConflicting( $show ) ) {
182                $this->dieWithError( 'apierror-show' );
183            }
184
185            // Check permissions.
186            if ( isset( $show[WatchedItemQueryService::FILTER_PATROLLED] )
187                || isset( $show[WatchedItemQueryService::FILTER_NOT_PATROLLED] )
188            ) {
189                if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
190                    $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
191                }
192            }
193
194            $options['filters'] = array_keys( $show );
195        }
196
197        if ( $params['type'] !== null ) {
198            $rcTypes = RecentChange::parseToRCType( $params['type'] );
199            if ( $rcTypes ) {
200                $options['rcTypes'] = $rcTypes;
201            }
202        }
203
204        $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
205        if ( $params['user'] !== null ) {
206            $options['onlyByUser'] = $params['user'];
207        }
208        if ( $params['excludeuser'] !== null ) {
209            $options['notByUser'] = $params['excludeuser'];
210        }
211
212        $options['limit'] = $params['limit'];
213
214        $this->getHookRunner()->onApiQueryWatchlistPrepareWatchedItemQueryServiceOptions(
215            $this, $params, $options );
216
217        $ids = [];
218        $items = $this->watchedItemQueryService->getWatchedItemsWithRecentChangeInfo( $wlowner, $options, $startFrom );
219
220        // Get gender information
221        if ( $items !== [] && $resultPageSet === null && $this->fld_title &&
222            $this->contentLanguage->needsGenderDistinction()
223        ) {
224            $usernames = [];
225            foreach ( $items as [ $watchedItem, ] ) {
226                /** @var WatchedItem $watchedItem */
227                $linkTarget = $watchedItem->getTarget();
228                if ( $this->namespaceInfo->hasGenderDistinction( $linkTarget->getNamespace() ) ) {
229                    $usernames[] = $linkTarget->getText();
230                }
231            }
232            if ( $usernames !== [] ) {
233                $this->genderCache->doQuery( $usernames, __METHOD__ );
234            }
235        }
236
237        foreach ( $items as [ $watchedItem, $recentChangeInfo ] ) {
238            /** @var WatchedItem $watchedItem */
239            if ( $resultPageSet === null ) {
240                $vals = $this->extractOutputData( $watchedItem, $recentChangeInfo );
241                $fit = $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
242                if ( !$fit ) {
243                    $startFrom = [ $recentChangeInfo['rc_timestamp'], $recentChangeInfo['rc_id'] ];
244                    break;
245                }
246            } elseif ( $params['allrev'] ) {
247                $ids[] = (int)$recentChangeInfo['rc_this_oldid'];
248            } else {
249                $ids[] = (int)$recentChangeInfo['rc_cur_id'];
250            }
251        }
252
253        if ( $startFrom !== null ) {
254            $this->setContinueEnumParameter( 'continue', implode( '|', $startFrom ) );
255        }
256
257        if ( $resultPageSet === null ) {
258            $this->getResult()->addIndexedTagName(
259                [ 'query', $this->getModuleName() ],
260                'item'
261            );
262        } elseif ( $params['allrev'] ) {
263            $resultPageSet->populateFromRevisionIDs( $ids );
264        } else {
265            $resultPageSet->populateFromPageIDs( $ids );
266        }
267    }
268
269    private function getFieldsToInclude() {
270        $includeFields = [];
271        if ( $this->fld_flags ) {
272            $includeFields[] = WatchedItemQueryService::INCLUDE_FLAGS;
273        }
274        if ( $this->fld_user || $this->fld_userid || $this->fld_loginfo ) {
275            $includeFields[] = WatchedItemQueryService::INCLUDE_USER_ID;
276        }
277        if ( $this->fld_user || $this->fld_loginfo ) {
278            $includeFields[] = WatchedItemQueryService::INCLUDE_USER;
279        }
280        if ( $this->fld_comment || $this->fld_parsedcomment ) {
281            $includeFields[] = WatchedItemQueryService::INCLUDE_COMMENT;
282        }
283        if ( $this->fld_patrol ) {
284            $includeFields[] = WatchedItemQueryService::INCLUDE_PATROL_INFO;
285            $includeFields[] = WatchedItemQueryService::INCLUDE_AUTOPATROL_INFO;
286        }
287        if ( $this->fld_sizes ) {
288            $includeFields[] = WatchedItemQueryService::INCLUDE_SIZES;
289        }
290        if ( $this->fld_loginfo ) {
291            $includeFields[] = WatchedItemQueryService::INCLUDE_LOG_INFO;
292        }
293        if ( $this->fld_tags ) {
294            $includeFields[] = WatchedItemQueryService::INCLUDE_TAGS;
295        }
296        return $includeFields;
297    }
298
299    private function showParamsConflicting( array $show ) {
300        return ( isset( $show[WatchedItemQueryService::FILTER_MINOR] )
301            && isset( $show[WatchedItemQueryService::FILTER_NOT_MINOR] ) )
302        || ( isset( $show[WatchedItemQueryService::FILTER_BOT] )
303            && isset( $show[WatchedItemQueryService::FILTER_NOT_BOT] ) )
304        || ( isset( $show[WatchedItemQueryService::FILTER_ANON] )
305            && isset( $show[WatchedItemQueryService::FILTER_NOT_ANON] ) )
306        || ( isset( $show[WatchedItemQueryService::FILTER_PATROLLED] )
307            && isset( $show[WatchedItemQueryService::FILTER_NOT_PATROLLED] ) )
308        || ( isset( $show[WatchedItemQueryService::FILTER_AUTOPATROLLED] )
309            && isset( $show[WatchedItemQueryService::FILTER_NOT_AUTOPATROLLED] ) )
310        || ( isset( $show[WatchedItemQueryService::FILTER_AUTOPATROLLED] )
311            && isset( $show[WatchedItemQueryService::FILTER_NOT_PATROLLED] ) )
312        || ( isset( $show[WatchedItemQueryService::FILTER_UNREAD] )
313            && isset( $show[WatchedItemQueryService::FILTER_NOT_UNREAD] ) );
314    }
315
316    private function extractOutputData( WatchedItem $watchedItem, array $recentChangeInfo ) {
317        /* Determine the title of the page that has been changed. */
318        $target = $watchedItem->getTarget();
319        if ( $target instanceof LinkTarget ) {
320            $title = Title::newFromLinkTarget( $target );
321        } else {
322            $title = Title::newFromPageIdentity( $target );
323        }
324        $user = $this->getUser();
325
326        /* Our output data. */
327        $vals = [];
328        $type = (int)$recentChangeInfo['rc_type'];
329        $vals['type'] = RecentChange::parseFromRCType( $type );
330        $anyHidden = false;
331
332        /* Create a new entry in the result for the title. */
333        if ( $this->fld_title || $this->fld_ids ) {
334            // These should already have been filtered out of the query, but just in case.
335            if ( $type === RC_LOG && ( $recentChangeInfo['rc_deleted'] & LogPage::DELETED_ACTION ) ) {
336                $vals['actionhidden'] = true;
337                $anyHidden = true;
338            }
339            if ( $type !== RC_LOG ||
340                LogEventsList::userCanBitfield(
341                    $recentChangeInfo['rc_deleted'],
342                    LogPage::DELETED_ACTION,
343                    $user
344                )
345            ) {
346                if ( $this->fld_title ) {
347                    ApiQueryBase::addTitleInfo( $vals, $title );
348                }
349                if ( $this->fld_ids ) {
350                    $vals['pageid'] = (int)$recentChangeInfo['rc_cur_id'];
351                    $vals['revid'] = (int)$recentChangeInfo['rc_this_oldid'];
352                    $vals['old_revid'] = (int)$recentChangeInfo['rc_last_oldid'];
353                }
354            }
355        }
356
357        if ( $this->fld_user || $this->fld_userid ) {
358            if ( $recentChangeInfo['rc_deleted'] & RevisionRecord::DELETED_USER ) {
359                $vals['userhidden'] = true;
360                $anyHidden = true;
361            }
362            if ( RevisionRecord::userCanBitfield(
363                $recentChangeInfo['rc_deleted'],
364                RevisionRecord::DELETED_USER,
365                $user
366            ) ) {
367                if ( $this->fld_userid ) {
368                    $vals['userid'] = (int)$recentChangeInfo['rc_user'];
369                    // for backwards compatibility
370                    $vals['user'] = (int)$recentChangeInfo['rc_user'];
371                }
372
373                if ( $this->fld_user ) {
374                    $vals['user'] = $recentChangeInfo['rc_user_text'];
375                    $vals['temp'] = $this->tempUserConfig->isTempName(
376                        $recentChangeInfo['rc_user_text']
377                    );
378                }
379
380                // Whether the user is a logged-out user (IP user). This does
381                // not include temporary users, though they are grouped with IP
382                // users for FILTER_NOT_ANON and FILTER_ANON, to match the
383                // recent changes filters (T343322).
384                $vals['anon'] = !$recentChangeInfo['rc_user'];
385
386            }
387        }
388
389        /* Add flags, such as new, minor, bot. */
390        if ( $this->fld_flags ) {
391            $vals['bot'] = (bool)$recentChangeInfo['rc_bot'];
392            $vals['new'] = $recentChangeInfo['rc_type'] == RC_NEW;
393            $vals['minor'] = (bool)$recentChangeInfo['rc_minor'];
394        }
395
396        /* Add sizes of each revision. (Only available on 1.10+) */
397        if ( $this->fld_sizes ) {
398            $vals['oldlen'] = (int)$recentChangeInfo['rc_old_len'];
399            $vals['newlen'] = (int)$recentChangeInfo['rc_new_len'];
400        }
401
402        /* Add the timestamp. */
403        if ( $this->fld_timestamp ) {
404            $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $recentChangeInfo['rc_timestamp'] );
405        }
406
407        if ( $this->fld_notificationtimestamp ) {
408            $vals['notificationtimestamp'] = ( $watchedItem->getNotificationTimestamp() == null )
409                ? ''
410                : wfTimestamp( TS_ISO_8601, $watchedItem->getNotificationTimestamp() );
411        }
412
413        /* Add edit summary / log summary. */
414        if ( $this->fld_comment || $this->fld_parsedcomment ) {
415            if ( $recentChangeInfo['rc_deleted'] & RevisionRecord::DELETED_COMMENT ) {
416                $vals['commenthidden'] = true;
417                $anyHidden = true;
418            }
419            if ( RevisionRecord::userCanBitfield(
420                $recentChangeInfo['rc_deleted'],
421                RevisionRecord::DELETED_COMMENT,
422                $user
423            ) ) {
424                $comment = $this->commentStore->getComment( 'rc_comment', $recentChangeInfo )->text;
425                if ( $this->fld_comment ) {
426                    $vals['comment'] = $comment;
427                }
428
429                if ( $this->fld_parsedcomment ) {
430                    $vals['parsedcomment'] = $this->commentFormatter->format( $comment, $title );
431                }
432            }
433        }
434
435        /* Add the patrolled flag */
436        if ( $this->fld_patrol ) {
437            $vals['patrolled'] = $recentChangeInfo['rc_patrolled'] != RecentChange::PRC_UNPATROLLED;
438            $vals['unpatrolled'] = ChangesList::isUnpatrolled( (object)$recentChangeInfo, $user );
439            $vals['autopatrolled'] = $recentChangeInfo['rc_patrolled'] == RecentChange::PRC_AUTOPATROLLED;
440        }
441
442        if ( $this->fld_loginfo && $recentChangeInfo['rc_type'] == RC_LOG ) {
443            if ( $recentChangeInfo['rc_deleted'] & LogPage::DELETED_ACTION ) {
444                $vals['actionhidden'] = true;
445                $anyHidden = true;
446            }
447            if ( LogEventsList::userCanBitfield(
448                $recentChangeInfo['rc_deleted'],
449                LogPage::DELETED_ACTION,
450                $user
451            ) ) {
452                $vals['logid'] = (int)$recentChangeInfo['rc_logid'];
453                $vals['logtype'] = $recentChangeInfo['rc_log_type'];
454                $vals['logaction'] = $recentChangeInfo['rc_log_action'];
455
456                $logFormatter = LogFormatter::newFromRow( $recentChangeInfo );
457                $vals['logparams'] = $logFormatter->formatParametersForApi();
458                $vals['logdisplay'] = $logFormatter->getActionText();
459            }
460        }
461
462        if ( $this->fld_tags ) {
463            if ( $recentChangeInfo['rc_tags'] ) {
464                $tags = explode( ',', $recentChangeInfo['rc_tags'] );
465                ApiResult::setIndexedTagName( $tags, 'tag' );
466                $vals['tags'] = $tags;
467            } else {
468                $vals['tags'] = [];
469            }
470        }
471
472        if ( $this->fld_expiry ) {
473            // Add expiration, T263796
474            $expiry = $watchedItem->getExpiry( TS_ISO_8601 );
475            $vals['expiry'] = ( $expiry ?? false );
476        }
477
478        if ( $anyHidden && ( $recentChangeInfo['rc_deleted'] & RevisionRecord::DELETED_RESTRICTED ) ) {
479            $vals['suppressed'] = true;
480        }
481
482        $this->getHookRunner()->onApiQueryWatchlistExtractOutputData(
483            $this, $watchedItem, $recentChangeInfo, $vals );
484
485        return $vals;
486    }
487
488    public function getAllowedParams() {
489        return [
490            'allrev' => false,
491            'start' => [
492                ParamValidator::PARAM_TYPE => 'timestamp'
493            ],
494            'end' => [
495                ParamValidator::PARAM_TYPE => 'timestamp'
496            ],
497            'namespace' => [
498                ParamValidator::PARAM_ISMULTI => true,
499                ParamValidator::PARAM_TYPE => 'namespace'
500            ],
501            'user' => [
502                ParamValidator::PARAM_TYPE => 'user',
503                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
504            ],
505            'excludeuser' => [
506                ParamValidator::PARAM_TYPE => 'user',
507                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
508            ],
509            'dir' => [
510                ParamValidator::PARAM_DEFAULT => 'older',
511                ParamValidator::PARAM_TYPE => [
512                    'newer',
513                    'older'
514                ],
515                ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
516                ApiBase::PARAM_HELP_MSG_PER_VALUE => [
517                    'newer' => 'api-help-paramvalue-direction-newer',
518                    'older' => 'api-help-paramvalue-direction-older',
519                ],
520            ],
521            'limit' => [
522                ParamValidator::PARAM_DEFAULT => 10,
523                ParamValidator::PARAM_TYPE => 'limit',
524                IntegerDef::PARAM_MIN => 1,
525                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
526                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
527            ],
528            'prop' => [
529                ParamValidator::PARAM_ISMULTI => true,
530                ParamValidator::PARAM_DEFAULT => 'ids|title|flags',
531                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
532                ParamValidator::PARAM_TYPE => [
533                    'ids',
534                    'title',
535                    'flags',
536                    'user',
537                    'userid',
538                    'comment',
539                    'parsedcomment',
540                    'timestamp',
541                    'patrol',
542                    'sizes',
543                    'notificationtimestamp',
544                    'loginfo',
545                    'tags',
546                    'expiry',
547                ]
548            ],
549            'show' => [
550                ParamValidator::PARAM_ISMULTI => true,
551                ParamValidator::PARAM_TYPE => [
552                    WatchedItemQueryService::FILTER_MINOR,
553                    WatchedItemQueryService::FILTER_NOT_MINOR,
554                    WatchedItemQueryService::FILTER_BOT,
555                    WatchedItemQueryService::FILTER_NOT_BOT,
556                    WatchedItemQueryService::FILTER_ANON,
557                    WatchedItemQueryService::FILTER_NOT_ANON,
558                    WatchedItemQueryService::FILTER_PATROLLED,
559                    WatchedItemQueryService::FILTER_NOT_PATROLLED,
560                    WatchedItemQueryService::FILTER_AUTOPATROLLED,
561                    WatchedItemQueryService::FILTER_NOT_AUTOPATROLLED,
562                    WatchedItemQueryService::FILTER_UNREAD,
563                    WatchedItemQueryService::FILTER_NOT_UNREAD,
564                ]
565            ],
566            'type' => [
567                ParamValidator::PARAM_DEFAULT => 'edit|new|log|categorize',
568                ParamValidator::PARAM_ISMULTI => true,
569                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
570                ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
571            ],
572            'owner' => [
573                ParamValidator::PARAM_TYPE => 'user',
574                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name' ],
575            ],
576            'token' => [
577                ParamValidator::PARAM_TYPE => 'string',
578                ParamValidator::PARAM_SENSITIVE => true,
579            ],
580            'continue' => [
581                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
582            ],
583        ];
584    }
585
586    protected function getExamplesMessages() {
587        return [
588            'action=query&list=watchlist'
589                => 'apihelp-query+watchlist-example-simple',
590            'action=query&list=watchlist&wlprop=ids|title|timestamp|user|comment'
591                => 'apihelp-query+watchlist-example-props',
592            'action=query&list=watchlist&wlprop=ids|title|timestamp|user|comment|expiry'
593                => 'apihelp-query+watchlist-example-expiry',
594            'action=query&list=watchlist&wlallrev=&wlprop=ids|title|timestamp|user|comment'
595                => 'apihelp-query+watchlist-example-allrev',
596            'action=query&generator=watchlist&prop=info'
597                => 'apihelp-query+watchlist-example-generator',
598            'action=query&generator=watchlist&gwlallrev=&prop=revisions&rvprop=timestamp|user'
599                => 'apihelp-query+watchlist-example-generator-rev',
600            'action=query&list=watchlist&wlowner=Example&wltoken=123ABC'
601                => 'apihelp-query+watchlist-example-wlowner',
602        ];
603    }
604
605    public function getHelpUrls() {
606        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Watchlist';
607    }
608}