37 parent::__construct( $query, $moduleName,
'rc' );
59 if ( isset( $this->tokenFunctions ) ) {
69 $this->tokenFunctions = [
70 'patrol' => [ self::class,
'getPatrolToken' ]
72 $this->
getHookRunner()->onAPIQueryRecentChangesTokens( $this->tokenFunctions );
87 $validTokenUser =
false;
90 if ( ( $wgUser->useRCPatrol() && $rc->getAttribute(
'rc_type' ) ==
RC_EDIT ) ||
91 ( $wgUser->useNPPatrol() && $rc->getAttribute(
'rc_type' ) ==
RC_NEW )
93 $validTokenUser =
true;
95 } elseif ( $wgUser->useRCPatrol() || $wgUser->useNPPatrol() ) {
96 $validTokenUser =
true;
99 if ( $validTokenUser ) {
101 static $cachedPatrolToken =
null;
103 if ( $cachedPatrolToken ===
null ) {
104 $cachedPatrolToken = $wgUser->getEditToken(
'patrol' );
107 return $cachedPatrolToken;
118 $this->fld_comment = isset( $prop[
'comment'] );
119 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
120 $this->fld_user = isset( $prop[
'user'] );
121 $this->fld_userid = isset( $prop[
'userid'] );
122 $this->fld_flags = isset( $prop[
'flags'] );
123 $this->fld_timestamp = isset( $prop[
'timestamp'] );
124 $this->fld_title = isset( $prop[
'title'] );
125 $this->fld_ids = isset( $prop[
'ids'] );
126 $this->fld_sizes = isset( $prop[
'sizes'] );
127 $this->fld_redirect = isset( $prop[
'redirect'] );
128 $this->fld_patrolled = isset( $prop[
'patrolled'] );
129 $this->fld_loginfo = isset( $prop[
'loginfo'] );
130 $this->fld_tags = isset( $prop[
'tags'] );
131 $this->fld_sha1 = isset( $prop[
'sha1'] );
139 $this->
run( $resultPageSet );
147 public function run( $resultPageSet =
null ) {
159 if ( $params[
'continue'] !==
null ) {
160 $cont = explode(
'|', $params[
'continue'] );
162 $db = $this->
getDB();
163 $timestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
166 $op = $params[
'dir'] ===
'older' ?
'<' :
'>';
168 "rc_timestamp $op $timestamp OR " .
169 "(rc_timestamp = $timestamp AND " .
174 $order = $params[
'dir'] ===
'older' ?
'DESC' :
'ASC';
176 "rc_timestamp $order",
180 $this->
addWhereFld(
'rc_namespace', $params[
'namespace'] );
182 if ( $params[
'type'] !==
null ) {
184 $this->
addWhereFld(
'rc_type', RecentChange::parseToRCType( $params[
'type'] ) );
185 }
catch ( Exception $e ) {
190 $title = $params[
'title'];
192 $titleObj = Title::newFromText(
$title );
193 if ( $titleObj ===
null ) {
196 $this->
addWhereFld(
'rc_namespace', $titleObj->getNamespace() );
197 $this->
addWhereFld(
'rc_title', $titleObj->getDBkey() );
200 if ( $params[
'show'] !==
null ) {
201 $show = array_flip( $params[
'show'] );
204 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
205 || ( isset( $show[
'bot'] ) && isset( $show[
'!bot'] ) )
206 || ( isset( $show[
'anon'] ) && isset( $show[
'!anon'] ) )
207 || ( isset( $show[
'redirect'] ) && isset( $show[
'!redirect'] ) )
208 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
209 || ( isset( $show[
'patrolled'] ) && isset( $show[
'unpatrolled'] ) )
210 || ( isset( $show[
'!patrolled'] ) && isset( $show[
'unpatrolled'] ) )
211 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
212 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'unpatrolled'] ) )
213 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
220 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
221 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
226 $this->
addWhereIf(
'rc_minor = 0', isset( $show[
'!minor'] ) );
227 $this->
addWhereIf(
'rc_minor != 0', isset( $show[
'minor'] ) );
228 $this->
addWhereIf(
'rc_bot = 0', isset( $show[
'!bot'] ) );
229 $this->
addWhereIf(
'rc_bot != 0', isset( $show[
'bot'] ) );
230 if ( isset( $show[
'anon'] ) || isset( $show[
'!anon'] ) ) {
231 $actorMigration = ActorMigration::newMigration();
232 $actorQuery = $actorMigration->getJoin(
'rc_user' );
233 $this->
addTables( $actorQuery[
'tables'] );
236 $actorMigration->isAnon( $actorQuery[
'fields'][
'rc_user'] ), isset( $show[
'anon'] )
239 $actorMigration->isNotAnon( $actorQuery[
'fields'][
'rc_user'] ), isset( $show[
'!anon'] )
242 $this->
addWhereIf(
'rc_patrolled = 0', isset( $show[
'!patrolled'] ) );
243 $this->
addWhereIf(
'rc_patrolled != 0', isset( $show[
'patrolled'] ) );
244 $this->
addWhereIf(
'page_is_redirect = 1', isset( $show[
'redirect'] ) );
246 if ( isset( $show[
'unpatrolled'] ) ) {
248 if ( $user->useRCPatrol() ) {
249 $this->
addWhere(
'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
250 } elseif ( $user->useNPPatrol() ) {
251 $this->
addWhere(
'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
257 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
258 isset( $show[
'!autopatrolled'] )
261 'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
262 isset( $show[
'autopatrolled'] )
267 'page_is_redirect = 0 OR page_is_redirect IS NULL',
268 isset( $show[
'!redirect'] )
274 if ( $params[
'user'] !==
null ) {
276 $actorQuery = ActorMigration::newMigration()
277 ->getWhere( $this->
getDB(),
'rc_user', $params[
'user'],
false );
278 $this->
addTables( $actorQuery[
'tables'] );
280 $this->
addWhere( $actorQuery[
'conds'] );
283 if ( $params[
'excludeuser'] !==
null ) {
285 $actorQuery = ActorMigration::newMigration()
286 ->getWhere( $this->
getDB(),
'rc_user', $params[
'excludeuser'] );
287 $this->
addTables( $actorQuery[
'tables'] );
289 $this->
addWhere(
'NOT(' . $actorQuery[
'conds'] .
')' );
303 $showRedirects =
false;
305 if ( $params[
'prop'] !==
null ) {
306 $prop = array_flip( $params[
'prop'] );
311 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
312 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
316 $this->
addFieldsIf( [
'rc_this_oldid',
'rc_last_oldid' ], $this->fld_ids );
317 $this->
addFieldsIf( [
'rc_minor',
'rc_type',
'rc_bot' ], $this->fld_flags );
318 $this->
addFieldsIf( [
'rc_old_len',
'rc_new_len' ], $this->fld_sizes );
319 $this->
addFieldsIf( [
'rc_patrolled',
'rc_log_type' ], $this->fld_patrolled );
321 [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ],
324 $showRedirects = $this->fld_redirect || isset( $show[
'redirect'] )
325 || isset( $show[
'!redirect'] );
328 $resultPageSet && $params[
'generaterevisions'] );
330 if ( $this->fld_tags ) {
334 if ( $this->fld_sha1 ) {
337 [
'rc_this_oldid=rev_id' ] ] ] );
338 $this->
addFields( [
'rev_sha1',
'rev_deleted' ] );
341 if ( $params[
'toponly'] || $showRedirects ) {
344 [
'rc_namespace=page_namespace',
'rc_title=page_title' ] ] ] );
347 if ( $params[
'toponly'] ) {
348 $this->
addWhere(
'rc_this_oldid = page_latest' );
352 if ( $params[
'tag'] !==
null ) {
354 $this->
addJoinConds( [
'change_tag' => [
'JOIN', [
'rc_id=ct_rc_id' ] ] ] );
355 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
357 $this->
addWhereFld(
'ct_tag_id', $changeTagDefStore->getId( $params[
'tag'] ) );
365 if ( $params[
'user'] !==
null || $params[
'excludeuser'] !==
null ) {
367 $bitmask = RevisionRecord::DELETED_USER;
369 ->userHasAnyRight( $user,
'suppressrevision',
'viewsuppressed' )
371 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
376 $this->
addWhere( $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask" );
379 if ( $this->
getRequest()->getCheck(
'namespace' ) ) {
384 ->userHasAnyRight( $user,
'suppressrevision',
'viewsuppressed' )
393 $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask",
398 $this->token = $params[
'token'];
400 if ( $this->fld_comment || $this->fld_parsedcomment || $this->token ) {
401 $this->commentStore = CommentStore::getStore();
402 $commentQuery = $this->commentStore->getJoin(
'rc_comment' );
403 $this->
addTables( $commentQuery[
'tables'] );
404 $this->
addFields( $commentQuery[
'fields'] );
408 if ( $this->fld_user || $this->fld_userid || $this->token !==
null ) {
410 $actorQuery = ActorMigration::newMigration()->getJoin(
'rc_user' );
411 $this->
addTables( $actorQuery[
'tables'] );
412 $this->
addFields( $actorQuery[
'fields'] );
416 if ( $params[
'slot'] !==
null ) {
418 $slotId = MediaWikiServices::getInstance()->getSlotRoleStore()->getId(
421 }
catch ( \Exception $e ) {
426 'slot' =>
'slots',
'parent_slot' =>
'slots'
429 'slot' => [
'LEFT JOIN', [
430 'rc_this_oldid = slot.slot_revision_id',
431 'slot.slot_role_id' => $slotId,
433 'parent_slot' => [
'LEFT JOIN', [
434 'rc_last_oldid = parent_slot.slot_revision_id',
435 'parent_slot.slot_role_id' => $slotId,
445 'slot.slot_origin = slot.slot_revision_id OR ' .
446 'slot.slot_content_id != parent_slot.slot_content_id OR ' .
447 '(slot.slot_content_id IS NULL AND parent_slot.slot_content_id IS NOT NULL) OR ' .
448 '(slot.slot_content_id IS NOT NULL AND parent_slot.slot_content_id IS NULL)'
451 $changeTypes = RecentChange::parseToRCType(
452 array_intersect( $params[
'type'], [
'new',
'edit' ] )
454 if ( count( $changeTypes ) ) {
459 $this->
addWhere(
'rc_type IS NULL' );
463 $this->
addOption(
'LIMIT', $params[
'limit'] + 1 );
465 'MAX_EXECUTION_TIME',
466 $this->
getConfig()->
get(
'MaxExecutionTimeForExpensiveQueries' )
472 $res = $this->
select( __METHOD__, [], $hookData );
474 if ( $this->fld_title && $resultPageSet ===
null ) {
484 foreach (
$res as $row ) {
485 if ( $count === 0 && $resultPageSet !==
null ) {
489 $this,
'continue',
"$row->rc_timestamp|$row->rc_id"
492 if ( ++$count > $params[
'limit'] ) {
499 if ( $resultPageSet ===
null ) {
504 $fit = $this->
processRow( $row, $vals, $hookData ) &&
505 $result->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
510 } elseif ( $params[
'generaterevisions'] ) {
511 $revid = (int)$row->rc_this_oldid;
516 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
520 if ( $resultPageSet ===
null ) {
522 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'rc' );
523 } elseif ( $params[
'generaterevisions'] ) {
524 $resultPageSet->populateFromRevisionIDs( $revids );
526 $resultPageSet->populateFromTitles( $titles );
538 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
544 $type = (int)$row->rc_type;
545 $vals[
'type'] = RecentChange::parseFromRCType(
$type );
550 if ( $this->fld_title || $this->fld_ids ) {
552 $vals[
'actionhidden'] =
true;
558 if ( $this->fld_title ) {
561 if ( $this->fld_ids ) {
562 $vals[
'pageid'] = (int)$row->rc_cur_id;
563 $vals[
'revid'] = (int)$row->rc_this_oldid;
564 $vals[
'old_revid'] = (int)$row->rc_last_oldid;
569 if ( $this->fld_ids ) {
570 $vals[
'rcid'] = (int)$row->rc_id;
574 if ( $this->fld_user || $this->fld_userid ) {
575 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
576 $vals[
'userhidden'] =
true;
579 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
580 if ( $this->fld_user ) {
581 $vals[
'user'] = $row->rc_user_text;
584 if ( $this->fld_userid ) {
585 $vals[
'userid'] = (int)$row->rc_user;
588 if ( !$row->rc_user ) {
589 $vals[
'anon'] =
true;
595 if ( $this->fld_flags ) {
596 $vals[
'bot'] = (bool)$row->rc_bot;
597 $vals[
'new'] = $row->rc_type ==
RC_NEW;
598 $vals[
'minor'] = (bool)$row->rc_minor;
602 if ( $this->fld_sizes ) {
603 $vals[
'oldlen'] = (int)$row->rc_old_len;
604 $vals[
'newlen'] = (int)$row->rc_new_len;
608 if ( $this->fld_timestamp ) {
609 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
613 if ( $this->fld_comment || $this->fld_parsedcomment ) {
614 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
615 $vals[
'commenthidden'] =
true;
618 if ( RevisionRecord::userCanBitfield(
619 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
621 $comment = $this->commentStore->getComment(
'rc_comment', $row )->text;
622 if ( $this->fld_comment ) {
623 $vals[
'comment'] = $comment;
626 if ( $this->fld_parsedcomment ) {
632 if ( $this->fld_redirect ) {
633 $vals[
'redirect'] = (bool)$row->page_is_redirect;
637 if ( $this->fld_patrolled ) {
638 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
639 $vals[
'unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
640 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
643 if ( $this->fld_loginfo && $row->rc_type ==
RC_LOG ) {
645 $vals[
'actionhidden'] =
true;
649 $vals[
'logid'] = (int)$row->rc_logid;
650 $vals[
'logtype'] = $row->rc_log_type;
651 $vals[
'logaction'] = $row->rc_log_action;
656 if ( $this->fld_tags ) {
657 if ( $row->ts_tags ) {
658 $tags = explode(
',', $row->ts_tags );
659 ApiResult::setIndexedTagName( $tags,
'tag' );
660 $vals[
'tags'] = $tags;
666 if ( $this->fld_sha1 && $row->rev_sha1 !==
null ) {
667 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
668 $vals[
'sha1hidden'] =
true;
671 if ( RevisionRecord::userCanBitfield(
672 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
674 if ( $row->rev_sha1 !==
'' ) {
675 $vals[
'sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
682 if ( $this->token !==
null ) {
684 foreach ( $this->token as
$t ) {
686 $title, RecentChange::newFromRow( $row ) );
687 if ( $val ===
false ) {
688 $this->
addWarning( [
'apiwarn-tokennotallowed', $t ] );
690 $vals[
$t .
'token'] = $val;
695 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
696 $vals[
'suppressed'] =
true;
707 return isset( $flagsArray[
'patrolled'] ) ||
708 isset( $flagsArray[
'!patrolled'] ) ||
709 isset( $flagsArray[
'unpatrolled'] ) ||
710 isset( $flagsArray[
'autopatrolled'] ) ||
711 isset( $flagsArray[
'!autopatrolled'] );
715 if ( isset( $params[
'show'] ) &&
720 if ( isset( $params[
'token'] ) ) {
726 if ( $params[
'prop'] !==
null && in_array(
'parsedcomment', $params[
'prop'] ) ) {
728 return 'anon-public-user-private';
735 $slotRoles = MediaWikiServices::getInstance()->getSlotRoleRegistry()->getKnownRoles();
736 sort( $slotRoles, SORT_STRING );
760 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'id',
'interwiki' ],
761 UserDef::PARAM_RETURN_OBJECT =>
true,
765 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'id',
'interwiki' ],
766 UserDef::PARAM_RETURN_OBJECT =>
true,
830 'generaterevisions' =>
false,
839 'action=query&list=recentchanges'
840 =>
'apihelp-query+recentchanges-example-simple',
841 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
842 =>
'apihelp-query+recentchanges-example-generator',
847 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Recentchanges';
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
const LIMIT_BIG1
Fast query, standard limit.
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is 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_EXTRA_NAMESPACES
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
const LIMIT_BIG2
Fast query, apihighlimits limit.
getModuleName()
Get the name of the module being executed by this instance.
getHookRunner()
Get an ApiHookRunner for running core API hooks.
getContinuationManager()
Get the continuation manager.
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
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) Stable to override.
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.
includesPatrollingFlags(array $flagsArray)
__construct(ApiQuery $query, $moduleName)
static getPatrolToken( $pageid, $title, $rc=null)
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.
CommentStore $commentStore
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.
getTokenFunctions()
Get an array mapping token names to their handler functions.
This is the main query class.
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...