88 public const SEND_NONE =
true;
93 public const SEND_FEED =
false;
130 private const CHANGE_TYPES = [
159 if ( is_array(
$type ) ) {
162 $retval[] = self::parseToRCType(
$t );
168 if ( !array_key_exists(
$type, self::CHANGE_TYPES ) ) {
171 return self::CHANGE_TYPES[
$type];
181 return array_search( $rcType, self::CHANGE_TYPES,
true ) ?:
"$rcType";
192 return array_keys( self::CHANGE_TYPES );
202 return self::newFromConds( [
'rc_id' => $rcid ], __METHOD__ );
220 $rcQuery = self::getQueryInfo();
221 $row = $db->selectRow(
222 $rcQuery[
'tables'], $rcQuery[
'fields'], $conds, $fname, [], $rcQuery[
'joins']
224 if ( $row !==
false ) {
225 return self::newFromRow( $row );
241 $commentQuery = CommentStore::getStore()->getJoin(
'rc_comment' );
242 $actorQuery = ActorMigration::newMigration()->getJoin(
'rc_user' );
244 'tables' => [
'recentchanges' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
267 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
268 'joins' => $commentQuery[
'joins'] + $actorQuery[
'joins'],
278 $this->mAttribs = $attribs;
285 $this->mExtra = $extra;
292 if ( $this->mTitle ===
false ) {
293 $this->mTitle = Title::makeTitle( $this->mAttribs[
'rc_namespace'], $this->mAttribs[
'rc_title'] );
305 if ( $this->mPerformer ===
false ) {
306 if ( !empty( $this->mAttribs[
'rc_actor'] ) ) {
308 } elseif ( !empty( $this->mAttribs[
'rc_user'] ) ) {
310 } elseif ( !empty( $this->mAttribs[
'rc_user_text'] ) ) {
313 throw new MWException(
'RecentChange object lacks rc_actor, rc_user, and rc_user_text' );
317 return $this->mPerformer;
329 public function save( $send = self::SEND_FEED ) {
333 if ( !is_array( $this->mExtra ) ) {
338 $this->mAttribs[
'rc_ip'] =
'';
341 # Strict mode fixups (not-NULL fields)
342 foreach ( [
'minor',
'bot',
'new',
'patrolled',
'deleted' ] as $field ) {
343 $this->mAttribs[
"rc_$field"] = (int)$this->mAttribs[
"rc_$field"];
345 # ...more fixups (NULL fields)
346 foreach ( [
'old_len',
'new_len' ] as $field ) {
347 $this->mAttribs[
"rc_$field"] = isset( $this->mAttribs[
"rc_$field"] )
348 ? (int)$this->mAttribs[
"rc_$field"]
352 # If our database is strict about IP addresses, use NULL instead of an empty string
353 $strictIPs = $dbw->getType() ===
'postgres';
354 if ( $strictIPs && $this->mAttribs[
'rc_ip'] ==
'' ) {
355 unset( $this->mAttribs[
'rc_ip'] );
358 # Trim spaces on user supplied text
359 $this->mAttribs[
'rc_comment'] = trim( $this->mAttribs[
'rc_comment'] );
361 # Fixup database timestamps
362 $this->mAttribs[
'rc_timestamp'] = $dbw->timestamp( $this->mAttribs[
'rc_timestamp'] );
364 # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
365 if ( $this->mAttribs[
'rc_cur_id'] == 0 ) {
366 unset( $this->mAttribs[
'rc_cur_id'] );
369 $row = $this->mAttribs;
371 # Convert mAttribs['rc_comment'] for CommentStore
372 $comment = $row[
'rc_comment'];
373 unset( $row[
'rc_comment'], $row[
'rc_comment_text'], $row[
'rc_comment_data'] );
374 $row += CommentStore::getStore()->insert( $dbw,
'rc_comment', $comment );
376 # Convert mAttribs['rc_user'] etc for ActorMigration
378 $row[
'rc_user'] ??
null,
379 $row[
'rc_user_text'] ??
null,
380 $row[
'rc_actor'] ??
null
382 unset( $row[
'rc_user'], $row[
'rc_user_text'], $row[
'rc_actor'] );
383 $row += ActorMigration::newMigration()->getInsertValues( $dbw,
'rc_user', $user );
385 # Don't reuse an existing rc_id for the new row, if one happens to be
386 # set for some reason.
387 unset( $row[
'rc_id'] );
390 $dbw->insert(
'recentchanges', $row, __METHOD__ );
393 $this->mAttribs[
'rc_id'] = $dbw->insertId();
396 Hooks::runner()->onRecentChange_save( $this );
398 if ( count( $this->tags ) ) {
400 $this->mAttribs[
'rc_this_oldid'], $this->mAttribs[
'rc_logid'],
null, $this );
403 if ( $send === self::SEND_FEED ) {
408 # E-mail notifications
415 Hooks::runner()->onAbortEmailNotification( $editor,
$title, $this ) &&
420 $dbw->onTransactionCommitOrIdle(
421 function () use ( $editor,
$title ) {
423 $enotif->notifyOnPageChange(
426 $this->mAttribs[
'rc_timestamp'],
427 $this->mAttribs[
'rc_comment'],
428 $this->mAttribs[
'rc_minor'],
429 $this->mAttribs[
'rc_last_oldid'],
430 $this->mExtra[
'pageStatus']
439 if ( $this->mAttribs[
'rc_user'] > 0 ) {
440 JobQueueGroup::singleton()->lazyPush( RecentChangesUpdateJob::newCacheUpdateJob() );
450 if ( $feeds ===
null ) {
456 foreach ( $feeds as $params ) {
458 'omit_bots' =>
false,
459 'omit_anon' =>
false,
460 'omit_user' =>
false,
461 'omit_minor' =>
false,
462 'omit_patrolled' =>
false,
466 ( $params[
'omit_bots'] && $this->mAttribs[
'rc_bot'] ) ||
467 ( $params[
'omit_anon'] && $performer->isAnon() ) ||
468 ( $params[
'omit_user'] && !$performer->isAnon() ) ||
469 ( $params[
'omit_minor'] && $this->mAttribs[
'rc_minor'] ) ||
470 ( $params[
'omit_patrolled'] && $this->mAttribs[
'rc_patrolled'] ) ||
476 $actionComment = $this->mExtra[
'actionCommentIRC'] ??
null;
479 $feed->notify( $this, $actionComment );
491 public static function getEngine( $uri, $params = [] ) {
494 $scheme = parse_url( $uri, PHP_URL_SCHEME );
496 throw new MWException(
"Invalid RCFeed uri: '$uri'" );
499 throw new MWException(
"Unknown RCFeedEngine scheme: '$scheme'" );
501 if ( defined(
'MW_PHPUNIT_TEST' ) && is_object(
$wgRCEngines[$scheme] ) ) {
518 public static function markPatrolled( $change, $auto =
false, $tags =
null ) {
525 : self::newFromId( $change );
531 return $change->doMarkPatrolled( $wgUser, $auto,
$tags );
548 $permManager = MediaWikiServices::getInstance()->getPermissionManager();
551 if (
$tags ===
null ) {
553 } elseif ( is_string(
$tags ) ) {
562 $this->
getAttribute(
'rc_log_type' ) ==
'upload' ) ) ) {
563 $errors[] = [
'rcpatroldisabled' ];
566 $right = $auto ?
'autopatrol' :
'patrol';
567 $errors = array_merge(
569 $permManager->getPermissionErrors( $right, $user, $this->getTitle() )
571 if ( !Hooks::runner()->onMarkPatrolled(
574 $errors[] = [
'hookaborted' ];
578 if ( $user->
getName() === $this->getAttribute(
'rc_user_text' ) &&
579 !$permManager->userHasRight( $user,
'autopatrol' )
581 $errors[] = [
'markedaspatrollederror-noautopatrol' ];
595 Hooks::runner()->onMarkPatrolledComplete(
610 'rc_patrolled' => self::PRC_PATROLLED
619 $this->
getTitle()->invalidateCache();
621 return $dbw->affectedRows();
644 $timestamp,
$title, $minor, $user, $comment, $oldId, $lastTimestamp,
645 $bot, $ip =
'', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
650 $rc->mPerformer = $user;
652 'rc_timestamp' => $timestamp,
653 'rc_namespace' =>
$title->getNamespace(),
654 'rc_title' =>
$title->getDBkey(),
656 'rc_source' => self::SRC_EDIT,
657 'rc_minor' => $minor ? 1 : 0,
658 'rc_cur_id' =>
$title->getArticleID(),
659 'rc_user' => $user->getId(),
660 'rc_user_text' => $user->getName(),
661 'rc_actor' => $user->getActorId(),
662 'rc_comment' => &$comment,
663 'rc_comment_text' => &$comment,
664 'rc_comment_data' =>
null,
665 'rc_this_oldid' => $newId,
666 'rc_last_oldid' => $oldId,
667 'rc_bot' => $bot ? 1 : 0,
668 'rc_ip' => self::checkIPAddress( $ip ),
669 'rc_patrolled' => intval( $patrol ),
670 'rc_new' => 0, # obsolete
671 'rc_old_len' => $oldSize,
672 'rc_new_len' => $newSize,
675 'rc_log_type' =>
null,
676 'rc_log_action' =>
'',
681 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
682 'lastTimestamp' => $lastTimestamp,
683 'oldSize' => $oldSize,
684 'newSize' => $newSize,
685 'pageStatus' =>
'changed'
688 DeferredUpdates::addCallableUpdate(
689 function () use ( $rc,
$tags ) {
690 $rc->addTags(
$tags );
693 DeferredUpdates::POSTSEND,
718 $timestamp,
$title, $minor, $user, $comment, $bot,
719 $ip =
'', $size = 0, $newId = 0, $patrol = 0, $tags = []
723 $rc->mPerformer = $user;
725 'rc_timestamp' => $timestamp,
726 'rc_namespace' =>
$title->getNamespace(),
727 'rc_title' =>
$title->getDBkey(),
729 'rc_source' => self::SRC_NEW,
730 'rc_minor' => $minor ? 1 : 0,
731 'rc_cur_id' =>
$title->getArticleID(),
732 'rc_user' => $user->getId(),
733 'rc_user_text' => $user->getName(),
734 'rc_actor' => $user->getActorId(),
735 'rc_comment' => &$comment,
736 'rc_comment_text' => &$comment,
737 'rc_comment_data' =>
null,
738 'rc_this_oldid' => $newId,
739 'rc_last_oldid' => 0,
740 'rc_bot' => $bot ? 1 : 0,
741 'rc_ip' => self::checkIPAddress( $ip ),
742 'rc_patrolled' => intval( $patrol ),
743 'rc_new' => 1, # obsolete
745 'rc_new_len' => $size,
748 'rc_log_type' =>
null,
749 'rc_log_action' =>
'',
754 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
755 'lastTimestamp' => 0,
758 'pageStatus' =>
'created'
761 DeferredUpdates::addCallableUpdate(
762 function () use ( $rc,
$tags ) {
763 $rc->addTags(
$tags );
766 DeferredUpdates::POSTSEND,
789 $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC =
''
793 # Don't add private logs to RC!
797 $rc = self::newLogEntry( $timestamp,
$title, $user, $actionComment, $ip,
$type, $action,
798 $target, $logComment, $params, $newId, $actionCommentIRC );
822 $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC =
'',
823 $revId = 0, $isPatrollable =
false ) {
825 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
827 # # Get pageStatus for email notification
828 switch (
$type .
'-' . $action ) {
829 case 'delete-delete':
830 case 'delete-delete_redir':
831 $pageStatus =
'deleted';
834 case 'move-move_redir':
835 $pageStatus =
'moved';
837 case 'delete-restore':
838 $pageStatus =
'restored';
840 case 'upload-upload':
841 $pageStatus =
'created';
843 case 'upload-overwrite':
845 $pageStatus =
'changed';
850 $canAutopatrol = $permissionManager->userHasRight( $user,
'autopatrol' );
851 $markPatrolled = $isPatrollable ? $canAutopatrol :
true;
854 $rc->mTitle = $target;
855 $rc->mPerformer = $user;
857 'rc_timestamp' => $timestamp,
858 'rc_namespace' => $target->getNamespace(),
859 'rc_title' => $target->getDBkey(),
861 'rc_source' => self::SRC_LOG,
863 'rc_cur_id' => $target->getArticleID(),
864 'rc_user' => $user->getId(),
865 'rc_user_text' => $user->getName(),
866 'rc_actor' => $user->getActorId(),
867 'rc_comment' => &$logComment,
868 'rc_comment_text' => &$logComment,
869 'rc_comment_data' =>
null,
870 'rc_this_oldid' => $revId,
871 'rc_last_oldid' => 0,
872 'rc_bot' => $permissionManager->userHasRight( $user,
'bot' ) ?
874 'rc_ip' => self::checkIPAddress( $ip ),
875 'rc_patrolled' => $markPatrolled ? self::PRC_AUTOPATROLLED : self::PRC_UNPATROLLED,
876 'rc_new' => 0, # obsolete
877 'rc_old_len' =>
null,
878 'rc_new_len' =>
null,
880 'rc_logid' => $newId,
881 'rc_log_type' =>
$type,
882 'rc_log_action' => $action,
883 'rc_params' => $params
887 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
888 'lastTimestamp' => 0,
889 'actionComment' => $actionComment,
890 'pageStatus' => $pageStatus,
891 'actionCommentIRC' => $actionCommentIRC
920 Title $categoryTitle,
936 if ( $added !==
null ) {
937 $params[
'added'] = $added;
941 $rc->mTitle = $categoryTitle;
942 $rc->mPerformer = $user;
944 'rc_timestamp' => $timestamp,
946 'rc_title' => $categoryTitle->
getDBkey(),
948 'rc_source' => self::SRC_CATEGORIZE,
951 'rc_user' => $user ? $user->
getId() : 0,
952 'rc_user_text' => $user ? $user->
getName() :
'',
953 'rc_actor' => $user ? $user->
getActorId() :
null,
954 'rc_comment' => &$comment,
955 'rc_comment_text' => &$comment,
956 'rc_comment_data' =>
null,
957 'rc_this_oldid' => $newRevId,
958 'rc_last_oldid' => $oldRevId,
959 'rc_bot' => $bot ? 1 : 0,
960 'rc_ip' => self::checkIPAddress( $ip ),
961 'rc_patrolled' => self::PRC_AUTOPATROLLED,
962 'rc_new' => 0, # obsolete
963 'rc_old_len' =>
null,
964 'rc_new_len' =>
null,
965 'rc_deleted' => $deleted,
967 'rc_log_type' =>
null,
968 'rc_log_action' =>
'',
974 'lastTimestamp' => $lastTimestamp,
977 'pageStatus' =>
'changed'
993 return $params[$name] ??
null;
1002 $this->mAttribs = get_object_vars( $row );
1003 $this->mAttribs[
'rc_timestamp'] =
wfTimestamp( TS_MW, $this->mAttribs[
'rc_timestamp'] );
1005 $this->mAttribs[
'rc_deleted'] = $row->rc_deleted;
1007 if ( isset( $this->mAttribs[
'rc_ip'] ) ) {
1009 $n = strpos( $this->mAttribs[
'rc_ip'],
'/' );
1010 if ( $n !==
false ) {
1011 $this->mAttribs[
'rc_ip'] = substr( $this->mAttribs[
'rc_ip'], 0, $n );
1015 $comment = CommentStore::getStore()
1019 $this->mAttribs[
'rc_comment'] = &$comment;
1020 $this->mAttribs[
'rc_comment_text'] = &$comment;
1021 $this->mAttribs[
'rc_comment_data'] =
null;
1024 $this->mAttribs[
'rc_user'] ??
null,
1025 $this->mAttribs[
'rc_user_text'] ??
null,
1026 $this->mAttribs[
'rc_actor'] ??
null
1028 $this->mAttribs[
'rc_user'] = $user->getId();
1029 $this->mAttribs[
'rc_user_text'] = $user->getName();
1030 $this->mAttribs[
'rc_actor'] = $user->getActorId();
1033 if ( isset( $row->we_expiry ) && $row->we_expiry ) {
1034 $this->watchlistExpiry =
wfTimestamp( TS_MW, $row->we_expiry );
1045 if ( $name ===
'rc_comment' ) {
1046 return CommentStore::getStore()
1047 ->getComment(
'rc_comment', $this->mAttribs,
true )->text;
1050 if ( $name ===
'rc_user' || $name ===
'rc_user_text' || $name ===
'rc_actor' ) {
1052 $this->mAttribs[
'rc_user'] ??
null,
1053 $this->mAttribs[
'rc_user_text'] ??
null,
1054 $this->mAttribs[
'rc_actor'] ??
null
1056 if ( $name ===
'rc_user' ) {
1057 return $user->getId();
1059 if ( $name ===
'rc_user_text' ) {
1060 return $user->getName();
1062 if ( $name ===
'rc_actor' ) {
1063 return $user->getActorId();
1067 return $this->mAttribs[$name] ??
null;
1074 return $this->mAttribs;
1084 if ( $this->mAttribs[
'rc_type'] ==
RC_EDIT ) {
1085 $trail =
"curid=" . (int)( $this->mAttribs[
'rc_cur_id'] ) .
1086 "&oldid=" . (int)( $this->mAttribs[
'rc_last_oldid'] );
1088 $trail .=
'&diff=0';
1090 $trail .=
'&diff=' . (int)( $this->mAttribs[
'rc_this_oldid'] );
1108 $old = $this->mAttribs[
'rc_old_len'];
1111 $new = $this->mAttribs[
'rc_new_len'];
1113 if ( $old ===
null || $new ===
null ) {
1117 return ChangesList::showCharacterDifference( $old, $new );
1123 if ( !IPUtils::isIPAddress( $ip ) ) {
1124 throw new MWException(
"Attempt to write \"" . $ip .
1125 "\" as an IP address into recent changes" );
1162 Wikimedia\suppressWarnings();
1164 Wikimedia\restoreWarnings();
1166 return $unserializedParams;
1178 if ( is_string(
$tags ) ) {
1179 $this->tags[] =
$tags;
1181 $this->tags = array_merge(
$tags, $this->tags );
unserialize( $serialized)
$wgRCFeeds
Configuration for feeds to which notifications about recent changes will be sent.
$wgPutIPinRC
Log IP addresses in the recentchanges table; can be accessed only by extensions (e....
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
$wgLogRestrictions
This restricts log access to those who have a certain right Users without this will not see it in the...
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
$wgRCMaxAge
Recentchanges items are periodically purged; entries older than this many seconds will go.
$wgRCEngines
Used by RecentChange::getEngine to find the correct engine for a given URI scheme.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
if(! $wgDBerrorLogTZ) $wgRequest
This module processes the email notifications when the current page is changed.
static record( $rc, $auto=false, User $user=null, $tags=null)
Record a log event for a change being patrolled.
static factory(array $params)
Utility class for creating new RC entries.
static getEngine( $uri, $params=[])
static parseToRCType( $type)
Parsing text to RC_* constants.
reallyMarkPatrolled()
Mark this RecentChange patrolled, without error checking.
parseParams()
Parses and returns the rc_params attribute.
static notifyEdit( $timestamp, $title, $minor, $user, $comment, $oldId, $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0, $tags=[])
Makes an entry in the database corresponding to an edit.
getPerformer()
Get the User object of the person who performed this change.
static checkIPAddress( $ip)
static newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='', $revId=0, $isPatrollable=false)
static getChangeTypes()
Get an array of all change types.
static isInRCLifespan( $timestamp, $tolerance=0)
Check whether the given timestamp is new enough to have a RC row with a given tolerance as the recent...
static markPatrolled( $change, $auto=false, $tags=null)
Mark a given change as patrolled.
int $counter
Line number of recent change.
static newForCategorization( $timestamp, Title $categoryTitle, ?User $user, $comment, Title $pageTitle, $oldRevId, $newRevId, $lastTimestamp, $bot, $ip='', $deleted=0, $added=null)
Constructs a RecentChange object for the given categorization This does not call save() on the object...
save( $send=self::SEND_FEED)
Writes the data in this object to the database.
doMarkPatrolled(User $user, $auto=false, $tags=null)
Mark this RecentChange as patrolled.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
getCharacterDifference( $old=0, $new=0)
Returns the change size (HTML).
static parseFromRCType( $rcType)
Parsing RC_* constants to human-readable test.
notifyRCFeeds(array $feeds=null)
Notify all the feeds about the change.
array $tags
List of tags to apply.
getParam( $name)
Get a parameter value.
addTags( $tags)
Tags to append to the recent change, and associated revision/log.
static notifyNew( $timestamp, $title, $minor, $user, $comment, $bot, $ip='', $size=0, $newId=0, $patrol=0, $tags=[])
Makes an entry in the database corresponding to page creation Note: the title object must be loaded w...
loadFromRow( $row)
Initialises the members of this object from a mysql row object.
getAttribute( $name)
Get an attribute value.
string null $watchlistExpiry
The expiry time, if this is a temporary watchlist item.
diffLinkTrail( $forceCur)
Gets the end part of the diff URL associated with this object Blank if no diff link should be display...
static notifyLog( $timestamp, $title, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='')
static newFromId( $rcid)
Obtain the recent change with a given rc_id value.
This is to display changes made to all articles linked in an article.
Represents a title within MediaWiki.
getNamespace()
Get the namespace index, i.e.
getPrefixedDBkey()
Get the prefixed database key form.
getDBkey()
Get the main part with underscores.
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
getName()
Get the user name, or the IP of an anonymous user.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
getId()
Get the user's ID.
static newFromId( $id)
Static factory method for creation from a given user ID.
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.