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 $fld_comment =
false, $fld_parsedcomment =
false, $fld_user =
false, $fld_userid =
false,
84 $fld_flags =
false, $fld_timestamp =
false, $fld_title =
false, $fld_ids =
false,
85 $fld_sizes =
false, $fld_redirect =
false, $fld_patrolled =
false, $fld_loginfo =
false,
86 $fld_tags =
false, $fld_sha1 =
false;
93 $this->fld_comment = isset( $prop[
'comment'] );
94 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
95 $this->fld_user = isset( $prop[
'user'] );
96 $this->fld_userid = isset( $prop[
'userid'] );
97 $this->fld_flags = isset( $prop[
'flags'] );
98 $this->fld_timestamp = isset( $prop[
'timestamp'] );
99 $this->fld_title = isset( $prop[
'title'] );
100 $this->fld_ids = isset( $prop[
'ids'] );
101 $this->fld_sizes = isset( $prop[
'sizes'] );
102 $this->fld_redirect = isset( $prop[
'redirect'] );
103 $this->fld_patrolled = isset( $prop[
'patrolled'] );
104 $this->fld_loginfo = isset( $prop[
'loginfo'] );
105 $this->fld_tags = isset( $prop[
'tags'] );
106 $this->fld_sha1 = isset( $prop[
'sha1'] );
114 $this->
run( $resultPageSet );
122 public function run( $resultPageSet =
null ) {
134 if (
$params[
'continue'] !==
null ) {
136 $db = $this->
getDB();
137 $op =
$params[
'dir'] ===
'older' ?
'<=' :
'>=';
138 $this->
addWhere( $db->buildComparison( $op, [
139 'rc_timestamp' => $db->timestamp( $cont[0] ),
144 $order =
$params[
'dir'] ===
'older' ?
'DESC' :
'ASC';
146 "rc_timestamp $order",
150 if (
$params[
'type'] !==
null ) {
155 if ( $title !==
null ) {
156 $titleObj = Title::newFromText( $title );
157 if ( $titleObj ===
null || $titleObj->isExternal() ) {
159 } elseif (
$params[
'namespace'] && !in_array( $titleObj->getNamespace(),
$params[
'namespace'] ) ) {
162 $this->
addWhereFld(
'rc_namespace', $titleObj->getNamespace() );
163 $this->
addWhereFld(
'rc_title', $titleObj->getDBkey() );
168 if (
$params[
'show'] !==
null ) {
169 $show = array_fill_keys(
$params[
'show'],
true );
172 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
173 || ( isset( $show[
'bot'] ) && isset( $show[
'!bot'] ) )
174 || ( isset( $show[
'anon'] ) && isset( $show[
'!anon'] ) )
175 || ( isset( $show[
'redirect'] ) && isset( $show[
'!redirect'] ) )
176 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
177 || ( isset( $show[
'patrolled'] ) && isset( $show[
'unpatrolled'] ) )
178 || ( isset( $show[
'!patrolled'] ) && isset( $show[
'unpatrolled'] ) )
179 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
180 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'unpatrolled'] ) )
181 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
187 if ( $this->includesPatrollingFlags( $show ) && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
188 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
192 $this->
addWhereIf( [
'rc_minor' => 0 ], isset( $show[
'!minor'] ) );
193 $this->
addWhereIf(
'rc_minor != 0', isset( $show[
'minor'] ) );
194 $this->
addWhereIf( [
'rc_bot' => 0 ], isset( $show[
'!bot'] ) );
195 $this->
addWhereIf(
'rc_bot != 0', isset( $show[
'bot'] ) );
196 if ( isset( $show[
'anon'] ) || isset( $show[
'!anon'] ) ) {
198 $this->
addJoinConds( [
'actor' => [
'JOIN',
'actor_id=rc_actor' ] ] );
200 [
'actor_user' =>
null ], isset( $show[
'anon'] )
203 'actor_user IS NOT NULL', isset( $show[
'!anon'] )
206 $this->
addWhereIf( [
'rc_patrolled' => 0 ], isset( $show[
'!patrolled'] ) );
207 $this->
addWhereIf(
'rc_patrolled != 0', isset( $show[
'patrolled'] ) );
208 $this->
addWhereIf( [
'page_is_redirect' => 1 ], isset( $show[
'redirect'] ) );
210 if ( isset( $show[
'unpatrolled'] ) ) {
212 if ( $user->useRCPatrol() ) {
213 $this->
addWhereFld(
'rc_patrolled', RecentChange::PRC_UNPATROLLED );
214 } elseif ( $user->useNPPatrol() ) {
215 $this->
addWhereFld(
'rc_patrolled', RecentChange::PRC_UNPATROLLED );
221 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
222 isset( $show[
'!autopatrolled'] )
225 [
'rc_patrolled' => RecentChange::PRC_AUTOPATROLLED ],
226 isset( $show[
'autopatrolled'] )
231 [
'page_is_redirect' => [ 0,
null ] ],
232 isset( $show[
'!redirect'] )
238 if (
$params[
'prop'] !==
null ) {
239 $prop = array_fill_keys(
$params[
'prop'],
true );
248 ||
$params[
'excludeuser'] !==
null
251 $this->
addFields( [
'actor_name',
'actor_user',
'rc_actor' ] );
252 $this->
addJoinConds( [
'actor' => [
'JOIN',
'actor_id=rc_actor' ] ] );
255 if (
$params[
'user'] !==
null ) {
259 if (
$params[
'excludeuser'] !==
null ) {
274 $showRedirects =
false;
276 if (
$params[
'prop'] !==
null ) {
277 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
278 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
282 $this->
addFieldsIf( [
'rc_this_oldid',
'rc_last_oldid' ], $this->fld_ids );
283 $this->
addFieldsIf( [
'rc_minor',
'rc_type',
'rc_bot' ], $this->fld_flags );
284 $this->
addFieldsIf( [
'rc_old_len',
'rc_new_len' ], $this->fld_sizes );
285 $this->
addFieldsIf( [
'rc_patrolled',
'rc_log_type' ], $this->fld_patrolled );
287 [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ],
290 $showRedirects = $this->fld_redirect || isset( $show[
'redirect'] )
291 || isset( $show[
'!redirect'] );
294 $resultPageSet &&
$params[
'generaterevisions'] );
296 if ( $this->fld_tags ) {
300 if ( $this->fld_sha1 ) {
303 [
'rc_this_oldid=rev_id' ] ] ] );
304 $this->
addFields( [
'rev_sha1',
'rev_deleted' ] );
307 if (
$params[
'toponly'] || $showRedirects ) {
310 [
'rc_namespace=page_namespace',
'rc_title=page_title' ] ] ] );
314 $this->
addWhere(
'rc_this_oldid = page_latest' );
318 if (
$params[
'tag'] !==
null ) {
320 $this->
addJoinConds( [
'change_tag' => [
'JOIN', [
'rc_id=ct_rc_id' ] ] ] );
330 if (
$params[
'user'] !==
null ||
$params[
'excludeuser'] !==
null ) {
331 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
332 $bitmask = RevisionRecord::DELETED_USER;
333 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
334 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
339 $this->
addWhere( $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask" );
342 if ( $this->
getRequest()->getCheck(
'namespace' ) ) {
344 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
345 $bitmask = LogPage::DELETED_ACTION;
346 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
347 $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
354 $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask",
359 if ( $this->fld_comment || $this->fld_parsedcomment ) {
360 $commentQuery = $this->commentStore->getJoin(
'rc_comment' );
361 $this->
addTables( $commentQuery[
'tables'] );
362 $this->
addFields( $commentQuery[
'fields'] );
366 if (
$params[
'slot'] !==
null ) {
368 $slotId = $this->slotRoleStore->getId(
$params[
'slot'] );
369 }
catch ( Exception $e ) {
374 'slot' =>
'slots',
'parent_slot' =>
'slots'
377 'slot' => [
'LEFT JOIN', [
378 'rc_this_oldid = slot.slot_revision_id',
379 'slot.slot_role_id' => $slotId,
381 'parent_slot' => [
'LEFT JOIN', [
382 'rc_last_oldid = parent_slot.slot_revision_id',
383 'parent_slot.slot_role_id' => $slotId,
393 'slot.slot_origin = slot.slot_revision_id OR ' .
394 'slot.slot_content_id != parent_slot.slot_content_id OR ' .
395 '(slot.slot_content_id IS NULL AND parent_slot.slot_content_id IS NOT NULL) OR ' .
396 '(slot.slot_content_id IS NOT NULL AND parent_slot.slot_content_id IS NULL)'
399 $changeTypes = RecentChange::parseToRCType(
400 array_intersect(
$params[
'type'], [
'new',
'edit' ] )
402 if ( count( $changeTypes ) ) {
407 $this->
addWhere( [
'rc_type' =>
null ] );
413 'MAX_EXECUTION_TIME',
414 $this->
getConfig()->
get( MainConfigNames::MaxExecutionTimeForExpensiveQueries )
420 $res = $this->
select( __METHOD__, [], $hookData );
423 if ( $this->fld_title && $resultPageSet ===
null ) {
426 if ( $this->fld_parsedcomment ) {
427 $this->formattedComments = $this->commentFormatter->formatItems(
428 $this->commentFormatter->rows( $res )
429 ->indexField(
'rc_id' )
430 ->commentKey(
'rc_comment' )
431 ->namespaceField(
'rc_namespace' )
432 ->titleField(
'rc_title' )
442 foreach ( $res as $row ) {
443 if ( $count === 0 && $resultPageSet !==
null ) {
447 $this,
'continue',
"$row->rc_timestamp|$row->rc_id"
450 if ( ++$count >
$params[
'limit'] ) {
457 if ( $resultPageSet ===
null ) {
462 $fit = $this->
processRow( $row, $vals, $hookData ) &&
463 $result->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
468 } elseif (
$params[
'generaterevisions'] ) {
469 $revid = (int)$row->rc_this_oldid;
474 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
478 if ( $resultPageSet ===
null ) {
480 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'rc' );
481 } elseif (
$params[
'generaterevisions'] ) {
482 $resultPageSet->populateFromRevisionIDs( $revids );
484 $resultPageSet->populateFromTitles( $titles );
496 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
502 $type = (int)$row->rc_type;
503 $vals[
'type'] = RecentChange::parseFromRCType( $type );
508 if ( $this->fld_title || $this->fld_ids ) {
509 if ( $type ===
RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
510 $vals[
'actionhidden'] =
true;
514 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
516 if ( $this->fld_title ) {
519 if ( $this->fld_ids ) {
520 $vals[
'pageid'] = (int)$row->rc_cur_id;
521 $vals[
'revid'] = (int)$row->rc_this_oldid;
522 $vals[
'old_revid'] = (int)$row->rc_last_oldid;
527 if ( $this->fld_ids ) {
528 $vals[
'rcid'] = (int)$row->rc_id;
532 if ( $this->fld_user || $this->fld_userid ) {
533 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
534 $vals[
'userhidden'] =
true;
537 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
538 if ( $this->fld_user ) {
539 $vals[
'user'] = $row->actor_name;
542 if ( $this->fld_userid ) {
543 $vals[
'userid'] = (int)$row->actor_user;
546 if ( isset( $row->actor_name ) && $this->userNameUtils->isTemp( $row->actor_name ) ) {
547 $vals[
'temp'] =
true;
550 if ( !$row->actor_user ) {
551 $vals[
'anon'] =
true;
557 if ( $this->fld_flags ) {
558 $vals[
'bot'] = (bool)$row->rc_bot;
559 $vals[
'new'] = $row->rc_type ==
RC_NEW;
560 $vals[
'minor'] = (bool)$row->rc_minor;
564 if ( $this->fld_sizes ) {
565 $vals[
'oldlen'] = (int)$row->rc_old_len;
566 $vals[
'newlen'] = (int)$row->rc_new_len;
570 if ( $this->fld_timestamp ) {
571 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
575 if ( $this->fld_comment || $this->fld_parsedcomment ) {
576 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
577 $vals[
'commenthidden'] =
true;
580 if ( RevisionRecord::userCanBitfield(
581 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
583 if ( $this->fld_comment ) {
584 $vals[
'comment'] = $this->commentStore->getComment(
'rc_comment', $row )->text;
587 if ( $this->fld_parsedcomment ) {
588 $vals[
'parsedcomment'] = $this->formattedComments[$row->rc_id];
593 if ( $this->fld_redirect ) {
594 $vals[
'redirect'] = (bool)$row->page_is_redirect;
598 if ( $this->fld_patrolled ) {
599 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
600 $vals[
'unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
601 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
604 if ( $this->fld_loginfo && $row->rc_type ==
RC_LOG ) {
605 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
606 $vals[
'actionhidden'] =
true;
609 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
610 $vals[
'logid'] = (int)$row->rc_logid;
611 $vals[
'logtype'] = $row->rc_log_type;
612 $vals[
'logaction'] = $row->rc_log_action;
613 $vals[
'logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
617 if ( $this->fld_tags ) {
618 if ( $row->ts_tags ) {
619 $tags = explode(
',', $row->ts_tags );
620 ApiResult::setIndexedTagName( $tags,
'tag' );
621 $vals[
'tags'] = $tags;
627 if ( $this->fld_sha1 && $row->rev_sha1 !==
null ) {
628 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
629 $vals[
'sha1hidden'] =
true;
632 if ( RevisionRecord::userCanBitfield(
633 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
635 if ( $row->rev_sha1 !==
'' ) {
636 $vals[
'sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
643 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
644 $vals[
'suppressed'] =
true;
654 private function includesPatrollingFlags( array $flagsArray ) {
655 return isset( $flagsArray[
'patrolled'] ) ||
656 isset( $flagsArray[
'!patrolled'] ) ||
657 isset( $flagsArray[
'unpatrolled'] ) ||
658 isset( $flagsArray[
'autopatrolled'] ) ||
659 isset( $flagsArray[
'!autopatrolled'] );
663 if ( isset(
$params[
'show'] ) &&
664 $this->includesPatrollingFlags( array_fill_keys(
$params[
'show'],
true ) )
671 if (
$params[
'prop'] !==
null && in_array(
'parsedcomment',
$params[
'prop'] ) ) {
673 return 'anon-public-user-private';
680 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
681 sort( $slotRoles, SORT_STRING );
685 ParamValidator::PARAM_TYPE =>
'timestamp'
688 ParamValidator::PARAM_TYPE =>
'timestamp'
691 ParamValidator::PARAM_DEFAULT =>
'older',
692 ParamValidator::PARAM_TYPE => [
698 'newer' =>
'api-help-paramvalue-direction-newer',
699 'older' =>
'api-help-paramvalue-direction-older',
703 ParamValidator::PARAM_ISMULTI =>
true,
704 ParamValidator::PARAM_TYPE =>
'namespace',
708 ParamValidator::PARAM_TYPE =>
'user',
709 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
712 ParamValidator::PARAM_TYPE =>
'user',
713 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'temp',
'id',
'interwiki' ],
717 ParamValidator::PARAM_ISMULTI =>
true,
718 ParamValidator::PARAM_DEFAULT =>
'title|timestamp|ids',
719 ParamValidator::PARAM_TYPE => [
738 ParamValidator::PARAM_ISMULTI =>
true,
739 ParamValidator::PARAM_TYPE => [
756 ParamValidator::PARAM_DEFAULT => 10,
757 ParamValidator::PARAM_TYPE =>
'limit',
758 IntegerDef::PARAM_MIN => 1,
763 ParamValidator::PARAM_DEFAULT =>
'edit|new|log|categorize',
764 ParamValidator::PARAM_ISMULTI =>
true,
765 ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
772 'generaterevisions' =>
false,
774 ParamValidator::PARAM_TYPE => $slotRoles
781 'action=query&list=recentchanges'
782 =>
'apihelp-query+recentchanges-example-simple',
783 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
784 =>
'apihelp-query+recentchanges-example-generator',
789 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.