Go to the documentation of this file.
26 use Wikimedia\IPUtils;
90 public const SEND_NONE =
true;
95 public const SEND_FEED =
false;
137 private const CHANGE_TYPES = [
166 if ( is_array(
$type ) ) {
175 if ( !array_key_exists(
$type, self::CHANGE_TYPES ) ) {
178 return self::CHANGE_TYPES[
$type];
188 return array_search( $rcType, self::CHANGE_TYPES,
true ) ?:
"$rcType";
199 return array_keys( self::CHANGE_TYPES );
228 $row = $db->selectRow(
229 $rcQuery[
'tables'], $rcQuery[
'fields'], $conds, $fname, [], $rcQuery[
'joins']
231 if ( $row !==
false ) {
251 'tables' => [
'recentchanges' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
274 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
275 'joins' => $commentQuery[
'joins'] + $actorQuery[
'joins'],
285 $this->mAttribs = $attribs;
292 $this->mExtra = $extra;
299 if ( $this->mTitle ===
false ) {
300 $this->mTitle =
Title::makeTitle( $this->mAttribs[
'rc_namespace'], $this->mAttribs[
'rc_title'] );
312 if ( $this->mPerformer ===
false ) {
313 if ( !empty( $this->mAttribs[
'rc_actor'] ) ) {
315 } elseif ( !empty( $this->mAttribs[
'rc_user'] ) ) {
317 } elseif ( !empty( $this->mAttribs[
'rc_user_text'] ) ) {
320 throw new MWException(
'RecentChange object lacks rc_actor, rc_user, and rc_user_text' );
336 public function save( $send = self::SEND_FEED ) {
340 if ( !is_array( $this->mExtra ) ) {
345 $this->mAttribs[
'rc_ip'] =
'';
348 # Strict mode fixups (not-NULL fields)
349 foreach ( [
'minor',
'bot',
'new',
'patrolled',
'deleted' ] as $field ) {
350 $this->mAttribs[
"rc_$field"] = (int)$this->mAttribs[
"rc_$field"];
352 # ...more fixups (NULL fields)
353 foreach ( [
'old_len',
'new_len' ] as $field ) {
354 $this->mAttribs[
"rc_$field"] = isset( $this->mAttribs[
"rc_$field"] )
355 ? (int)$this->mAttribs[
"rc_$field"]
359 # If our database is strict about IP addresses, use NULL instead of an empty string
360 $strictIPs = $dbw->getType() ===
'postgres';
361 if ( $strictIPs && $this->mAttribs[
'rc_ip'] ==
'' ) {
362 unset( $this->mAttribs[
'rc_ip'] );
365 # Trim spaces on user supplied text
366 $this->mAttribs[
'rc_comment'] = trim( $this->mAttribs[
'rc_comment'] );
368 # Fixup database timestamps
369 $this->mAttribs[
'rc_timestamp'] = $dbw->timestamp( $this->mAttribs[
'rc_timestamp'] );
371 # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
372 if ( $this->mAttribs[
'rc_cur_id'] == 0 ) {
373 unset( $this->mAttribs[
'rc_cur_id'] );
378 # Convert mAttribs['rc_comment'] for CommentStore
379 $comment = $row[
'rc_comment'];
380 unset( $row[
'rc_comment'], $row[
'rc_comment_text'], $row[
'rc_comment_data'] );
383 # Convert mAttribs['rc_user'] etc for ActorMigration
385 $row[
'rc_user'] ??
null,
386 $row[
'rc_user_text'] ??
null,
387 $row[
'rc_actor'] ??
null
389 unset( $row[
'rc_user'], $row[
'rc_user_text'], $row[
'rc_actor'] );
392 # Don't reuse an existing rc_id for the new row, if one happens to be
393 # set for some reason.
394 unset( $row[
'rc_id'] );
397 $dbw->insert(
'recentchanges', $row, __METHOD__ );
400 $this->mAttribs[
'rc_id'] = $dbw->insertId();
406 if ( $this->editResult !==
null && count( $this->editResult->getRevertTags() ) ) {
408 $this->editResult->getRevertTags(),
409 $this->mAttribs[
'rc_id'],
410 $this->mAttribs[
'rc_this_oldid'],
411 $this->mAttribs[
'rc_logid'],
417 if ( count( $this->tags ) ) {
422 $this->mAttribs[
'rc_id'],
423 $this->mAttribs[
'rc_this_oldid'],
424 $this->mAttribs[
'rc_logid'],
430 if ( $send === self::SEND_FEED ) {
435 # E-mail notifications
447 $dbw->onTransactionCommitOrIdle(
448 function () use ( $editor,
$title ) {
450 $enotif->notifyOnPageChange(
453 $this->mAttribs[
'rc_timestamp'],
454 $this->mAttribs[
'rc_comment'],
455 $this->mAttribs[
'rc_minor'],
456 $this->mAttribs[
'rc_last_oldid'],
457 $this->mExtra[
'pageStatus']
466 if ( $this->mAttribs[
'rc_user'] > 0 ) {
477 if ( $feeds ===
null ) {
483 foreach ( $feeds as $params ) {
485 'omit_bots' =>
false,
486 'omit_anon' =>
false,
487 'omit_user' =>
false,
488 'omit_minor' =>
false,
489 'omit_patrolled' =>
false,
493 ( $params[
'omit_bots'] && $this->mAttribs[
'rc_bot'] ) ||
494 ( $params[
'omit_anon'] && $performer->isAnon() ) ||
495 ( $params[
'omit_user'] && !$performer->isAnon() ) ||
496 ( $params[
'omit_minor'] && $this->mAttribs[
'rc_minor'] ) ||
497 ( $params[
'omit_patrolled'] && $this->mAttribs[
'rc_patrolled'] ) ||
503 $actionComment = $this->mExtra[
'actionCommentIRC'] ??
null;
506 $feed->notify( $this, $actionComment );
518 public static function getEngine( $uri, $params = [] ) {
521 $scheme = parse_url( $uri, PHP_URL_SCHEME );
523 throw new MWException(
"Invalid RCFeed uri: '$uri'" );
526 throw new MWException(
"Unknown RCFeedEngine scheme: '$scheme'" );
528 if ( defined(
'MW_PHPUNIT_TEST' ) && is_object(
$wgRCEngines[$scheme] ) ) {
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() )
574 $errors[] = [
'hookaborted' ];
578 if ( $user->
getName() === $this->getAttribute(
'rc_user_text' ) &&
579 !$permManager->userHasRight( $user,
'autopatrol' )
581 $errors[] = [
'markedaspatrollederror-noautopatrol' ];
610 'rc_patrolled' => self::PRC_PATROLLED
619 $this->
getTitle()->invalidateCache();
624 $revertedTagUpdateManager =
625 MediaWikiServices::getInstance()->getRevertedTagUpdateManager();
626 $revertedTagUpdateManager->approveRevertedTagForRevision( $revisionId );
629 return $dbw->affectedRows();
657 $timestamp,
$title, $minor, $user, $comment, $oldId, $lastTimestamp,
658 $bot, $ip =
'', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
663 $rc->mPerformer = $user;
665 'rc_timestamp' => $timestamp,
666 'rc_namespace' =>
$title->getNamespace(),
667 'rc_title' =>
$title->getDBkey(),
670 'rc_minor' => $minor ? 1 : 0,
671 'rc_cur_id' =>
$title->getArticleID(),
672 'rc_user' => $user->getId(),
673 'rc_user_text' => $user->getName(),
674 'rc_actor' => $user->getActorId(),
675 'rc_comment' => &$comment,
676 'rc_comment_text' => &$comment,
677 'rc_comment_data' =>
null,
678 'rc_this_oldid' => (int)$newId,
679 'rc_last_oldid' => $oldId,
680 'rc_bot' => $bot ? 1 : 0,
681 'rc_ip' => self::checkIPAddress( $ip ),
682 'rc_patrolled' => intval( $patrol ),
683 'rc_new' => 0, # obsolete
684 'rc_old_len' => $oldSize,
685 'rc_new_len' => $newSize,
688 'rc_log_type' =>
null,
689 'rc_log_action' =>
'',
694 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
695 'lastTimestamp' => $lastTimestamp,
696 'oldSize' => $oldSize,
697 'newSize' => $newSize,
698 'pageStatus' =>
'changed'
703 $rc->addTags(
$tags );
707 DeferredUpdates::POSTSEND,
732 $timestamp,
$title, $minor, $user, $comment, $bot,
733 $ip =
'', $size = 0, $newId = 0, $patrol = 0,
$tags = []
737 $rc->mPerformer = $user;
739 'rc_timestamp' => $timestamp,
740 'rc_namespace' =>
$title->getNamespace(),
741 'rc_title' =>
$title->getDBkey(),
744 'rc_minor' => $minor ? 1 : 0,
745 'rc_cur_id' =>
$title->getArticleID(),
746 'rc_user' => $user->getId(),
747 'rc_user_text' => $user->getName(),
748 'rc_actor' => $user->getActorId(),
749 'rc_comment' => &$comment,
750 'rc_comment_text' => &$comment,
751 'rc_comment_data' =>
null,
752 'rc_this_oldid' => (int)$newId,
753 'rc_last_oldid' => 0,
754 'rc_bot' => $bot ? 1 : 0,
755 'rc_ip' => self::checkIPAddress( $ip ),
756 'rc_patrolled' => intval( $patrol ),
757 'rc_new' => 1, # obsolete
759 'rc_new_len' => $size,
762 'rc_log_type' =>
null,
763 'rc_log_action' =>
'',
768 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
769 'lastTimestamp' => 0,
772 'pageStatus' =>
'created'
776 static function () use ( $rc,
$tags ) {
777 $rc->addTags(
$tags );
780 DeferredUpdates::POSTSEND,
803 $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC =
''
807 # Don't add private logs to RC!
812 $target, $logComment, $params, $newId, $actionCommentIRC );
836 $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC =
'',
837 $revId = 0, $isPatrollable =
false ) {
839 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
841 # # Get pageStatus for email notification
842 switch (
$type .
'-' . $action ) {
843 case 'delete-delete':
844 case 'delete-delete_redir':
845 case 'delete-delete_redir2':
846 $pageStatus =
'deleted';
849 case 'move-move_redir':
850 $pageStatus =
'moved';
852 case 'delete-restore':
853 $pageStatus =
'restored';
855 case 'upload-upload':
856 $pageStatus =
'created';
858 case 'upload-overwrite':
860 $pageStatus =
'changed';
865 $canAutopatrol = $permissionManager->userHasRight( $user,
'autopatrol' );
866 $markPatrolled = $isPatrollable ? $canAutopatrol :
true;
869 $rc->mTitle = $target;
872 'rc_timestamp' => $timestamp,
873 'rc_namespace' => $target->getNamespace(),
874 'rc_title' => $target->getDBkey(),
878 'rc_cur_id' => $target->getArticleID(),
879 'rc_user' => $user->getId(),
880 'rc_user_text' => $user->getName(),
881 'rc_actor' => $user->getActorId(),
882 'rc_comment' => &$logComment,
883 'rc_comment_text' => &$logComment,
884 'rc_comment_data' =>
null,
885 'rc_this_oldid' => (int)$revId,
886 'rc_last_oldid' => 0,
887 'rc_bot' => $permissionManager->userHasRight( $user,
'bot' ) ?
891 'rc_new' => 0, # obsolete
892 'rc_old_len' =>
null,
893 'rc_new_len' =>
null,
895 'rc_logid' => $newId,
896 'rc_log_type' =>
$type,
897 'rc_log_action' => $action,
898 'rc_params' => $params
902 'prefixedDBkey' =>
$title->getPrefixedDBkey(),
903 'lastTimestamp' => 0,
904 'actionComment' => $actionComment,
905 'pageStatus' => $pageStatus,
906 'actionCommentIRC' => $actionCommentIRC
935 Title $categoryTitle,
948 $categoryWikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $categoryTitle );
949 '@phan-var WikiCategoryPage $categoryWikiPage';
951 'hidden-cat' => $categoryWikiPage->isHidden()
953 if ( $added !==
null ) {
954 $params[
'added'] = $added;
958 $rc->mTitle = $categoryTitle;
959 $rc->mPerformer = $user;
961 'rc_timestamp' => $timestamp,
963 'rc_title' => $categoryTitle->
getDBkey(),
968 'rc_user' => $user ? $user->
getId() : 0,
969 'rc_user_text' => $user ? $user->
getName() :
'',
970 'rc_actor' => $user ? $user->
getActorId() :
null,
971 'rc_comment' => &$comment,
972 'rc_comment_text' => &$comment,
973 'rc_comment_data' =>
null,
974 'rc_this_oldid' => (int)$newRevId,
975 'rc_last_oldid' => $oldRevId,
976 'rc_bot' => $bot ? 1 : 0,
977 'rc_ip' => self::checkIPAddress( $ip ),
979 'rc_new' => 0, # obsolete
980 'rc_old_len' =>
null,
981 'rc_new_len' =>
null,
982 'rc_deleted' => $deleted,
984 'rc_log_type' =>
null,
985 'rc_log_action' =>
'',
991 'lastTimestamp' => $lastTimestamp,
994 'pageStatus' =>
'changed'
1010 return $params[$name] ??
null;
1019 $this->mAttribs = get_object_vars( $row );
1020 $this->mAttribs[
'rc_timestamp'] =
wfTimestamp( TS_MW, $this->mAttribs[
'rc_timestamp'] );
1022 $this->mAttribs[
'rc_deleted'] = $row->rc_deleted;
1024 if ( isset( $this->mAttribs[
'rc_ip'] ) ) {
1026 $n = strpos( $this->mAttribs[
'rc_ip'],
'/' );
1027 if ( $n !==
false ) {
1028 $this->mAttribs[
'rc_ip'] = substr( $this->mAttribs[
'rc_ip'], 0, $n );
1036 $this->mAttribs[
'rc_comment'] = &$comment;
1037 $this->mAttribs[
'rc_comment_text'] = &$comment;
1038 $this->mAttribs[
'rc_comment_data'] =
null;
1041 $this->mAttribs[
'rc_user'] ??
null,
1042 $this->mAttribs[
'rc_user_text'] ??
null,
1043 $this->mAttribs[
'rc_actor'] ??
null
1045 $this->mAttribs[
'rc_user'] = $user->getId();
1046 $this->mAttribs[
'rc_user_text'] = $user->getName();
1047 $this->mAttribs[
'rc_actor'] = $user->getActorId();
1050 if ( isset( $row->we_expiry ) && $row->we_expiry ) {
1051 $this->watchlistExpiry =
wfTimestamp( TS_MW, $row->we_expiry );
1062 if ( $name ===
'rc_comment' ) {
1064 ->getComment(
'rc_comment', $this->mAttribs,
true )->text;
1067 if ( $name ===
'rc_user' || $name ===
'rc_user_text' || $name ===
'rc_actor' ) {
1069 $this->mAttribs[
'rc_user'] ??
null,
1070 $this->mAttribs[
'rc_user_text'] ??
null,
1071 $this->mAttribs[
'rc_actor'] ??
null
1073 if ( $name ===
'rc_user' ) {
1074 return $user->getId();
1076 if ( $name ===
'rc_user_text' ) {
1077 return $user->getName();
1079 if ( $name ===
'rc_actor' ) {
1080 return $user->getActorId();
1084 return $this->mAttribs[$name] ??
null;
1101 if ( $this->mAttribs[
'rc_type'] ==
RC_EDIT ) {
1102 $trail =
"curid=" . (int)( $this->mAttribs[
'rc_cur_id'] ) .
1103 "&oldid=" . (int)( $this->mAttribs[
'rc_last_oldid'] );
1105 $trail .=
'&diff=0';
1107 $trail .=
'&diff=' . (int)( $this->mAttribs[
'rc_this_oldid'] );
1125 $old = $this->mAttribs[
'rc_old_len'];
1128 $new = $this->mAttribs[
'rc_new_len'];
1130 if ( $old ===
null || $new ===
null ) {
1140 if ( !IPUtils::isIPAddress( $ip ) ) {
1141 throw new MWException(
"Attempt to write \"" . $ip .
1142 "\" as an IP address into recent changes" );
1179 Wikimedia\suppressWarnings();
1181 Wikimedia\restoreWarnings();
1183 return $unserializedParams;
1195 if ( is_string(
$tags ) ) {
1196 $this->tags[] =
$tags;
1198 $this->tags = array_merge(
$tags, $this->tags );
getCharacterDifference( $old=0, $new=0)
Returns the change size (HTML).
save( $send=self::SEND_FEED)
Writes the data in this object to the database.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
static newFromId( $id)
Static factory method for creation from a given user ID.
EditResult null $editResult
EditResult associated with the edit.
array $tags
List of tags to apply.
getId()
Get the user's ID.
setEditResult(?EditResult $editResult)
Sets the EditResult associated with the edit.
This is to display changes made to all articles linked in an article.
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
getPrefixedDBkey()
Get the prefixed database key form.
Utility class for creating new RC entries.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
static notifyLog( $timestamp, $title, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='')
static getEngine( $uri, $params=[])
loadFromRow( $row)
Initialises the members of this object from a mysql row object.
static getChangeTypes()
Get an array of all change types.
reallyMarkPatrolled()
Mark this RecentChange patrolled, without error checking.
static newFromName( $name, $validate='valid')
int $counter
Line number of recent change.
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
getActorId( $dbwOrWikiId=self::LOCAL)
Get the user's actor ID.
static parseToRCType( $type)
Parsing text to RC_* constants.
static notifyEdit( $timestamp, $title, $minor, $user, $comment, $oldId, $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0, $tags=[], EditResult $editResult=null)
Makes an entry in the database corresponding to an edit.
$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.
static newCacheUpdateJob()
static newMigration()
Static constructor.
parseParams()
Parses and returns the rc_params attribute.
$wgPutIPinRC
Log IP addresses in the recentchanges table; can be accessed only by extensions (e....
getDBkey()
Get the main part with underscores.
getNamespace()
Get the namespace index, i.e.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
string null $watchlistExpiry
The expiry time, if this is a temporary watchlist item.
$wgRCFeeds
Configuration for feeds to which notifications about recent changes will be sent.
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
diffLinkTrail( $forceCur)
Gets the end part of the diff URL associated with this object Blank if no diff link should be display...
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
static newFromId( $rcid)
Obtain the recent change with a given rc_id value.
doMarkPatrolled(User $user, $auto=false, $tags=null)
Mark this RecentChange as patrolled.
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...
$wgLogRestrictions
This restricts log access to those who have a certain right Users without this will not see it in the...
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
$wgRCMaxAge
Recentchanges items are periodically purged; entries older than this many seconds will go.
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
addTags( $tags)
Tags to append to the recent change, and associated revision/log.
getAttribute( $name)
Get an attribute value.
static checkIPAddress( $ip)
static newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='', $revId=0, $isPatrollable=false)
unserialize( $serialized)
Represents a title within MediaWiki.
static singleton( $domain=false)
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...
static parseFromRCType( $rcType)
Parsing RC_* constants to human-readable test.
$wgRCEngines
Used by RecentChange::getEngine to find the correct engine for a given URI scheme.
static factory(array $params)
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 newFromActorId( $id)
Static factory method for creation from a given actor ID.
notifyRCFeeds(array $feeds=null)
Notify all the feeds about the change.
getPerformer()
Get the User object of the person who performed this change.
This module processes the email notifications when the current page is changed.
if(! $wgDBerrorLogTZ) $wgRequest
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
getName()
Get the user name, or the IP of an anonymous user.
getParam( $name)
Get a parameter value.
static record( $rc, $auto, User $user, $tags=null)
Record a log event for a change being patrolled.