65 parent::__construct( $query, $moduleName,
'rc' );
82 $this->fld_comment = isset( $prop[
'comment'] );
83 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
84 $this->fld_user = isset( $prop[
'user'] );
85 $this->fld_userid = isset( $prop[
'userid'] );
86 $this->fld_flags = isset( $prop[
'flags'] );
87 $this->fld_timestamp = isset( $prop[
'timestamp'] );
88 $this->fld_title = isset( $prop[
'title'] );
89 $this->fld_ids = isset( $prop[
'ids'] );
90 $this->fld_sizes = isset( $prop[
'sizes'] );
91 $this->fld_redirect = isset( $prop[
'redirect'] );
92 $this->fld_patrolled = isset( $prop[
'patrolled'] );
93 $this->fld_loginfo = isset( $prop[
'loginfo'] );
94 $this->fld_tags = isset( $prop[
'tags'] );
95 $this->fld_sha1 = isset( $prop[
'sha1'] );
103 $this->
run( $resultPageSet );
111 public function run( $resultPageSet =
null ) {
123 if ( $params[
'continue'] !==
null ) {
124 $cont = explode(
'|', $params[
'continue'] );
126 $db = $this->
getDB();
127 $timestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
130 $op = $params[
'dir'] ===
'older' ?
'<' :
'>';
132 "rc_timestamp $op $timestamp OR " .
133 "(rc_timestamp = $timestamp AND " .
138 $order = $params[
'dir'] ===
'older' ?
'DESC' :
'ASC';
140 "rc_timestamp $order",
144 $this->
addWhereFld(
'rc_namespace', $params[
'namespace'] );
146 if ( $params[
'type'] !==
null ) {
148 $this->
addWhereFld(
'rc_type', RecentChange::parseToRCType( $params[
'type'] ) );
149 }
catch ( Exception $e ) {
154 $title = $params[
'title'];
156 $titleObj = Title::newFromText(
$title );
157 if ( $titleObj ===
null ) {
160 $this->
addWhereFld(
'rc_namespace', $titleObj->getNamespace() );
161 $this->
addWhereFld(
'rc_title', $titleObj->getDBkey() );
164 if ( $params[
'show'] !==
null ) {
165 $show = array_fill_keys( $params[
'show'],
true );
168 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
169 || ( isset( $show[
'bot'] ) && isset( $show[
'!bot'] ) )
170 || ( isset( $show[
'anon'] ) && isset( $show[
'!anon'] ) )
171 || ( isset( $show[
'redirect'] ) && isset( $show[
'!redirect'] ) )
172 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
173 || ( isset( $show[
'patrolled'] ) && isset( $show[
'unpatrolled'] ) )
174 || ( isset( $show[
'!patrolled'] ) && isset( $show[
'unpatrolled'] ) )
175 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
176 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'unpatrolled'] ) )
177 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
184 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
185 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
190 $this->
addWhereIf(
'rc_minor = 0', isset( $show[
'!minor'] ) );
191 $this->
addWhereIf(
'rc_minor != 0', isset( $show[
'minor'] ) );
192 $this->
addWhereIf(
'rc_bot = 0', isset( $show[
'!bot'] ) );
193 $this->
addWhereIf(
'rc_bot != 0', isset( $show[
'bot'] ) );
194 if ( isset( $show[
'anon'] ) || isset( $show[
'!anon'] ) ) {
196 $this->
addJoinConds( [
'actor' => [
'JOIN',
'actor_id=rc_actor' ] ] );
198 'actor_user IS NULL', isset( $show[
'anon'] )
201 'actor_user IS NOT NULL', isset( $show[
'!anon'] )
204 $this->
addWhereIf(
'rc_patrolled = 0', isset( $show[
'!patrolled'] ) );
205 $this->
addWhereIf(
'rc_patrolled != 0', isset( $show[
'patrolled'] ) );
206 $this->
addWhereIf(
'page_is_redirect = 1', isset( $show[
'redirect'] ) );
208 if ( isset( $show[
'unpatrolled'] ) ) {
210 if ( $user->useRCPatrol() ) {
211 $this->
addWhere(
'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
212 } elseif ( $user->useNPPatrol() ) {
213 $this->
addWhere(
'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
219 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
220 isset( $show[
'!autopatrolled'] )
223 'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
224 isset( $show[
'autopatrolled'] )
229 'page_is_redirect = 0 OR page_is_redirect IS NULL',
230 isset( $show[
'!redirect'] )
236 if ( $params[
'prop'] !==
null ) {
237 $prop = array_fill_keys( $params[
'prop'],
true );
245 || $params[
'user'] !==
null
246 || $params[
'excludeuser'] !==
null
249 $this->
addFields( [
'actor_name',
'actor_user',
'rc_actor' ] );
250 $this->
addJoinConds( [
'actor' => [
'JOIN',
'actor_id=rc_actor' ] ] );
253 if ( $params[
'user'] !==
null ) {
254 $this->
addWhereFld(
'actor_name', $params[
'user'] );
257 if ( $params[
'excludeuser'] !==
null ) {
258 $this->
addWhere(
'actor_name<>' . $this->
getDB()->addQuotes( $params[
'excludeuser'] ) );
272 $showRedirects =
false;
274 if ( $params[
'prop'] !==
null ) {
275 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
276 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
280 $this->
addFieldsIf( [
'rc_this_oldid',
'rc_last_oldid' ], $this->fld_ids );
281 $this->
addFieldsIf( [
'rc_minor',
'rc_type',
'rc_bot' ], $this->fld_flags );
282 $this->
addFieldsIf( [
'rc_old_len',
'rc_new_len' ], $this->fld_sizes );
283 $this->
addFieldsIf( [
'rc_patrolled',
'rc_log_type' ], $this->fld_patrolled );
285 [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ],
288 $showRedirects = $this->fld_redirect || isset( $show[
'redirect'] )
289 || isset( $show[
'!redirect'] );
292 $resultPageSet && $params[
'generaterevisions'] );
294 if ( $this->fld_tags ) {
298 if ( $this->fld_sha1 ) {
301 [
'rc_this_oldid=rev_id' ] ] ] );
302 $this->
addFields( [
'rev_sha1',
'rev_deleted' ] );
305 if ( $params[
'toponly'] || $showRedirects ) {
308 [
'rc_namespace=page_namespace',
'rc_title=page_title' ] ] ] );
311 if ( $params[
'toponly'] ) {
312 $this->
addWhere(
'rc_this_oldid = page_latest' );
316 if ( $params[
'tag'] !==
null ) {
318 $this->
addJoinConds( [
'change_tag' => [
'JOIN', [
'rc_id=ct_rc_id' ] ] ] );
320 $this->
addWhereFld(
'ct_tag_id', $this->changeTagDefStore->getId( $params[
'tag'] ) );
328 if ( $params[
'user'] !==
null || $params[
'excludeuser'] !==
null ) {
329 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
330 $bitmask = RevisionRecord::DELETED_USER;
331 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
332 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
337 $this->
addWhere( $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask" );
340 if ( $this->
getRequest()->getCheck(
'namespace' ) ) {
342 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
344 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
352 $this->
getDB()->bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask",
357 if ( $this->fld_comment || $this->fld_parsedcomment ) {
358 $commentQuery = $this->commentStore->getJoin(
'rc_comment' );
359 $this->
addTables( $commentQuery[
'tables'] );
360 $this->
addFields( $commentQuery[
'fields'] );
364 if ( $params[
'slot'] !==
null ) {
366 $slotId = $this->slotRoleStore->getId( $params[
'slot'] );
367 }
catch ( \Exception $e ) {
372 'slot' =>
'slots',
'parent_slot' =>
'slots'
375 'slot' => [
'LEFT JOIN', [
376 'rc_this_oldid = slot.slot_revision_id',
377 'slot.slot_role_id' => $slotId,
379 'parent_slot' => [
'LEFT JOIN', [
380 'rc_last_oldid = parent_slot.slot_revision_id',
381 'parent_slot.slot_role_id' => $slotId,
391 'slot.slot_origin = slot.slot_revision_id OR ' .
392 'slot.slot_content_id != parent_slot.slot_content_id OR ' .
393 '(slot.slot_content_id IS NULL AND parent_slot.slot_content_id IS NOT NULL) OR ' .
394 '(slot.slot_content_id IS NOT NULL AND parent_slot.slot_content_id IS NULL)'
397 $changeTypes = RecentChange::parseToRCType(
398 array_intersect( $params[
'type'], [
'new',
'edit' ] )
400 if ( count( $changeTypes ) ) {
405 $this->
addWhere(
'rc_type IS NULL' );
409 $this->
addOption(
'LIMIT', $params[
'limit'] + 1 );
411 'MAX_EXECUTION_TIME',
412 $this->
getConfig()->
get(
'MaxExecutionTimeForExpensiveQueries' )
418 $res = $this->
select( __METHOD__, [], $hookData );
420 if ( $this->fld_title && $resultPageSet ===
null ) {
430 foreach (
$res as $row ) {
431 if ( $count === 0 && $resultPageSet !==
null ) {
435 $this,
'continue',
"$row->rc_timestamp|$row->rc_id"
438 if ( ++$count > $params[
'limit'] ) {
445 if ( $resultPageSet ===
null ) {
450 $fit = $this->
processRow( $row, $vals, $hookData ) &&
451 $result->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
456 } elseif ( $params[
'generaterevisions'] ) {
457 $revid = (int)$row->rc_this_oldid;
462 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
466 if ( $resultPageSet ===
null ) {
468 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'rc' );
469 } elseif ( $params[
'generaterevisions'] ) {
470 $resultPageSet->populateFromRevisionIDs( $revids );
472 $resultPageSet->populateFromTitles( $titles );
484 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
490 $type = (int)$row->rc_type;
491 $vals[
'type'] = RecentChange::parseFromRCType(
$type );
496 if ( $this->fld_title || $this->fld_ids ) {
498 $vals[
'actionhidden'] =
true;
504 if ( $this->fld_title ) {
507 if ( $this->fld_ids ) {
508 $vals[
'pageid'] = (int)$row->rc_cur_id;
509 $vals[
'revid'] = (int)$row->rc_this_oldid;
510 $vals[
'old_revid'] = (int)$row->rc_last_oldid;
515 if ( $this->fld_ids ) {
516 $vals[
'rcid'] = (int)$row->rc_id;
520 if ( $this->fld_user || $this->fld_userid ) {
521 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
522 $vals[
'userhidden'] =
true;
525 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
526 if ( $this->fld_user ) {
527 $vals[
'user'] = $row->actor_name;
530 if ( $this->fld_userid ) {
531 $vals[
'userid'] = (int)$row->actor_user;
534 if ( !$row->actor_user ) {
535 $vals[
'anon'] =
true;
541 if ( $this->fld_flags ) {
542 $vals[
'bot'] = (bool)$row->rc_bot;
543 $vals[
'new'] = $row->rc_type ==
RC_NEW;
544 $vals[
'minor'] = (bool)$row->rc_minor;
548 if ( $this->fld_sizes ) {
549 $vals[
'oldlen'] = (int)$row->rc_old_len;
550 $vals[
'newlen'] = (int)$row->rc_new_len;
554 if ( $this->fld_timestamp ) {
555 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
559 if ( $this->fld_comment || $this->fld_parsedcomment ) {
560 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
561 $vals[
'commenthidden'] =
true;
564 if ( RevisionRecord::userCanBitfield(
565 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
567 $comment = $this->commentStore->getComment(
'rc_comment', $row )->text;
568 if ( $this->fld_comment ) {
569 $vals[
'comment'] = $comment;
572 if ( $this->fld_parsedcomment ) {
578 if ( $this->fld_redirect ) {
579 $vals[
'redirect'] = (bool)$row->page_is_redirect;
583 if ( $this->fld_patrolled ) {
584 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
585 $vals[
'unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
586 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
589 if ( $this->fld_loginfo && $row->rc_type ==
RC_LOG ) {
591 $vals[
'actionhidden'] =
true;
595 $vals[
'logid'] = (int)$row->rc_logid;
596 $vals[
'logtype'] = $row->rc_log_type;
597 $vals[
'logaction'] = $row->rc_log_action;
602 if ( $this->fld_tags ) {
603 if ( $row->ts_tags ) {
604 $tags = explode(
',', $row->ts_tags );
605 ApiResult::setIndexedTagName( $tags,
'tag' );
606 $vals[
'tags'] = $tags;
612 if ( $this->fld_sha1 && $row->rev_sha1 !==
null ) {
613 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
614 $vals[
'sha1hidden'] =
true;
617 if ( RevisionRecord::userCanBitfield(
618 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
620 if ( $row->rev_sha1 !==
'' ) {
621 $vals[
'sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
628 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
629 $vals[
'suppressed'] =
true;
640 return isset( $flagsArray[
'patrolled'] ) ||
641 isset( $flagsArray[
'!patrolled'] ) ||
642 isset( $flagsArray[
'unpatrolled'] ) ||
643 isset( $flagsArray[
'autopatrolled'] ) ||
644 isset( $flagsArray[
'!autopatrolled'] );
648 if ( isset( $params[
'show'] ) &&
656 if ( $params[
'prop'] !==
null && in_array(
'parsedcomment', $params[
'prop'] ) ) {
658 return 'anon-public-user-private';
665 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
666 sort( $slotRoles, SORT_STRING );
690 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'id',
'interwiki' ],
694 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'id',
'interwiki' ],
753 'generaterevisions' =>
false,
762 'action=query&list=recentchanges'
763 =>
'apihelp-query+recentchanges-example-simple',
764 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
765 =>
'apihelp-query+recentchanges-example-generator',
770 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.
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.
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.
SlotRoleRegistry $slotRoleRegistry
includesPatrollingFlags(array $flagsArray)
__construct(ApiQuery $query, $moduleName, CommentStore $commentStore, NameTableStore $changeTagDefStore, NameTableStore $slotRoleStore, SlotRoleRegistry $slotRoleRegistry)
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
NameTableStore $slotRoleStore
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.
NameTableStore $changeTagDefStore
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.
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...