97 $this->fld_comment = isset( $prop[
'comment'] );
98 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
99 $this->fld_user = isset( $prop[
'user'] );
100 $this->fld_userid = isset( $prop[
'userid'] );
101 $this->fld_flags = isset( $prop[
'flags'] );
102 $this->fld_timestamp = isset( $prop[
'timestamp'] );
103 $this->fld_title = isset( $prop[
'title'] );
104 $this->fld_ids = isset( $prop[
'ids'] );
105 $this->fld_sizes = isset( $prop[
'sizes'] );
106 $this->fld_redirect = isset( $prop[
'redirect'] );
107 $this->fld_patrolled = isset( $prop[
'patrolled'] );
108 $this->fld_loginfo = isset( $prop[
'loginfo'] );
109 $this->fld_tags = isset( $prop[
'tags'] );
110 $this->fld_sha1 = isset( $prop[
'sha1'] );
127 public function run( $resultPageSet =
null ) {
132 $query = $this->changesListQueryFactory->newQuery();
133 $query->watchlistUser( $user )
136 $sources = $this->recentChangeLookup->getAllSources();
138 if ( $params[
'dir'] ===
'newer' ) {
139 $query->orderBy( ChangesListQuery::SORT_TIMESTAMP_ASC );
141 $startTimestamp = $params[
'start'];
142 $end = $params[
'end'];
143 if ( $params[
'continue'] !==
null ) {
145 $startTimestamp = $cont[0];
150 if ( $startTimestamp !==
null ) {
151 $query->startAt( $startTimestamp, $startId );
153 if ( $end !==
null ) {
154 $query->endAt( $end );
157 if ( $params[
'type'] !==
null ) {
158 $sources = array_intersect( $sources,
159 $this->recentChangeLookup->convertTypeToSources( $params[
'type'] ) );
162 $title = $params[
'title'];
163 if ( $title !==
null ) {
164 $titleObj = Title::newFromText( $title );
165 if ( $titleObj ===
null || $titleObj->isExternal() ) {
168 if ( $params[
'namespace'] && !in_array( $titleObj->getNamespace(), $params[
'namespace'] ) ) {
171 $query->requireTitle( $titleObj );
173 } elseif ( $params[
'namespace'] !==
null ) {
174 $query->requireNamespaces( $params[
'namespace'] );
177 if ( $params[
'show'] !==
null ) {
178 $show = array_fill_keys( $params[
'show'],
true );
181 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
182 || ( isset( $show[
'bot'] ) && isset( $show[
'!bot'] ) )
183 || ( isset( $show[
'anon'] ) && isset( $show[
'!anon'] ) )
184 || ( isset( $show[
'redirect'] ) && isset( $show[
'!redirect'] ) )
185 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
186 || ( isset( $show[
'patrolled'] ) && isset( $show[
'unpatrolled'] ) )
187 || ( isset( $show[
'!patrolled'] ) && isset( $show[
'unpatrolled'] ) )
188 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
189 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'unpatrolled'] ) )
190 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
196 if ( $this->includesPatrollingFlags( $show ) && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
197 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
202 'minor' => [
'require',
'minor', true ],
203 '!minor' => [
'exclude',
'minor', true ],
204 'bot' => [
'require',
'bot', true ],
205 '!bot' => [
'exclude',
'bot', true ],
206 'anon' => [
'exclude',
'named' ],
207 '!anon' => [
'require',
'named' ],
208 'patrolled' => [
'exclude',
'patrolled', RecentChange::PRC_UNPATROLLED ],
209 '!patrolled' => [
'require',
'patrolled', RecentChange::PRC_UNPATROLLED ],
210 'redirect' => [
'require',
'redirect', true ],
211 '!redirect' => [
'exclude',
'redirect', true ],
212 'autopatrolled' => [
'require',
'patrolled', RecentChange::PRC_AUTOPATROLLED ],
213 '!autopatrolled' => [
'exclude',
'patrolled', RecentChange::PRC_AUTOPATROLLED ],
215 foreach ( $show as $name => $unused ) {
216 if ( isset( $showActions[$name] ) ) {
217 $query->applyAction( ...$showActions[$name] );
221 if ( isset( $show[
'unpatrolled'] ) ) {
223 if ( $user->useRCPatrol() ) {
224 $query->requirePatrolled( RecentChange::PRC_UNPATROLLED );
225 } elseif ( $user->useNPPatrol() ) {
226 $query->requirePatrolled( RecentChange::PRC_UNPATROLLED );
227 $sources = array_intersect( $sources, [ RecentChange::SRC_NEW ] );
234 if ( $params[
'prop'] !==
null ) {
235 $prop = array_fill_keys( $params[
'prop'],
true );
241 if ( $this->fld_user || $this->fld_userid ) {
242 $query->rcUserFields();
245 if ( $params[
'user'] !==
null ) {
246 $query->requireUser( $params[
'user'] );
249 if ( $params[
'excludeuser'] !==
null ) {
250 $query->excludeUser( $params[
'excludeuser'] );
265 if ( $params[
'prop'] !==
null ) {
266 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
267 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
271 if ( $this->fld_ids ) {
272 $query->fields( [
'rc_this_oldid',
'rc_last_oldid' ] );
274 if ( $this->fld_flags ) {
275 $query->fields( [
'rc_minor',
'rc_bot' ] );
277 if ( $this->fld_sizes ) {
278 $query->fields( [
'rc_old_len',
'rc_new_len' ] );
280 if ( $this->fld_patrolled ) {
281 $query->fields( [
'rc_patrolled',
'rc_log_type' ] );
283 if ( $this->fld_loginfo ) {
284 $query->fields( [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ] );
286 if ( $this->fld_redirect ) {
287 $query->addRedirectField();
290 if ( $resultPageSet && $params[
'generaterevisions'] ) {
291 $query->fields( [
'rc_this_oldid' ] );
293 if ( $this->fld_tags ) {
294 $query->addChangeTagSummaryField();
296 if ( $this->fld_sha1 ) {
297 $query->sha1Fields();
299 if ( $params[
'toponly'] ) {
300 $query->requireLatest();
302 if ( $params[
'tag'] !==
null ) {
303 $query->requireChangeTags( [ $params[
'tag'] ] );
307 if ( $params[
'user'] !==
null || $params[
'excludeuser'] !==
null ) {
308 $query->excludeDeletedUser();
310 if ( $this->
getRequest()->getCheck(
'namespace' ) ) {
311 $query->excludeDeletedLogAction();
314 if ( $this->fld_comment || $this->fld_parsedcomment ) {
315 $query->commentFields();
318 if ( $params[
'slot'] !==
null ) {
320 $sources = array_intersect( $sources,
321 [ RecentChange::SRC_NEW, RecentChange::SRC_EDIT ] );
322 $query->requireSlotChanged( $params[
'slot'] );
325 $query->requireSources( $sources )
326 ->limit( $params[
'limit'] + 1 )
327 ->maxExecutionTime( $this->
getConfig()->
get(
329 ->caller( __METHOD__ );
332 if ( $this->
getHookContainer()->isRegistered(
'ApiQueryBaseBeforeQuery' ) ) {
333 $query->legacyMutator(
334 function ( &$tables, &$fields, &$conds, &$options, &$join_conds ) use ( &$hookData ) {
336 $this, $tables, $fields, $conds,
337 $options, $join_conds, $hookData );
344 $res = $query->fetchResult()->getResultWrapper();
346 $this->
getHookRunner()->onApiQueryBaseAfterQuery( $this, $res, $hookData );
349 if ( $this->fld_title && $resultPageSet ===
null ) {
352 if ( $this->fld_parsedcomment ) {
353 $this->formattedComments = $this->commentFormatter->formatItems(
354 $this->commentFormatter->rows( $res )
355 ->indexField(
'rc_id' )
356 ->commentKey(
'rc_comment' )
357 ->namespaceField(
'rc_namespace' )
358 ->titleField(
'rc_title' )
368 foreach ( $res as $row ) {
369 if ( $count === 0 && $resultPageSet !==
null ) {
373 $this,
'continue',
"$row->rc_timestamp|$row->rc_id"
376 if ( ++$count > $params[
'limit'] ) {
383 if ( $resultPageSet ===
null ) {
388 $fit = $this->
processRow( $row, $vals, $hookData ) &&
389 $result->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
394 } elseif ( $params[
'generaterevisions'] ) {
395 $revid = (int)$row->rc_this_oldid;
404 if ( $resultPageSet ===
null ) {
406 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'rc' );
407 } elseif ( $params[
'generaterevisions'] ) {
408 $resultPageSet->populateFromRevisionIDs( $revids );
410 $resultPageSet->populateFromTitles( $titles );
428 $vals[
'type'] = $this->recentChangeLookup->convertSourceToType( $row->rc_source );
429 $isLog = $row->rc_source === RecentChange::SRC_LOG;
434 if ( $this->fld_title || $this->fld_ids ) {
435 if ( $isLog && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
436 $vals[
'actionhidden'] =
true;
440 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
442 if ( $this->fld_title ) {
445 if ( $this->fld_ids ) {
446 $vals[
'pageid'] = (int)$row->rc_cur_id;
447 $vals[
'revid'] = (int)$row->rc_this_oldid;
448 $vals[
'old_revid'] = (int)$row->rc_last_oldid;
453 if ( $this->fld_ids ) {
454 $vals[
'rcid'] = (int)$row->rc_id;
458 if ( $this->fld_user || $this->fld_userid ) {
459 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
460 $vals[
'userhidden'] =
true;
463 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
464 if ( $this->fld_user ) {
465 $vals[
'user'] = $row->rc_user_text;
468 if ( $this->fld_userid ) {
469 $vals[
'userid'] = (int)$row->rc_user;
472 if ( isset( $row->rc_user_text ) && $this->userNameUtils->isTemp( $row->rc_user_text ) ) {
473 $vals[
'temp'] =
true;
476 if ( !$row->rc_user ) {
477 $vals[
'anon'] =
true;
483 if ( $this->fld_flags ) {
484 $vals[
'bot'] = (bool)$row->rc_bot;
485 $vals[
'new'] = $row->rc_source == RecentChange::SRC_NEW;
486 $vals[
'minor'] = (bool)$row->rc_minor;
490 if ( $this->fld_sizes ) {
491 $vals[
'oldlen'] = (int)$row->rc_old_len;
492 $vals[
'newlen'] = (int)$row->rc_new_len;
496 if ( $this->fld_timestamp ) {
497 $vals[
'timestamp'] =
wfTimestamp( TS::ISO_8601, $row->rc_timestamp );
501 if ( $this->fld_comment || $this->fld_parsedcomment ) {
502 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
503 $vals[
'commenthidden'] =
true;
506 if ( RevisionRecord::userCanBitfield(
507 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
509 if ( $this->fld_comment ) {
510 $vals[
'comment'] = $this->commentStore->getComment(
'rc_comment', $row )->text;
513 if ( $this->fld_parsedcomment ) {
514 $vals[
'parsedcomment'] = $this->formattedComments[$row->rc_id];
519 if ( $this->fld_redirect ) {
520 $vals[
'redirect'] = (bool)$row->page_is_redirect;
524 if ( $this->fld_patrolled ) {
525 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
526 $vals[
'unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
527 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
530 if ( $this->fld_loginfo && $row->rc_source == RecentChange::SRC_LOG ) {
531 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
532 $vals[
'actionhidden'] =
true;
535 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
536 $vals[
'logid'] = (int)$row->rc_logid;
537 $vals[
'logtype'] = $row->rc_log_type;
538 $vals[
'logaction'] = $row->rc_log_action;
539 $vals[
'logparams'] = $this->logFormatterFactory->newFromRow( $row )->formatParametersForApi();
543 if ( $this->fld_tags ) {
544 if ( $row->ts_tags ) {
545 $tags = explode(
',', $row->ts_tags );
547 $vals[
'tags'] = $tags;
553 if ( $this->fld_sha1 && $row->rev_slot_pairs !==
null ) {
554 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
555 $vals[
'sha1hidden'] =
true;
558 if ( RevisionRecord::userCanBitfield(
559 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
561 $combinedBase36 =
'';
562 if ( $row->rev_slot_pairs !==
'' ) {
563 $items = explode(
',', $row->rev_slot_pairs );
565 foreach ( $items as $item ) {
566 $parts = explode(
':', $item );
567 $slotHashes[$parts[0]] = $parts[1];
569 ksort( $slotHashes );
572 foreach ( $slotHashes as $slotHash ) {
573 $accu = $accu ===
null
575 : SlotRecord::base36Sha1( $accu . $slotHash );
577 $combinedBase36 = $accu ?? SlotRecord::base36Sha1(
'' );
580 $vals[
'sha1'] = $combinedBase36 !==
''
581 ? \Wikimedia\base_convert( $combinedBase36, 36, 16, 40 )
586 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
587 $vals[
'suppressed'] =
true;
625 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
626 sort( $slotRoles, SORT_STRING );
630 ParamValidator::PARAM_TYPE =>
'timestamp'
633 ParamValidator::PARAM_TYPE =>
'timestamp'
636 ParamValidator::PARAM_DEFAULT =>
'older',
637 ParamValidator::PARAM_TYPE => [
643 'newer' =>
'api-help-paramvalue-direction-newer',
644 'older' =>
'api-help-paramvalue-direction-older',
648 ParamValidator::PARAM_ISMULTI =>
true,
649 ParamValidator::PARAM_TYPE =>
'namespace',
653 ParamValidator::PARAM_TYPE =>
'user',
654 UserDef::PARAM_RETURN_OBJECT =>
true,
655 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
658 ParamValidator::PARAM_TYPE =>
'user',
659 UserDef::PARAM_RETURN_OBJECT =>
true,
660 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
664 ParamValidator::PARAM_ISMULTI =>
true,
665 ParamValidator::PARAM_DEFAULT =>
'title|timestamp|ids',
666 ParamValidator::PARAM_TYPE => [
685 ParamValidator::PARAM_ISMULTI =>
true,
686 ParamValidator::PARAM_TYPE => [
703 ParamValidator::PARAM_DEFAULT => 10,
704 ParamValidator::PARAM_TYPE =>
'limit',
705 IntegerDef::PARAM_MIN => 1,
711 ParamValidator::PARAM_DEFAULT =>
'edit|new|log|categorize',
712 ParamValidator::PARAM_ISMULTI =>
true,
713 ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
720 'generaterevisions' =>
false,
722 ParamValidator::PARAM_TYPE => $slotRoles