36 parent::__construct( $query, $moduleName,
'rc' );
57 if ( isset( $this->tokenFunctions ) ) {
67 $this->tokenFunctions = [
68 'patrol' => [ self::class,
'getPatrolToken' ]
70 Hooks::run(
'APIQueryRecentChangesTokens', [ &$this->tokenFunctions ] );
85 $validTokenUser =
false;
88 if ( ( $wgUser->useRCPatrol() && $rc->getAttribute(
'rc_type' ) ==
RC_EDIT ) ||
89 ( $wgUser->useNPPatrol() && $rc->getAttribute(
'rc_type' ) ==
RC_NEW )
91 $validTokenUser =
true;
93 } elseif ( $wgUser->useRCPatrol() || $wgUser->useNPPatrol() ) {
94 $validTokenUser =
true;
97 if ( $validTokenUser ) {
99 static $cachedPatrolToken =
null;
101 if ( is_null( $cachedPatrolToken ) ) {
102 $cachedPatrolToken = $wgUser->getEditToken(
'patrol' );
105 return $cachedPatrolToken;
116 $this->fld_comment = isset( $prop[
'comment'] );
117 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
118 $this->fld_user = isset( $prop[
'user'] );
119 $this->fld_userid = isset( $prop[
'userid'] );
120 $this->fld_flags = isset( $prop[
'flags'] );
121 $this->fld_timestamp = isset( $prop[
'timestamp'] );
122 $this->fld_title = isset( $prop[
'title'] );
123 $this->fld_ids = isset( $prop[
'ids'] );
124 $this->fld_sizes = isset( $prop[
'sizes'] );
125 $this->fld_redirect = isset( $prop[
'redirect'] );
126 $this->fld_patrolled = isset( $prop[
'patrolled'] );
127 $this->fld_loginfo = isset( $prop[
'loginfo'] );
128 $this->fld_tags = isset( $prop[
'tags'] );
129 $this->fld_sha1 = isset( $prop[
'sha1'] );
137 $this->
run( $resultPageSet );
145 public function run( $resultPageSet =
null ) {
157 if ( !is_null( $params[
'continue'] ) ) {
158 $cont = explode(
'|', $params[
'continue'] );
160 $db = $this->
getDB();
161 $timestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
164 $op = $params[
'dir'] ===
'older' ?
'<' :
'>';
166 "rc_timestamp $op $timestamp OR " .
167 "(rc_timestamp = $timestamp AND " .
172 $order = $params[
'dir'] ===
'older' ?
'DESC' :
'ASC';
174 "rc_timestamp $order",
178 $this->
addWhereFld(
'rc_namespace', $params[
'namespace'] );
180 if ( !is_null( $params[
'type'] ) ) {
182 $this->
addWhereFld(
'rc_type', RecentChange::parseToRCType( $params[
'type'] ) );
183 }
catch ( Exception $e ) {
188 $title = $params[
'title'];
189 if ( !is_null(
$title ) ) {
190 $titleObj = Title::newFromText(
$title );
191 if ( is_null( $titleObj ) ) {
194 $this->
addWhereFld(
'rc_namespace', $titleObj->getNamespace() );
195 $this->
addWhereFld(
'rc_title', $titleObj->getDBkey() );
198 if ( !is_null( $params[
'show'] ) ) {
199 $show = array_flip( $params[
'show'] );
202 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
203 || ( isset( $show[
'bot'] ) && isset( $show[
'!bot'] ) )
204 || ( isset( $show[
'anon'] ) && isset( $show[
'!anon'] ) )
205 || ( isset( $show[
'redirect'] ) && isset( $show[
'!redirect'] ) )
206 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
207 || ( isset( $show[
'patrolled'] ) && isset( $show[
'unpatrolled'] ) )
208 || ( isset( $show[
'!patrolled'] ) && isset( $show[
'unpatrolled'] ) )
209 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
210 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'unpatrolled'] ) )
211 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
218 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
219 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
224 $this->
addWhereIf(
'rc_minor = 0', isset( $show[
'!minor'] ) );
225 $this->
addWhereIf(
'rc_minor != 0', isset( $show[
'minor'] ) );
226 $this->
addWhereIf(
'rc_bot = 0', isset( $show[
'!bot'] ) );
227 $this->
addWhereIf(
'rc_bot != 0', isset( $show[
'bot'] ) );
228 if ( isset( $show[
'anon'] ) || isset( $show[
'!anon'] ) ) {
229 $actorMigration = ActorMigration::newMigration();
230 $actorQuery = $actorMigration->getJoin(
'rc_user' );
231 $this->
addTables( $actorQuery[
'tables'] );
234 $actorMigration->isAnon( $actorQuery[
'fields'][
'rc_user'] ), isset( $show[
'anon'] )
237 $actorMigration->isNotAnon( $actorQuery[
'fields'][
'rc_user'] ), isset( $show[
'!anon'] )
240 $this->
addWhereIf(
'rc_patrolled = 0', isset( $show[
'!patrolled'] ) );
241 $this->
addWhereIf(
'rc_patrolled != 0', isset( $show[
'patrolled'] ) );
242 $this->
addWhereIf(
'page_is_redirect = 1', isset( $show[
'redirect'] ) );
244 if ( isset( $show[
'unpatrolled'] ) ) {
246 if ( $user->useRCPatrol() ) {
247 $this->
addWhere(
'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
248 } elseif ( $user->useNPPatrol() ) {
249 $this->
addWhere(
'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
255 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
256 isset( $show[
'!autopatrolled'] )
259 'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
260 isset( $show[
'autopatrolled'] )
265 'page_is_redirect = 0 OR page_is_redirect IS NULL',
266 isset( $show[
'!redirect'] )
272 if ( !is_null( $params[
'user'] ) ) {
274 $actorQuery = ActorMigration::newMigration()
275 ->getWhere( $this->
getDB(),
'rc_user', User::newFromName( $params[
'user'],
false ),
false );
276 $this->
addTables( $actorQuery[
'tables'] );
278 $this->
addWhere( $actorQuery[
'conds'] );
281 if ( !is_null( $params[
'excludeuser'] ) ) {
283 $actorQuery = ActorMigration::newMigration()
284 ->getWhere( $this->
getDB(),
'rc_user', User::newFromName( $params[
'excludeuser'],
false ) );
285 $this->
addTables( $actorQuery[
'tables'] );
287 $this->
addWhere(
'NOT(' . $actorQuery[
'conds'] .
')' );
301 $showRedirects =
false;
303 if ( !is_null( $params[
'prop'] ) ) {
304 $prop = array_flip( $params[
'prop'] );
309 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
310 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
314 $this->
addFieldsIf( [
'rc_this_oldid',
'rc_last_oldid' ], $this->fld_ids );
315 $this->
addFieldsIf( [
'rc_minor',
'rc_type',
'rc_bot' ], $this->fld_flags );
316 $this->
addFieldsIf( [
'rc_old_len',
'rc_new_len' ], $this->fld_sizes );
317 $this->
addFieldsIf( [
'rc_patrolled',
'rc_log_type' ], $this->fld_patrolled );
319 [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ],
322 $showRedirects = $this->fld_redirect || isset( $show[
'redirect'] )
323 || isset( $show[
'!redirect'] );
326 $resultPageSet && $params[
'generaterevisions'] );
328 if ( $this->fld_tags ) {
332 if ( $this->fld_sha1 ) {
335 [
'rc_this_oldid=rev_id' ] ] ] );
336 $this->
addFields( [
'rev_sha1',
'rev_deleted' ] );
339 if ( $params[
'toponly'] || $showRedirects ) {
342 [
'rc_namespace=page_namespace',
'rc_title=page_title' ] ] ] );
345 if ( $params[
'toponly'] ) {
346 $this->
addWhere(
'rc_this_oldid = page_latest' );
350 if ( !is_null( $params[
'tag'] ) ) {
352 $this->
addJoinConds( [
'change_tag' => [
'JOIN', [
'rc_id=ct_rc_id' ] ] ] );
353 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
355 $this->
addWhereFld(
'ct_tag_id', $changeTagDefStore->getId( $params[
'tag'] ) );
363 if ( !is_null( $params[
'user'] ) || !is_null( $params[
'excludeuser'] ) ) {
365 $bitmask = RevisionRecord::DELETED_USER;
367 ->userHasAnyRight( $user,
'suppressrevision',
'viewsuppressed' )
369 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
374 $this->
addWhere( $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask" );
377 if ( $this->
getRequest()->getCheck(
'namespace' ) ) {
382 ->userHasAnyRight( $user,
'suppressrevision',
'viewsuppressed' )
391 $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask",
396 $this->token = $params[
'token'];
398 if ( $this->fld_comment || $this->fld_parsedcomment || $this->token ) {
399 $this->commentStore = CommentStore::getStore();
400 $commentQuery = $this->commentStore->getJoin(
'rc_comment' );
401 $this->
addTables( $commentQuery[
'tables'] );
402 $this->
addFields( $commentQuery[
'fields'] );
406 if ( $this->fld_user || $this->fld_userid || !is_null( $this->token ) ) {
408 $actorQuery = ActorMigration::newMigration()->getJoin(
'rc_user' );
409 $this->
addTables( $actorQuery[
'tables'] );
410 $this->
addFields( $actorQuery[
'fields'] );
414 $this->
addOption(
'LIMIT', $params[
'limit'] + 1 );
419 $res = $this->
select( __METHOD__, [], $hookData );
427 foreach (
$res as $row ) {
428 if ( $count === 0 && $resultPageSet !==
null ) {
432 $this,
'continue',
"$row->rc_timestamp|$row->rc_id"
435 if ( ++$count > $params[
'limit'] ) {
442 if ( is_null( $resultPageSet ) ) {
447 $fit = $this->
processRow( $row, $vals, $hookData ) &&
448 $result->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
453 } elseif ( $params[
'generaterevisions'] ) {
454 $revid = (int)$row->rc_this_oldid;
459 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
463 if ( is_null( $resultPageSet ) ) {
465 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'rc' );
466 } elseif ( $params[
'generaterevisions'] ) {
467 $resultPageSet->populateFromRevisionIDs( $revids );
469 $resultPageSet->populateFromTitles( $titles );
481 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
487 $type = (int)$row->rc_type;
488 $vals[
'type'] = RecentChange::parseFromRCType(
$type );
493 if ( $this->fld_title || $this->fld_ids ) {
495 $vals[
'actionhidden'] =
true;
501 if ( $this->fld_title ) {
504 if ( $this->fld_ids ) {
505 $vals[
'pageid'] = (int)$row->rc_cur_id;
506 $vals[
'revid'] = (int)$row->rc_this_oldid;
507 $vals[
'old_revid'] = (int)$row->rc_last_oldid;
512 if ( $this->fld_ids ) {
513 $vals[
'rcid'] = (int)$row->rc_id;
517 if ( $this->fld_user || $this->fld_userid ) {
518 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
519 $vals[
'userhidden'] =
true;
522 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
523 if ( $this->fld_user ) {
524 $vals[
'user'] = $row->rc_user_text;
527 if ( $this->fld_userid ) {
528 $vals[
'userid'] = (int)$row->rc_user;
531 if ( !$row->rc_user ) {
532 $vals[
'anon'] =
true;
538 if ( $this->fld_flags ) {
539 $vals[
'bot'] = (bool)$row->rc_bot;
540 $vals[
'new'] = $row->rc_type ==
RC_NEW;
541 $vals[
'minor'] = (bool)$row->rc_minor;
545 if ( $this->fld_sizes ) {
546 $vals[
'oldlen'] = (int)$row->rc_old_len;
547 $vals[
'newlen'] = (int)$row->rc_new_len;
551 if ( $this->fld_timestamp ) {
552 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
556 if ( $this->fld_comment || $this->fld_parsedcomment ) {
557 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
558 $vals[
'commenthidden'] =
true;
561 if ( RevisionRecord::userCanBitfield(
562 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
564 $comment = $this->commentStore->getComment(
'rc_comment', $row )->text;
565 if ( $this->fld_comment ) {
566 $vals[
'comment'] = $comment;
569 if ( $this->fld_parsedcomment ) {
575 if ( $this->fld_redirect ) {
576 $vals[
'redirect'] = (bool)$row->page_is_redirect;
580 if ( $this->fld_patrolled ) {
581 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
583 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
586 if ( $this->fld_loginfo && $row->rc_type ==
RC_LOG ) {
588 $vals[
'actionhidden'] =
true;
592 $vals[
'logid'] = (int)$row->rc_logid;
593 $vals[
'logtype'] = $row->rc_log_type;
594 $vals[
'logaction'] = $row->rc_log_action;
599 if ( $this->fld_tags ) {
600 if ( $row->ts_tags ) {
601 $tags = explode(
',', $row->ts_tags );
602 ApiResult::setIndexedTagName( $tags,
'tag' );
603 $vals[
'tags'] = $tags;
609 if ( $this->fld_sha1 && $row->rev_sha1 !==
null ) {
610 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
611 $vals[
'sha1hidden'] =
true;
614 if ( RevisionRecord::userCanBitfield(
615 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
617 if ( $row->rev_sha1 !==
'' ) {
618 $vals[
'sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
625 if ( !is_null( $this->token ) ) {
627 foreach ( $this->token as
$t ) {
629 $title, RecentChange::newFromRow( $row ) );
630 if ( $val ===
false ) {
631 $this->
addWarning( [
'apiwarn-tokennotallowed', $t ] );
633 $vals[
$t .
'token'] = $val;
638 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
639 $vals[
'suppressed'] =
true;
650 return isset( $flagsArray[
'patrolled'] ) ||
651 isset( $flagsArray[
'!patrolled'] ) ||
652 isset( $flagsArray[
'unpatrolled'] ) ||
653 isset( $flagsArray[
'autopatrolled'] ) ||
654 isset( $flagsArray[
'!autopatrolled'] );
658 if ( isset( $params[
'show'] ) &&
663 if ( isset( $params[
'token'] ) ) {
669 if ( !is_null( $params[
'prop'] ) && in_array(
'parsedcomment', $params[
'prop'] ) ) {
671 return 'anon-public-user-private';
766 'generaterevisions' =>
false,
772 'action=query&list=recentchanges'
773 =>
'apihelp-query+recentchanges-example-simple',
774 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
775 =>
'apihelp-query+recentchanges-example-generator',
780 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,...
const PARAM_MAX2
(integer) Max value allowed for the parameter for users with the apihighlimits right,...
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
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.
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
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 PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
const LIMIT_BIG1
Fast query, standard limit.
getResult()
Get the result object.
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
const PARAM_EXTRA_NAMESPACES
(int[]) When PARAM_TYPE is 'namespace', include these as additional possible values.
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.
getContinuationManager()
Get the continuation manager.
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
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)
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.
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 isUnpatrolled( $rc, User $user)
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...
static userCanBitfield( $bitfield, $field, User $user=null)
Determine if the current user is allowed to view a particular field of this log row,...