86 const SEND_NONE =
true;
91 const SEND_FEED =
false;
152 if ( is_array(
$type ) ) {
155 $retval[] = self::parseToRCType(
$t );
161 if ( !array_key_exists(
$type, self::$changeTypes ) ) {
164 return self::$changeTypes[
$type];
174 return array_search( $rcType, self::$changeTypes,
true ) ?:
"$rcType";
185 return array_keys( self::$changeTypes );
195 return self::newFromConds( [
'rc_id' => $rcid ], __METHOD__ );
213 $rcQuery = self::getQueryInfo();
214 $row = $db->selectRow(
215 $rcQuery[
'tables'], $rcQuery[
'fields'], $conds, $fname, [], $rcQuery[
'joins']
217 if ( $row !==
false ) {
218 return self::newFromRow( $row );
234 $commentQuery = CommentStore::getStore()->getJoin(
'rc_comment' );
235 $actorQuery = ActorMigration::newMigration()->getJoin(
'rc_user' );
237 'tables' => [
'recentchanges' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
260 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
261 'joins' => $commentQuery[
'joins'] + $actorQuery[
'joins'],
271 $this->mAttribs = $attribs;
278 $this->mExtra = $extra;
285 if ( $this->mTitle ===
false ) {
286 $this->mTitle = Title::makeTitle( $this->mAttribs[
'rc_namespace'], $this->mAttribs[
'rc_title'] );
298 if ( $this->mPerformer ===
false ) {
299 if ( !empty( $this->mAttribs[
'rc_actor'] ) ) {
300 $this->mPerformer = User::newFromActorId( $this->mAttribs[
'rc_actor'] );
301 } elseif ( !empty( $this->mAttribs[
'rc_user'] ) ) {
302 $this->mPerformer = User::newFromId( $this->mAttribs[
'rc_user'] );
303 } elseif ( !empty( $this->mAttribs[
'rc_user_text'] ) ) {
304 $this->mPerformer = User::newFromName( $this->mAttribs[
'rc_user_text'],
false );
306 throw new MWException(
'RecentChange object lacks rc_actor, rc_user, and rc_user_text' );
310 return $this->mPerformer;
322 public function save( $send = self::SEND_FEED ) {
326 if ( !is_array( $this->mExtra ) ) {
331 $this->mAttribs[
'rc_ip'] =
'';
334 # Strict mode fixups (not-NULL fields)
335 foreach ( [
'minor',
'bot',
'new',
'patrolled',
'deleted' ] as $field ) {
336 $this->mAttribs[
"rc_$field"] = (int)$this->mAttribs[
"rc_$field"];
338 # ...more fixups (NULL fields)
339 foreach ( [
'old_len',
'new_len' ] as $field ) {
340 $this->mAttribs[
"rc_$field"] = isset( $this->mAttribs[
"rc_$field"] )
341 ? (int)$this->mAttribs[
"rc_$field"]
345 # If our database is strict about IP addresses, use NULL instead of an empty string
346 $strictIPs = $dbw->getType() ===
'postgres';
347 if ( $strictIPs && $this->mAttribs[
'rc_ip'] ==
'' ) {
348 unset( $this->mAttribs[
'rc_ip'] );
351 # Trim spaces on user supplied text
352 $this->mAttribs[
'rc_comment'] = trim( $this->mAttribs[
'rc_comment'] );
354 # Fixup database timestamps
355 $this->mAttribs[
'rc_timestamp'] = $dbw->timestamp( $this->mAttribs[
'rc_timestamp'] );
357 # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
358 if ( $this->mAttribs[
'rc_cur_id'] == 0 ) {
359 unset( $this->mAttribs[
'rc_cur_id'] );
362 $row = $this->mAttribs;
364 # Convert mAttribs['rc_comment'] for CommentStore
365 $comment = $row[
'rc_comment'];
366 unset( $row[
'rc_comment'], $row[
'rc_comment_text'], $row[
'rc_comment_data'] );
367 $row += CommentStore::getStore()->insert( $dbw,
'rc_comment', $comment );
369 # Convert mAttribs['rc_user'] etc for ActorMigration
370 $user = User::newFromAnyId(
371 $row[
'rc_user'] ??
null,
372 $row[
'rc_user_text'] ??
null,
373 $row[
'rc_actor'] ??
null
375 unset( $row[
'rc_user'], $row[
'rc_user_text'], $row[
'rc_actor'] );
376 $row += ActorMigration::newMigration()->getInsertValues( $dbw,
'rc_user', $user );
378 # Don't reuse an existing rc_id for the new row, if one happens to be
379 # set for some reason.
380 unset( $row[
'rc_id'] );
383 $dbw->insert(
'recentchanges', $row, __METHOD__ );
386 $this->mAttribs[
'rc_id'] = $dbw->insertId();
391 Hooks::run(
'RecentChange_save', [ &$rc ] );
393 if ( count( $this->tags ) ) {
395 $this->mAttribs[
'rc_this_oldid'], $this->mAttribs[
'rc_logid'],
null, $this );
398 if ( $send === self::SEND_FEED ) {
403 # E-mail notifications
410 Hooks::run(
'AbortEmailNotification', [ $editor,
$title, $this ] ) &&
415 $dbw->onTransactionCommitOrIdle(
416 function () use ( $editor,
$title ) {
418 $enotif->notifyOnPageChange(
421 $this->mAttribs[
'rc_timestamp'],
422 $this->mAttribs[
'rc_comment'],
423 $this->mAttribs[
'rc_minor'],
424 $this->mAttribs[
'rc_last_oldid'],
425 $this->mExtra[
'pageStatus']
434 if ( $this->mAttribs[
'rc_user'] > 0 ) {
435 JobQueueGroup::singleton()->lazyPush( RecentChangesUpdateJob::newCacheUpdateJob() );
445 if ( $feeds ===
null ) {
451 foreach ( $feeds as $params ) {
453 'omit_bots' =>
false,
454 'omit_anon' =>
false,
455 'omit_user' =>
false,
456 'omit_minor' =>
false,
457 'omit_patrolled' =>
false,
461 ( $params[
'omit_bots'] && $this->mAttribs[
'rc_bot'] ) ||
462 ( $params[
'omit_anon'] && $performer->isAnon() ) ||
463 ( $params[
'omit_user'] && !$performer->isAnon() ) ||
464 ( $params[
'omit_minor'] && $this->mAttribs[
'rc_minor'] ) ||
465 ( $params[
'omit_patrolled'] && $this->mAttribs[
'rc_patrolled'] ) ||
471 $actionComment = $this->mExtra[
'actionCommentIRC'] ??
null;
474 $feed->notify( $this, $actionComment );
486 public static function getEngine( $uri, $params = [] ) {
489 $scheme = parse_url( $uri, PHP_URL_SCHEME );
491 throw new MWException(
"Invalid RCFeed uri: '$uri'" );
494 throw new MWException(
"Unknown RCFeedEngine scheme: '$scheme'" );
496 if ( defined(
'MW_PHPUNIT_TEST' ) && is_object(
$wgRCEngines[$scheme] ) ) {
511 public static function markPatrolled( $change, $auto =
false, $tags =
null ) {
516 : self::newFromId( $change );
522 return $change->doMarkPatrolled( $wgUser, $auto,
$tags );
540 if (
$tags ===
null ) {
542 } elseif ( is_string(
$tags ) ) {
551 $this->
getAttribute(
'rc_log_type' ) ==
'upload' ) ) ) {
552 $errors[] = [
'rcpatroldisabled' ];
555 $right = $auto ?
'autopatrol' :
'patrol';
556 $errors = array_merge( $errors, $this->
getTitle()->getUserPermissionsErrors( $right, $user ) );
557 if ( !Hooks::run(
'MarkPatrolled',
560 $errors[] = [
'hookaborted' ];
564 if ( $user->
getName() === $this->getAttribute(
'rc_user_text' ) &&
565 !MediaWikiServices::getInstance()->getPermissionManager()
566 ->userHasRight( $user,
'autopatrol' )
568 $errors[] = [
'markedaspatrollederror-noautopatrol' ];
583 'MarkPatrolledComplete',
584 [ $this->
getAttribute(
'rc_id' ), &$user,
false, $auto ]
599 'rc_patrolled' => self::PRC_PATROLLED
608 $this->
getTitle()->invalidateCache();
610 return $dbw->affectedRows();
633 $timestamp,
$title, $minor, $user, $comment, $oldId, $lastTimestamp,
634 $bot, $ip =
'', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
639 $rc->mPerformer = $user;
641 'rc_timestamp' => $timestamp,
642 'rc_namespace' =>
$title->getNamespace(),
643 'rc_title' =>
$title->getDBkey(),
645 'rc_source' => self::SRC_EDIT,
646 'rc_minor' => $minor ? 1 : 0,
647 'rc_cur_id' =>
$title->getArticleID(),
648 'rc_user' => $user->getId(),
649 'rc_user_text' => $user->getName(),
650 'rc_actor' => $user->getActorId(),
651 'rc_comment' => &$comment,
652 'rc_comment_text' => &$comment,
653 'rc_comment_data' =>
null,
654 'rc_this_oldid' => $newId,
655 'rc_last_oldid' => $oldId,
656 'rc_bot' => $bot ? 1 : 0,
657 'rc_ip' => self::checkIPAddress( $ip ),
658 'rc_patrolled' => intval( $patrol ),
659 'rc_new' => 0, # obsolete
660 'rc_old_len' => $oldSize,
661 'rc_new_len' => $newSize,
664 'rc_log_type' =>
null,
665 'rc_log_action' =>
'',
670 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
671 'lastTimestamp' => $lastTimestamp,
672 'oldSize' => $oldSize,
673 'newSize' => $newSize,
674 'pageStatus' =>
'changed'
677 DeferredUpdates::addCallableUpdate(
678 function () use ( $rc,
$tags ) {
679 $rc->addTags(
$tags );
682 DeferredUpdates::POSTSEND,
707 $timestamp,
$title, $minor, $user, $comment, $bot,
708 $ip =
'', $size = 0, $newId = 0, $patrol = 0, $tags = []
712 $rc->mPerformer = $user;
714 'rc_timestamp' => $timestamp,
715 'rc_namespace' =>
$title->getNamespace(),
716 'rc_title' =>
$title->getDBkey(),
718 'rc_source' => self::SRC_NEW,
719 'rc_minor' => $minor ? 1 : 0,
720 'rc_cur_id' =>
$title->getArticleID(),
721 'rc_user' => $user->getId(),
722 'rc_user_text' => $user->getName(),
723 'rc_actor' => $user->getActorId(),
724 'rc_comment' => &$comment,
725 'rc_comment_text' => &$comment,
726 'rc_comment_data' =>
null,
727 'rc_this_oldid' => $newId,
728 'rc_last_oldid' => 0,
729 'rc_bot' => $bot ? 1 : 0,
730 'rc_ip' => self::checkIPAddress( $ip ),
731 'rc_patrolled' => intval( $patrol ),
732 'rc_new' => 1, # obsolete
734 'rc_new_len' => $size,
737 'rc_log_type' =>
null,
738 'rc_log_action' =>
'',
743 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
744 'lastTimestamp' => 0,
747 'pageStatus' =>
'created'
750 DeferredUpdates::addCallableUpdate(
751 function () use ( $rc,
$tags ) {
752 $rc->addTags(
$tags );
755 DeferredUpdates::POSTSEND,
778 $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC =
''
782 # Don't add private logs to RC!
786 $rc = self::newLogEntry( $timestamp,
$title, $user, $actionComment, $ip,
$type, $action,
787 $target, $logComment, $params, $newId, $actionCommentIRC );
811 $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC =
'',
812 $revId = 0, $isPatrollable =
false ) {
814 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
816 # # Get pageStatus for email notification
817 switch (
$type .
'-' . $action ) {
818 case 'delete-delete':
819 case 'delete-delete_redir':
820 $pageStatus =
'deleted';
823 case 'move-move_redir':
824 $pageStatus =
'moved';
826 case 'delete-restore':
827 $pageStatus =
'restored';
829 case 'upload-upload':
830 $pageStatus =
'created';
832 case 'upload-overwrite':
834 $pageStatus =
'changed';
839 $canAutopatrol = $permissionManager->userHasRight( $user,
'autopatrol' );
840 $markPatrolled = $isPatrollable ? $canAutopatrol :
true;
843 $rc->mTitle = $target;
844 $rc->mPerformer = $user;
846 'rc_timestamp' => $timestamp,
847 'rc_namespace' => $target->getNamespace(),
848 'rc_title' => $target->getDBkey(),
850 'rc_source' => self::SRC_LOG,
852 'rc_cur_id' => $target->getArticleID(),
853 'rc_user' => $user->getId(),
854 'rc_user_text' => $user->getName(),
855 'rc_actor' => $user->getActorId(),
856 'rc_comment' => &$logComment,
857 'rc_comment_text' => &$logComment,
858 'rc_comment_data' =>
null,
859 'rc_this_oldid' => $revId,
860 'rc_last_oldid' => 0,
861 'rc_bot' => $permissionManager->userHasRight( $user,
'bot' ) ?
863 'rc_ip' => self::checkIPAddress( $ip ),
864 'rc_patrolled' => $markPatrolled ? self::PRC_AUTOPATROLLED : self::PRC_UNPATROLLED,
865 'rc_new' => 0, # obsolete
866 'rc_old_len' =>
null,
867 'rc_new_len' =>
null,
869 'rc_logid' => $newId,
870 'rc_log_type' =>
$type,
871 'rc_log_action' => $action,
872 'rc_params' => $params
876 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
877 'lastTimestamp' => 0,
878 'actionComment' => $actionComment,
879 'pageStatus' => $pageStatus,
880 'actionCommentIRC' => $actionCommentIRC
909 Title $categoryTitle,
925 if ( $added !==
null ) {
926 $params[
'added'] = $added;
930 $rc->mTitle = $categoryTitle;
931 $rc->mPerformer = $user;
933 'rc_timestamp' => $timestamp,
935 'rc_title' => $categoryTitle->
getDBkey(),
937 'rc_source' => self::SRC_CATEGORIZE,
940 'rc_user' => $user ? $user->getId() : 0,
941 'rc_user_text' => $user ? $user->getName() :
'',
942 'rc_actor' => $user ? $user->getActorId() :
null,
943 'rc_comment' => &$comment,
944 'rc_comment_text' => &$comment,
945 'rc_comment_data' =>
null,
946 'rc_this_oldid' => $newRevId,
947 'rc_last_oldid' => $oldRevId,
948 'rc_bot' => $bot ? 1 : 0,
949 'rc_ip' => self::checkIPAddress( $ip ),
950 'rc_patrolled' => self::PRC_AUTOPATROLLED,
951 'rc_new' => 0, # obsolete
952 'rc_old_len' =>
null,
953 'rc_new_len' =>
null,
954 'rc_deleted' => $deleted,
956 'rc_log_type' =>
null,
957 'rc_log_action' =>
'',
963 'lastTimestamp' => $lastTimestamp,
966 'pageStatus' =>
'changed'
982 return $params[$name] ??
null;
991 $this->mAttribs = get_object_vars( $row );
992 $this->mAttribs[
'rc_timestamp'] =
wfTimestamp( TS_MW, $this->mAttribs[
'rc_timestamp'] );
994 $this->mAttribs[
'rc_deleted'] = $row->rc_deleted;
996 if ( isset( $this->mAttribs[
'rc_ip'] ) ) {
998 $n = strpos( $this->mAttribs[
'rc_ip'],
'/' );
999 if ( $n !==
false ) {
1000 $this->mAttribs[
'rc_ip'] = substr( $this->mAttribs[
'rc_ip'], 0, $n );
1004 $comment = CommentStore::getStore()
1008 $this->mAttribs[
'rc_comment'] = &$comment;
1009 $this->mAttribs[
'rc_comment_text'] = &$comment;
1010 $this->mAttribs[
'rc_comment_data'] =
null;
1012 $user = User::newFromAnyId(
1013 $this->mAttribs[
'rc_user'] ??
null,
1014 $this->mAttribs[
'rc_user_text'] ??
null,
1015 $this->mAttribs[
'rc_actor'] ??
null
1017 $this->mAttribs[
'rc_user'] = $user->getId();
1018 $this->mAttribs[
'rc_user_text'] = $user->getName();
1019 $this->mAttribs[
'rc_actor'] = $user->getActorId();
1029 if ( $name ===
'rc_comment' ) {
1030 return CommentStore::getStore()
1031 ->getComment(
'rc_comment', $this->mAttribs,
true )->text;
1034 if ( $name ===
'rc_user' || $name ===
'rc_user_text' || $name ===
'rc_actor' ) {
1035 $user = User::newFromAnyId(
1036 $this->mAttribs[
'rc_user'] ??
null,
1037 $this->mAttribs[
'rc_user_text'] ??
null,
1038 $this->mAttribs[
'rc_actor'] ??
null
1040 if ( $name ===
'rc_user' ) {
1041 return $user->getId();
1043 if ( $name ===
'rc_user_text' ) {
1044 return $user->getName();
1046 if ( $name ===
'rc_actor' ) {
1047 return $user->getActorId();
1051 return $this->mAttribs[$name] ??
null;
1058 return $this->mAttribs;
1068 if ( $this->mAttribs[
'rc_type'] ==
RC_EDIT ) {
1069 $trail =
"curid=" . (int)( $this->mAttribs[
'rc_cur_id'] ) .
1070 "&oldid=" . (int)( $this->mAttribs[
'rc_last_oldid'] );
1072 $trail .=
'&diff=0';
1074 $trail .=
'&diff=' . (int)( $this->mAttribs[
'rc_this_oldid'] );
1092 $old = $this->mAttribs[
'rc_old_len'];
1095 $new = $this->mAttribs[
'rc_new_len'];
1097 if ( $old ===
null || $new ===
null ) {
1107 if ( !IP::isIPAddress( $ip ) ) {
1108 throw new MWException(
"Attempt to write \"" . $ip .
1109 "\" as an IP address into recent changes" );
1146 Wikimedia\suppressWarnings();
1148 Wikimedia\restoreWarnings();
1150 return $unserializedParams;
1162 if ( is_string(
$tags ) ) {
1163 $this->tags[] =
$tags;
1165 $this->tags = array_merge(
$tags, $this->tags );
unserialize( $serialized)
$wgRCFeeds
Recentchanges items are periodically purged; entries older than this many seconds will go.
$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.
if(! $wgDBerrorLogTZ) $wgRequest
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
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 array $changeTypes
Array of change types.
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 newForCategorization( $timestamp, Title $categoryTitle, User $user=null, $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...
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.
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.
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 factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.