MediaWiki  master
RecentChange.php
Go to the documentation of this file.
1 <?php
24 use Wikimedia\IPUtils;
25 
71 class RecentChange implements Taggable {
72  // Constants for the rc_source field. Extensions may also have
73  // their own source constants.
74  const SRC_EDIT = 'mw.edit';
75  const SRC_NEW = 'mw.new';
76  const SRC_LOG = 'mw.log';
77  const SRC_EXTERNAL = 'mw.external'; // obsolete
78  const SRC_CATEGORIZE = 'mw.categorize';
79 
80  const PRC_UNPATROLLED = 0;
81  const PRC_PATROLLED = 1;
82  const PRC_AUTOPATROLLED = 2;
83 
87  const SEND_NONE = true;
88 
92  const SEND_FEED = false;
93 
95  public $mAttribs = [];
96  public $mExtra = [];
97 
101  public $mTitle = false;
102 
106  private $mPerformer = false;
107 
108  public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked
110 
114  public $counter = -1;
115 
119  private $tags = [];
120 
124  private const CHANGE_TYPES = [
125  'edit' => RC_EDIT,
126  'new' => RC_NEW,
127  'log' => RC_LOG,
128  'external' => RC_EXTERNAL,
129  'categorize' => RC_CATEGORIZE,
130  ];
131 
132  # Factory methods
133 
138  public static function newFromRow( $row ) {
139  $rc = new RecentChange;
140  $rc->loadFromRow( $row );
141 
142  return $rc;
143  }
144 
152  public static function parseToRCType( $type ) {
153  if ( is_array( $type ) ) {
154  $retval = [];
155  foreach ( $type as $t ) {
156  $retval[] = self::parseToRCType( $t );
157  }
158 
159  return $retval;
160  }
161 
162  if ( !array_key_exists( $type, self::CHANGE_TYPES ) ) {
163  throw new MWException( "Unknown type '$type'" );
164  }
165  return self::CHANGE_TYPES[$type];
166  }
167 
174  public static function parseFromRCType( $rcType ) {
175  return array_search( $rcType, self::CHANGE_TYPES, true ) ?: "$rcType";
176  }
177 
185  public static function getChangeTypes() {
186  return array_keys( self::CHANGE_TYPES );
187  }
188 
195  public static function newFromId( $rcid ) {
196  return self::newFromConds( [ 'rc_id' => $rcid ], __METHOD__ );
197  }
198 
208  public static function newFromConds(
209  $conds,
210  $fname = __METHOD__,
211  $dbType = DB_REPLICA
212  ) {
213  $db = wfGetDB( $dbType );
214  $rcQuery = self::getQueryInfo();
215  $row = $db->selectRow(
216  $rcQuery['tables'], $rcQuery['fields'], $conds, $fname, [], $rcQuery['joins']
217  );
218  if ( $row !== false ) {
219  return self::newFromRow( $row );
220  } else {
221  return null;
222  }
223  }
224 
234  public static function getQueryInfo() {
235  $commentQuery = CommentStore::getStore()->getJoin( 'rc_comment' );
236  $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
237  return [
238  'tables' => [ 'recentchanges' ] + $commentQuery['tables'] + $actorQuery['tables'],
239  'fields' => [
240  'rc_id',
241  'rc_timestamp',
242  'rc_namespace',
243  'rc_title',
244  'rc_minor',
245  'rc_bot',
246  'rc_new',
247  'rc_cur_id',
248  'rc_this_oldid',
249  'rc_last_oldid',
250  'rc_type',
251  'rc_source',
252  'rc_patrolled',
253  'rc_ip',
254  'rc_old_len',
255  'rc_new_len',
256  'rc_deleted',
257  'rc_logid',
258  'rc_log_type',
259  'rc_log_action',
260  'rc_params',
261  ] + $commentQuery['fields'] + $actorQuery['fields'],
262  'joins' => $commentQuery['joins'] + $actorQuery['joins'],
263  ];
264  }
265 
266  # Accessors
267 
271  public function setAttribs( $attribs ) {
272  $this->mAttribs = $attribs;
273  }
274 
278  public function setExtra( $extra ) {
279  $this->mExtra = $extra;
280  }
281 
285  public function &getTitle() {
286  if ( $this->mTitle === false ) {
287  $this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
288  }
289 
290  return $this->mTitle;
291  }
292 
298  public function getPerformer() {
299  if ( $this->mPerformer === false ) {
300  if ( !empty( $this->mAttribs['rc_actor'] ) ) {
301  $this->mPerformer = User::newFromActorId( $this->mAttribs['rc_actor'] );
302  } elseif ( !empty( $this->mAttribs['rc_user'] ) ) {
303  $this->mPerformer = User::newFromId( $this->mAttribs['rc_user'] );
304  } elseif ( !empty( $this->mAttribs['rc_user_text'] ) ) {
305  $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false );
306  } else {
307  throw new MWException( 'RecentChange object lacks rc_actor, rc_user, and rc_user_text' );
308  }
309  }
310 
311  return $this->mPerformer;
312  }
313 
323  public function save( $send = self::SEND_FEED ) {
325 
326  $dbw = wfGetDB( DB_MASTER );
327  if ( !is_array( $this->mExtra ) ) {
328  $this->mExtra = [];
329  }
330 
331  if ( !$wgPutIPinRC ) {
332  $this->mAttribs['rc_ip'] = '';
333  }
334 
335  # Strict mode fixups (not-NULL fields)
336  foreach ( [ 'minor', 'bot', 'new', 'patrolled', 'deleted' ] as $field ) {
337  $this->mAttribs["rc_$field"] = (int)$this->mAttribs["rc_$field"];
338  }
339  # ...more fixups (NULL fields)
340  foreach ( [ 'old_len', 'new_len' ] as $field ) {
341  $this->mAttribs["rc_$field"] = isset( $this->mAttribs["rc_$field"] )
342  ? (int)$this->mAttribs["rc_$field"]
343  : null;
344  }
345 
346  # If our database is strict about IP addresses, use NULL instead of an empty string
347  $strictIPs = $dbw->getType() === 'postgres'; // legacy
348  if ( $strictIPs && $this->mAttribs['rc_ip'] == '' ) {
349  unset( $this->mAttribs['rc_ip'] );
350  }
351 
352  # Trim spaces on user supplied text
353  $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] );
354 
355  # Fixup database timestamps
356  $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] );
357 
358  # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
359  if ( $this->mAttribs['rc_cur_id'] == 0 ) {
360  unset( $this->mAttribs['rc_cur_id'] );
361  }
362 
363  $row = $this->mAttribs;
364 
365  # Convert mAttribs['rc_comment'] for CommentStore
366  $comment = $row['rc_comment'];
367  unset( $row['rc_comment'], $row['rc_comment_text'], $row['rc_comment_data'] );
368  $row += CommentStore::getStore()->insert( $dbw, 'rc_comment', $comment );
369 
370  # Convert mAttribs['rc_user'] etc for ActorMigration
371  $user = User::newFromAnyId(
372  $row['rc_user'] ?? null,
373  $row['rc_user_text'] ?? null,
374  $row['rc_actor'] ?? null
375  );
376  unset( $row['rc_user'], $row['rc_user_text'], $row['rc_actor'] );
377  $row += ActorMigration::newMigration()->getInsertValues( $dbw, 'rc_user', $user );
378 
379  # Don't reuse an existing rc_id for the new row, if one happens to be
380  # set for some reason.
381  unset( $row['rc_id'] );
382 
383  # Insert new row
384  $dbw->insert( 'recentchanges', $row, __METHOD__ );
385 
386  # Set the ID
387  $this->mAttribs['rc_id'] = $dbw->insertId();
388 
389  # Notify extensions
390  // Avoid PHP 7.1 warning from passing $this by reference
391  $rc = $this;
392  Hooks::run( 'RecentChange_save', [ &$rc ] );
393 
394  if ( count( $this->tags ) ) {
395  ChangeTags::addTags( $this->tags, $this->mAttribs['rc_id'],
396  $this->mAttribs['rc_this_oldid'], $this->mAttribs['rc_logid'], null, $this );
397  }
398 
399  if ( $send === self::SEND_FEED ) {
400  // Emit the change to external applications via RCFeeds.
401  $this->notifyRCFeeds();
402  }
403 
404  # E-mail notifications
405  if ( $wgUseEnotif || $wgShowUpdatedMarker ) {
406  $editor = $this->getPerformer();
407  $title = $this->getTitle();
408 
409  // Never send an RC notification email about categorization changes
410  if (
411  Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] ) &&
412  $this->mAttribs['rc_type'] != RC_CATEGORIZE
413  ) {
414  // @FIXME: This would be better as an extension hook
415  // Send emails or email jobs once this row is safely committed
416  $dbw->onTransactionCommitOrIdle(
417  function () use ( $editor, $title ) {
418  $enotif = new EmailNotification();
419  $enotif->notifyOnPageChange(
420  $editor,
421  $title,
422  $this->mAttribs['rc_timestamp'],
423  $this->mAttribs['rc_comment'],
424  $this->mAttribs['rc_minor'],
425  $this->mAttribs['rc_last_oldid'],
426  $this->mExtra['pageStatus']
427  );
428  },
429  __METHOD__
430  );
431  }
432  }
433 
434  // Update the cached list of active users
435  if ( $this->mAttribs['rc_user'] > 0 ) {
437  }
438  }
439 
444  public function notifyRCFeeds( array $feeds = null ) {
445  global $wgRCFeeds;
446  if ( $feeds === null ) {
447  $feeds = $wgRCFeeds;
448  }
449 
450  $performer = $this->getPerformer();
451 
452  foreach ( $feeds as $params ) {
453  $params += [
454  'omit_bots' => false,
455  'omit_anon' => false,
456  'omit_user' => false,
457  'omit_minor' => false,
458  'omit_patrolled' => false,
459  ];
460 
461  if (
462  ( $params['omit_bots'] && $this->mAttribs['rc_bot'] ) ||
463  ( $params['omit_anon'] && $performer->isAnon() ) ||
464  ( $params['omit_user'] && !$performer->isAnon() ) ||
465  ( $params['omit_minor'] && $this->mAttribs['rc_minor'] ) ||
466  ( $params['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) ||
467  $this->mAttribs['rc_type'] == RC_EXTERNAL
468  ) {
469  continue;
470  }
471 
472  $actionComment = $this->mExtra['actionCommentIRC'] ?? null;
473 
474  $feed = RCFeed::factory( $params );
475  $feed->notify( $this, $actionComment );
476  }
477  }
478 
487  public static function getEngine( $uri, $params = [] ) {
488  // TODO: Merge into RCFeed::factory().
489  global $wgRCEngines;
490  $scheme = parse_url( $uri, PHP_URL_SCHEME );
491  if ( !$scheme ) {
492  throw new MWException( "Invalid RCFeed uri: '$uri'" );
493  }
494  if ( !isset( $wgRCEngines[$scheme] ) ) {
495  throw new MWException( "Unknown RCFeedEngine scheme: '$scheme'" );
496  }
497  if ( defined( 'MW_PHPUNIT_TEST' ) && is_object( $wgRCEngines[$scheme] ) ) {
498  return $wgRCEngines[$scheme];
499  }
500  return new $wgRCEngines[$scheme]( $params );
501  }
502 
512  public static function markPatrolled( $change, $auto = false, $tags = null ) {
513  global $wgUser;
514 
515  $change = $change instanceof RecentChange
516  ? $change
517  : self::newFromId( $change );
518 
519  if ( !$change instanceof RecentChange ) {
520  return null;
521  }
522 
523  return $change->doMarkPatrolled( $wgUser, $auto, $tags );
524  }
525 
537  public function doMarkPatrolled( User $user, $auto = false, $tags = null ) {
539 
540  // Fix up $tags so that the MarkPatrolled hook below always gets an array
541  if ( $tags === null ) {
542  $tags = [];
543  } elseif ( is_string( $tags ) ) {
544  $tags = [ $tags ];
545  }
546 
547  $errors = [];
548  // If recentchanges patrol is disabled, only new pages or new file versions
549  // can be patrolled, provided the appropriate config variable is set
550  if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) &&
551  ( !$wgUseFilePatrol || !( $this->getAttribute( 'rc_type' ) == RC_LOG &&
552  $this->getAttribute( 'rc_log_type' ) == 'upload' ) ) ) {
553  $errors[] = [ 'rcpatroldisabled' ];
554  }
555  // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
556  $right = $auto ? 'autopatrol' : 'patrol';
557  $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) );
558  if ( !Hooks::run( 'MarkPatrolled',
559  [ $this->getAttribute( 'rc_id' ), &$user, false, $auto, &$tags ] )
560  ) {
561  $errors[] = [ 'hookaborted' ];
562  }
563  // Users without the 'autopatrol' right can't patrol their
564  // own revisions
565  if ( $user->getName() === $this->getAttribute( 'rc_user_text' ) &&
566  !MediaWikiServices::getInstance()->getPermissionManager()
567  ->userHasRight( $user, 'autopatrol' )
568  ) {
569  $errors[] = [ 'markedaspatrollederror-noautopatrol' ];
570  }
571  if ( $errors ) {
572  return $errors;
573  }
574  // If the change was patrolled already, do nothing
575  if ( $this->getAttribute( 'rc_patrolled' ) ) {
576  return [];
577  }
578  // Actually set the 'patrolled' flag in RC
579  $this->reallyMarkPatrolled();
580  // Log this patrol event
581  PatrolLog::record( $this, $auto, $user, $tags );
582 
583  Hooks::run(
584  'MarkPatrolledComplete',
585  [ $this->getAttribute( 'rc_id' ), &$user, false, $auto ]
586  );
587 
588  return [];
589  }
590 
595  public function reallyMarkPatrolled() {
596  $dbw = wfGetDB( DB_MASTER );
597  $dbw->update(
598  'recentchanges',
599  [
600  'rc_patrolled' => self::PRC_PATROLLED
601  ],
602  [
603  'rc_id' => $this->getAttribute( 'rc_id' )
604  ],
605  __METHOD__
606  );
607  // Invalidate the page cache after the page has been patrolled
608  // to make sure that the Patrol link isn't visible any longer!
609  $this->getTitle()->invalidateCache();
610 
611  return $dbw->affectedRows();
612  }
613 
633  public static function notifyEdit(
634  $timestamp, $title, $minor, $user, $comment, $oldId, $lastTimestamp,
635  $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
636  $tags = []
637  ) {
638  $rc = new RecentChange;
639  $rc->mTitle = $title;
640  $rc->mPerformer = $user;
641  $rc->mAttribs = [
642  'rc_timestamp' => $timestamp,
643  'rc_namespace' => $title->getNamespace(),
644  'rc_title' => $title->getDBkey(),
645  'rc_type' => RC_EDIT,
646  'rc_source' => self::SRC_EDIT,
647  'rc_minor' => $minor ? 1 : 0,
648  'rc_cur_id' => $title->getArticleID(),
649  'rc_user' => $user->getId(),
650  'rc_user_text' => $user->getName(),
651  'rc_actor' => $user->getActorId(),
652  'rc_comment' => &$comment,
653  'rc_comment_text' => &$comment,
654  'rc_comment_data' => null,
655  'rc_this_oldid' => $newId,
656  'rc_last_oldid' => $oldId,
657  'rc_bot' => $bot ? 1 : 0,
658  'rc_ip' => self::checkIPAddress( $ip ),
659  'rc_patrolled' => intval( $patrol ),
660  'rc_new' => 0, # obsolete
661  'rc_old_len' => $oldSize,
662  'rc_new_len' => $newSize,
663  'rc_deleted' => 0,
664  'rc_logid' => 0,
665  'rc_log_type' => null,
666  'rc_log_action' => '',
667  'rc_params' => ''
668  ];
669 
670  $rc->mExtra = [
671  'prefixedDBkey' => $title->getPrefixedDBkey(),
672  'lastTimestamp' => $lastTimestamp,
673  'oldSize' => $oldSize,
674  'newSize' => $newSize,
675  'pageStatus' => 'changed'
676  ];
677 
679  function () use ( $rc, $tags ) {
680  $rc->addTags( $tags );
681  $rc->save();
682  },
684  wfGetDB( DB_MASTER )
685  );
686 
687  return $rc;
688  }
689 
707  public static function notifyNew(
708  $timestamp, $title, $minor, $user, $comment, $bot,
709  $ip = '', $size = 0, $newId = 0, $patrol = 0, $tags = []
710  ) {
711  $rc = new RecentChange;
712  $rc->mTitle = $title;
713  $rc->mPerformer = $user;
714  $rc->mAttribs = [
715  'rc_timestamp' => $timestamp,
716  'rc_namespace' => $title->getNamespace(),
717  'rc_title' => $title->getDBkey(),
718  'rc_type' => RC_NEW,
719  'rc_source' => self::SRC_NEW,
720  'rc_minor' => $minor ? 1 : 0,
721  'rc_cur_id' => $title->getArticleID(),
722  'rc_user' => $user->getId(),
723  'rc_user_text' => $user->getName(),
724  'rc_actor' => $user->getActorId(),
725  'rc_comment' => &$comment,
726  'rc_comment_text' => &$comment,
727  'rc_comment_data' => null,
728  'rc_this_oldid' => $newId,
729  'rc_last_oldid' => 0,
730  'rc_bot' => $bot ? 1 : 0,
731  'rc_ip' => self::checkIPAddress( $ip ),
732  'rc_patrolled' => intval( $patrol ),
733  'rc_new' => 1, # obsolete
734  'rc_old_len' => 0,
735  'rc_new_len' => $size,
736  'rc_deleted' => 0,
737  'rc_logid' => 0,
738  'rc_log_type' => null,
739  'rc_log_action' => '',
740  'rc_params' => ''
741  ];
742 
743  $rc->mExtra = [
744  'prefixedDBkey' => $title->getPrefixedDBkey(),
745  'lastTimestamp' => 0,
746  'oldSize' => 0,
747  'newSize' => $size,
748  'pageStatus' => 'created'
749  ];
750 
752  function () use ( $rc, $tags ) {
753  $rc->addTags( $tags );
754  $rc->save();
755  },
757  wfGetDB( DB_MASTER )
758  );
759 
760  return $rc;
761  }
762 
778  public static function notifyLog( $timestamp, $title, $user, $actionComment, $ip, $type,
779  $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = ''
780  ) {
781  global $wgLogRestrictions;
782 
783  # Don't add private logs to RC!
784  if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) {
785  return false;
786  }
787  $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action,
788  $target, $logComment, $params, $newId, $actionCommentIRC );
789  $rc->save();
790 
791  return true;
792  }
793 
811  public static function newLogEntry( $timestamp, $title, $user, $actionComment, $ip,
812  $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '',
813  $revId = 0, $isPatrollable = false ) {
814  global $wgRequest;
815  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
816 
817  # # Get pageStatus for email notification
818  switch ( $type . '-' . $action ) {
819  case 'delete-delete':
820  case 'delete-delete_redir':
821  $pageStatus = 'deleted';
822  break;
823  case 'move-move':
824  case 'move-move_redir':
825  $pageStatus = 'moved';
826  break;
827  case 'delete-restore':
828  $pageStatus = 'restored';
829  break;
830  case 'upload-upload':
831  $pageStatus = 'created';
832  break;
833  case 'upload-overwrite':
834  default:
835  $pageStatus = 'changed';
836  break;
837  }
838 
839  // Allow unpatrolled status for patrollable log entries
840  $canAutopatrol = $permissionManager->userHasRight( $user, 'autopatrol' );
841  $markPatrolled = $isPatrollable ? $canAutopatrol : true;
842 
843  $rc = new RecentChange;
844  $rc->mTitle = $target;
845  $rc->mPerformer = $user;
846  $rc->mAttribs = [
847  'rc_timestamp' => $timestamp,
848  'rc_namespace' => $target->getNamespace(),
849  'rc_title' => $target->getDBkey(),
850  'rc_type' => RC_LOG,
851  'rc_source' => self::SRC_LOG,
852  'rc_minor' => 0,
853  'rc_cur_id' => $target->getArticleID(),
854  'rc_user' => $user->getId(),
855  'rc_user_text' => $user->getName(),
856  'rc_actor' => $user->getActorId(),
857  'rc_comment' => &$logComment,
858  'rc_comment_text' => &$logComment,
859  'rc_comment_data' => null,
860  'rc_this_oldid' => $revId,
861  'rc_last_oldid' => 0,
862  'rc_bot' => $permissionManager->userHasRight( $user, 'bot' ) ?
863  (int)$wgRequest->getBool( 'bot', true ) : 0,
864  'rc_ip' => self::checkIPAddress( $ip ),
865  'rc_patrolled' => $markPatrolled ? self::PRC_AUTOPATROLLED : self::PRC_UNPATROLLED,
866  'rc_new' => 0, # obsolete
867  'rc_old_len' => null,
868  'rc_new_len' => null,
869  'rc_deleted' => 0,
870  'rc_logid' => $newId,
871  'rc_log_type' => $type,
872  'rc_log_action' => $action,
873  'rc_params' => $params
874  ];
875 
876  $rc->mExtra = [
877  'prefixedDBkey' => $title->getPrefixedDBkey(),
878  'lastTimestamp' => 0,
879  'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
880  'pageStatus' => $pageStatus,
881  'actionCommentIRC' => $actionCommentIRC
882  ];
883 
884  return $rc;
885  }
886 
908  public static function newForCategorization(
909  $timestamp,
910  Title $categoryTitle,
911  ?User $user,
912  $comment,
913  Title $pageTitle,
914  $oldRevId,
915  $newRevId,
916  $lastTimestamp,
917  $bot,
918  $ip = '',
919  $deleted = 0,
920  $added = null
921  ) {
922  // Done in a backwards compatible way.
923  $params = [
924  'hidden-cat' => WikiCategoryPage::factory( $categoryTitle )->isHidden()
925  ];
926  if ( $added !== null ) {
927  $params['added'] = $added;
928  }
929 
930  $rc = new RecentChange;
931  $rc->mTitle = $categoryTitle;
932  $rc->mPerformer = $user;
933  $rc->mAttribs = [
934  'rc_timestamp' => $timestamp,
935  'rc_namespace' => $categoryTitle->getNamespace(),
936  'rc_title' => $categoryTitle->getDBkey(),
937  'rc_type' => RC_CATEGORIZE,
938  'rc_source' => self::SRC_CATEGORIZE,
939  'rc_minor' => 0,
940  'rc_cur_id' => $pageTitle->getArticleID(),
941  'rc_user' => $user ? $user->getId() : 0,
942  'rc_user_text' => $user ? $user->getName() : '',
943  'rc_actor' => $user ? $user->getActorId() : null,
944  'rc_comment' => &$comment,
945  'rc_comment_text' => &$comment,
946  'rc_comment_data' => null,
947  'rc_this_oldid' => $newRevId,
948  'rc_last_oldid' => $oldRevId,
949  'rc_bot' => $bot ? 1 : 0,
950  'rc_ip' => self::checkIPAddress( $ip ),
951  'rc_patrolled' => self::PRC_AUTOPATROLLED, // Always patrolled, just like log entries
952  'rc_new' => 0, # obsolete
953  'rc_old_len' => null,
954  'rc_new_len' => null,
955  'rc_deleted' => $deleted,
956  'rc_logid' => 0,
957  'rc_log_type' => null,
958  'rc_log_action' => '',
959  'rc_params' => serialize( $params )
960  ];
961 
962  $rc->mExtra = [
963  'prefixedDBkey' => $categoryTitle->getPrefixedDBkey(),
964  'lastTimestamp' => $lastTimestamp,
965  'oldSize' => 0,
966  'newSize' => 0,
967  'pageStatus' => 'changed'
968  ];
969 
970  return $rc;
971  }
972 
981  public function getParam( $name ) {
982  $params = $this->parseParams();
983  return $params[$name] ?? null;
984  }
985 
991  public function loadFromRow( $row ) {
992  $this->mAttribs = get_object_vars( $row );
993  $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] );
994  // rc_deleted MUST be set
995  $this->mAttribs['rc_deleted'] = $row->rc_deleted;
996 
997  if ( isset( $this->mAttribs['rc_ip'] ) ) {
998  // Clean up CIDRs for Postgres per T164898. ("127.0.0.1" casts to "127.0.0.1/32")
999  $n = strpos( $this->mAttribs['rc_ip'], '/' );
1000  if ( $n !== false ) {
1001  $this->mAttribs['rc_ip'] = substr( $this->mAttribs['rc_ip'], 0, $n );
1002  }
1003  }
1004 
1005  $comment = CommentStore::getStore()
1006  // Legacy because $row may have come from self::selectFields()
1007  ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $row, true )
1008  ->text;
1009  $this->mAttribs['rc_comment'] = &$comment;
1010  $this->mAttribs['rc_comment_text'] = &$comment;
1011  $this->mAttribs['rc_comment_data'] = null;
1012 
1013  $user = User::newFromAnyId(
1014  $this->mAttribs['rc_user'] ?? null,
1015  $this->mAttribs['rc_user_text'] ?? null,
1016  $this->mAttribs['rc_actor'] ?? null
1017  );
1018  $this->mAttribs['rc_user'] = $user->getId();
1019  $this->mAttribs['rc_user_text'] = $user->getName();
1020  $this->mAttribs['rc_actor'] = $user->getActorId();
1021  }
1022 
1029  public function getAttribute( $name ) {
1030  if ( $name === 'rc_comment' ) {
1031  return CommentStore::getStore()
1032  ->getComment( 'rc_comment', $this->mAttribs, true )->text;
1033  }
1034 
1035  if ( $name === 'rc_user' || $name === 'rc_user_text' || $name === 'rc_actor' ) {
1036  $user = User::newFromAnyId(
1037  $this->mAttribs['rc_user'] ?? null,
1038  $this->mAttribs['rc_user_text'] ?? null,
1039  $this->mAttribs['rc_actor'] ?? null
1040  );
1041  if ( $name === 'rc_user' ) {
1042  return $user->getId();
1043  }
1044  if ( $name === 'rc_user_text' ) {
1045  return $user->getName();
1046  }
1047  if ( $name === 'rc_actor' ) {
1048  return $user->getActorId();
1049  }
1050  }
1051 
1052  return $this->mAttribs[$name] ?? null;
1053  }
1054 
1058  public function getAttributes() {
1059  return $this->mAttribs;
1060  }
1061 
1068  public function diffLinkTrail( $forceCur ) {
1069  if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
1070  $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) .
1071  "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] );
1072  if ( $forceCur ) {
1073  $trail .= '&diff=0';
1074  } else {
1075  $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] );
1076  }
1077  } else {
1078  $trail = '';
1079  }
1080 
1081  return $trail;
1082  }
1083 
1091  public function getCharacterDifference( $old = 0, $new = 0 ) {
1092  if ( $old === 0 ) {
1093  $old = $this->mAttribs['rc_old_len'];
1094  }
1095  if ( $new === 0 ) {
1096  $new = $this->mAttribs['rc_new_len'];
1097  }
1098  if ( $old === null || $new === null ) {
1099  return '';
1100  }
1101 
1102  return ChangesList::showCharacterDifference( $old, $new );
1103  }
1104 
1105  private static function checkIPAddress( $ip ) {
1106  global $wgRequest;
1107  if ( $ip ) {
1108  if ( !IPUtils::isIPAddress( $ip ) ) {
1109  throw new MWException( "Attempt to write \"" . $ip .
1110  "\" as an IP address into recent changes" );
1111  }
1112  } else {
1113  $ip = $wgRequest->getIP();
1114  if ( !$ip ) {
1115  $ip = '';
1116  }
1117  }
1118 
1119  return $ip;
1120  }
1121 
1131  public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
1132  global $wgRCMaxAge;
1133 
1134  return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge;
1135  }
1136 
1144  public function parseParams() {
1145  $rcParams = $this->getAttribute( 'rc_params' );
1146 
1147  Wikimedia\suppressWarnings();
1148  $unserializedParams = unserialize( $rcParams );
1149  Wikimedia\restoreWarnings();
1150 
1151  return $unserializedParams;
1152  }
1153 
1162  public function addTags( $tags ) {
1163  if ( is_string( $tags ) ) {
1164  $this->tags[] = $tags;
1165  } else {
1166  $this->tags = array_merge( $tags, $this->tags );
1167  }
1168  }
1169 }
RecentChange\getCharacterDifference
getCharacterDifference( $old=0, $new=0)
Returns the change size (HTML).
Definition: RecentChange.php:1091
RecentChange\save
save( $send=self::SEND_FEED)
Writes the data in this object to the database.
Definition: RecentChange.php:323
RecentChange\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
Definition: RecentChange.php:234
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:561
RC_EXTERNAL
const RC_EXTERNAL
Definition: Defines.php:125
RecentChange\$tags
array $tags
List of tags to apply.
Definition: RecentChange.php:119
User\getId
getId()
Get the user's ID.
Definition: User.php:2255
RecentChange\setExtra
setExtra( $extra)
Definition: RecentChange.php:278
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2323
SpecialRecentChangesLinked
This is to display changes made to all articles linked in an article.
Definition: SpecialRecentChangesLinked.php:31
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:208
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:7016
Title\getPrefixedDBkey
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1841
RecentChange\getAttributes
getAttributes()
Definition: RecentChange.php:1058
PatrolLog\record
static record( $rc, $auto=false, User $user=null, $tags=null)
Record a log event for a change being patrolled.
Definition: PatrolLog.php:42
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:71
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1871
RC_LOG
const RC_LOG
Definition: Defines.php:124
Title\getArticleID
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3161
RecentChange\notifyLog
static notifyLog( $timestamp, $title, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='')
Definition: RecentChange.php:778
RecentChange\getEngine
static getEngine( $uri, $params=[])
Definition: RecentChange.php:487
RecentChange\loadFromRow
loadFromRow( $row)
Initialises the members of this object from a mysql row object.
Definition: RecentChange.php:991
RecentChange\getChangeTypes
static getChangeTypes()
Get an array of all change types.
Definition: RecentChange.php:185
RecentChange\reallyMarkPatrolled
reallyMarkPatrolled()
Mark this RecentChange patrolled, without error checking.
Definition: RecentChange.php:595
RC_EDIT
const RC_EDIT
Definition: Defines.php:122
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:537
RecentChange\$counter
int $counter
Line number of recent change.
Definition: RecentChange.php:114
RecentChange\setAttribs
setAttribs( $attribs)
Definition: RecentChange.php:271
MediaWiki\ChangeTags\Taggable
Interface that defines how to tag objects.
Definition: Taggable.php:32
RecentChange\SRC_CATEGORIZE
const SRC_CATEGORIZE
Definition: RecentChange.php:78
RecentChange\parseToRCType
static parseToRCType( $type)
Parsing text to RC_* constants.
Definition: RecentChange.php:152
serialize
serialize()
Definition: ApiMessageTrait.php:138
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6909
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6925
RecentChange\SRC_LOG
const SRC_LOG
Definition: RecentChange.php:76
RecentChangesUpdateJob\newCacheUpdateJob
static newCacheUpdateJob()
Definition: RecentChangesUpdateJob.php:54
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:139
RecentChange\parseParams
parseParams()
Parses and returns the rc_params attribute.
Definition: RecentChange.php:1144
$wgPutIPinRC
$wgPutIPinRC
Log IP addresses in the recentchanges table; can be accessed only by extensions (e....
Definition: DefaultSettings.php:5773
RecentChange\$mTitle
Title false $mTitle
Definition: RecentChange.php:101
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:1014
MWException
MediaWiki exception.
Definition: MWException.php:26
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:141
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1035
RecentChange\$notificationtimestamp
$notificationtimestamp
Definition: RecentChange.php:109
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2562
RecentChange\notifyEdit
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.
Definition: RecentChange.php:633
$wgRCFeeds
$wgRCFeeds
Recentchanges items are periodically purged; entries older than this many seconds will go.
Definition: DefaultSettings.php:6876
RecentChange\newFromRow
static newFromRow( $row)
Definition: RecentChange.php:138
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:70
$title
$title
Definition: testCompression.php:36
RecentChange\SRC_EDIT
const SRC_EDIT
Definition: RecentChange.php:74
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:584
RecentChange\diffLinkTrail
diffLinkTrail( $forceCur)
Gets the end part of the diff URL associated with this object Blank if no diff link should be display...
Definition: RecentChange.php:1068
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:618
DB_MASTER
const DB_MASTER
Definition: defines.php:26
RecentChange\SRC_NEW
const SRC_NEW
Definition: RecentChange.php:75
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:6936
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:81
RecentChange\newFromId
static newFromId( $rcid)
Obtain the recent change with a given rc_id value.
Definition: RecentChange.php:195
RecentChange\doMarkPatrolled
doMarkPatrolled(User $user, $auto=false, $tags=null)
Mark this RecentChange as patrolled.
Definition: RecentChange.php:537
RecentChange\$mExtra
$mExtra
Definition: RecentChange.php:96
RecentChange\newForCategorization
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...
Definition: RecentChange.php:908
$wgLogRestrictions
$wgLogRestrictions
This restricts log access to those who have a certain right Users without this will not see it in the...
Definition: DefaultSettings.php:7700
$wgRCMaxAge
$wgRCMaxAge
Recentchanges items are periodically purged; entries older than this many seconds will go.
Definition: DefaultSettings.php:6783
ChangesList\showCharacterDifference
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
Definition: ChangesList.php:326
RC_NEW
const RC_NEW
Definition: Defines.php:123
RecentChange\markPatrolled
static markPatrolled( $change, $auto=false, $tags=null)
Mark a given change as patrolled.
Definition: RecentChange.php:512
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:82
RecentChange\addTags
addTags( $tags)
Tags to append to the recent change, and associated revision/log.
Definition: RecentChange.php:1162
RecentChange\getAttribute
getAttribute( $name)
Get an attribute value.
Definition: RecentChange.php:1029
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:429
RecentChange\checkIPAddress
static checkIPAddress( $ip)
Definition: RecentChange.php:1105
RecentChange\newLogEntry
static newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='', $revId=0, $isPatrollable=false)
Definition: RecentChange.php:811
unserialize
unserialize( $serialized)
Definition: ApiMessageTrait.php:146
Title
Represents a title within MediaWiki.
Definition: Title.php:42
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:70
RecentChange\notifyNew
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...
Definition: RecentChange.php:707
RecentChange\parseFromRCType
static parseFromRCType( $rcType)
Parsing RC_* constants to human-readable test.
Definition: RecentChange.php:174
$wgRCEngines
$wgRCEngines
Used by RecentChange::getEngine to find the correct engine for a given URI scheme.
Definition: DefaultSettings.php:6883
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:80
RCFeed\factory
static factory(array $params)
Definition: RCFeed.php:46
RecentChange\isInRCLifespan
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...
Definition: RecentChange.php:1131
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:576
RecentChange\$mPerformer
User false $mPerformer
Definition: RecentChange.php:106
RC_CATEGORIZE
const RC_CATEGORIZE
Definition: Defines.php:126
RecentChange\notifyRCFeeds
notifyRCFeeds(array $feeds=null)
Notify all the feeds about the change.
Definition: RecentChange.php:444
$t
$t
Definition: testCompression.php:71
RecentChange\$mAttribs
array $mAttribs
Definition: RecentChange.php:95
RecentChange\getPerformer
getPerformer()
Get the User object of the person who performed this change.
Definition: RecentChange.php:298
EmailNotification
This module processes the email notifications when the current page is changed.
Definition: EmailNotification.php:48
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:713
RecentChange\getTitle
& getTitle()
Definition: RecentChange.php:285
CommentStore\getStore
static getStore()
Definition: CommentStore.php:139
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:52
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:125
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2284
RecentChange\getParam
getParam( $name)
Get a parameter value.
Definition: RecentChange.php:981
RecentChange\$numberofWatchingusers
$numberofWatchingusers
Definition: RecentChange.php:108
ChangeTags\addTags
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.
Definition: ChangeTags.php:256
RecentChange\SRC_EXTERNAL
const SRC_EXTERNAL
Definition: RecentChange.php:77
$type
$type
Definition: testCompression.php:50