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;
88 $this->
run( $resultPageSet );
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;
110 private function run( $resultPageSet =
null ) {
111 $params = $this->extractRequestParams();
114 $wlowner = $this->getWatchlistUser(
$params );
116 if (
$params[
'prop'] !==
null && $resultPageSet ===
null ) {
117 $prop = array_fill_keys(
$params[
'prop'],
true );
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'] );
134 if ( $this->fld_patrol && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
135 $this->dieWithError(
'apierror-permissiondenied-patrolflag',
'patrol' );
140 'dir' =>
$params[
'dir'] ===
'older'
145 if ( $resultPageSet ===
null ) {
146 $options[
'includeFields'] = $this->getFieldsToInclude();
148 $options[
'usedInGenerator'] =
true;
152 $options[
'start'] =
$params[
'start'];
155 $options[
'end'] =
$params[
'end'];
159 if (
$params[
'continue'] !==
null ) {
164 if ( $wlowner !== $user ) {
165 $options[
'watchlistOwner'] = $wlowner;
166 $options[
'watchlistOwnerToken'] =
$params[
'token'];
169 if (
$params[
'namespace'] !==
null ) {
170 $options[
'namespaceIds'] =
$params[
'namespace'];
174 $options[
'allRevisions'] =
true;
177 if (
$params[
'show'] !==
null ) {
178 $show = array_fill_keys(
$params[
'show'],
true );
181 if ( $this->showParamsConflicting( $show ) ) {
186 if ( isset( $show[WatchedItemQueryService::FILTER_PATROLLED] )
187 || isset( $show[WatchedItemQueryService::FILTER_NOT_PATROLLED] )
189 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
190 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
194 $options[
'filters'] = array_keys( $show );
197 if (
$params[
'type'] !==
null ) {
200 $options[
'rcTypes'] = $rcTypes;
205 if (
$params[
'user'] !==
null ) {
206 $options[
'onlyByUser'] =
$params[
'user'];
208 if (
$params[
'excludeuser'] !==
null ) {
209 $options[
'notByUser'] =
$params[
'excludeuser'];
212 $options[
'limit'] =
$params[
'limit'];
214 $this->
getHookRunner()->onApiQueryWatchlistPrepareWatchedItemQueryServiceOptions(
218 $items = $this->watchedItemQueryService->getWatchedItemsWithRecentChangeInfo( $wlowner, $options, $startFrom );
221 if ( $items !== [] && $resultPageSet ===
null && $this->fld_title &&
222 $this->contentLanguage->needsGenderDistinction()
225 foreach ( $items as [ $watchedItem, ] ) {
228 if ( $this->namespaceInfo->hasGenderDistinction( $linkTarget->getNamespace() ) ) {
229 $usernames[] = $linkTarget->getText();
232 if ( $usernames !== [] ) {
233 $this->genderCache->doQuery( $usernames, __METHOD__ );
237 foreach ( $items as [ $watchedItem, $recentChangeInfo ] ) {
239 if ( $resultPageSet ===
null ) {
240 $vals = $this->extractOutputData( $watchedItem, $recentChangeInfo );
243 $startFrom = [ $recentChangeInfo[
'rc_timestamp'], $recentChangeInfo[
'rc_id'] ];
246 } elseif (
$params[
'allrev'] ) {
247 $ids[] = (int)$recentChangeInfo[
'rc_this_oldid'];
249 $ids[] = (int)$recentChangeInfo[
'rc_cur_id'];
253 if ( $startFrom !==
null ) {
257 if ( $resultPageSet ===
null ) {
262 } elseif (
$params[
'allrev'] ) {
263 $resultPageSet->populateFromRevisionIDs( $ids );
265 $resultPageSet->populateFromPageIDs( $ids );
269 private function getFieldsToInclude() {
271 if ( $this->fld_flags ) {
274 if ( $this->fld_user || $this->fld_userid || $this->fld_loginfo ) {
277 if ( $this->fld_user || $this->fld_loginfo ) {
280 if ( $this->fld_comment || $this->fld_parsedcomment ) {
283 if ( $this->fld_patrol ) {
287 if ( $this->fld_sizes ) {
290 if ( $this->fld_loginfo ) {
293 if ( $this->fld_tags ) {
296 return $includeFields;
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] ) );
316 private function extractOutputData(
WatchedItem $watchedItem, array $recentChangeInfo ) {
320 $title = Title::newFromLinkTarget( $target );
322 $title = Title::newFromPageIdentity( $target );
328 $type = (int)$recentChangeInfo[
'rc_type'];
333 if ( $this->fld_title || $this->fld_ids ) {
335 if ( $type ===
RC_LOG && ( $recentChangeInfo[
'rc_deleted'] & LogPage::DELETED_ACTION ) ) {
336 $vals[
'actionhidden'] =
true;
340 LogEventsList::userCanBitfield(
341 $recentChangeInfo[
'rc_deleted'],
342 LogPage::DELETED_ACTION,
346 if ( $this->fld_title ) {
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'];
357 if ( $this->fld_user || $this->fld_userid ) {
358 if ( $recentChangeInfo[
'rc_deleted'] & RevisionRecord::DELETED_USER ) {
359 $vals[
'userhidden'] =
true;
362 if ( RevisionRecord::userCanBitfield(
363 $recentChangeInfo[
'rc_deleted'],
364 RevisionRecord::DELETED_USER,
367 if ( $this->fld_userid ) {
368 $vals[
'userid'] = (int)$recentChangeInfo[
'rc_user'];
370 $vals[
'user'] = (int)$recentChangeInfo[
'rc_user'];
373 if ( $this->fld_user ) {
374 $vals[
'user'] = $recentChangeInfo[
'rc_user_text'];
375 $vals[
'temp'] = $this->tempUserConfig->isTempName(
376 $recentChangeInfo[
'rc_user_text']
384 $vals[
'anon'] = !$recentChangeInfo[
'rc_user'];
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'];
397 if ( $this->fld_sizes ) {
398 $vals[
'oldlen'] = (int)$recentChangeInfo[
'rc_old_len'];
399 $vals[
'newlen'] = (int)$recentChangeInfo[
'rc_new_len'];
403 if ( $this->fld_timestamp ) {
404 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $recentChangeInfo[
'rc_timestamp'] );
407 if ( $this->fld_notificationtimestamp ) {
410 :
wfTimestamp( TS_ISO_8601, $watchedItem->getNotificationTimestamp() );
414 if ( $this->fld_comment || $this->fld_parsedcomment ) {
415 if ( $recentChangeInfo[
'rc_deleted'] & RevisionRecord::DELETED_COMMENT ) {
416 $vals[
'commenthidden'] =
true;
419 if ( RevisionRecord::userCanBitfield(
420 $recentChangeInfo[
'rc_deleted'],
421 RevisionRecord::DELETED_COMMENT,
424 $comment = $this->commentStore->getComment(
'rc_comment', $recentChangeInfo )->text;
425 if ( $this->fld_comment ) {
426 $vals[
'comment'] = $comment;
429 if ( $this->fld_parsedcomment ) {
430 $vals[
'parsedcomment'] = $this->commentFormatter->format( $comment, $title );
436 if ( $this->fld_patrol ) {
442 if ( $this->fld_loginfo && $recentChangeInfo[
'rc_type'] ==
RC_LOG ) {
443 if ( $recentChangeInfo[
'rc_deleted'] & LogPage::DELETED_ACTION ) {
444 $vals[
'actionhidden'] =
true;
447 if ( LogEventsList::userCanBitfield(
448 $recentChangeInfo[
'rc_deleted'],
449 LogPage::DELETED_ACTION,
452 $vals[
'logid'] = (int)$recentChangeInfo[
'rc_logid'];
453 $vals[
'logtype'] = $recentChangeInfo[
'rc_log_type'];
454 $vals[
'logaction'] = $recentChangeInfo[
'rc_log_action'];
457 $vals[
'logparams'] = $logFormatter->formatParametersForApi();
458 $vals[
'logdisplay'] = $logFormatter->getActionText();
462 if ( $this->fld_tags ) {
463 if ( $recentChangeInfo[
'rc_tags'] ) {
464 $tags = explode(
',', $recentChangeInfo[
'rc_tags'] );
466 $vals[
'tags'] = $tags;
472 if ( $this->fld_expiry ) {
474 $expiry = $watchedItem->
getExpiry( TS_ISO_8601 );
475 $vals[
'expiry'] = ( $expiry ?? false );
478 if ( $anyHidden && ( $recentChangeInfo[
'rc_deleted'] & RevisionRecord::DELETED_RESTRICTED ) ) {
479 $vals[
'suppressed'] =
true;
482 $this->
getHookRunner()->onApiQueryWatchlistExtractOutputData(
483 $this, $watchedItem, $recentChangeInfo, $vals );
492 ParamValidator::PARAM_TYPE =>
'timestamp'
495 ParamValidator::PARAM_TYPE =>
'timestamp'
498 ParamValidator::PARAM_ISMULTI =>
true,
499 ParamValidator::PARAM_TYPE =>
'namespace'
502 ParamValidator::PARAM_TYPE =>
'user',
503 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
506 ParamValidator::PARAM_TYPE =>
'user',
507 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
510 ParamValidator::PARAM_DEFAULT =>
'older',
511 ParamValidator::PARAM_TYPE => [
517 'newer' =>
'api-help-paramvalue-direction-newer',
518 'older' =>
'api-help-paramvalue-direction-older',
522 ParamValidator::PARAM_DEFAULT => 10,
523 ParamValidator::PARAM_TYPE =>
'limit',
524 IntegerDef::PARAM_MIN => 1,
529 ParamValidator::PARAM_ISMULTI =>
true,
530 ParamValidator::PARAM_DEFAULT =>
'ids|title|flags',
532 ParamValidator::PARAM_TYPE => [
543 'notificationtimestamp',
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,
567 ParamValidator::PARAM_DEFAULT =>
'edit|new|log|categorize',
568 ParamValidator::PARAM_ISMULTI =>
true,
570 ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
573 ParamValidator::PARAM_TYPE =>
'user',
574 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name' ],
577 ParamValidator::PARAM_TYPE =>
'string',
578 ParamValidator::PARAM_SENSITIVE =>
true,
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',
606 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Watchlist';