MediaWiki  master
RecentChange.php
Go to the documentation of this file.
1 <?php
32 use Wikimedia\Assert\Assert;
33 use Wikimedia\IPUtils;
34 
80 class RecentChange implements Taggable {
82 
83  // Constants for the rc_source field. Extensions may also have
84  // their own source constants.
85  public const SRC_EDIT = 'mw.edit';
86  public const SRC_NEW = 'mw.new';
87  public const SRC_LOG = 'mw.log';
88  public const SRC_EXTERNAL = 'mw.external'; // obsolete
89  public const SRC_CATEGORIZE = 'mw.categorize';
90 
91  public const PRC_UNPATROLLED = 0;
92  public const PRC_PATROLLED = 1;
93  public const PRC_AUTOPATROLLED = 2;
94 
98  public const SEND_NONE = true;
99 
103  public const SEND_FEED = false;
104 
106  public $mAttribs = [];
107  public $mExtra = [];
108 
112  private $mPage = null;
113 
117  private $mPerformer = null;
118 
119  public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked
121 
126 
130  public $counter = -1;
131 
135  private $tags = [];
136 
140  private $editResult = null;
141 
145  private const CHANGE_TYPES = [
146  'edit' => RC_EDIT,
147  'new' => RC_NEW,
148  'log' => RC_LOG,
149  'external' => RC_EXTERNAL,
150  'categorize' => RC_CATEGORIZE,
151  ];
152 
153  # Factory methods
154 
159  public static function newFromRow( $row ) {
160  $rc = new RecentChange;
161  $rc->loadFromRow( $row );
162 
163  return $rc;
164  }
165 
173  public static function parseToRCType( $type ) {
174  if ( is_array( $type ) ) {
175  $retval = [];
176  foreach ( $type as $t ) {
177  $retval[] = self::parseToRCType( $t );
178  }
179 
180  return $retval;
181  }
182 
183  if ( !array_key_exists( $type, self::CHANGE_TYPES ) ) {
184  throw new MWException( "Unknown type '$type'" );
185  }
186  return self::CHANGE_TYPES[$type];
187  }
188 
195  public static function parseFromRCType( $rcType ) {
196  return array_search( $rcType, self::CHANGE_TYPES, true ) ?: "$rcType";
197  }
198 
206  public static function getChangeTypes() {
207  return array_keys( self::CHANGE_TYPES );
208  }
209 
216  public static function newFromId( $rcid ) {
217  return self::newFromConds( [ 'rc_id' => $rcid ], __METHOD__ );
218  }
219 
229  public static function newFromConds(
230  $conds,
231  $fname = __METHOD__,
232  $dbType = DB_REPLICA
233  ) {
234  $db = wfGetDB( $dbType );
235  $rcQuery = self::getQueryInfo();
236  $row = $db->selectRow(
237  $rcQuery['tables'], $rcQuery['fields'], $conds, $fname, [], $rcQuery['joins']
238  );
239  if ( $row !== false ) {
240  return self::newFromRow( $row );
241  } else {
242  return null;
243  }
244  }
245 
260  public static function getQueryInfo() {
261  $commentQuery = CommentStore::getStore()->getJoin( 'rc_comment' );
262  return [
263  'tables' => [
264  'recentchanges',
265  'recentchanges_actor' => 'actor'
266  ] + $commentQuery['tables'],
267  'fields' => [
268  'rc_id',
269  'rc_timestamp',
270  'rc_namespace',
271  'rc_title',
272  'rc_minor',
273  'rc_bot',
274  'rc_new',
275  'rc_cur_id',
276  'rc_this_oldid',
277  'rc_last_oldid',
278  'rc_type',
279  'rc_source',
280  'rc_patrolled',
281  'rc_ip',
282  'rc_old_len',
283  'rc_new_len',
284  'rc_deleted',
285  'rc_logid',
286  'rc_log_type',
287  'rc_log_action',
288  'rc_params',
289  'rc_actor',
290  'rc_user' => 'recentchanges_actor.actor_user',
291  'rc_user_text' => 'recentchanges_actor.actor_name',
292  ] + $commentQuery['fields'],
293  'joins' => [
294  'recentchanges_actor' => [ 'JOIN', 'actor_id=rc_actor' ]
295  ] + $commentQuery['joins'],
296  ];
297  }
298 
299  public function __construct() {
301  'mTitle',
302  '1.37',
303  function () {
304  return Title::castFromPageReference( $this->mPage );
305  },
306  function ( ?Title $title ) {
307  $this->mPage = $title;
308  }
309  );
310  }
311 
312  # Accessors
313 
317  public function setAttribs( $attribs ) {
318  $this->mAttribs = $attribs;
319  }
320 
324  public function setExtra( $extra ) {
325  $this->mExtra = $extra;
326  }
327 
332  public function getTitle() {
333  $this->mPage = Title::castFromPageReference( $this->getPage() );
334  return $this->mPage ?: Title::makeTitle( NS_SPECIAL, 'BadTitle' );
335  }
336 
341  public function getPage(): ?PageReference {
342  if ( !$this->mPage ) {
343  // NOTE: As per the 1.36 release, we always provide rc_title,
344  // even in cases where it doesn't really make sense.
345  // In the future, rc_title may be nullable, or we may use
346  // empty strings in entries that do not refer to a page.
347  if ( ( $this->mAttribs['rc_title'] ?? '' ) === '' ) {
348  return null;
349  }
350 
351  // XXX: We could use rc_cur_id to create a PageIdentityValue,
352  // at least if it's not a special page.
353  // However, newForCategorization() puts the ID of the categorized page into
354  // rc_cur_id, but the title of the category page into rc_title.
355  $this->mPage = new PageReferenceValue(
356  (int)$this->mAttribs['rc_namespace'],
357  $this->mAttribs['rc_title'],
358  PageReference::LOCAL
359  );
360  }
361 
362  return $this->mPage;
363  }
364 
371  public function getPerformer(): User {
372  wfDeprecated( __METHOD__, '1.36' );
373  if ( !$this->mPerformer instanceof User ) {
374  $this->mPerformer = User::newFromIdentity( $this->getPerformerIdentity() );
375  }
376 
377  return $this->mPerformer;
378  }
379 
387  public function getPerformerIdentity(): UserIdentity {
388  if ( !$this->mPerformer ) {
389  $this->mPerformer = $this->getUserIdentityFromAnyId(
390  $this->mAttribs['rc_user'] ?? null,
391  $this->mAttribs['rc_user_text'] ?? null,
392  $this->mAttribs['rc_actor'] ?? null
393  );
394  }
395 
396  return $this->mPerformer;
397  }
398 
408  public function save( $send = self::SEND_FEED ) {
410 
411  $dbw = wfGetDB( DB_PRIMARY );
412  if ( !is_array( $this->mExtra ) ) {
413  $this->mExtra = [];
414  }
415 
416  if ( !$wgPutIPinRC ) {
417  $this->mAttribs['rc_ip'] = '';
418  }
419 
420  # Strict mode fixups (not-NULL fields)
421  foreach ( [ 'minor', 'bot', 'new', 'patrolled', 'deleted' ] as $field ) {
422  $this->mAttribs["rc_$field"] = (int)$this->mAttribs["rc_$field"];
423  }
424  # ...more fixups (NULL fields)
425  foreach ( [ 'old_len', 'new_len' ] as $field ) {
426  $this->mAttribs["rc_$field"] = isset( $this->mAttribs["rc_$field"] )
427  ? (int)$this->mAttribs["rc_$field"]
428  : null;
429  }
430 
431  # If our database is strict about IP addresses, use NULL instead of an empty string
432  $strictIPs = $dbw->getType() === 'postgres'; // legacy
433  if ( $strictIPs && $this->mAttribs['rc_ip'] == '' ) {
434  unset( $this->mAttribs['rc_ip'] );
435  }
436 
437  $row = $this->mAttribs;
438 
439  # Trim spaces on user supplied text
440  $row['rc_comment'] = trim( $row['rc_comment'] );
441 
442  # Fixup database timestamps
443  $row['rc_timestamp'] = $dbw->timestamp( $row['rc_timestamp'] );
444 
445  # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
446  if ( $row['rc_cur_id'] == 0 ) {
447  unset( $row['rc_cur_id'] );
448  }
449 
450  # Convert mAttribs['rc_comment'] for CommentStore
451  $comment = $row['rc_comment'];
452  unset( $row['rc_comment'], $row['rc_comment_text'], $row['rc_comment_data'] );
453  $row += CommentStore::getStore()->insert( $dbw, 'rc_comment', $comment );
454 
455  # Normalize UserIdentity to actor ID
456  $user = $this->getPerformerIdentity();
457  $actorStore = MediaWikiServices::getInstance()->getActorStore();
458  $row['rc_actor'] = $actorStore->acquireActorId( $user, $dbw );
459  unset( $row['rc_user'], $row['rc_user_text'] );
460 
461  # Don't reuse an existing rc_id for the new row, if one happens to be
462  # set for some reason.
463  unset( $row['rc_id'] );
464 
465  # Insert new row
466  $dbw->insert( 'recentchanges', $row, __METHOD__ );
467 
468  # Set the ID
469  $this->mAttribs['rc_id'] = $dbw->insertId();
470 
471  # Notify extensions
472  Hooks::runner()->onRecentChange_save( $this );
473 
474  // Apply revert tags (if needed)
475  if ( $this->editResult !== null && count( $this->editResult->getRevertTags() ) ) {
477  $this->editResult->getRevertTags(),
478  $this->mAttribs['rc_id'],
479  $this->mAttribs['rc_this_oldid'],
480  $this->mAttribs['rc_logid'],
481  FormatJson::encode( $this->editResult ),
482  $this
483  );
484  }
485 
486  if ( count( $this->tags ) ) {
487  // $this->tags may contain revert tags we already applied above, they will
488  // just be ignored.
490  $this->tags,
491  $this->mAttribs['rc_id'],
492  $this->mAttribs['rc_this_oldid'],
493  $this->mAttribs['rc_logid'],
494  null,
495  $this
496  );
497  }
498 
499  if ( $send === self::SEND_FEED ) {
500  // Emit the change to external applications via RCFeeds.
501  $this->notifyRCFeeds();
502  }
503 
504  # E-mail notifications
505  if ( $wgUseEnotif || $wgShowUpdatedMarker ) {
506  $userFactory = MediaWikiServices::getInstance()->getUserFactory();
507  $editor = $userFactory->newFromUserIdentity( $this->getPerformerIdentity() );
508  $page = $this->getPage();
510 
511  // Never send an RC notification email about categorization changes
512  if (
513  $title &&
514  Hooks::runner()->onAbortEmailNotification( $editor, $title, $this ) &&
515  $this->mAttribs['rc_type'] != RC_CATEGORIZE
516  ) {
517  // @FIXME: This would be better as an extension hook
518  // Send emails or email jobs once this row is safely committed
519  $dbw->onTransactionCommitOrIdle(
520  function () use ( $editor, $title ) {
521  $enotif = new EmailNotification();
522  $enotif->notifyOnPageChange(
523  $editor,
524  $title,
525  $this->mAttribs['rc_timestamp'],
526  $this->mAttribs['rc_comment'],
527  $this->mAttribs['rc_minor'],
528  $this->mAttribs['rc_last_oldid'],
529  $this->mExtra['pageStatus']
530  );
531  },
532  __METHOD__
533  );
534  }
535  }
536 
537  $jobs = [];
538  // Flush old entries from the `recentchanges` table
539  if ( mt_rand( 0, 9 ) == 0 ) {
541  }
542  // Update the cached list of active users
543  if ( $this->mAttribs['rc_user'] > 0 ) {
545  }
546  JobQueueGroup::singleton()->lazyPush( $jobs );
547  }
548 
553  public function notifyRCFeeds( array $feeds = null ) {
554  global $wgRCFeeds;
555  if ( $feeds === null ) {
556  $feeds = $wgRCFeeds;
557  }
558 
559  $performer = $this->getPerformerIdentity();
560 
561  foreach ( $feeds as $params ) {
562  $params += [
563  'omit_bots' => false,
564  'omit_anon' => false,
565  'omit_user' => false,
566  'omit_minor' => false,
567  'omit_patrolled' => false,
568  ];
569 
570  if (
571  ( $params['omit_bots'] && $this->mAttribs['rc_bot'] ) ||
572  ( $params['omit_anon'] && !$performer->isRegistered() ) ||
573  ( $params['omit_user'] && $performer->isRegistered() ) ||
574  ( $params['omit_minor'] && $this->mAttribs['rc_minor'] ) ||
575  ( $params['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) ||
576  $this->mAttribs['rc_type'] == RC_EXTERNAL
577  ) {
578  continue;
579  }
580 
581  $actionComment = $this->mExtra['actionCommentIRC'] ?? null;
582 
583  $feed = RCFeed::factory( $params );
584  $feed->notify( $this, $actionComment );
585  }
586  }
587 
596  public static function getEngine( $uri, $params = [] ) {
597  // TODO: Merge into RCFeed::factory().
598  global $wgRCEngines;
599  $scheme = parse_url( $uri, PHP_URL_SCHEME );
600  if ( !$scheme ) {
601  throw new MWException( "Invalid RCFeed uri: '$uri'" );
602  }
603  if ( !isset( $wgRCEngines[$scheme] ) ) {
604  throw new MWException( "Unknown RCFeedEngine scheme: '$scheme'" );
605  }
606  if ( defined( 'MW_PHPUNIT_TEST' ) && is_object( $wgRCEngines[$scheme] ) ) {
607  return $wgRCEngines[$scheme];
608  }
609  return new $wgRCEngines[$scheme]( $params );
610  }
611 
623  public function doMarkPatrolled( Authority $performer, $auto = false, $tags = null ) {
625 
626  // Fix up $tags so that the MarkPatrolled hook below always gets an array
627  if ( $tags === null ) {
628  $tags = [];
629  } elseif ( is_string( $tags ) ) {
630  $tags = [ $tags ];
631  }
632 
633  $status = PermissionStatus::newEmpty();
634  // If recentchanges patrol is disabled, only new pages or new file versions
635  // can be patrolled, provided the appropriate config variable is set
636  if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) &&
637  ( !$wgUseFilePatrol || !( $this->getAttribute( 'rc_type' ) == RC_LOG &&
638  $this->getAttribute( 'rc_log_type' ) == 'upload' ) ) ) {
639  $status->fatal( 'rcpatroldisabled' );
640  }
641  // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
642  $performer->authorizeWrite( $auto ? 'autopatrol' : 'patrol', $this->getTitle(), $status );
643  $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
644  if ( !Hooks::runner()->onMarkPatrolled(
645  $this->getAttribute( 'rc_id' ), $user, false, $auto, $tags )
646  ) {
647  $status->fatal( 'hookaborted' );
648  }
649  // Users without the 'autopatrol' right can't patrol their own revisions
650  if ( $performer->getUser()->getName() === $this->getAttribute( 'rc_user_text' ) &&
651  !$performer->isAllowed( 'autopatrol' )
652  ) {
653  $status->fatal( 'markedaspatrollederror-noautopatrol' );
654  }
655  if ( !$status->isGood() ) {
656  return $status->toLegacyErrorArray();
657  }
658  // If the change was patrolled already, do nothing
659  if ( $this->getAttribute( 'rc_patrolled' ) ) {
660  return [];
661  }
662  // Actually set the 'patrolled' flag in RC
663  $this->reallyMarkPatrolled();
664  // Log this patrol event
665  PatrolLog::record( $this, $auto, $performer->getUser(), $tags );
666 
667  Hooks::runner()->onMarkPatrolledComplete(
668  $this->getAttribute( 'rc_id' ), $user, false, $auto );
669 
670  return [];
671  }
672 
677  public function reallyMarkPatrolled() {
678  $dbw = wfGetDB( DB_PRIMARY );
679  $dbw->update(
680  'recentchanges',
681  [
682  'rc_patrolled' => self::PRC_PATROLLED
683  ],
684  [
685  'rc_id' => $this->getAttribute( 'rc_id' )
686  ],
687  __METHOD__
688  );
689  // Invalidate the page cache after the page has been patrolled
690  // to make sure that the Patrol link isn't visible any longer!
691  $this->getTitle()->invalidateCache();
692 
693  // Enqueue a reverted tag update (in case the edit was a revert)
694  $revisionId = $this->getAttribute( 'rc_this_oldid' );
695  if ( $revisionId ) {
696  $revertedTagUpdateManager =
697  MediaWikiServices::getInstance()->getRevertedTagUpdateManager();
698  $revertedTagUpdateManager->approveRevertedTagForRevision( $revisionId );
699  }
700 
701  return $dbw->affectedRows();
702  }
703 
728  public static function notifyEdit(
729  $timestamp, $page, $minor, $user, $comment, $oldId, $lastTimestamp,
730  $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
731  $tags = [], EditResult $editResult = null
732  ) {
733  Assert::parameter( $page->exists(), '$page', 'must represent an existing page' );
734 
735  $rc = new RecentChange;
736  $rc->mPage = $page;
737  $rc->mPerformer = $user;
738  $rc->mAttribs = [
739  'rc_timestamp' => $timestamp,
740  'rc_namespace' => $page->getNamespace(),
741  'rc_title' => $page->getDBkey(),
742  'rc_type' => RC_EDIT,
743  'rc_source' => self::SRC_EDIT,
744  'rc_minor' => $minor ? 1 : 0,
745  'rc_cur_id' => $page->getId(),
746  'rc_user' => $user->getId(),
747  'rc_user_text' => $user->getName(),
748  'rc_comment' => &$comment,
749  'rc_comment_text' => &$comment,
750  'rc_comment_data' => null,
751  'rc_this_oldid' => (int)$newId,
752  'rc_last_oldid' => $oldId,
753  'rc_bot' => $bot ? 1 : 0,
754  'rc_ip' => self::checkIPAddress( $ip ),
755  'rc_patrolled' => intval( $patrol ),
756  'rc_new' => 0, # obsolete
757  'rc_old_len' => $oldSize,
758  'rc_new_len' => $newSize,
759  'rc_deleted' => 0,
760  'rc_logid' => 0,
761  'rc_log_type' => null,
762  'rc_log_action' => '',
763  'rc_params' => ''
764  ];
765 
766  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
767  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
768 
769  $rc->mExtra = [
770  'prefixedDBkey' => $formatter->getPrefixedDBkey( $page ),
771  'lastTimestamp' => $lastTimestamp,
772  'oldSize' => $oldSize,
773  'newSize' => $newSize,
774  'pageStatus' => 'changed'
775  ];
776 
778  static function () use ( $rc, $tags, $editResult ) {
779  $rc->addTags( $tags );
780  $rc->setEditResult( $editResult );
781  $rc->save();
782  },
783  DeferredUpdates::POSTSEND,
785  );
786 
787  return $rc;
788  }
789 
809  public static function notifyNew(
810  $timestamp,
811  $page, $minor, $user, $comment, $bot,
812  $ip = '', $size = 0, $newId = 0, $patrol = 0, $tags = []
813  ) {
814  Assert::parameter( $page->exists(), '$page', 'must represent an existing page' );
815 
816  $rc = new RecentChange;
817  $rc->mPage = $page;
818  $rc->mPerformer = $user;
819  $rc->mAttribs = [
820  'rc_timestamp' => $timestamp,
821  'rc_namespace' => $page->getNamespace(),
822  'rc_title' => $page->getDBkey(),
823  'rc_type' => RC_NEW,
824  'rc_source' => self::SRC_NEW,
825  'rc_minor' => $minor ? 1 : 0,
826  'rc_cur_id' => $page->getId(),
827  'rc_user' => $user->getId(),
828  'rc_user_text' => $user->getName(),
829  'rc_comment' => &$comment,
830  'rc_comment_text' => &$comment,
831  'rc_comment_data' => null,
832  'rc_this_oldid' => (int)$newId,
833  'rc_last_oldid' => 0,
834  'rc_bot' => $bot ? 1 : 0,
835  'rc_ip' => self::checkIPAddress( $ip ),
836  'rc_patrolled' => intval( $patrol ),
837  'rc_new' => 1, # obsolete
838  'rc_old_len' => 0,
839  'rc_new_len' => $size,
840  'rc_deleted' => 0,
841  'rc_logid' => 0,
842  'rc_log_type' => null,
843  'rc_log_action' => '',
844  'rc_params' => ''
845  ];
846 
847  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
848  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
849 
850  $rc->mExtra = [
851  'prefixedDBkey' => $formatter->getPrefixedDBkey( $page ),
852  'lastTimestamp' => 0,
853  'oldSize' => 0,
854  'newSize' => $size,
855  'pageStatus' => 'created'
856  ];
857 
859  static function () use ( $rc, $tags ) {
860  $rc->addTags( $tags );
861  $rc->save();
862  },
863  DeferredUpdates::POSTSEND,
865  );
866 
867  return $rc;
868  }
869 
886  public static function notifyLog( $timestamp,
887  $logPage, $user, $actionComment, $ip, $type,
888  $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = ''
889  ) {
890  global $wgLogRestrictions;
891 
892  # Don't add private logs to RC!
893  if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) {
894  return false;
895  }
896  $rc = self::newLogEntry( $timestamp,
897  $logPage, $user, $actionComment, $ip, $type, $action,
898  $target, $logComment, $params, $newId, $actionCommentIRC );
899  $rc->save();
900 
901  return true;
902  }
903 
922  public static function newLogEntry( $timestamp,
923  $logPage, $user, $actionComment, $ip,
924  $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '',
925  $revId = 0, $isPatrollable = false ) {
926  global $wgRequest;
927  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
928 
929  # # Get pageStatus for email notification
930  switch ( $type . '-' . $action ) {
931  case 'delete-delete':
932  case 'delete-delete_redir':
933  case 'delete-delete_redir2':
934  $pageStatus = 'deleted';
935  break;
936  case 'move-move':
937  case 'move-move_redir':
938  $pageStatus = 'moved';
939  break;
940  case 'delete-restore':
941  $pageStatus = 'restored';
942  break;
943  case 'upload-upload':
944  $pageStatus = 'created';
945  break;
946  case 'upload-overwrite':
947  default:
948  $pageStatus = 'changed';
949  break;
950  }
951 
952  // Allow unpatrolled status for patrollable log entries
953  $canAutopatrol = $permissionManager->userHasRight( $user, 'autopatrol' );
954  $markPatrolled = $isPatrollable ? $canAutopatrol : true;
955 
956  if ( $target instanceof PageIdentity && $target->canExist() ) {
957  $pageId = $target->getId();
958  } else {
959  $pageId = 0;
960  }
961 
962  $rc = new RecentChange;
963  $rc->mPage = $target;
964  $rc->mPerformer = $user;
965  $rc->mAttribs = [
966  'rc_timestamp' => $timestamp,
967  'rc_namespace' => $target->getNamespace(),
968  'rc_title' => $target->getDBkey(),
969  'rc_type' => RC_LOG,
970  'rc_source' => self::SRC_LOG,
971  'rc_minor' => 0,
972  'rc_cur_id' => $pageId,
973  'rc_user' => $user->getId(),
974  'rc_user_text' => $user->getName(),
975  'rc_comment' => &$logComment,
976  'rc_comment_text' => &$logComment,
977  'rc_comment_data' => null,
978  'rc_this_oldid' => (int)$revId,
979  'rc_last_oldid' => 0,
980  'rc_bot' => $permissionManager->userHasRight( $user, 'bot' ) ?
981  (int)$wgRequest->getBool( 'bot', true ) : 0,
982  'rc_ip' => self::checkIPAddress( $ip ),
983  'rc_patrolled' => $markPatrolled ? self::PRC_AUTOPATROLLED : self::PRC_UNPATROLLED,
984  'rc_new' => 0, # obsolete
985  'rc_old_len' => null,
986  'rc_new_len' => null,
987  'rc_deleted' => 0,
988  'rc_logid' => $newId,
989  'rc_log_type' => $type,
990  'rc_log_action' => $action,
991  'rc_params' => $params
992  ];
993 
994  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
995  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
996 
997  $rc->mExtra = [
998  // XXX: This does not correspond to rc_namespace/rc_title/rc_cur_id.
999  // Is that intentional? For all other kinds of RC entries, prefixedDBkey
1000  // matches rc_namespace/rc_title. Do we even need $logPage?
1001  'prefixedDBkey' => $formatter->getPrefixedDBkey( $logPage ),
1002  'lastTimestamp' => 0,
1003  'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
1004  'pageStatus' => $pageStatus,
1005  'actionCommentIRC' => $actionCommentIRC
1006  ];
1007 
1008  return $rc;
1009  }
1010 
1032  public static function newForCategorization(
1033  $timestamp,
1034  PageIdentity $categoryTitle,
1035  ?UserIdentity $user,
1036  $comment,
1037  PageIdentity $pageTitle,
1038  $oldRevId,
1039  $newRevId,
1040  $lastTimestamp,
1041  $bot,
1042  $ip = '',
1043  $deleted = 0,
1044  $added = null
1045  ) {
1046  // Done in a backwards compatible way.
1047  $categoryWikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()
1048  ->newFromTitle( $categoryTitle );
1049 
1050  '@phan-var WikiCategoryPage $categoryWikiPage';
1051  $params = [
1052  'hidden-cat' => $categoryWikiPage->isHidden()
1053  ];
1054  if ( $added !== null ) {
1055  $params['added'] = $added;
1056  }
1057 
1058  if ( !$user ) {
1059  // XXX: when and why do we need this?
1060  $user = MediaWikiServices::getInstance()->getActorStore()->getUnknownActor();
1061  }
1062 
1063  $rc = new RecentChange;
1064  $rc->mPage = $categoryTitle;
1065  $rc->mPerformer = $user;
1066  $rc->mAttribs = [
1067  'rc_timestamp' => MWTimestamp::convert( TS_MW, $timestamp ),
1068  'rc_namespace' => $categoryTitle->getNamespace(),
1069  'rc_title' => $categoryTitle->getDBkey(),
1070  'rc_type' => RC_CATEGORIZE,
1071  'rc_source' => self::SRC_CATEGORIZE,
1072  'rc_minor' => 0,
1073  // XXX: rc_cur_id does not correspond to rc_namespace/rc_title.
1074  // They refer to different pages. Is that intentional?
1075  'rc_cur_id' => $pageTitle->getId(),
1076  'rc_user' => $user->getId(),
1077  'rc_user_text' => $user->getName(),
1078  'rc_comment' => &$comment,
1079  'rc_comment_text' => &$comment,
1080  'rc_comment_data' => null,
1081  'rc_this_oldid' => (int)$newRevId,
1082  'rc_last_oldid' => $oldRevId,
1083  'rc_bot' => $bot ? 1 : 0,
1084  'rc_ip' => self::checkIPAddress( $ip ),
1085  'rc_patrolled' => self::PRC_AUTOPATROLLED, // Always patrolled, just like log entries
1086  'rc_new' => 0, # obsolete
1087  'rc_old_len' => null,
1088  'rc_new_len' => null,
1089  'rc_deleted' => $deleted,
1090  'rc_logid' => 0,
1091  'rc_log_type' => null,
1092  'rc_log_action' => '',
1093  'rc_params' => serialize( $params )
1094  ];
1095 
1096  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
1097  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
1098 
1099  $rc->mExtra = [
1100  'prefixedDBkey' => $formatter->getPrefixedDBkey( $categoryTitle ),
1101  'lastTimestamp' => $lastTimestamp,
1102  'oldSize' => 0,
1103  'newSize' => 0,
1104  'pageStatus' => 'changed'
1105  ];
1106 
1107  return $rc;
1108  }
1109 
1118  public function getParam( $name ) {
1119  $params = $this->parseParams();
1120  return $params[$name] ?? null;
1121  }
1122 
1128  public function loadFromRow( $row ) {
1129  $this->mAttribs = get_object_vars( $row );
1130  $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] );
1131  // rc_deleted MUST be set
1132  $this->mAttribs['rc_deleted'] = $row->rc_deleted;
1133 
1134  if ( isset( $this->mAttribs['rc_ip'] ) ) {
1135  // Clean up CIDRs for Postgres per T164898. ("127.0.0.1" casts to "127.0.0.1/32")
1136  $n = strpos( $this->mAttribs['rc_ip'], '/' );
1137  if ( $n !== false ) {
1138  $this->mAttribs['rc_ip'] = substr( $this->mAttribs['rc_ip'], 0, $n );
1139  }
1140  }
1141 
1142  $comment = CommentStore::getStore()
1143  // Legacy because $row may have come from self::selectFields()
1144  ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $row, true )
1145  ->text;
1146  $this->mAttribs['rc_comment'] = &$comment;
1147  $this->mAttribs['rc_comment_text'] = &$comment;
1148  $this->mAttribs['rc_comment_data'] = null;
1149 
1150  $this->mPerformer = $this->getUserIdentityFromAnyId(
1151  $row->rc_user ?? null,
1152  $row->rc_user_text ?? null,
1153  $row->rc_actor ?? null
1154  );
1155  $this->mAttribs['rc_user'] = $this->mPerformer->getId();
1156  $this->mAttribs['rc_user_text'] = $this->mPerformer->getName();
1157 
1158  // Watchlist expiry.
1159  if ( isset( $row->we_expiry ) && $row->we_expiry ) {
1160  $this->watchlistExpiry = wfTimestamp( TS_MW, $row->we_expiry );
1161  }
1162  }
1163 
1170  public function getAttribute( $name ) {
1171  if ( $name === 'rc_comment' ) {
1172  return CommentStore::getStore()
1173  ->getComment( 'rc_comment', $this->mAttribs, true )->text;
1174  }
1175 
1176  if ( $name === 'rc_user' || $name === 'rc_user_text' || $name === 'rc_actor' ) {
1177  $user = $this->getPerformerIdentity();
1178 
1179  if ( $name === 'rc_user' ) {
1180  return $user->getId();
1181  }
1182  if ( $name === 'rc_user_text' ) {
1183  return $user->getName();
1184  }
1185  if ( $name === 'rc_actor' ) {
1186  // NOTE: rc_actor exists in the database, but application logic should not use it.
1187  wfDeprecatedMsg( 'Accessing deprecated field rc_actor', '1.36' );
1188  $actorStore = MediaWikiServices::getInstance()->getActorStore();
1189  $db = wfGetDB( DB_REPLICA );
1190  return $actorStore->findActorId( $user, $db );
1191  }
1192  }
1193 
1194  return $this->mAttribs[$name] ?? null;
1195  }
1196 
1200  public function getAttributes() {
1201  return $this->mAttribs;
1202  }
1203 
1210  public function diffLinkTrail( $forceCur ) {
1211  if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
1212  $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) .
1213  "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] );
1214  if ( $forceCur ) {
1215  $trail .= '&diff=0';
1216  } else {
1217  $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] );
1218  }
1219  } else {
1220  $trail = '';
1221  }
1222 
1223  return $trail;
1224  }
1225 
1233  public function getCharacterDifference( $old = 0, $new = 0 ) {
1234  if ( $old === 0 ) {
1235  $old = $this->mAttribs['rc_old_len'];
1236  }
1237  if ( $new === 0 ) {
1238  $new = $this->mAttribs['rc_new_len'];
1239  }
1240  if ( $old === null || $new === null ) {
1241  return '';
1242  }
1243 
1244  return ChangesList::showCharacterDifference( $old, $new );
1245  }
1246 
1247  private static function checkIPAddress( $ip ) {
1248  global $wgRequest;
1249  if ( $ip ) {
1250  if ( !IPUtils::isIPAddress( $ip ) ) {
1251  throw new MWException( "Attempt to write \"" . $ip .
1252  "\" as an IP address into recent changes" );
1253  }
1254  } else {
1255  $ip = $wgRequest->getIP();
1256  if ( !$ip ) {
1257  $ip = '';
1258  }
1259  }
1260 
1261  return $ip;
1262  }
1263 
1273  public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
1274  global $wgRCMaxAge;
1275 
1276  return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge;
1277  }
1278 
1286  public function parseParams() {
1287  $rcParams = $this->getAttribute( 'rc_params' );
1288 
1289  Wikimedia\suppressWarnings();
1290  $unserializedParams = unserialize( $rcParams );
1291  Wikimedia\restoreWarnings();
1292 
1293  return $unserializedParams;
1294  }
1295 
1304  public function addTags( $tags ) {
1305  if ( is_string( $tags ) ) {
1306  $this->tags[] = $tags;
1307  } else {
1308  $this->tags = array_merge( $tags, $this->tags );
1309  }
1310  }
1311 
1319  public function setEditResult( ?EditResult $editResult ) {
1320  $this->editResult = $editResult;
1321  }
1322 
1330  private function getUserIdentityFromAnyId(
1331  $userId,
1332  $userName,
1333  $actorId = null
1334  ): UserIdentity {
1335  // XXX: Is this logic needed elsewhere? Should it be reusable?
1336 
1337  $userId = isset( $userId ) ? (int)$userId : null;
1338  $actorId = isset( $actorId ) ? (int)$actorId : 0;
1339 
1340  $actorStore = MediaWikiServices::getInstance()->getActorStore();
1341  if ( $userName && $actorId ) {
1342  // Likely the fields are coming from a join on actor table,
1343  // so can definitely build a UserIdentityValue.
1344  return $actorStore->newActorFromRowFields( $userId, $userName, $actorId );
1345  }
1346  if ( $userId !== null ) {
1347  if ( $userName !== null ) {
1348  // NOTE: For IPs and external users, $userId will be 0.
1349  $user = new UserIdentityValue( $userId, $userName );
1350  } else {
1351  $user = $actorStore->getUserIdentityByUserId( $userId );
1352 
1353  if ( !$user ) {
1354  throw new RuntimeException( "User not found by ID: $userId" );
1355  }
1356  }
1357  } elseif ( $actorId > 0 ) {
1358  $db = wfGetDB( DB_REPLICA );
1359  $user = $actorStore->getActorById( $actorId, $db );
1360 
1361  if ( !$user ) {
1362  throw new RuntimeException( "User not found by actor ID: $actorId" );
1363  }
1364  } elseif ( $userName !== null ) {
1365  $user = $actorStore->getUserIdentityByName( $userName );
1366 
1367  if ( !$user ) {
1368  throw new RuntimeException( "User not found by name: $userName" );
1369  }
1370  } else {
1371  throw new RuntimeException( 'At least one of user ID, actor ID or user name must be given' );
1372  }
1373 
1374  return $user;
1375  }
1376 }
RecentChange\getCharacterDifference
getCharacterDifference( $old=0, $new=0)
Returns the change size (HTML).
Definition: RecentChange.php:1233
MediaWiki\User\UserIdentityValue
Value object representing a user's identity.
Definition: UserIdentityValue.php:35
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
RecentChange\save
save( $send=self::SEND_FEED)
Writes the data in this object to the database.
Definition: RecentChange.php:408
RecentChange\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
Definition: RecentChange.php:260
RecentChange\$editResult
EditResult null $editResult
EditResult associated with the edit.
Definition: RecentChange.php:140
RecentChange\newLogEntry
static newLogEntry( $timestamp, $logPage, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='', $revId=0, $isPatrollable=false)
Definition: RecentChange.php:922
RecentChange\notifyEdit
static notifyEdit( $timestamp, $page, $minor, $user, $comment, $oldId, $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0, $tags=[], EditResult $editResult=null)
Makes an entry in the database corresponding to an edit.
Definition: RecentChange.php:728
RecentChange\$tags
array $tags
List of tags to apply.
Definition: RecentChange.php:135
RC_EDIT
const RC_EDIT
Definition: Defines.php:115
RecentChange\setEditResult
setEditResult(?EditResult $editResult)
Sets the EditResult associated with the edit.
Definition: RecentChange.php:1319
RecentChange\setExtra
setExtra( $extra)
Definition: RecentChange.php:324
RecentChange\getUserIdentityFromAnyId
getUserIdentityFromAnyId( $userId, $userName, $actorId=null)
Definition: RecentChange.php:1330
RecentChange\$mPage
PageReference null $mPage
Definition: RecentChange.php:112
SpecialRecentChangesLinked
This is to display changes made to all articles linked in an article.
Definition: SpecialRecentChangesLinked.php:32
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:180
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:229
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:8105
RecentChange\getAttributes
getAttributes()
Definition: RecentChange.php:1200
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:80
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1692
RecentChange\getEngine
static getEngine( $uri, $params=[])
Definition: RecentChange.php:596
RecentChange\loadFromRow
loadFromRow( $row)
Initialises the members of this object from a mysql row object.
Definition: RecentChange.php:1128
RecentChange\getChangeTypes
static getChangeTypes()
Get an array of all change types.
Definition: RecentChange.php:206
Page\PageIdentity\getId
getId( $wikiId=self::LOCAL)
Returns the page ID.
RecentChange\reallyMarkPatrolled
reallyMarkPatrolled()
Mark this RecentChange patrolled, without error checking.
Definition: RecentChange.php:677
RecentChange\$counter
int $counter
Line number of recent change.
Definition: RecentChange.php:130
RecentChange\setAttribs
setAttribs( $attribs)
Definition: RecentChange.php:317
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:669
MediaWiki\User\UserIdentity\getId
getId( $wikiId=self::LOCAL)
RecentChange\getPerformerIdentity
getPerformerIdentity()
Get the UserIdentity of the client that performed this change.
Definition: RecentChange.php:387
MediaWiki\ChangeTags\Taggable
Interface that defines how to tag objects.
Definition: Taggable.php:32
RecentChange\SRC_CATEGORIZE
const SRC_CATEGORIZE
Definition: RecentChange.php:89
RecentChange\parseToRCType
static parseToRCType( $type)
Parsing text to RC_* constants.
Definition: RecentChange.php:173
RC_NEW
const RC_NEW
Definition: Defines.php:116
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:7998
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:8014
MediaWiki\Permissions\Authority\getUser
getUser()
Returns the performer of the actions associated with this authority.
RecentChange\SRC_LOG
const SRC_LOG
Definition: RecentChange.php:87
RecentChangesUpdateJob\newCacheUpdateJob
static newCacheUpdateJob()
Definition: RecentChangesUpdateJob.php:54
Page\PageReference
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
Definition: PageReference.php:49
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
RecentChange\getTitle
getTitle()
Definition: RecentChange.php:332
RecentChange\parseParams
parseParams()
Parses and returns the rc_params attribute.
Definition: RecentChange.php:1286
$wgPutIPinRC
$wgPutIPinRC
Log IP addresses in the recentchanges table; can be accessed only by extensions (e....
Definition: DefaultSettings.php:6578
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:53
RC_LOG
const RC_LOG
Definition: Defines.php:117
FormatJson\encode
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:115
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1028
MWException
MediaWiki exception.
Definition: MWException.php:29
deprecatePublicPropertyFallback
deprecatePublicPropertyFallback(string $property, string $version, callable $getter, ?callable $setter=null, $class=null, $component=null)
Mark a removed public property as deprecated and provide fallback getter and setter callables.
Definition: DeprecationHelper.php:116
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:996
Page\PageReference\getNamespace
getNamespace()
Returns the page's namespace number.
RecentChange\$notificationtimestamp
$notificationtimestamp
Definition: RecentChange.php:120
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2306
RecentChange\$mPerformer
UserIdentity null $mPerformer
Definition: RecentChange.php:117
RecentChange\$watchlistExpiry
string null $watchlistExpiry
The expiry time, if this is a temporary watchlist item.
Definition: RecentChange.php:125
$wgRCFeeds
$wgRCFeeds
Configuration for feeds to which notifications about recent changes will be sent.
Definition: DefaultSettings.php:7965
RecentChange\newFromRow
static newFromRow( $row)
Definition: RecentChange.php:159
PatrolLog\record
static record( $rc, $auto, UserIdentity $user, $tags=null)
Record a log event for a change being patrolled.
Definition: PatrolLog.php:44
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:38
RecentChange\SRC_EDIT
const SRC_EDIT
Definition: RecentChange.php:85
RecentChange\getPage
getPage()
Definition: RecentChange.php:341
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:650
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:1210
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
MediaWiki\Permissions\Authority\authorizeWrite
authorizeWrite(string $action, PageIdentity $target, PermissionStatus $status=null)
Authorize write access.
RecentChange\SRC_NEW
const SRC_NEW
Definition: RecentChange.php:86
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:8025
RecentChange\notifyLog
static notifyLog( $timestamp, $logPage, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='')
Definition: RecentChange.php:886
MediaWiki\Storage\EditResult
Object for storing information about the effects of an edit.
Definition: EditResult.php:38
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:35
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:92
RecentChange\newFromId
static newFromId( $rcid)
Obtain the recent change with a given rc_id value.
Definition: RecentChange.php:216
RecentChange\$mExtra
$mExtra
Definition: RecentChange.php:107
RC_EXTERNAL
const RC_EXTERNAL
Definition: Defines.php:118
Page\PageReference\getDBkey
getDBkey()
Get the page title in DB key form.
$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:8879
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
$wgRCMaxAge
$wgRCMaxAge
Recentchanges items are periodically purged; entries older than this many seconds will go.
Definition: DefaultSettings.php:7865
ChangesList\showCharacterDifference
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
Definition: ChangesList.php:336
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:93
RecentChange\addTags
addTags( $tags)
Tags to append to the recent change, and associated revision/log.
Definition: RecentChange.php:1304
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:34
RecentChange\getAttribute
getAttribute( $name)
Get an attribute value.
Definition: RecentChange.php:1170
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:446
RecentChange\checkIPAddress
static checkIPAddress( $ip)
Definition: RecentChange.php:1247
unserialize
unserialize( $serialized)
Definition: ApiMessageTrait.php:146
RecentChange\newForCategorization
static newForCategorization( $timestamp, PageIdentity $categoryTitle, ?UserIdentity $user, $comment, PageIdentity $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:1032
Title
Represents a title within MediaWiki.
Definition: Title.php:49
MediaWiki\Permissions\Authority\isAllowed
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:114
RC_CATEGORIZE
const RC_CATEGORIZE
Definition: Defines.php:119
RecentChange\parseFromRCType
static parseFromRCType( $rcType)
Parsing RC_* constants to human-readable test.
Definition: RecentChange.php:195
$wgRCEngines
$wgRCEngines
Used by RecentChange::getEngine to find the correct engine for a given URI scheme.
Definition: DefaultSettings.php:7972
Page\PageReferenceValue
Immutable value object representing a page reference.
Definition: PageReferenceValue.php:42
RecentChange\notifyNew
static notifyNew( $timestamp, $page, $minor, $user, $comment, $bot, $ip='', $size=0, $newId=0, $patrol=0, $tags=[])
Makes an entry in the database corresponding to page creation.
Definition: RecentChange.php:809
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:91
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:1273
RecentChangesUpdateJob\newPurgeJob
static newPurgeJob()
Definition: RecentChangesUpdateJob.php:44
RecentChange\doMarkPatrolled
doMarkPatrolled(Authority $performer, $auto=false, $tags=null)
Mark this RecentChange as patrolled.
Definition: RecentChange.php:623
Title\castFromPageReference
static castFromPageReference(?PageReference $pageReference)
Return a Title for a given Reference.
Definition: Title.php:347
RecentChange\notifyRCFeeds
notifyRCFeeds(array $feeds=null)
Notify all the feeds about the change.
Definition: RecentChange.php:553
$t
$t
Definition: testCompression.php:74
RecentChange\$mAttribs
array $mAttribs
Definition: RecentChange.php:106
RecentChange\getPerformer
getPerformer()
Get the User object of the person who performed this change.
Definition: RecentChange.php:371
EmailNotification
This module processes the email notifications when the current page is changed.
Definition: EmailNotification.php:53
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:680
RecentChange\__construct
__construct()
Definition: RecentChange.php:299
DeprecationHelper
trait DeprecationHelper
Use this trait in classes which have properties for which public access is deprecated or implementati...
Definition: DeprecationHelper.php:60
Page\PageIdentity\canExist
canExist()
Checks whether this PageIdentity represents a "proper" page, meaning that it could exist as an editab...
CommentStore\getStore
static getStore()
Definition: CommentStore.php:120
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:67
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
Definition: DeferredUpdates.php:145
RecentChange\getParam
getParam( $name)
Get a parameter value.
Definition: RecentChange.php:1118
RecentChange\$numberofWatchingusers
$numberofWatchingusers
Definition: RecentChange.php:119
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:328
RecentChange\SRC_EXTERNAL
const SRC_EXTERNAL
Definition: RecentChange.php:88
$type
$type
Definition: testCompression.php:52