82 $this->fld_comment = isset( $prop[
'comment'] );
83 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
84 $this->fld_user = isset( $prop[
'user'] );
85 $this->fld_userid = isset( $prop[
'userid'] );
86 $this->fld_flags = isset( $prop[
'flags'] );
87 $this->fld_timestamp = isset( $prop[
'timestamp'] );
88 $this->fld_title = isset( $prop[
'title'] );
89 $this->fld_ids = isset( $prop[
'ids'] );
90 $this->fld_sizes = isset( $prop[
'sizes'] );
91 $this->fld_redirect = isset( $prop[
'redirect'] );
92 $this->fld_patrolled = isset( $prop[
'patrolled'] );
93 $this->fld_loginfo = isset( $prop[
'loginfo'] );
94 $this->fld_tags = isset( $prop[
'tags'] );
95 $this->fld_sha1 = isset( $prop[
'sha1'] );
112 public function run( $resultPageSet =
null ) {
117 $query = $this->changesListQueryFactory->newQuery();
118 $query->watchlistUser( $user )
121 $sources = $this->recentChangeLookup->getAllSources();
123 if ( $params[
'dir'] ===
'newer' ) {
124 $query->orderBy( ChangesListQuery::SORT_TIMESTAMP_ASC );
126 $startTimestamp = $params[
'start'];
127 $end = $params[
'end'];
128 if ( $params[
'continue'] !==
null ) {
130 $startTimestamp = $cont[0];
135 if ( $startTimestamp !==
null ) {
136 $query->startAt( $startTimestamp, $startId );
138 if ( $end !==
null ) {
139 $query->endAt( $end );
142 if ( $params[
'type'] !==
null ) {
143 $sources = array_intersect( $sources,
144 $this->recentChangeLookup->convertTypeToSources( $params[
'type'] ) );
147 $title = $params[
'title'];
148 if ( $title !==
null ) {
149 $titleObj = Title::newFromText( $title );
150 if ( $titleObj ===
null || $titleObj->isExternal() ) {
153 if ( $params[
'namespace'] &&
154 !$titleObj->inNamespaces( $params[
'namespace'] )
158 $query->requireTitle( $titleObj );
160 } elseif ( $params[
'namespace'] !==
null ) {
161 $query->requireNamespaces( $params[
'namespace'] );
164 if ( $params[
'show'] !==
null ) {
165 $show = array_fill_keys( $params[
'show'],
true );
168 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
169 || ( isset( $show[
'bot'] ) && isset( $show[
'!bot'] ) )
170 || ( isset( $show[
'anon'] ) && isset( $show[
'!anon'] ) )
171 || ( isset( $show[
'redirect'] ) && isset( $show[
'!redirect'] ) )
172 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
173 || ( isset( $show[
'patrolled'] ) && isset( $show[
'unpatrolled'] ) )
174 || ( isset( $show[
'!patrolled'] ) && isset( $show[
'unpatrolled'] ) )
175 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
176 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'unpatrolled'] ) )
177 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
183 if ( $this->includesPatrollingFlags( $show ) && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
184 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
189 'minor' => [
'require',
'minor', true ],
190 '!minor' => [
'exclude',
'minor', true ],
191 'bot' => [
'require',
'bot', true ],
192 '!bot' => [
'exclude',
'bot', true ],
193 'anon' => [
'exclude',
'named' ],
194 '!anon' => [
'require',
'named' ],
195 'patrolled' => [
'exclude',
'patrolled', RecentChange::PRC_UNPATROLLED ],
196 '!patrolled' => [
'require',
'patrolled', RecentChange::PRC_UNPATROLLED ],
197 'redirect' => [
'require',
'redirect', true ],
198 '!redirect' => [
'exclude',
'redirect', true ],
199 'autopatrolled' => [
'require',
'patrolled', RecentChange::PRC_AUTOPATROLLED ],
200 '!autopatrolled' => [
'exclude',
'patrolled', RecentChange::PRC_AUTOPATROLLED ],
202 foreach ( $show as $name => $unused ) {
203 if ( isset( $showActions[$name] ) ) {
204 $query->applyAction( ...$showActions[$name] );
208 if ( isset( $show[
'unpatrolled'] ) ) {
210 if ( $user->useRCPatrol() ) {
211 $query->requirePatrolled( RecentChange::PRC_UNPATROLLED );
212 } elseif ( $user->useNPPatrol() ) {
213 $query->requirePatrolled( RecentChange::PRC_UNPATROLLED );
214 $sources = array_intersect( $sources, [ RecentChange::SRC_NEW ] );
221 if ( $params[
'prop'] !==
null ) {
222 $prop = array_fill_keys( $params[
'prop'],
true );
228 if ( $this->fld_user || $this->fld_userid ) {
229 $query->rcUserFields();
232 if ( $params[
'user'] !==
null ) {
233 $query->requireUser( $params[
'user'] );
236 if ( $params[
'excludeuser'] !==
null ) {
237 $query->excludeUser( $params[
'excludeuser'] );
252 if ( $params[
'prop'] !==
null ) {
253 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
254 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
258 if ( $this->fld_ids ) {
259 $query->fields( [
'rc_this_oldid',
'rc_last_oldid' ] );
261 if ( $this->fld_flags ) {
262 $query->fields( [
'rc_minor',
'rc_bot' ] );
264 if ( $this->fld_sizes ) {
265 $query->fields( [
'rc_old_len',
'rc_new_len' ] );
267 if ( $this->fld_patrolled ) {
268 $query->fields( [
'rc_patrolled',
'rc_log_type' ] );
270 if ( $this->fld_loginfo ) {
271 $query->fields( [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ] );
273 if ( $this->fld_redirect ) {
274 $query->addRedirectField();
277 if ( $resultPageSet && $params[
'generaterevisions'] ) {
278 $query->fields( [
'rc_this_oldid' ] );
280 if ( $this->fld_tags ) {
281 $query->addChangeTagSummaryField();
283 if ( $this->fld_sha1 ) {
284 $query->sha1Fields();
286 if ( $params[
'toponly'] ) {
287 $query->requireLatest();
289 if ( $params[
'tag'] !==
null ) {
290 $query->requireChangeTags( [ $params[
'tag'] ] );
294 if ( $params[
'user'] !==
null || $params[
'excludeuser'] !==
null ) {
295 $query->excludeDeletedUser();
297 if ( $this->
getRequest()->getCheck(
'namespace' ) ) {
298 $query->excludeDeletedLogAction();
301 if ( $this->fld_comment || $this->fld_parsedcomment ) {
302 $query->commentFields();
305 if ( $params[
'slot'] !==
null ) {
307 $sources = array_intersect( $sources,
308 [ RecentChange::SRC_NEW, RecentChange::SRC_EDIT ] );
309 $query->requireSlotChanged( $params[
'slot'] );
312 $query->requireSources( $sources )
313 ->limit( $params[
'limit'] + 1 )
314 ->maxExecutionTime( $this->
getConfig()->
get(
316 ->caller( __METHOD__ );
319 if ( $this->
getHookContainer()->isRegistered(
'ApiQueryBaseBeforeQuery' ) ) {
320 $query->legacyMutator(
321 function ( &$tables, &$fields, &$conds, &$options, &$join_conds ) use ( &$hookData ) {
323 $this, $tables, $fields, $conds,
324 $options, $join_conds, $hookData );
331 $res = $query->fetchResult()->getResultWrapper();
333 $this->
getHookRunner()->onApiQueryBaseAfterQuery( $this, $res, $hookData );
336 if ( $this->fld_title && $resultPageSet ===
null ) {
339 if ( $this->fld_parsedcomment ) {
340 $this->formattedComments = $this->commentFormatter->formatItems(
341 $this->commentFormatter->rows( $res )
342 ->indexField(
'rc_id' )
343 ->commentKey(
'rc_comment' )
344 ->namespaceField(
'rc_namespace' )
345 ->titleField(
'rc_title' )
355 foreach ( $res as $row ) {
356 if ( $count === 0 && $resultPageSet !==
null ) {
360 $this,
'continue',
"$row->rc_timestamp|$row->rc_id"
363 if ( ++$count > $params[
'limit'] ) {
370 if ( $resultPageSet ===
null ) {
375 $fit = $this->
processRow( $row, $vals, $hookData ) &&
376 $result->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
381 } elseif ( $params[
'generaterevisions'] ) {
382 $revid = (int)$row->rc_this_oldid;
391 if ( $resultPageSet ===
null ) {
393 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'rc' );
394 } elseif ( $params[
'generaterevisions'] ) {
395 $resultPageSet->populateFromRevisionIDs( $revids );
397 $resultPageSet->populateFromTitles( $titles );
415 $vals[
'type'] = $this->recentChangeLookup->convertSourceToType( $row->rc_source );
416 $isLog = $row->rc_source === RecentChange::SRC_LOG;
421 if ( $this->fld_title || $this->fld_ids ) {
422 if ( $isLog && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
423 $vals[
'actionhidden'] =
true;
427 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
429 if ( $this->fld_title ) {
432 if ( $this->fld_ids ) {
433 $vals[
'pageid'] = (int)$row->rc_cur_id;
434 $vals[
'revid'] = (int)$row->rc_this_oldid;
435 $vals[
'old_revid'] = (int)$row->rc_last_oldid;
440 if ( $this->fld_ids ) {
441 $vals[
'rcid'] = (int)$row->rc_id;
445 if ( $this->fld_user || $this->fld_userid ) {
446 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
447 $vals[
'userhidden'] =
true;
450 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
451 if ( $this->fld_user ) {
452 $vals[
'user'] = $row->rc_user_text;
455 if ( $this->fld_userid ) {
456 $vals[
'userid'] = (int)$row->rc_user;
459 if ( isset( $row->rc_user_text ) && $this->userNameUtils->isTemp( $row->rc_user_text ) ) {
460 $vals[
'temp'] =
true;
463 if ( !$row->rc_user ) {
464 $vals[
'anon'] =
true;
470 if ( $this->fld_flags ) {
471 $vals[
'bot'] = (bool)$row->rc_bot;
472 $vals[
'new'] = $row->rc_source == RecentChange::SRC_NEW;
473 $vals[
'minor'] = (bool)$row->rc_minor;
477 if ( $this->fld_sizes ) {
478 $vals[
'oldlen'] = (int)$row->rc_old_len;
479 $vals[
'newlen'] = (int)$row->rc_new_len;
483 if ( $this->fld_timestamp ) {
484 $vals[
'timestamp'] =
wfTimestamp( TS::ISO_8601, $row->rc_timestamp );
488 if ( $this->fld_comment || $this->fld_parsedcomment ) {
489 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
490 $vals[
'commenthidden'] =
true;
493 if ( RevisionRecord::userCanBitfield(
494 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
496 if ( $this->fld_comment ) {
497 $vals[
'comment'] = $this->commentStore->getComment(
'rc_comment', $row )->text;
500 if ( $this->fld_parsedcomment ) {
501 $vals[
'parsedcomment'] = $this->formattedComments[$row->rc_id];
506 if ( $this->fld_redirect ) {
507 $vals[
'redirect'] = (bool)$row->page_is_redirect;
511 if ( $this->fld_patrolled ) {
512 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
513 $vals[
'unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
514 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
517 if ( $this->fld_loginfo && $row->rc_source == RecentChange::SRC_LOG ) {
518 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
519 $vals[
'actionhidden'] =
true;
522 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
523 $vals[
'logid'] = (int)$row->rc_logid;
524 $vals[
'logtype'] = $row->rc_log_type;
525 $vals[
'logaction'] = $row->rc_log_action;
526 $vals[
'logparams'] = $this->logFormatterFactory->newFromRow( $row )->formatParametersForApi();
530 if ( $this->fld_tags ) {
531 if ( $row->ts_tags ) {
532 $tags = explode(
',', $row->ts_tags );
534 $vals[
'tags'] = $tags;
540 if ( $this->fld_sha1 && $row->rev_slot_pairs !==
null ) {
541 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
542 $vals[
'sha1hidden'] =
true;
545 if ( RevisionRecord::userCanBitfield(
546 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
548 $combinedBase36 =
'';
549 if ( $row->rev_slot_pairs !==
'' ) {
550 $items = explode(
',', $row->rev_slot_pairs );
552 foreach ( $items as $item ) {
553 $parts = explode(
':', $item );
554 $slotHashes[$parts[0]] = $parts[1];
556 ksort( $slotHashes );
559 foreach ( $slotHashes as $slotHash ) {
560 $accu = $accu ===
null
562 : SlotRecord::base36Sha1( $accu . $slotHash );
564 $combinedBase36 = $accu ?? SlotRecord::base36Sha1(
'' );
567 $vals[
'sha1'] = $combinedBase36 !==
''
568 ? \Wikimedia\base_convert( $combinedBase36, 36, 16, 40 )
573 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
574 $vals[
'suppressed'] =
true;
612 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
613 sort( $slotRoles, SORT_STRING );
617 ParamValidator::PARAM_TYPE =>
'timestamp'
620 ParamValidator::PARAM_TYPE =>
'timestamp'
623 ParamValidator::PARAM_DEFAULT =>
'older',
624 ParamValidator::PARAM_TYPE => [
630 'newer' =>
'api-help-paramvalue-direction-newer',
631 'older' =>
'api-help-paramvalue-direction-older',
635 ParamValidator::PARAM_ISMULTI =>
true,
636 ParamValidator::PARAM_TYPE =>
'namespace',
640 ParamValidator::PARAM_TYPE =>
'user',
641 UserDef::PARAM_RETURN_OBJECT =>
true,
642 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
645 ParamValidator::PARAM_TYPE =>
'user',
646 UserDef::PARAM_RETURN_OBJECT =>
true,
647 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
651 ParamValidator::PARAM_ISMULTI =>
true,
652 ParamValidator::PARAM_DEFAULT =>
'title|timestamp|ids',
653 ParamValidator::PARAM_TYPE => [
672 ParamValidator::PARAM_ISMULTI =>
true,
673 ParamValidator::PARAM_TYPE => [
690 ParamValidator::PARAM_DEFAULT => 10,
691 ParamValidator::PARAM_TYPE =>
'limit',
692 IntegerDef::PARAM_MIN => 1,
698 ParamValidator::PARAM_DEFAULT =>
'edit|new|log|categorize',
699 ParamValidator::PARAM_ISMULTI =>
true,
700 ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
707 'generaterevisions' =>
false,
709 ParamValidator::PARAM_TYPE => $slotRoles