MediaWiki REL1_35
RecentChange.php
Go to the documentation of this file.
1<?php
24use Wikimedia\IPUtils;
25
72class RecentChange implements Taggable {
73 // Constants for the rc_source field. Extensions may also have
74 // their own source constants.
75 public const SRC_EDIT = 'mw.edit';
76 public const SRC_NEW = 'mw.new';
77 public const SRC_LOG = 'mw.log';
78 public const SRC_EXTERNAL = 'mw.external'; // obsolete
79 public const SRC_CATEGORIZE = 'mw.categorize';
80
81 public const PRC_UNPATROLLED = 0;
82 public const PRC_PATROLLED = 1;
83 public const PRC_AUTOPATROLLED = 2;
84
88 public const SEND_NONE = true;
89
93 public const SEND_FEED = false;
94
96 public $mAttribs = [];
97 public $mExtra = [];
98
102 public $mTitle = false;
103
107 private $mPerformer = false;
108
109 public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked
111
116
120 public $counter = -1;
121
125 private $tags = [];
126
130 private const CHANGE_TYPES = [
131 'edit' => RC_EDIT,
132 'new' => RC_NEW,
133 'log' => RC_LOG,
134 'external' => RC_EXTERNAL,
135 'categorize' => RC_CATEGORIZE,
136 ];
137
138 # Factory methods
139
144 public static function newFromRow( $row ) {
145 $rc = new RecentChange;
146 $rc->loadFromRow( $row );
147
148 return $rc;
149 }
150
158 public static function parseToRCType( $type ) {
159 if ( is_array( $type ) ) {
160 $retval = [];
161 foreach ( $type as $t ) {
162 $retval[] = self::parseToRCType( $t );
163 }
164
165 return $retval;
166 }
167
168 if ( !array_key_exists( $type, self::CHANGE_TYPES ) ) {
169 throw new MWException( "Unknown type '$type'" );
170 }
171 return self::CHANGE_TYPES[$type];
172 }
173
180 public static function parseFromRCType( $rcType ) {
181 return array_search( $rcType, self::CHANGE_TYPES, true ) ?: "$rcType";
182 }
183
191 public static function getChangeTypes() {
192 return array_keys( self::CHANGE_TYPES );
193 }
194
201 public static function newFromId( $rcid ) {
202 return self::newFromConds( [ 'rc_id' => $rcid ], __METHOD__ );
203 }
204
214 public static function newFromConds(
215 $conds,
216 $fname = __METHOD__,
217 $dbType = DB_REPLICA
218 ) {
219 $db = wfGetDB( $dbType );
220 $rcQuery = self::getQueryInfo();
221 $row = $db->selectRow(
222 $rcQuery['tables'], $rcQuery['fields'], $conds, $fname, [], $rcQuery['joins']
223 );
224 if ( $row !== false ) {
225 return self::newFromRow( $row );
226 } else {
227 return null;
228 }
229 }
230
240 public static function getQueryInfo() {
241 $commentQuery = CommentStore::getStore()->getJoin( 'rc_comment' );
242 $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
243 return [
244 'tables' => [ 'recentchanges' ] + $commentQuery['tables'] + $actorQuery['tables'],
245 'fields' => [
246 'rc_id',
247 'rc_timestamp',
248 'rc_namespace',
249 'rc_title',
250 'rc_minor',
251 'rc_bot',
252 'rc_new',
253 'rc_cur_id',
254 'rc_this_oldid',
255 'rc_last_oldid',
256 'rc_type',
257 'rc_source',
258 'rc_patrolled',
259 'rc_ip',
260 'rc_old_len',
261 'rc_new_len',
262 'rc_deleted',
263 'rc_logid',
264 'rc_log_type',
265 'rc_log_action',
266 'rc_params',
267 ] + $commentQuery['fields'] + $actorQuery['fields'],
268 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
269 ];
270 }
271
272 # Accessors
273
277 public function setAttribs( $attribs ) {
278 $this->mAttribs = $attribs;
279 }
280
284 public function setExtra( $extra ) {
285 $this->mExtra = $extra;
286 }
287
291 public function &getTitle() {
292 if ( $this->mTitle === false ) {
293 $this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
294 }
295
296 return $this->mTitle;
297 }
298
304 public function getPerformer() {
305 if ( $this->mPerformer === false ) {
306 if ( !empty( $this->mAttribs['rc_actor'] ) ) {
307 $this->mPerformer = User::newFromActorId( $this->mAttribs['rc_actor'] );
308 } elseif ( !empty( $this->mAttribs['rc_user'] ) ) {
309 $this->mPerformer = User::newFromId( $this->mAttribs['rc_user'] );
310 } elseif ( !empty( $this->mAttribs['rc_user_text'] ) ) {
311 $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false );
312 } else {
313 throw new MWException( 'RecentChange object lacks rc_actor, rc_user, and rc_user_text' );
314 }
315 }
316
317 return $this->mPerformer;
318 }
319
329 public function save( $send = self::SEND_FEED ) {
331
332 $dbw = wfGetDB( DB_MASTER );
333 if ( !is_array( $this->mExtra ) ) {
334 $this->mExtra = [];
335 }
336
337 if ( !$wgPutIPinRC ) {
338 $this->mAttribs['rc_ip'] = '';
339 }
340
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"];
344 }
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"]
349 : null;
350 }
351
352 # If our database is strict about IP addresses, use NULL instead of an empty string
353 $strictIPs = $dbw->getType() === 'postgres'; // legacy
354 if ( $strictIPs && $this->mAttribs['rc_ip'] == '' ) {
355 unset( $this->mAttribs['rc_ip'] );
356 }
357
358 # Trim spaces on user supplied text
359 $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] );
360
361 # Fixup database timestamps
362 $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] );
363
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'] );
367 }
368
369 $row = $this->mAttribs;
370
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 );
375
376 # Convert mAttribs['rc_user'] etc for ActorMigration
377 $user = User::newFromAnyId(
378 $row['rc_user'] ?? null,
379 $row['rc_user_text'] ?? null,
380 $row['rc_actor'] ?? null
381 );
382 unset( $row['rc_user'], $row['rc_user_text'], $row['rc_actor'] );
383 $row += ActorMigration::newMigration()->getInsertValues( $dbw, 'rc_user', $user );
384
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'] );
388
389 # Insert new row
390 $dbw->insert( 'recentchanges', $row, __METHOD__ );
391
392 # Set the ID
393 $this->mAttribs['rc_id'] = $dbw->insertId();
394
395 # Notify extensions
396 Hooks::runner()->onRecentChange_save( $this );
397
398 if ( count( $this->tags ) ) {
399 ChangeTags::addTags( $this->tags, $this->mAttribs['rc_id'],
400 $this->mAttribs['rc_this_oldid'], $this->mAttribs['rc_logid'], null, $this );
401 }
402
403 if ( $send === self::SEND_FEED ) {
404 // Emit the change to external applications via RCFeeds.
405 $this->notifyRCFeeds();
406 }
407
408 # E-mail notifications
410 $editor = $this->getPerformer();
411 $title = $this->getTitle();
412
413 // Never send an RC notification email about categorization changes
414 if (
415 Hooks::runner()->onAbortEmailNotification( $editor, $title, $this ) &&
416 $this->mAttribs['rc_type'] != RC_CATEGORIZE
417 ) {
418 // @FIXME: This would be better as an extension hook
419 // Send emails or email jobs once this row is safely committed
420 $dbw->onTransactionCommitOrIdle(
421 function () use ( $editor, $title ) {
422 $enotif = new EmailNotification();
423 $enotif->notifyOnPageChange(
424 $editor,
425 $title,
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']
431 );
432 },
433 __METHOD__
434 );
435 }
436 }
437
438 // Update the cached list of active users
439 if ( $this->mAttribs['rc_user'] > 0 ) {
440 JobQueueGroup::singleton()->lazyPush( RecentChangesUpdateJob::newCacheUpdateJob() );
441 }
442 }
443
448 public function notifyRCFeeds( array $feeds = null ) {
449 global $wgRCFeeds;
450 if ( $feeds === null ) {
451 $feeds = $wgRCFeeds;
452 }
453
454 $performer = $this->getPerformer();
455
456 foreach ( $feeds as $params ) {
457 $params += [
458 'omit_bots' => false,
459 'omit_anon' => false,
460 'omit_user' => false,
461 'omit_minor' => false,
462 'omit_patrolled' => false,
463 ];
464
465 if (
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'] ) ||
471 $this->mAttribs['rc_type'] == RC_EXTERNAL
472 ) {
473 continue;
474 }
475
476 $actionComment = $this->mExtra['actionCommentIRC'] ?? null;
477
478 $feed = RCFeed::factory( $params );
479 $feed->notify( $this, $actionComment );
480 }
481 }
482
491 public static function getEngine( $uri, $params = [] ) {
492 // TODO: Merge into RCFeed::factory().
493 global $wgRCEngines;
494 $scheme = parse_url( $uri, PHP_URL_SCHEME );
495 if ( !$scheme ) {
496 throw new MWException( "Invalid RCFeed uri: '$uri'" );
497 }
498 if ( !isset( $wgRCEngines[$scheme] ) ) {
499 throw new MWException( "Unknown RCFeedEngine scheme: '$scheme'" );
500 }
501 if ( defined( 'MW_PHPUNIT_TEST' ) && is_object( $wgRCEngines[$scheme] ) ) {
502 return $wgRCEngines[$scheme];
503 }
504 return new $wgRCEngines[$scheme]( $params );
505 }
506
518 public static function markPatrolled( $change, $auto = false, $tags = null ) {
519 wfDeprecated( __METHOD__, '1.35' );
520
521 global $wgUser;
522
523 $change = $change instanceof RecentChange
524 ? $change
525 : self::newFromId( $change );
526
527 if ( !$change instanceof RecentChange ) {
528 return null;
529 }
530
531 return $change->doMarkPatrolled( $wgUser, $auto, $tags );
532 }
533
545 public function doMarkPatrolled( User $user, $auto = false, $tags = null ) {
547
548 $permManager = MediaWikiServices::getInstance()->getPermissionManager();
549
550 // Fix up $tags so that the MarkPatrolled hook below always gets an array
551 if ( $tags === null ) {
552 $tags = [];
553 } elseif ( is_string( $tags ) ) {
554 $tags = [ $tags ];
555 }
556
557 $errors = [];
558 // If recentchanges patrol is disabled, only new pages or new file versions
559 // can be patrolled, provided the appropriate config variable is set
560 if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) &&
561 ( !$wgUseFilePatrol || !( $this->getAttribute( 'rc_type' ) == RC_LOG &&
562 $this->getAttribute( 'rc_log_type' ) == 'upload' ) ) ) {
563 $errors[] = [ 'rcpatroldisabled' ];
564 }
565 // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
566 $right = $auto ? 'autopatrol' : 'patrol';
567 $errors = array_merge(
568 $errors,
569 $permManager->getPermissionErrors( $right, $user, $this->getTitle() )
570 );
571 if ( !Hooks::runner()->onMarkPatrolled(
572 $this->getAttribute( 'rc_id' ), $user, false, $auto, $tags )
573 ) {
574 $errors[] = [ 'hookaborted' ];
575 }
576 // Users without the 'autopatrol' right can't patrol their
577 // own revisions
578 if ( $user->getName() === $this->getAttribute( 'rc_user_text' ) &&
579 !$permManager->userHasRight( $user, 'autopatrol' )
580 ) {
581 $errors[] = [ 'markedaspatrollederror-noautopatrol' ];
582 }
583 if ( $errors ) {
584 return $errors;
585 }
586 // If the change was patrolled already, do nothing
587 if ( $this->getAttribute( 'rc_patrolled' ) ) {
588 return [];
589 }
590 // Actually set the 'patrolled' flag in RC
591 $this->reallyMarkPatrolled();
592 // Log this patrol event
593 PatrolLog::record( $this, $auto, $user, $tags );
594
595 Hooks::runner()->onMarkPatrolledComplete(
596 $this->getAttribute( 'rc_id' ), $user, false, $auto );
597
598 return [];
599 }
600
605 public function reallyMarkPatrolled() {
606 $dbw = wfGetDB( DB_MASTER );
607 $dbw->update(
608 'recentchanges',
609 [
610 'rc_patrolled' => self::PRC_PATROLLED
611 ],
612 [
613 'rc_id' => $this->getAttribute( 'rc_id' )
614 ],
615 __METHOD__
616 );
617 // Invalidate the page cache after the page has been patrolled
618 // to make sure that the Patrol link isn't visible any longer!
619 $this->getTitle()->invalidateCache();
620
621 return $dbw->affectedRows();
622 }
623
643 public static function notifyEdit(
644 $timestamp, $title, $minor, $user, $comment, $oldId, $lastTimestamp,
645 $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
646 $tags = []
647 ) {
648 $rc = new RecentChange;
649 $rc->mTitle = $title;
650 $rc->mPerformer = $user;
651 $rc->mAttribs = [
652 'rc_timestamp' => $timestamp,
653 'rc_namespace' => $title->getNamespace(),
654 'rc_title' => $title->getDBkey(),
655 'rc_type' => RC_EDIT,
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,
673 'rc_deleted' => 0,
674 'rc_logid' => 0,
675 'rc_log_type' => null,
676 'rc_log_action' => '',
677 'rc_params' => ''
678 ];
679
680 $rc->mExtra = [
681 'prefixedDBkey' => $title->getPrefixedDBkey(),
682 'lastTimestamp' => $lastTimestamp,
683 'oldSize' => $oldSize,
684 'newSize' => $newSize,
685 'pageStatus' => 'changed'
686 ];
687
688 DeferredUpdates::addCallableUpdate(
689 function () use ( $rc, $tags ) {
690 $rc->addTags( $tags );
691 $rc->save();
692 },
693 DeferredUpdates::POSTSEND,
695 );
696
697 return $rc;
698 }
699
717 public static function notifyNew(
718 $timestamp, $title, $minor, $user, $comment, $bot,
719 $ip = '', $size = 0, $newId = 0, $patrol = 0, $tags = []
720 ) {
721 $rc = new RecentChange;
722 $rc->mTitle = $title;
723 $rc->mPerformer = $user;
724 $rc->mAttribs = [
725 'rc_timestamp' => $timestamp,
726 'rc_namespace' => $title->getNamespace(),
727 'rc_title' => $title->getDBkey(),
728 'rc_type' => RC_NEW,
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
744 'rc_old_len' => 0,
745 'rc_new_len' => $size,
746 'rc_deleted' => 0,
747 'rc_logid' => 0,
748 'rc_log_type' => null,
749 'rc_log_action' => '',
750 'rc_params' => ''
751 ];
752
753 $rc->mExtra = [
754 'prefixedDBkey' => $title->getPrefixedDBkey(),
755 'lastTimestamp' => 0,
756 'oldSize' => 0,
757 'newSize' => $size,
758 'pageStatus' => 'created'
759 ];
760
761 DeferredUpdates::addCallableUpdate(
762 function () use ( $rc, $tags ) {
763 $rc->addTags( $tags );
764 $rc->save();
765 },
766 DeferredUpdates::POSTSEND,
768 );
769
770 return $rc;
771 }
772
788 public static function notifyLog( $timestamp, $title, $user, $actionComment, $ip, $type,
789 $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = ''
790 ) {
791 global $wgLogRestrictions;
792
793 # Don't add private logs to RC!
794 if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) {
795 return false;
796 }
797 $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action,
798 $target, $logComment, $params, $newId, $actionCommentIRC );
799 $rc->save();
800
801 return true;
802 }
803
821 public static function newLogEntry( $timestamp, $title, $user, $actionComment, $ip,
822 $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '',
823 $revId = 0, $isPatrollable = false ) {
824 global $wgRequest;
825 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
826
827 # # Get pageStatus for email notification
828 switch ( $type . '-' . $action ) {
829 case 'delete-delete':
830 case 'delete-delete_redir':
831 $pageStatus = 'deleted';
832 break;
833 case 'move-move':
834 case 'move-move_redir':
835 $pageStatus = 'moved';
836 break;
837 case 'delete-restore':
838 $pageStatus = 'restored';
839 break;
840 case 'upload-upload':
841 $pageStatus = 'created';
842 break;
843 case 'upload-overwrite':
844 default:
845 $pageStatus = 'changed';
846 break;
847 }
848
849 // Allow unpatrolled status for patrollable log entries
850 $canAutopatrol = $permissionManager->userHasRight( $user, 'autopatrol' );
851 $markPatrolled = $isPatrollable ? $canAutopatrol : true;
852
853 $rc = new RecentChange;
854 $rc->mTitle = $target;
855 $rc->mPerformer = $user;
856 $rc->mAttribs = [
857 'rc_timestamp' => $timestamp,
858 'rc_namespace' => $target->getNamespace(),
859 'rc_title' => $target->getDBkey(),
860 'rc_type' => RC_LOG,
861 'rc_source' => self::SRC_LOG,
862 'rc_minor' => 0,
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' ) ?
873 (int)$wgRequest->getBool( 'bot', true ) : 0,
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,
879 'rc_deleted' => 0,
880 'rc_logid' => $newId,
881 'rc_log_type' => $type,
882 'rc_log_action' => $action,
883 'rc_params' => $params
884 ];
885
886 $rc->mExtra = [
887 'prefixedDBkey' => $title->getPrefixedDBkey(),
888 'lastTimestamp' => 0,
889 'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
890 'pageStatus' => $pageStatus,
891 'actionCommentIRC' => $actionCommentIRC
892 ];
893
894 return $rc;
895 }
896
918 public static function newForCategorization(
919 $timestamp,
920 Title $categoryTitle,
921 ?User $user,
922 $comment,
923 Title $pageTitle,
924 $oldRevId,
925 $newRevId,
926 $lastTimestamp,
927 $bot,
928 $ip = '',
929 $deleted = 0,
930 $added = null
931 ) {
932 // Done in a backwards compatible way.
933 $params = [
934 'hidden-cat' => WikiCategoryPage::factory( $categoryTitle )->isHidden()
935 ];
936 if ( $added !== null ) {
937 $params['added'] = $added;
938 }
939
940 $rc = new RecentChange;
941 $rc->mTitle = $categoryTitle;
942 $rc->mPerformer = $user;
943 $rc->mAttribs = [
944 'rc_timestamp' => $timestamp,
945 'rc_namespace' => $categoryTitle->getNamespace(),
946 'rc_title' => $categoryTitle->getDBkey(),
947 'rc_type' => RC_CATEGORIZE,
948 'rc_source' => self::SRC_CATEGORIZE,
949 'rc_minor' => 0,
950 'rc_cur_id' => $pageTitle->getArticleID(),
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, // Always patrolled, just like log entries
962 'rc_new' => 0, # obsolete
963 'rc_old_len' => null,
964 'rc_new_len' => null,
965 'rc_deleted' => $deleted,
966 'rc_logid' => 0,
967 'rc_log_type' => null,
968 'rc_log_action' => '',
969 'rc_params' => serialize( $params )
970 ];
971
972 $rc->mExtra = [
973 'prefixedDBkey' => $categoryTitle->getPrefixedDBkey(),
974 'lastTimestamp' => $lastTimestamp,
975 'oldSize' => 0,
976 'newSize' => 0,
977 'pageStatus' => 'changed'
978 ];
979
980 return $rc;
981 }
982
991 public function getParam( $name ) {
992 $params = $this->parseParams();
993 return $params[$name] ?? null;
994 }
995
1001 public function loadFromRow( $row ) {
1002 $this->mAttribs = get_object_vars( $row );
1003 $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] );
1004 // rc_deleted MUST be set
1005 $this->mAttribs['rc_deleted'] = $row->rc_deleted;
1006
1007 if ( isset( $this->mAttribs['rc_ip'] ) ) {
1008 // Clean up CIDRs for Postgres per T164898. ("127.0.0.1" casts to "127.0.0.1/32")
1009 $n = strpos( $this->mAttribs['rc_ip'], '/' );
1010 if ( $n !== false ) {
1011 $this->mAttribs['rc_ip'] = substr( $this->mAttribs['rc_ip'], 0, $n );
1012 }
1013 }
1014
1015 $comment = CommentStore::getStore()
1016 // Legacy because $row may have come from self::selectFields()
1017 ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $row, true )
1018 ->text;
1019 $this->mAttribs['rc_comment'] = &$comment;
1020 $this->mAttribs['rc_comment_text'] = &$comment;
1021 $this->mAttribs['rc_comment_data'] = null;
1022
1023 $user = User::newFromAnyId(
1024 $this->mAttribs['rc_user'] ?? null,
1025 $this->mAttribs['rc_user_text'] ?? null,
1026 $this->mAttribs['rc_actor'] ?? null
1027 );
1028 $this->mAttribs['rc_user'] = $user->getId();
1029 $this->mAttribs['rc_user_text'] = $user->getName();
1030 $this->mAttribs['rc_actor'] = $user->getActorId();
1031
1032 // Watchlist expiry.
1033 if ( isset( $row->we_expiry ) && $row->we_expiry ) {
1034 $this->watchlistExpiry = wfTimestamp( TS_MW, $row->we_expiry );
1035 }
1036 }
1037
1044 public function getAttribute( $name ) {
1045 if ( $name === 'rc_comment' ) {
1046 return CommentStore::getStore()
1047 ->getComment( 'rc_comment', $this->mAttribs, true )->text;
1048 }
1049
1050 if ( $name === 'rc_user' || $name === 'rc_user_text' || $name === 'rc_actor' ) {
1051 $user = User::newFromAnyId(
1052 $this->mAttribs['rc_user'] ?? null,
1053 $this->mAttribs['rc_user_text'] ?? null,
1054 $this->mAttribs['rc_actor'] ?? null
1055 );
1056 if ( $name === 'rc_user' ) {
1057 return $user->getId();
1058 }
1059 if ( $name === 'rc_user_text' ) {
1060 return $user->getName();
1061 }
1062 if ( $name === 'rc_actor' ) {
1063 return $user->getActorId();
1064 }
1065 }
1066
1067 return $this->mAttribs[$name] ?? null;
1068 }
1069
1073 public function getAttributes() {
1074 return $this->mAttribs;
1075 }
1076
1083 public function diffLinkTrail( $forceCur ) {
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'] );
1087 if ( $forceCur ) {
1088 $trail .= '&diff=0';
1089 } else {
1090 $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] );
1091 }
1092 } else {
1093 $trail = '';
1094 }
1095
1096 return $trail;
1097 }
1098
1106 public function getCharacterDifference( $old = 0, $new = 0 ) {
1107 if ( $old === 0 ) {
1108 $old = $this->mAttribs['rc_old_len'];
1109 }
1110 if ( $new === 0 ) {
1111 $new = $this->mAttribs['rc_new_len'];
1112 }
1113 if ( $old === null || $new === null ) {
1114 return '';
1115 }
1116
1117 return ChangesList::showCharacterDifference( $old, $new );
1118 }
1119
1120 private static function checkIPAddress( $ip ) {
1121 global $wgRequest;
1122 if ( $ip ) {
1123 if ( !IPUtils::isIPAddress( $ip ) ) {
1124 throw new MWException( "Attempt to write \"" . $ip .
1125 "\" as an IP address into recent changes" );
1126 }
1127 } else {
1128 $ip = $wgRequest->getIP();
1129 if ( !$ip ) {
1130 $ip = '';
1131 }
1132 }
1133
1134 return $ip;
1135 }
1136
1146 public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
1147 global $wgRCMaxAge;
1148
1149 return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge;
1150 }
1151
1159 public function parseParams() {
1160 $rcParams = $this->getAttribute( 'rc_params' );
1161
1162 Wikimedia\suppressWarnings();
1163 $unserializedParams = unserialize( $rcParams );
1164 Wikimedia\restoreWarnings();
1165
1166 return $unserializedParams;
1167 }
1168
1177 public function addTags( $tags ) {
1178 if ( is_string( $tags ) ) {
1179 $this->tags[] = $tags;
1180 } else {
1181 $this->tags = array_merge( $tags, $this->tags );
1182 }
1183 }
1184}
serialize()
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.
Title null $mTitle
$wgUseEnotif
Definition Setup.php:441
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:643
static addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params=null, RecentChange $rc=null)
Add tags to a change given its rc_id, rev_id and/or log_id.
This module processes the email notifications when the current page is changed.
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static record( $rc, $auto=false, User $user=null, $tags=null)
Record a log event for a change being patrolled.
Definition PatrolLog.php:43
static factory(array $params)
Definition RCFeed.php:46
Utility class for creating new RC entries.
setExtra( $extra)
const PRC_UNPATROLLED
static getEngine( $uri, $params=[])
static parseToRCType( $type)
Parsing text to RC_* constants.
reallyMarkPatrolled()
Mark this RecentChange patrolled, without error checking.
static newFromRow( $row)
parseParams()
Parses and returns the rc_params attribute.
User false $mPerformer
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.
const SRC_CATEGORIZE
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.
setAttribs( $attribs)
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.
const PRC_PATROLLED
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.
const PRC_AUTOPATROLLED
This is to display changes made to all articles linked in an article.
Represents a title within MediaWiki.
Definition Title.php:42
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1041
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1847
getDBkey()
Get the main part with underscores.
Definition Title.php:1032
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:3225
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2150
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
getId()
Get the user's ID.
Definition User.php:2121
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:565
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:616
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition User.php:2189
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition User.php:580
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition WikiPage.php:154
const RC_NEW
Definition Defines.php:133
const RC_LOG
Definition Defines.php:134
const RC_EXTERNAL
Definition Defines.php:135
const RC_EDIT
Definition Defines.php:132
const RC_CATEGORIZE
Definition Defines.php:136
Interface that defines how to tag objects.
Definition Taggable.php:32
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:29