MediaWiki REL1_31
RecentChange.php
Go to the documentation of this file.
1<?php
69 // Constants for the rc_source field. Extensions may also have
70 // their own source constants.
71 const SRC_EDIT = 'mw.edit';
72 const SRC_NEW = 'mw.new';
73 const SRC_LOG = 'mw.log';
74 const SRC_EXTERNAL = 'mw.external'; // obsolete
75 const SRC_CATEGORIZE = 'mw.categorize';
76
77 const PRC_UNPATROLLED = 0;
78 const PRC_PATROLLED = 1;
80
81 public $mAttribs = [];
82 public $mExtra = [];
83
87 public $mTitle = false;
88
92 private $mPerformer = false;
93
94 public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked
96
100 public $counter = -1;
101
105 private $tags = [];
106
110 private static $changeTypes = [
111 'edit' => RC_EDIT,
112 'new' => RC_NEW,
113 'log' => RC_LOG,
114 'external' => RC_EXTERNAL,
115 'categorize' => RC_CATEGORIZE,
116 ];
117
118 # Factory methods
119
124 public static function newFromRow( $row ) {
125 $rc = new RecentChange;
126 $rc->loadFromRow( $row );
127
128 return $rc;
129 }
130
138 public static function parseToRCType( $type ) {
139 if ( is_array( $type ) ) {
140 $retval = [];
141 foreach ( $type as $t ) {
142 $retval[] = self::parseToRCType( $t );
143 }
144
145 return $retval;
146 }
147
148 if ( !array_key_exists( $type, self::$changeTypes ) ) {
149 throw new MWException( "Unknown type '$type'" );
150 }
151 return self::$changeTypes[$type];
152 }
153
160 public static function parseFromRCType( $rcType ) {
161 return array_search( $rcType, self::$changeTypes, true ) ?: "$rcType";
162 }
163
171 public static function getChangeTypes() {
172 return array_keys( self::$changeTypes );
173 }
174
181 public static function newFromId( $rcid ) {
182 return self::newFromConds( [ 'rc_id' => $rcid ], __METHOD__ );
183 }
184
194 public static function newFromConds(
195 $conds,
196 $fname = __METHOD__,
197 $dbType = DB_REPLICA
198 ) {
199 $db = wfGetDB( $dbType );
200 $rcQuery = self::getQueryInfo();
201 $row = $db->selectRow(
202 $rcQuery['tables'], $rcQuery['fields'], $conds, $fname, [], $rcQuery['joins']
203 );
204 if ( $row !== false ) {
205 return self::newFromRow( $row );
206 } else {
207 return null;
208 }
209 }
210
217 public static function selectFields() {
219
220 wfDeprecated( __METHOD__, '1.31' );
222 // If code is using this instead of self::getQueryInfo(), there's a
223 // decent chance it's going to try to directly access
224 // $row->rc_user or $row->rc_user_text and we can't give it
225 // useful values here once those aren't being written anymore.
226 throw new BadMethodCallException(
227 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
228 );
229 }
230
231 return [
232 'rc_id',
233 'rc_timestamp',
234 'rc_user',
235 'rc_user_text',
236 'rc_actor' => 'NULL',
237 'rc_namespace',
238 'rc_title',
239 'rc_minor',
240 'rc_bot',
241 'rc_new',
242 'rc_cur_id',
243 'rc_this_oldid',
244 'rc_last_oldid',
245 'rc_type',
246 'rc_source',
247 'rc_patrolled',
248 'rc_ip',
249 'rc_old_len',
250 'rc_new_len',
251 'rc_deleted',
252 'rc_logid',
253 'rc_log_type',
254 'rc_log_action',
255 'rc_params',
256 ] + CommentStore::getStore()->getFields( 'rc_comment' );
257 }
258
268 public static function getQueryInfo() {
269 $commentQuery = CommentStore::getStore()->getJoin( 'rc_comment' );
270 $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
271 return [
272 'tables' => [ 'recentchanges' ] + $commentQuery['tables'] + $actorQuery['tables'],
273 'fields' => [
274 'rc_id',
275 'rc_timestamp',
276 'rc_namespace',
277 'rc_title',
278 'rc_minor',
279 'rc_bot',
280 'rc_new',
281 'rc_cur_id',
282 'rc_this_oldid',
283 'rc_last_oldid',
284 'rc_type',
285 'rc_source',
286 'rc_patrolled',
287 'rc_ip',
288 'rc_old_len',
289 'rc_new_len',
290 'rc_deleted',
291 'rc_logid',
292 'rc_log_type',
293 'rc_log_action',
294 'rc_params',
295 ] + $commentQuery['fields'] + $actorQuery['fields'],
296 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
297 ];
298 }
299
300 # Accessors
301
305 public function setAttribs( $attribs ) {
306 $this->mAttribs = $attribs;
307 }
308
312 public function setExtra( $extra ) {
313 $this->mExtra = $extra;
314 }
315
319 public function &getTitle() {
320 if ( $this->mTitle === false ) {
321 $this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
322 }
323
324 return $this->mTitle;
325 }
326
332 public function getPerformer() {
333 if ( $this->mPerformer === false ) {
334 if ( !empty( $this->mAttribs['rc_actor'] ) ) {
335 $this->mPerformer = User::newFromActorId( $this->mAttribs['rc_actor'] );
336 } elseif ( !empty( $this->mAttribs['rc_user'] ) ) {
337 $this->mPerformer = User::newFromId( $this->mAttribs['rc_user'] );
338 } elseif ( !empty( $this->mAttribs['rc_user_text'] ) ) {
339 $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false );
340 } else {
341 throw new MWException( 'RecentChange object lacks rc_actor, rc_user, and rc_user_text' );
342 }
343 }
344
345 return $this->mPerformer;
346 }
347
352 public function save( $noudp = false ) {
354
355 $dbw = wfGetDB( DB_MASTER );
356 if ( !is_array( $this->mExtra ) ) {
357 $this->mExtra = [];
358 }
359
360 if ( !$wgPutIPinRC ) {
361 $this->mAttribs['rc_ip'] = '';
362 }
363
364 # Strict mode fixups (not-NULL fields)
365 foreach ( [ 'minor', 'bot', 'new', 'patrolled', 'deleted' ] as $field ) {
366 $this->mAttribs["rc_$field"] = (int)$this->mAttribs["rc_$field"];
367 }
368 # ...more fixups (NULL fields)
369 foreach ( [ 'old_len', 'new_len' ] as $field ) {
370 $this->mAttribs["rc_$field"] = isset( $this->mAttribs["rc_$field"] )
371 ? (int)$this->mAttribs["rc_$field"]
372 : null;
373 }
374
375 # If our database is strict about IP addresses, use NULL instead of an empty string
376 $strictIPs = in_array( $dbw->getType(), [ 'oracle', 'postgres' ] ); // legacy
377 if ( $strictIPs && $this->mAttribs['rc_ip'] == '' ) {
378 unset( $this->mAttribs['rc_ip'] );
379 }
380
381 # Trim spaces on user supplied text
382 $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] );
383
384 # Fixup database timestamps
385 $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] );
386
387 # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
388 if ( $this->mAttribs['rc_cur_id'] == 0 ) {
389 unset( $this->mAttribs['rc_cur_id'] );
390 }
391
392 $row = $this->mAttribs;
393
394 # Convert mAttribs['rc_comment'] for CommentStore
395 $comment = $row['rc_comment'];
396 unset( $row['rc_comment'], $row['rc_comment_text'], $row['rc_comment_data'] );
397 $row += CommentStore::getStore()->insert( $dbw, 'rc_comment', $comment );
398
399 # Convert mAttribs['rc_user'] etc for ActorMigration
400 $user = User::newFromAnyId(
401 isset( $row['rc_user'] ) ? $row['rc_user'] : null,
402 isset( $row['rc_user_text'] ) ? $row['rc_user_text'] : null,
403 isset( $row['rc_actor'] ) ? $row['rc_actor'] : null
404 );
405 unset( $row['rc_user'], $row['rc_user_text'], $row['rc_actor'] );
406 $row += ActorMigration::newMigration()->getInsertValues( $dbw, 'rc_user', $user );
407
408 # Don't reuse an existing rc_id for the new row, if one happens to be
409 # set for some reason.
410 unset( $row['rc_id'] );
411
412 # Insert new row
413 $dbw->insert( 'recentchanges', $row, __METHOD__ );
414
415 # Set the ID
416 $this->mAttribs['rc_id'] = $dbw->insertId();
417
418 # Notify extensions
419 // Avoid PHP 7.1 warning from passing $this by reference
420 $rc = $this;
421 Hooks::run( 'RecentChange_save', [ &$rc ] );
422
423 if ( count( $this->tags ) ) {
424 ChangeTags::addTags( $this->tags, $this->mAttribs['rc_id'],
425 $this->mAttribs['rc_this_oldid'], $this->mAttribs['rc_logid'], null, $this );
426 }
427
428 # Notify external application via UDP
429 if ( !$noudp ) {
430 $this->notifyRCFeeds();
431 }
432
433 # E-mail notifications
435 $editor = $this->getPerformer();
436 $title = $this->getTitle();
437
438 // Never send an RC notification email about categorization changes
439 if (
440 Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] ) &&
441 $this->mAttribs['rc_type'] != RC_CATEGORIZE
442 ) {
443 // @FIXME: This would be better as an extension hook
444 // Send emails or email jobs once this row is safely committed
445 $dbw->onTransactionIdle(
446 function () use ( $editor, $title ) {
447 $enotif = new EmailNotification();
448 $enotif->notifyOnPageChange(
449 $editor,
450 $title,
451 $this->mAttribs['rc_timestamp'],
452 $this->mAttribs['rc_comment'],
453 $this->mAttribs['rc_minor'],
454 $this->mAttribs['rc_last_oldid'],
455 $this->mExtra['pageStatus']
456 );
457 },
458 __METHOD__
459 );
460 }
461 }
462
463 // Update the cached list of active users
464 if ( $this->mAttribs['rc_user'] > 0 ) {
466 }
467 }
468
473 public function notifyRCFeeds( array $feeds = null ) {
474 global $wgRCFeeds;
475 if ( $feeds === null ) {
476 $feeds = $wgRCFeeds;
477 }
478
479 $performer = $this->getPerformer();
480
481 foreach ( $feeds as $params ) {
482 $params += [
483 'omit_bots' => false,
484 'omit_anon' => false,
485 'omit_user' => false,
486 'omit_minor' => false,
487 'omit_patrolled' => false,
488 ];
489
490 if (
491 ( $params['omit_bots'] && $this->mAttribs['rc_bot'] ) ||
492 ( $params['omit_anon'] && $performer->isAnon() ) ||
493 ( $params['omit_user'] && !$performer->isAnon() ) ||
494 ( $params['omit_minor'] && $this->mAttribs['rc_minor'] ) ||
495 ( $params['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) ||
496 $this->mAttribs['rc_type'] == RC_EXTERNAL
497 ) {
498 continue;
499 }
500
501 if ( isset( $this->mExtra['actionCommentIRC'] ) ) {
502 $actionComment = $this->mExtra['actionCommentIRC'];
503 } else {
504 $actionComment = null;
505 }
506
507 $feed = RCFeed::factory( $params );
508 $feed->notify( $this, $actionComment );
509 }
510 }
511
520 public static function getEngine( $uri, $params = [] ) {
521 // TODO: Merge into RCFeed::factory().
522 global $wgRCEngines;
523 $scheme = parse_url( $uri, PHP_URL_SCHEME );
524 if ( !$scheme ) {
525 throw new MWException( "Invalid RCFeed uri: '$uri'" );
526 }
527 if ( !isset( $wgRCEngines[$scheme] ) ) {
528 throw new MWException( "Unknown RCFeedEngine scheme: '$scheme'" );
529 }
530 if ( defined( 'MW_PHPUNIT_TEST' ) && is_object( $wgRCEngines[$scheme] ) ) {
531 return $wgRCEngines[$scheme];
532 }
533 return new $wgRCEngines[$scheme]( $params );
534 }
535
545 public static function markPatrolled( $change, $auto = false, $tags = null ) {
546 global $wgUser;
547
548 $change = $change instanceof RecentChange
549 ? $change
550 : self::newFromId( $change );
551
552 if ( !$change instanceof RecentChange ) {
553 return null;
554 }
555
556 return $change->doMarkPatrolled( $wgUser, $auto, $tags );
557 }
558
570 public function doMarkPatrolled( User $user, $auto = false, $tags = null ) {
572
573 $errors = [];
574 // If recentchanges patrol is disabled, only new pages or new file versions
575 // can be patrolled, provided the appropriate config variable is set
576 if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) &&
577 ( !$wgUseFilePatrol || !( $this->getAttribute( 'rc_type' ) == RC_LOG &&
578 $this->getAttribute( 'rc_log_type' ) == 'upload' ) ) ) {
579 $errors[] = [ 'rcpatroldisabled' ];
580 }
581 // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
582 $right = $auto ? 'autopatrol' : 'patrol';
583 $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) );
584 if ( !Hooks::run( 'MarkPatrolled',
585 [ $this->getAttribute( 'rc_id' ), &$user, false, $auto ] )
586 ) {
587 $errors[] = [ 'hookaborted' ];
588 }
589 // Users without the 'autopatrol' right can't patrol their
590 // own revisions
591 if ( $user->getName() === $this->getAttribute( 'rc_user_text' )
592 && !$user->isAllowed( 'autopatrol' )
593 ) {
594 $errors[] = [ 'markedaspatrollederror-noautopatrol' ];
595 }
596 if ( $errors ) {
597 return $errors;
598 }
599 // If the change was patrolled already, do nothing
600 if ( $this->getAttribute( 'rc_patrolled' ) ) {
601 return [];
602 }
603 // Actually set the 'patrolled' flag in RC
604 $this->reallyMarkPatrolled();
605 // Log this patrol event
606 PatrolLog::record( $this, $auto, $user, $tags );
607
608 Hooks::run(
609 'MarkPatrolledComplete',
610 [ $this->getAttribute( 'rc_id' ), &$user, false, $auto ]
611 );
612
613 return [];
614 }
615
620 public function reallyMarkPatrolled() {
621 $dbw = wfGetDB( DB_MASTER );
622 $dbw->update(
623 'recentchanges',
624 [
625 'rc_patrolled' => self::PRC_PATROLLED
626 ],
627 [
628 'rc_id' => $this->getAttribute( 'rc_id' )
629 ],
630 __METHOD__
631 );
632 // Invalidate the page cache after the page has been patrolled
633 // to make sure that the Patrol link isn't visible any longer!
634 $this->getTitle()->invalidateCache();
635
636 return $dbw->affectedRows();
637 }
638
658 public static function notifyEdit(
659 $timestamp, &$title, $minor, &$user, $comment, $oldId, $lastTimestamp,
660 $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
661 $tags = []
662 ) {
663 $rc = new RecentChange;
664 $rc->mTitle = $title;
665 $rc->mPerformer = $user;
666 $rc->mAttribs = [
667 'rc_timestamp' => $timestamp,
668 'rc_namespace' => $title->getNamespace(),
669 'rc_title' => $title->getDBkey(),
670 'rc_type' => RC_EDIT,
671 'rc_source' => self::SRC_EDIT,
672 'rc_minor' => $minor ? 1 : 0,
673 'rc_cur_id' => $title->getArticleID(),
674 'rc_user' => $user->getId(),
675 'rc_user_text' => $user->getName(),
676 'rc_actor' => $user->getActorId(),
677 'rc_comment' => &$comment,
678 'rc_comment_text' => &$comment,
679 'rc_comment_data' => null,
680 'rc_this_oldid' => $newId,
681 'rc_last_oldid' => $oldId,
682 'rc_bot' => $bot ? 1 : 0,
683 'rc_ip' => self::checkIPAddress( $ip ),
684 'rc_patrolled' => intval( $patrol ),
685 'rc_new' => 0, # obsolete
686 'rc_old_len' => $oldSize,
687 'rc_new_len' => $newSize,
688 'rc_deleted' => 0,
689 'rc_logid' => 0,
690 'rc_log_type' => null,
691 'rc_log_action' => '',
692 'rc_params' => ''
693 ];
694
695 $rc->mExtra = [
696 'prefixedDBkey' => $title->getPrefixedDBkey(),
697 'lastTimestamp' => $lastTimestamp,
698 'oldSize' => $oldSize,
699 'newSize' => $newSize,
700 'pageStatus' => 'changed'
701 ];
702
703 DeferredUpdates::addCallableUpdate(
704 function () use ( $rc, $tags ) {
705 $rc->addTags( $tags );
706 $rc->save();
707 },
708 DeferredUpdates::POSTSEND,
710 );
711
712 return $rc;
713 }
714
732 public static function notifyNew(
733 $timestamp, &$title, $minor, &$user, $comment, $bot,
734 $ip = '', $size = 0, $newId = 0, $patrol = 0, $tags = []
735 ) {
736 $rc = new RecentChange;
737 $rc->mTitle = $title;
738 $rc->mPerformer = $user;
739 $rc->mAttribs = [
740 'rc_timestamp' => $timestamp,
741 'rc_namespace' => $title->getNamespace(),
742 'rc_title' => $title->getDBkey(),
743 'rc_type' => RC_NEW,
744 'rc_source' => self::SRC_NEW,
745 'rc_minor' => $minor ? 1 : 0,
746 'rc_cur_id' => $title->getArticleID(),
747 'rc_user' => $user->getId(),
748 'rc_user_text' => $user->getName(),
749 'rc_actor' => $user->getActorId(),
750 'rc_comment' => &$comment,
751 'rc_comment_text' => &$comment,
752 'rc_comment_data' => null,
753 'rc_this_oldid' => $newId,
754 'rc_last_oldid' => 0,
755 'rc_bot' => $bot ? 1 : 0,
756 'rc_ip' => self::checkIPAddress( $ip ),
757 'rc_patrolled' => intval( $patrol ),
758 'rc_new' => 1, # obsolete
759 'rc_old_len' => 0,
760 'rc_new_len' => $size,
761 'rc_deleted' => 0,
762 'rc_logid' => 0,
763 'rc_log_type' => null,
764 'rc_log_action' => '',
765 'rc_params' => ''
766 ];
767
768 $rc->mExtra = [
769 'prefixedDBkey' => $title->getPrefixedDBkey(),
770 'lastTimestamp' => 0,
771 'oldSize' => 0,
772 'newSize' => $size,
773 'pageStatus' => 'created'
774 ];
775
776 DeferredUpdates::addCallableUpdate(
777 function () use ( $rc, $tags ) {
778 $rc->addTags( $tags );
779 $rc->save();
780 },
781 DeferredUpdates::POSTSEND,
783 );
784
785 return $rc;
786 }
787
803 public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type,
804 $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = ''
805 ) {
806 global $wgLogRestrictions;
807
808 # Don't add private logs to RC!
809 if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) {
810 return false;
811 }
812 $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action,
813 $target, $logComment, $params, $newId, $actionCommentIRC );
814 $rc->save();
815
816 return true;
817 }
818
836 public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip,
837 $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '',
838 $revId = 0, $isPatrollable = false ) {
839 global $wgRequest;
840
841 # # Get pageStatus for email notification
842 switch ( $type . '-' . $action ) {
843 case 'delete-delete':
844 case 'delete-delete_redir':
845 $pageStatus = 'deleted';
846 break;
847 case 'move-move':
848 case 'move-move_redir':
849 $pageStatus = 'moved';
850 break;
851 case 'delete-restore':
852 $pageStatus = 'restored';
853 break;
854 case 'upload-upload':
855 $pageStatus = 'created';
856 break;
857 case 'upload-overwrite':
858 default:
859 $pageStatus = 'changed';
860 break;
861 }
862
863 // Allow unpatrolled status for patrollable log entries
864 $markPatrolled = $isPatrollable ? $user->isAllowed( 'autopatrol' ) : true;
865
866 $rc = new RecentChange;
867 $rc->mTitle = $target;
868 $rc->mPerformer = $user;
869 $rc->mAttribs = [
870 'rc_timestamp' => $timestamp,
871 'rc_namespace' => $target->getNamespace(),
872 'rc_title' => $target->getDBkey(),
873 'rc_type' => RC_LOG,
874 'rc_source' => self::SRC_LOG,
875 'rc_minor' => 0,
876 'rc_cur_id' => $target->getArticleID(),
877 'rc_user' => $user->getId(),
878 'rc_user_text' => $user->getName(),
879 'rc_actor' => $user->getActorId(),
880 'rc_comment' => &$logComment,
881 'rc_comment_text' => &$logComment,
882 'rc_comment_data' => null,
883 'rc_this_oldid' => $revId,
884 'rc_last_oldid' => 0,
885 'rc_bot' => $user->isAllowed( 'bot' ) ? (int)$wgRequest->getBool( 'bot', true ) : 0,
886 'rc_ip' => self::checkIPAddress( $ip ),
887 'rc_patrolled' => $markPatrolled ? self::PRC_PATROLLED : self::PRC_UNPATROLLED,
888 'rc_new' => 0, # obsolete
889 'rc_old_len' => null,
890 'rc_new_len' => null,
891 'rc_deleted' => 0,
892 'rc_logid' => $newId,
893 'rc_log_type' => $type,
894 'rc_log_action' => $action,
895 'rc_params' => $params
896 ];
897
898 $rc->mExtra = [
899 'prefixedDBkey' => $title->getPrefixedDBkey(),
900 'lastTimestamp' => 0,
901 'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
902 'pageStatus' => $pageStatus,
903 'actionCommentIRC' => $actionCommentIRC
904 ];
905
906 return $rc;
907 }
908
930 public static function newForCategorization(
931 $timestamp,
932 Title $categoryTitle,
933 User $user = null,
934 $comment,
935 Title $pageTitle,
936 $oldRevId,
937 $newRevId,
938 $lastTimestamp,
939 $bot,
940 $ip = '',
941 $deleted = 0,
942 $added = null
943 ) {
944 // Done in a backwards compatible way.
945 $params = [
946 'hidden-cat' => WikiCategoryPage::factory( $categoryTitle )->isHidden()
947 ];
948 if ( $added !== null ) {
949 $params['added'] = $added;
950 }
951
952 $rc = new RecentChange;
953 $rc->mTitle = $categoryTitle;
954 $rc->mPerformer = $user;
955 $rc->mAttribs = [
956 'rc_timestamp' => $timestamp,
957 'rc_namespace' => $categoryTitle->getNamespace(),
958 'rc_title' => $categoryTitle->getDBkey(),
959 'rc_type' => RC_CATEGORIZE,
960 'rc_source' => self::SRC_CATEGORIZE,
961 'rc_minor' => 0,
962 'rc_cur_id' => $pageTitle->getArticleID(),
963 'rc_user' => $user ? $user->getId() : 0,
964 'rc_user_text' => $user ? $user->getName() : '',
965 'rc_actor' => $user ? $user->getActorId() : null,
966 'rc_comment' => &$comment,
967 'rc_comment_text' => &$comment,
968 'rc_comment_data' => null,
969 'rc_this_oldid' => $newRevId,
970 'rc_last_oldid' => $oldRevId,
971 'rc_bot' => $bot ? 1 : 0,
972 'rc_ip' => self::checkIPAddress( $ip ),
973 'rc_patrolled' => self::PRC_PATROLLED, // Always patrolled, just like log entries
974 'rc_new' => 0, # obsolete
975 'rc_old_len' => null,
976 'rc_new_len' => null,
977 'rc_deleted' => $deleted,
978 'rc_logid' => 0,
979 'rc_log_type' => null,
980 'rc_log_action' => '',
981 'rc_params' => serialize( $params )
982 ];
983
984 $rc->mExtra = [
985 'prefixedDBkey' => $categoryTitle->getPrefixedDBkey(),
986 'lastTimestamp' => $lastTimestamp,
987 'oldSize' => 0,
988 'newSize' => 0,
989 'pageStatus' => 'changed'
990 ];
991
992 return $rc;
993 }
994
1003 public function getParam( $name ) {
1004 $params = $this->parseParams();
1005 return isset( $params[$name] ) ? $params[$name] : null;
1006 }
1007
1013 public function loadFromRow( $row ) {
1014 $this->mAttribs = get_object_vars( $row );
1015 $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] );
1016 // rc_deleted MUST be set
1017 $this->mAttribs['rc_deleted'] = $row->rc_deleted;
1018
1019 if ( isset( $this->mAttribs['rc_ip'] ) ) {
1020 // Clean up CIDRs for Postgres per T164898. ("127.0.0.1" casts to "127.0.0.1/32")
1021 $n = strpos( $this->mAttribs['rc_ip'], '/' );
1022 if ( $n !== false ) {
1023 $this->mAttribs['rc_ip'] = substr( $this->mAttribs['rc_ip'], 0, $n );
1024 }
1025 }
1026
1027 $comment = CommentStore::getStore()
1028 // Legacy because $row may have come from self::selectFields()
1029 ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $row, true )
1030 ->text;
1031 $this->mAttribs['rc_comment'] = &$comment;
1032 $this->mAttribs['rc_comment_text'] = &$comment;
1033 $this->mAttribs['rc_comment_data'] = null;
1034
1035 $user = User::newFromAnyId(
1036 isset( $this->mAttribs['rc_user'] ) ? $this->mAttribs['rc_user'] : null,
1037 isset( $this->mAttribs['rc_user_text'] ) ? $this->mAttribs['rc_user_text'] : null,
1038 isset( $this->mAttribs['rc_actor'] ) ? $this->mAttribs['rc_actor'] : null
1039 );
1040 $this->mAttribs['rc_user'] = $user->getId();
1041 $this->mAttribs['rc_user_text'] = $user->getName();
1042 $this->mAttribs['rc_actor'] = $user->getActorId();
1043 }
1044
1051 public function getAttribute( $name ) {
1052 if ( $name === 'rc_comment' ) {
1053 return CommentStore::getStore()
1054 ->getComment( 'rc_comment', $this->mAttribs, true )->text;
1055 }
1056
1057 if ( $name === 'rc_user' || $name === 'rc_user_text' || $name === 'rc_actor' ) {
1058 $user = User::newFromAnyId(
1059 isset( $this->mAttribs['rc_user'] ) ? $this->mAttribs['rc_user'] : null,
1060 isset( $this->mAttribs['rc_user_text'] ) ? $this->mAttribs['rc_user_text'] : null,
1061 isset( $this->mAttribs['rc_actor'] ) ? $this->mAttribs['rc_actor'] : null
1062 );
1063 if ( $name === 'rc_user' ) {
1064 return $user->getId();
1065 }
1066 if ( $name === 'rc_user_text' ) {
1067 return $user->getName();
1068 }
1069 if ( $name === 'rc_actor' ) {
1070 return $user->getActorId();
1071 }
1072 }
1073
1074 return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : null;
1075 }
1076
1080 public function getAttributes() {
1081 return $this->mAttribs;
1082 }
1083
1090 public function diffLinkTrail( $forceCur ) {
1091 if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
1092 $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) .
1093 "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] );
1094 if ( $forceCur ) {
1095 $trail .= '&diff=0';
1096 } else {
1097 $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] );
1098 }
1099 } else {
1100 $trail = '';
1101 }
1102
1103 return $trail;
1104 }
1105
1113 public function getCharacterDifference( $old = 0, $new = 0 ) {
1114 if ( $old === 0 ) {
1115 $old = $this->mAttribs['rc_old_len'];
1116 }
1117 if ( $new === 0 ) {
1118 $new = $this->mAttribs['rc_new_len'];
1119 }
1120 if ( $old === null || $new === null ) {
1121 return '';
1122 }
1123
1124 return ChangesList::showCharacterDifference( $old, $new );
1125 }
1126
1127 private static function checkIPAddress( $ip ) {
1128 global $wgRequest;
1129 if ( $ip ) {
1130 if ( !IP::isIPAddress( $ip ) ) {
1131 throw new MWException( "Attempt to write \"" . $ip .
1132 "\" as an IP address into recent changes" );
1133 }
1134 } else {
1135 $ip = $wgRequest->getIP();
1136 if ( !$ip ) {
1137 $ip = '';
1138 }
1139 }
1140
1141 return $ip;
1142 }
1143
1153 public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
1154 global $wgRCMaxAge;
1155
1156 return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge;
1157 }
1158
1166 public function parseParams() {
1167 $rcParams = $this->getAttribute( 'rc_params' );
1168
1169 Wikimedia\suppressWarnings();
1170 $unserializedParams = unserialize( $rcParams );
1171 Wikimedia\restoreWarnings();
1172
1173 return $unserializedParams;
1174 }
1175
1184 public function addTags( $tags ) {
1185 if ( is_string( $tags ) ) {
1186 $this->tags[] = $tags;
1187 } else {
1188 $this->tags = array_merge( $tags, $this->tags );
1189 }
1190 }
1191}
serialize()
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.
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
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)
Throws a warning that $function is deprecated.
$wgUser
Definition Setup.php:902
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:112
$wgUseEnotif
Definition Setup.php:443
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:737
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.
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
This module processes the email notifications when the current page is changed.
static singleton( $domain=false)
MediaWiki exception.
static record( $rc, $auto=false, User $user=null, $tags=null)
Record a log event for a change being patrolled.
Definition PatrolLog.php:42
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.
static array $changeTypes
Array of change types.
static selectFields()
Return the list of recentchanges fields that should be selected to create a new recentchanges object.
const SRC_CATEGORIZE
getPerformer()
Get the User object of the person who performed this change.
static checkIPAddress( $ip)
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.
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 newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='', $revId=0, $isPatrollable=false)
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.
save( $noudp=false)
Writes the data in this object to the database.
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.
static notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='')
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.
diffLinkTrail( $forceCur)
Gets the end part of the diff URL associated with this object Blank if no diff link should be display...
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:39
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:3436
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:53
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:591
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:657
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:614
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition User.php:629
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition WikiPage.php:115
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition hooks.txt:266
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used the name of the old file when set the default code will be skipped true if there is text before this autocomment $auto
Definition hooks.txt:1587
passed in as a query string parameter to the various URLs constructed here(i.e. $prevlink) $ldel you ll need to handle error etc yourself modifying $error and returning true will cause the contents of $error to be echoed at the top of the edit form as wikitext Return true without altering $error to allow the edit to proceed & $editor
Definition hooks.txt:1419
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:2014
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:247
const RC_NEW
Definition Defines.php:153
const RC_LOG
Definition Defines.php:154
const RC_EXTERNAL
Definition Defines.php:155
const MIGRATION_WRITE_BOTH
Definition Defines.php:303
const RC_EDIT
Definition Defines.php:152
const RC_CATEGORIZE
Definition Defines.php:156
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:29
$params