52 private $formattedComments = [];
74 parent::__construct( $query, $moduleName,
'rc' );
75 $this->commentStore = $commentStore;
76 $this->commentFormatter = $commentFormatter;
77 $this->changeTagDefStore = $changeTagDefStore;
78 $this->slotRoleStore = $slotRoleStore;
79 $this->slotRoleRegistry = $slotRoleRegistry;
80 $this->userNameUtils = $userNameUtils;
83 private bool $fld_comment =
false;
84 private bool $fld_parsedcomment =
false;
85 private bool $fld_user =
false;
86 private bool $fld_userid =
false;
87 private bool $fld_flags =
false;
88 private bool $fld_timestamp =
false;
89 private bool $fld_title =
false;
90 private bool $fld_ids =
false;
91 private bool $fld_sizes =
false;
92 private bool $fld_redirect =
false;
93 private bool $fld_patrolled =
false;
94 private bool $fld_loginfo =
false;
95 private bool $fld_tags =
false;
96 private bool $fld_sha1 =
false;
103 $this->fld_comment = isset( $prop[
'comment'] );
104 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
105 $this->fld_user = isset( $prop[
'user'] );
106 $this->fld_userid = isset( $prop[
'userid'] );
107 $this->fld_flags = isset( $prop[
'flags'] );
108 $this->fld_timestamp = isset( $prop[
'timestamp'] );
109 $this->fld_title = isset( $prop[
'title'] );
110 $this->fld_ids = isset( $prop[
'ids'] );
111 $this->fld_sizes = isset( $prop[
'sizes'] );
112 $this->fld_redirect = isset( $prop[
'redirect'] );
113 $this->fld_patrolled = isset( $prop[
'patrolled'] );
114 $this->fld_loginfo = isset( $prop[
'loginfo'] );
115 $this->fld_tags = isset( $prop[
'tags'] );
116 $this->fld_sha1 = isset( $prop[
'sha1'] );
124 $this->
run( $resultPageSet );
132 public function run( $resultPageSet =
null ) {
144 if (
$params[
'continue'] !==
null ) {
146 $db = $this->
getDB();
147 $op =
$params[
'dir'] ===
'older' ?
'<=' :
'>=';
148 $this->
addWhere( $db->buildComparison( $op, [
149 'rc_timestamp' => $db->timestamp( $cont[0] ),
154 $order =
$params[
'dir'] ===
'older' ?
'DESC' :
'ASC';
156 "rc_timestamp $order",
160 if (
$params[
'type'] !==
null ) {
165 if ( $title !==
null ) {
166 $titleObj = Title::newFromText( $title );
167 if ( $titleObj ===
null || $titleObj->isExternal() ) {
169 } elseif (
$params[
'namespace'] && !in_array( $titleObj->getNamespace(),
$params[
'namespace'] ) ) {
172 $this->
addWhereFld(
'rc_namespace', $titleObj->getNamespace() );
173 $this->
addWhereFld(
'rc_title', $titleObj->getDBkey() );
178 if (
$params[
'show'] !==
null ) {
179 $show = array_fill_keys(
$params[
'show'],
true );
182 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
183 || ( isset( $show[
'bot'] ) && isset( $show[
'!bot'] ) )
184 || ( isset( $show[
'anon'] ) && isset( $show[
'!anon'] ) )
185 || ( isset( $show[
'redirect'] ) && isset( $show[
'!redirect'] ) )
186 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
187 || ( isset( $show[
'patrolled'] ) && isset( $show[
'unpatrolled'] ) )
188 || ( isset( $show[
'!patrolled'] ) && isset( $show[
'unpatrolled'] ) )
189 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
190 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'unpatrolled'] ) )
191 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
197 if ( $this->includesPatrollingFlags( $show ) && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
198 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
202 $this->
addWhereIf( [
'rc_minor' => 0 ], isset( $show[
'!minor'] ) );
203 $this->
addWhereIf(
'rc_minor != 0', isset( $show[
'minor'] ) );
204 $this->
addWhereIf( [
'rc_bot' => 0 ], isset( $show[
'!bot'] ) );
205 $this->
addWhereIf(
'rc_bot != 0', isset( $show[
'bot'] ) );
206 if ( isset( $show[
'anon'] ) || isset( $show[
'!anon'] ) ) {
208 $this->
addJoinConds( [
'actor' => [
'JOIN',
'actor_id=rc_actor' ] ] );
210 [
'actor_user' =>
null ], isset( $show[
'anon'] )
213 'actor_user IS NOT NULL', isset( $show[
'!anon'] )
216 $this->
addWhereIf( [
'rc_patrolled' => 0 ], isset( $show[
'!patrolled'] ) );
217 $this->
addWhereIf(
'rc_patrolled != 0', isset( $show[
'patrolled'] ) );
218 $this->
addWhereIf( [
'page_is_redirect' => 1 ], isset( $show[
'redirect'] ) );
220 if ( isset( $show[
'unpatrolled'] ) ) {
222 if ( $user->useRCPatrol() ) {
223 $this->
addWhereFld(
'rc_patrolled', RecentChange::PRC_UNPATROLLED );
224 } elseif ( $user->useNPPatrol() ) {
225 $this->
addWhereFld(
'rc_patrolled', RecentChange::PRC_UNPATROLLED );
231 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
232 isset( $show[
'!autopatrolled'] )
235 [
'rc_patrolled' => RecentChange::PRC_AUTOPATROLLED ],
236 isset( $show[
'autopatrolled'] )
241 [
'page_is_redirect' => [ 0,
null ] ],
242 isset( $show[
'!redirect'] )
248 if (
$params[
'prop'] !==
null ) {
249 $prop = array_fill_keys(
$params[
'prop'],
true );
258 ||
$params[
'excludeuser'] !==
null
261 $this->
addFields( [
'actor_name',
'actor_user',
'rc_actor' ] );
262 $this->
addJoinConds( [
'actor' => [
'JOIN',
'actor_id=rc_actor' ] ] );
265 if (
$params[
'user'] !==
null ) {
269 if (
$params[
'excludeuser'] !==
null ) {
284 $showRedirects =
false;
286 if (
$params[
'prop'] !==
null ) {
287 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
288 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
292 $this->
addFieldsIf( [
'rc_this_oldid',
'rc_last_oldid' ], $this->fld_ids );
293 $this->
addFieldsIf( [
'rc_minor',
'rc_type',
'rc_bot' ], $this->fld_flags );
294 $this->
addFieldsIf( [
'rc_old_len',
'rc_new_len' ], $this->fld_sizes );
295 $this->
addFieldsIf( [
'rc_patrolled',
'rc_log_type' ], $this->fld_patrolled );
297 [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ],
300 $showRedirects = $this->fld_redirect || isset( $show[
'redirect'] )
301 || isset( $show[
'!redirect'] );
304 $resultPageSet &&
$params[
'generaterevisions'] );
306 if ( $this->fld_tags ) {
310 if ( $this->fld_sha1 ) {
313 [
'rc_this_oldid=rev_id' ] ] ] );
314 $this->
addFields( [
'rev_sha1',
'rev_deleted' ] );
317 if (
$params[
'toponly'] || $showRedirects ) {
320 [
'rc_namespace=page_namespace',
'rc_title=page_title' ] ] ] );
324 $this->
addWhere(
'rc_this_oldid = page_latest' );
328 if (
$params[
'tag'] !==
null ) {
330 $this->
addJoinConds( [
'change_tag' => [
'JOIN', [
'rc_id=ct_rc_id' ] ] ] );
340 if (
$params[
'user'] !==
null ||
$params[
'excludeuser'] !==
null ) {
341 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
342 $bitmask = RevisionRecord::DELETED_USER;
343 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
344 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
349 $this->
addWhere( $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask" );
352 if ( $this->
getRequest()->getCheck(
'namespace' ) ) {
354 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
355 $bitmask = LogPage::DELETED_ACTION;
356 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
357 $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
364 $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask",
369 if ( $this->fld_comment || $this->fld_parsedcomment ) {
370 $commentQuery = $this->commentStore->getJoin(
'rc_comment' );
371 $this->
addTables( $commentQuery[
'tables'] );
372 $this->
addFields( $commentQuery[
'fields'] );
376 if (
$params[
'slot'] !==
null ) {
378 $slotId = $this->slotRoleStore->getId(
$params[
'slot'] );
379 }
catch ( Exception $e ) {
384 'slot' =>
'slots',
'parent_slot' =>
'slots'
387 'slot' => [
'LEFT JOIN', [
388 'rc_this_oldid = slot.slot_revision_id',
389 'slot.slot_role_id' => $slotId,
391 'parent_slot' => [
'LEFT JOIN', [
392 'rc_last_oldid = parent_slot.slot_revision_id',
393 'parent_slot.slot_role_id' => $slotId,
403 'slot.slot_origin = slot.slot_revision_id OR ' .
404 'slot.slot_content_id != parent_slot.slot_content_id OR ' .
405 '(slot.slot_content_id IS NULL AND parent_slot.slot_content_id IS NOT NULL) OR ' .
406 '(slot.slot_content_id IS NOT NULL AND parent_slot.slot_content_id IS NULL)'
409 $changeTypes = RecentChange::parseToRCType(
410 array_intersect(
$params[
'type'], [
'new',
'edit' ] )
412 if ( count( $changeTypes ) ) {
417 $this->
addWhere( [
'rc_type' =>
null ] );
423 'MAX_EXECUTION_TIME',
424 $this->
getConfig()->
get( MainConfigNames::MaxExecutionTimeForExpensiveQueries )
430 $res = $this->
select( __METHOD__, [], $hookData );
433 if ( $this->fld_title && $resultPageSet ===
null ) {
436 if ( $this->fld_parsedcomment ) {
437 $this->formattedComments = $this->commentFormatter->formatItems(
438 $this->commentFormatter->rows( $res )
439 ->indexField(
'rc_id' )
440 ->commentKey(
'rc_comment' )
441 ->namespaceField(
'rc_namespace' )
442 ->titleField(
'rc_title' )
452 foreach ( $res as $row ) {
453 if ( $count === 0 && $resultPageSet !==
null ) {
457 $this,
'continue',
"$row->rc_timestamp|$row->rc_id"
460 if ( ++$count >
$params[
'limit'] ) {
467 if ( $resultPageSet ===
null ) {
472 $fit = $this->
processRow( $row, $vals, $hookData ) &&
473 $result->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
478 } elseif (
$params[
'generaterevisions'] ) {
479 $revid = (int)$row->rc_this_oldid;
484 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
488 if ( $resultPageSet ===
null ) {
490 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'rc' );
491 } elseif (
$params[
'generaterevisions'] ) {
492 $resultPageSet->populateFromRevisionIDs( $revids );
494 $resultPageSet->populateFromTitles( $titles );
506 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
512 $type = (int)$row->rc_type;
513 $vals[
'type'] = RecentChange::parseFromRCType( $type );
518 if ( $this->fld_title || $this->fld_ids ) {
519 if ( $type ===
RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
520 $vals[
'actionhidden'] =
true;
524 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
526 if ( $this->fld_title ) {
529 if ( $this->fld_ids ) {
530 $vals[
'pageid'] = (int)$row->rc_cur_id;
531 $vals[
'revid'] = (int)$row->rc_this_oldid;
532 $vals[
'old_revid'] = (int)$row->rc_last_oldid;
537 if ( $this->fld_ids ) {
538 $vals[
'rcid'] = (int)$row->rc_id;
542 if ( $this->fld_user || $this->fld_userid ) {
543 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
544 $vals[
'userhidden'] =
true;
547 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
548 if ( $this->fld_user ) {
549 $vals[
'user'] = $row->actor_name;
552 if ( $this->fld_userid ) {
553 $vals[
'userid'] = (int)$row->actor_user;
556 if ( isset( $row->actor_name ) && $this->userNameUtils->isTemp( $row->actor_name ) ) {
557 $vals[
'temp'] =
true;
560 if ( !$row->actor_user ) {
561 $vals[
'anon'] =
true;
567 if ( $this->fld_flags ) {
568 $vals[
'bot'] = (bool)$row->rc_bot;
569 $vals[
'new'] = $row->rc_type ==
RC_NEW;
570 $vals[
'minor'] = (bool)$row->rc_minor;
574 if ( $this->fld_sizes ) {
575 $vals[
'oldlen'] = (int)$row->rc_old_len;
576 $vals[
'newlen'] = (int)$row->rc_new_len;
580 if ( $this->fld_timestamp ) {
581 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
585 if ( $this->fld_comment || $this->fld_parsedcomment ) {
586 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
587 $vals[
'commenthidden'] =
true;
590 if ( RevisionRecord::userCanBitfield(
591 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
593 if ( $this->fld_comment ) {
594 $vals[
'comment'] = $this->commentStore->getComment(
'rc_comment', $row )->text;
597 if ( $this->fld_parsedcomment ) {
598 $vals[
'parsedcomment'] = $this->formattedComments[$row->rc_id];
603 if ( $this->fld_redirect ) {
604 $vals[
'redirect'] = (bool)$row->page_is_redirect;
608 if ( $this->fld_patrolled ) {
609 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
610 $vals[
'unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
611 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
614 if ( $this->fld_loginfo && $row->rc_type ==
RC_LOG ) {
615 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
616 $vals[
'actionhidden'] =
true;
619 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
620 $vals[
'logid'] = (int)$row->rc_logid;
621 $vals[
'logtype'] = $row->rc_log_type;
622 $vals[
'logaction'] = $row->rc_log_action;
623 $vals[
'logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
627 if ( $this->fld_tags ) {
628 if ( $row->ts_tags ) {
629 $tags = explode(
',', $row->ts_tags );
630 ApiResult::setIndexedTagName( $tags,
'tag' );
631 $vals[
'tags'] = $tags;
637 if ( $this->fld_sha1 && $row->rev_sha1 !==
null ) {
638 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
639 $vals[
'sha1hidden'] =
true;
642 if ( RevisionRecord::userCanBitfield(
643 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
645 if ( $row->rev_sha1 !==
'' ) {
646 $vals[
'sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
653 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
654 $vals[
'suppressed'] =
true;
664 private function includesPatrollingFlags( array $flagsArray ) {
665 return isset( $flagsArray[
'patrolled'] ) ||
666 isset( $flagsArray[
'!patrolled'] ) ||
667 isset( $flagsArray[
'unpatrolled'] ) ||
668 isset( $flagsArray[
'autopatrolled'] ) ||
669 isset( $flagsArray[
'!autopatrolled'] );
673 if ( isset(
$params[
'show'] ) &&
674 $this->includesPatrollingFlags( array_fill_keys(
$params[
'show'],
true ) )
681 if (
$params[
'prop'] !==
null && in_array(
'parsedcomment',
$params[
'prop'] ) ) {
683 return 'anon-public-user-private';
690 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
691 sort( $slotRoles, SORT_STRING );
695 ParamValidator::PARAM_TYPE =>
'timestamp'
698 ParamValidator::PARAM_TYPE =>
'timestamp'
701 ParamValidator::PARAM_DEFAULT =>
'older',
702 ParamValidator::PARAM_TYPE => [
708 'newer' =>
'api-help-paramvalue-direction-newer',
709 'older' =>
'api-help-paramvalue-direction-older',
713 ParamValidator::PARAM_ISMULTI =>
true,
714 ParamValidator::PARAM_TYPE =>
'namespace',
718 ParamValidator::PARAM_TYPE =>
'user',
719 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
722 ParamValidator::PARAM_TYPE =>
'user',
723 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
727 ParamValidator::PARAM_ISMULTI =>
true,
728 ParamValidator::PARAM_DEFAULT =>
'title|timestamp|ids',
729 ParamValidator::PARAM_TYPE => [
748 ParamValidator::PARAM_ISMULTI =>
true,
749 ParamValidator::PARAM_TYPE => [
766 ParamValidator::PARAM_DEFAULT => 10,
767 ParamValidator::PARAM_TYPE =>
'limit',
768 IntegerDef::PARAM_MIN => 1,
773 ParamValidator::PARAM_DEFAULT =>
'edit|new|log|categorize',
774 ParamValidator::PARAM_ISMULTI =>
true,
775 ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
782 'generaterevisions' =>
false,
784 ParamValidator::PARAM_TYPE => $slotRoles
791 'action=query&list=recentchanges'
792 =>
'apihelp-query+recentchanges-example-simple',
793 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
794 =>
'apihelp-query+recentchanges-example-generator',
799 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Recentchanges';
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
array $params
The job parameters.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
const LIMIT_BIG1
Fast query, standard limit.
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
getResult()
Get the result object.
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
const LIMIT_BIG2
Fast query, apihighlimits limit.
getModuleName()
Get the name of the module being executed by this instance.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
getDB()
Get the Query database connection (read-only)
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addWhere( $value)
Add a set of WHERE clauses to the internal array.
userCanSeeRevDel()
Check whether the current user has permission to view revision-deleted fields.
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
A query action to enumerate the recent changes that were done to the wiki.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
initProperties( $prop)
Sets internal state to include the desired properties in the output.
getExamplesMessages()
Returns usage examples for this module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
executeGenerator( $resultPageSet)
Execute this module as a generator.
run( $resultPageSet=null)
Generates and outputs the result of this query based upon the provided parameters.
getHelpUrls()
Return links to more detailed help pages about the module.
getCacheMode( $params)
Get the cache mode for the data generated by this module.
extractRowInfo( $row)
Extracts from a single sql row the data needed to describe one recent change.
__construct(ApiQuery $query, $moduleName, CommentStore $commentStore, RowCommentFormatter $commentFormatter, NameTableStore $changeTagDefStore, NameTableStore $slotRoleStore, SlotRoleRegistry $slotRoleRegistry, UserNameUtils $userNameUtils)
This is the main query class.
A class containing constants representing the names of configuration variables.