MediaWiki  master
RecentChange.php
Go to the documentation of this file.
1 <?php
33 use Wikimedia\Assert\Assert;
34 use Wikimedia\IPUtils;
35 
81 class RecentChange implements Taggable {
83 
84  // Constants for the rc_source field. Extensions may also have
85  // their own source constants.
86  public const SRC_EDIT = 'mw.edit';
87  public const SRC_NEW = 'mw.new';
88  public const SRC_LOG = 'mw.log';
89  public const SRC_EXTERNAL = 'mw.external'; // obsolete
90  public const SRC_CATEGORIZE = 'mw.categorize';
91 
92  public const PRC_UNPATROLLED = 0;
93  public const PRC_PATROLLED = 1;
94  public const PRC_AUTOPATROLLED = 2;
95 
99  public const SEND_NONE = true;
100 
104  public const SEND_FEED = false;
105 
107  public $mAttribs = [];
108  public $mExtra = [];
109 
113  private $mPage = null;
114 
118  private $mPerformer = null;
119 
120  public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked
122 
127 
131  public $counter = -1;
132 
136  private $tags = [];
137 
141  private $editResult = null;
142 
143  private const CHANGE_TYPES = [
144  'edit' => RC_EDIT,
145  'new' => RC_NEW,
146  'log' => RC_LOG,
147  'external' => RC_EXTERNAL,
148  'categorize' => RC_CATEGORIZE,
149  ];
150 
151  # Factory methods
152 
157  public static function newFromRow( $row ) {
158  $rc = new RecentChange;
159  $rc->loadFromRow( $row );
160 
161  return $rc;
162  }
163 
171  public static function parseToRCType( $type ) {
172  if ( is_array( $type ) ) {
173  $retval = [];
174  foreach ( $type as $t ) {
175  $retval[] = self::parseToRCType( $t );
176  }
177 
178  return $retval;
179  }
180 
181  if ( !array_key_exists( $type, self::CHANGE_TYPES ) ) {
182  throw new MWException( "Unknown type '$type'" );
183  }
184  return self::CHANGE_TYPES[$type];
185  }
186 
193  public static function parseFromRCType( $rcType ) {
194  return array_search( $rcType, self::CHANGE_TYPES, true ) ?: "$rcType";
195  }
196 
204  public static function getChangeTypes() {
205  return array_keys( self::CHANGE_TYPES );
206  }
207 
214  public static function newFromId( $rcid ) {
215  return self::newFromConds( [ 'rc_id' => $rcid ], __METHOD__ );
216  }
217 
227  public static function newFromConds(
228  $conds,
229  $fname = __METHOD__,
230  $dbType = DB_REPLICA
231  ) {
232  $db = wfGetDB( $dbType );
233  $rcQuery = self::getQueryInfo();
234  $row = $db->selectRow(
235  $rcQuery['tables'], $rcQuery['fields'], $conds, $fname, [], $rcQuery['joins']
236  );
237  if ( $row !== false ) {
238  return self::newFromRow( $row );
239  } else {
240  return null;
241  }
242  }
243 
258  public static function getQueryInfo() {
259  $commentQuery = CommentStore::getStore()->getJoin( 'rc_comment' );
260  return [
261  'tables' => [
262  'recentchanges',
263  'recentchanges_actor' => 'actor'
264  ] + $commentQuery['tables'],
265  'fields' => [
266  'rc_id',
267  'rc_timestamp',
268  'rc_namespace',
269  'rc_title',
270  'rc_minor',
271  'rc_bot',
272  'rc_new',
273  'rc_cur_id',
274  'rc_this_oldid',
275  'rc_last_oldid',
276  'rc_type',
277  'rc_source',
278  'rc_patrolled',
279  'rc_ip',
280  'rc_old_len',
281  'rc_new_len',
282  'rc_deleted',
283  'rc_logid',
284  'rc_log_type',
285  'rc_log_action',
286  'rc_params',
287  'rc_actor',
288  'rc_user' => 'recentchanges_actor.actor_user',
289  'rc_user_text' => 'recentchanges_actor.actor_name',
290  ] + $commentQuery['fields'],
291  'joins' => [
292  'recentchanges_actor' => [ 'JOIN', 'actor_id=rc_actor' ]
293  ] + $commentQuery['joins'],
294  ];
295  }
296 
297  public function __construct() {
299  'mTitle',
300  '1.37',
301  function () {
302  return Title::castFromPageReference( $this->mPage );
303  },
304  function ( ?Title $title ) {
305  $this->mPage = $title;
306  }
307  );
308  }
309 
310  # Accessors
311 
315  public function setAttribs( $attribs ) {
316  $this->mAttribs = $attribs;
317  }
318 
322  public function setExtra( $extra ) {
323  $this->mExtra = $extra;
324  }
325 
330  public function getTitle() {
331  $this->mPage = Title::castFromPageReference( $this->getPage() );
332  return $this->mPage ?: Title::makeTitle( NS_SPECIAL, 'BadTitle' );
333  }
334 
339  public function getPage(): ?PageReference {
340  if ( !$this->mPage ) {
341  // NOTE: As per the 1.36 release, we always provide rc_title,
342  // even in cases where it doesn't really make sense.
343  // In the future, rc_title may be nullable, or we may use
344  // empty strings in entries that do not refer to a page.
345  if ( ( $this->mAttribs['rc_title'] ?? '' ) === '' ) {
346  return null;
347  }
348 
349  // XXX: We could use rc_cur_id to create a PageIdentityValue,
350  // at least if it's not a special page.
351  // However, newForCategorization() puts the ID of the categorized page into
352  // rc_cur_id, but the title of the category page into rc_title.
353  $this->mPage = new PageReferenceValue(
354  (int)$this->mAttribs['rc_namespace'],
355  $this->mAttribs['rc_title'],
356  PageReference::LOCAL
357  );
358  }
359 
360  return $this->mPage;
361  }
362 
369  public function getPerformer(): User {
370  wfDeprecated( __METHOD__, '1.36' );
371  if ( !$this->mPerformer instanceof User ) {
372  $this->mPerformer = User::newFromIdentity( $this->getPerformerIdentity() );
373  }
374 
375  return $this->mPerformer;
376  }
377 
385  public function getPerformerIdentity(): UserIdentity {
386  if ( !$this->mPerformer ) {
387  $this->mPerformer = $this->getUserIdentityFromAnyId(
388  $this->mAttribs['rc_user'] ?? null,
389  $this->mAttribs['rc_user_text'] ?? null,
390  $this->mAttribs['rc_actor'] ?? null
391  );
392  }
393 
394  return $this->mPerformer;
395  }
396 
406  public function save( $send = self::SEND_FEED ) {
407  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
408  $putIPinRC = $mainConfig->get( 'PutIPinRC' );
409  $useEnotif = $mainConfig->get( 'UseEnotif' );
410  $showUpdatedMarker = $mainConfig->get( 'ShowUpdatedMarker' );
411  $dbw = wfGetDB( DB_PRIMARY );
412  if ( !is_array( $this->mExtra ) ) {
413  $this->mExtra = [];
414  }
415 
416  if ( !$putIPinRC ) {
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 ( $useEnotif || $showUpdatedMarker ) {
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  $rcFeeds = MediaWikiServices::getInstance()->getMainConfig()->get( 'RCFeeds' );
555  if ( $feeds === null ) {
556  $feeds = $rcFeeds;
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  $rcEngines = MediaWikiServices::getInstance()->getMainConfig()->get( 'RCEngines' );
599  $scheme = parse_url( $uri, PHP_URL_SCHEME );
600  if ( !$scheme ) {
601  throw new MWException( "Invalid RCFeed uri: '$uri'" );
602  }
603  if ( !isset( $rcEngines[$scheme] ) ) {
604  throw new MWException( "Unknown RCFeedEngine scheme: '$scheme'" );
605  }
606  if ( defined( 'MW_PHPUNIT_TEST' ) && is_object( $rcEngines[$scheme] ) ) {
607  return $rcEngines[$scheme];
608  }
609  // TODO For non test a object could be here?
610  return new $rcEngines[$scheme]( $params );
611  }
612 
624  public function doMarkPatrolled( Authority $performer, $auto = false, $tags = null ) {
625  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
626  $useRCPatrol = $mainConfig->get( 'UseRCPatrol' );
627  $useNPPatrol = $mainConfig->get( 'UseNPPatrol' );
628  $useFilePatrol = $mainConfig->get( 'UseFilePatrol' );
629  // Fix up $tags so that the MarkPatrolled hook below always gets an array
630  if ( $tags === null ) {
631  $tags = [];
632  } elseif ( is_string( $tags ) ) {
633  $tags = [ $tags ];
634  }
635 
636  $status = PermissionStatus::newEmpty();
637  // If recentchanges patrol is disabled, only new pages or new file versions
638  // can be patrolled, provided the appropriate config variable is set
639  if ( !$useRCPatrol && ( !$useNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) &&
640  ( !$useFilePatrol || !( $this->getAttribute( 'rc_type' ) == RC_LOG &&
641  $this->getAttribute( 'rc_log_type' ) == 'upload' ) ) ) {
642  $status->fatal( 'rcpatroldisabled' );
643  }
644  // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
645  $performer->authorizeWrite( $auto ? 'autopatrol' : 'patrol', $this->getTitle(), $status );
646  $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
647  if ( !Hooks::runner()->onMarkPatrolled(
648  $this->getAttribute( 'rc_id' ), $user, false, $auto, $tags )
649  ) {
650  $status->fatal( 'hookaborted' );
651  }
652  // Users without the 'autopatrol' right can't patrol their own revisions
653  if ( $performer->getUser()->getName() === $this->getAttribute( 'rc_user_text' ) &&
654  !$performer->isAllowed( 'autopatrol' )
655  ) {
656  $status->fatal( 'markedaspatrollederror-noautopatrol' );
657  }
658  if ( !$status->isGood() ) {
659  return $status->toLegacyErrorArray();
660  }
661  // If the change was patrolled already, do nothing
662  if ( $this->getAttribute( 'rc_patrolled' ) ) {
663  return [];
664  }
665  // Actually set the 'patrolled' flag in RC
666  $this->reallyMarkPatrolled();
667  // Log this patrol event
668  PatrolLog::record( $this, $auto, $performer->getUser(), $tags );
669 
670  Hooks::runner()->onMarkPatrolledComplete(
671  $this->getAttribute( 'rc_id' ), $user, false, $auto );
672 
673  return [];
674  }
675 
680  public function reallyMarkPatrolled() {
681  $dbw = wfGetDB( DB_PRIMARY );
682  $dbw->update(
683  'recentchanges',
684  [
685  'rc_patrolled' => self::PRC_PATROLLED
686  ],
687  [
688  'rc_id' => $this->getAttribute( 'rc_id' )
689  ],
690  __METHOD__
691  );
692  // Invalidate the page cache after the page has been patrolled
693  // to make sure that the Patrol link isn't visible any longer!
694  $this->getTitle()->invalidateCache();
695 
696  // Enqueue a reverted tag update (in case the edit was a revert)
697  $revisionId = $this->getAttribute( 'rc_this_oldid' );
698  if ( $revisionId ) {
699  $revertedTagUpdateManager =
700  MediaWikiServices::getInstance()->getRevertedTagUpdateManager();
701  $revertedTagUpdateManager->approveRevertedTagForRevision( $revisionId );
702  }
703 
704  return $dbw->affectedRows();
705  }
706 
731  public static function notifyEdit(
732  $timestamp, $page, $minor, $user, $comment, $oldId, $lastTimestamp,
733  $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0,
734  $tags = [], EditResult $editResult = null
735  ) {
736  Assert::parameter( $page->exists(), '$page', 'must represent an existing page' );
737 
738  $rc = new RecentChange;
739  $rc->mPage = $page;
740  $rc->mPerformer = $user;
741  $rc->mAttribs = [
742  'rc_timestamp' => $timestamp,
743  'rc_namespace' => $page->getNamespace(),
744  'rc_title' => $page->getDBkey(),
745  'rc_type' => RC_EDIT,
746  'rc_source' => self::SRC_EDIT,
747  'rc_minor' => $minor ? 1 : 0,
748  'rc_cur_id' => $page->getId(),
749  'rc_user' => $user->getId(),
750  'rc_user_text' => $user->getName(),
751  'rc_comment' => &$comment,
752  'rc_comment_text' => &$comment,
753  'rc_comment_data' => null,
754  'rc_this_oldid' => (int)$newId,
755  'rc_last_oldid' => $oldId,
756  'rc_bot' => $bot ? 1 : 0,
757  'rc_ip' => self::checkIPAddress( $ip ),
758  'rc_patrolled' => intval( $patrol ),
759  'rc_new' => 0, # obsolete
760  'rc_old_len' => $oldSize,
761  'rc_new_len' => $newSize,
762  'rc_deleted' => 0,
763  'rc_logid' => 0,
764  'rc_log_type' => null,
765  'rc_log_action' => '',
766  'rc_params' => ''
767  ];
768 
769  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
770  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
771 
772  $rc->mExtra = [
773  'prefixedDBkey' => $formatter->getPrefixedDBkey( $page ),
774  'lastTimestamp' => $lastTimestamp,
775  'oldSize' => $oldSize,
776  'newSize' => $newSize,
777  'pageStatus' => 'changed'
778  ];
779 
781  static function () use ( $rc, $tags, $editResult ) {
782  $rc->addTags( $tags );
783  $rc->setEditResult( $editResult );
784  $rc->save();
785  },
786  DeferredUpdates::POSTSEND,
788  );
789 
790  return $rc;
791  }
792 
812  public static function notifyNew(
813  $timestamp,
814  $page, $minor, $user, $comment, $bot,
815  $ip = '', $size = 0, $newId = 0, $patrol = 0, $tags = []
816  ) {
817  Assert::parameter( $page->exists(), '$page', 'must represent an existing page' );
818 
819  $rc = new RecentChange;
820  $rc->mPage = $page;
821  $rc->mPerformer = $user;
822  $rc->mAttribs = [
823  'rc_timestamp' => $timestamp,
824  'rc_namespace' => $page->getNamespace(),
825  'rc_title' => $page->getDBkey(),
826  'rc_type' => RC_NEW,
827  'rc_source' => self::SRC_NEW,
828  'rc_minor' => $minor ? 1 : 0,
829  'rc_cur_id' => $page->getId(),
830  'rc_user' => $user->getId(),
831  'rc_user_text' => $user->getName(),
832  'rc_comment' => &$comment,
833  'rc_comment_text' => &$comment,
834  'rc_comment_data' => null,
835  'rc_this_oldid' => (int)$newId,
836  'rc_last_oldid' => 0,
837  'rc_bot' => $bot ? 1 : 0,
838  'rc_ip' => self::checkIPAddress( $ip ),
839  'rc_patrolled' => intval( $patrol ),
840  'rc_new' => 1, # obsolete
841  'rc_old_len' => 0,
842  'rc_new_len' => $size,
843  'rc_deleted' => 0,
844  'rc_logid' => 0,
845  'rc_log_type' => null,
846  'rc_log_action' => '',
847  'rc_params' => ''
848  ];
849 
850  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
851  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
852 
853  $rc->mExtra = [
854  'prefixedDBkey' => $formatter->getPrefixedDBkey( $page ),
855  'lastTimestamp' => 0,
856  'oldSize' => 0,
857  'newSize' => $size,
858  'pageStatus' => 'created'
859  ];
860 
862  static function () use ( $rc, $tags ) {
863  $rc->addTags( $tags );
864  $rc->save();
865  },
866  DeferredUpdates::POSTSEND,
868  );
869 
870  return $rc;
871  }
872 
889  public static function notifyLog( $timestamp,
890  $logPage, $user, $actionComment, $ip, $type,
891  $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = ''
892  ) {
893  $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( 'LogRestrictions' );
894 
895  # Don't add private logs to RC!
896  if ( isset( $logRestrictions[$type] ) && $logRestrictions[$type] != '*' ) {
897  return false;
898  }
899  $rc = self::newLogEntry( $timestamp,
900  $logPage, $user, $actionComment, $ip, $type, $action,
901  $target, $logComment, $params, $newId, $actionCommentIRC );
902  $rc->save();
903 
904  return true;
905  }
906 
925  public static function newLogEntry( $timestamp,
926  $logPage, $user, $actionComment, $ip,
927  $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '',
928  $revId = 0, $isPatrollable = false ) {
929  global $wgRequest;
930  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
931 
932  # # Get pageStatus for email notification
933  switch ( $type . '-' . $action ) {
934  case 'delete-delete':
935  case 'delete-delete_redir':
936  case 'delete-delete_redir2':
937  $pageStatus = 'deleted';
938  break;
939  case 'move-move':
940  case 'move-move_redir':
941  $pageStatus = 'moved';
942  break;
943  case 'delete-restore':
944  $pageStatus = 'restored';
945  break;
946  case 'upload-upload':
947  $pageStatus = 'created';
948  break;
949  case 'upload-overwrite':
950  default:
951  $pageStatus = 'changed';
952  break;
953  }
954 
955  // Allow unpatrolled status for patrollable log entries
956  $canAutopatrol = $permissionManager->userHasRight( $user, 'autopatrol' );
957  $markPatrolled = $isPatrollable ? $canAutopatrol : true;
958 
959  if ( $target instanceof PageIdentity && $target->canExist() ) {
960  $pageId = $target->getId();
961  } else {
962  $pageId = 0;
963  }
964 
965  $rc = new RecentChange;
966  $rc->mPage = $target;
967  $rc->mPerformer = $user;
968  $rc->mAttribs = [
969  'rc_timestamp' => $timestamp,
970  'rc_namespace' => $target->getNamespace(),
971  'rc_title' => $target->getDBkey(),
972  'rc_type' => RC_LOG,
973  'rc_source' => self::SRC_LOG,
974  'rc_minor' => 0,
975  'rc_cur_id' => $pageId,
976  'rc_user' => $user->getId(),
977  'rc_user_text' => $user->getName(),
978  'rc_comment' => &$logComment,
979  'rc_comment_text' => &$logComment,
980  'rc_comment_data' => null,
981  'rc_this_oldid' => (int)$revId,
982  'rc_last_oldid' => 0,
983  'rc_bot' => $permissionManager->userHasRight( $user, 'bot' ) ?
984  (int)$wgRequest->getBool( 'bot', true ) : 0,
985  'rc_ip' => self::checkIPAddress( $ip ),
986  'rc_patrolled' => $markPatrolled ? self::PRC_AUTOPATROLLED : self::PRC_UNPATROLLED,
987  'rc_new' => 0, # obsolete
988  'rc_old_len' => null,
989  'rc_new_len' => null,
990  'rc_deleted' => 0,
991  'rc_logid' => $newId,
992  'rc_log_type' => $type,
993  'rc_log_action' => $action,
994  'rc_params' => $params
995  ];
996 
997  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
998  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
999 
1000  $rc->mExtra = [
1001  // XXX: This does not correspond to rc_namespace/rc_title/rc_cur_id.
1002  // Is that intentional? For all other kinds of RC entries, prefixedDBkey
1003  // matches rc_namespace/rc_title. Do we even need $logPage?
1004  'prefixedDBkey' => $formatter->getPrefixedDBkey( $logPage ),
1005  'lastTimestamp' => 0,
1006  'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
1007  'pageStatus' => $pageStatus,
1008  'actionCommentIRC' => $actionCommentIRC
1009  ];
1010 
1011  return $rc;
1012  }
1013 
1035  public static function newForCategorization(
1036  $timestamp,
1037  PageIdentity $categoryTitle,
1038  ?UserIdentity $user,
1039  $comment,
1040  PageIdentity $pageTitle,
1041  $oldRevId,
1042  $newRevId,
1043  $lastTimestamp,
1044  $bot,
1045  $ip = '',
1046  $deleted = 0,
1047  $added = null
1048  ) {
1049  // Done in a backwards compatible way.
1050  $categoryWikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()
1051  ->newFromTitle( $categoryTitle );
1052 
1053  '@phan-var WikiCategoryPage $categoryWikiPage';
1054  $params = [
1055  'hidden-cat' => $categoryWikiPage->isHidden()
1056  ];
1057  if ( $added !== null ) {
1058  $params['added'] = $added;
1059  }
1060 
1061  if ( !$user ) {
1062  // XXX: when and why do we need this?
1063  $user = MediaWikiServices::getInstance()->getActorStore()->getUnknownActor();
1064  }
1065 
1066  $rc = new RecentChange;
1067  $rc->mPage = $categoryTitle;
1068  $rc->mPerformer = $user;
1069  $rc->mAttribs = [
1070  'rc_timestamp' => MWTimestamp::convert( TS_MW, $timestamp ),
1071  'rc_namespace' => $categoryTitle->getNamespace(),
1072  'rc_title' => $categoryTitle->getDBkey(),
1073  'rc_type' => RC_CATEGORIZE,
1074  'rc_source' => self::SRC_CATEGORIZE,
1075  'rc_minor' => 0,
1076  // XXX: rc_cur_id does not correspond to rc_namespace/rc_title.
1077  // They refer to different pages. Is that intentional?
1078  'rc_cur_id' => $pageTitle->getId(),
1079  'rc_user' => $user->getId(),
1080  'rc_user_text' => $user->getName(),
1081  'rc_comment' => &$comment,
1082  'rc_comment_text' => &$comment,
1083  'rc_comment_data' => null,
1084  'rc_this_oldid' => (int)$newRevId,
1085  'rc_last_oldid' => $oldRevId,
1086  'rc_bot' => $bot ? 1 : 0,
1087  'rc_ip' => self::checkIPAddress( $ip ),
1088  'rc_patrolled' => self::PRC_AUTOPATROLLED, // Always patrolled, just like log entries
1089  'rc_new' => 0, # obsolete
1090  'rc_old_len' => null,
1091  'rc_new_len' => null,
1092  'rc_deleted' => $deleted,
1093  'rc_logid' => 0,
1094  'rc_log_type' => null,
1095  'rc_log_action' => '',
1096  'rc_params' => serialize( $params )
1097  ];
1098 
1099  // TODO: deprecate the 'prefixedDBkey' entry, let callers do the formatting.
1100  $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
1101 
1102  $rc->mExtra = [
1103  'prefixedDBkey' => $formatter->getPrefixedDBkey( $categoryTitle ),
1104  'lastTimestamp' => $lastTimestamp,
1105  'oldSize' => 0,
1106  'newSize' => 0,
1107  'pageStatus' => 'changed'
1108  ];
1109 
1110  return $rc;
1111  }
1112 
1121  public function getParam( $name ) {
1122  $params = $this->parseParams();
1123  return $params[$name] ?? null;
1124  }
1125 
1131  public function loadFromRow( $row ) {
1132  $this->mAttribs = get_object_vars( $row );
1133  $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] );
1134  // rc_deleted MUST be set
1135  $this->mAttribs['rc_deleted'] = $row->rc_deleted;
1136 
1137  if ( isset( $this->mAttribs['rc_ip'] ) ) {
1138  // Clean up CIDRs for Postgres per T164898. ("127.0.0.1" casts to "127.0.0.1/32")
1139  $n = strpos( $this->mAttribs['rc_ip'], '/' );
1140  if ( $n !== false ) {
1141  $this->mAttribs['rc_ip'] = substr( $this->mAttribs['rc_ip'], 0, $n );
1142  }
1143  }
1144 
1145  $comment = CommentStore::getStore()
1146  // Legacy because $row may have come from self::selectFields()
1147  ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $row, true )
1148  ->text;
1149  $this->mAttribs['rc_comment'] = &$comment;
1150  $this->mAttribs['rc_comment_text'] = &$comment;
1151  $this->mAttribs['rc_comment_data'] = null;
1152 
1153  $this->mPerformer = $this->getUserIdentityFromAnyId(
1154  $row->rc_user ?? null,
1155  $row->rc_user_text ?? null,
1156  $row->rc_actor ?? null
1157  );
1158  $this->mAttribs['rc_user'] = $this->mPerformer->getId();
1159  $this->mAttribs['rc_user_text'] = $this->mPerformer->getName();
1160 
1161  // Watchlist expiry.
1162  if ( isset( $row->we_expiry ) && $row->we_expiry ) {
1163  $this->watchlistExpiry = wfTimestamp( TS_MW, $row->we_expiry );
1164  }
1165  }
1166 
1173  public function getAttribute( $name ) {
1174  if ( $name === 'rc_comment' ) {
1175  // @phan-suppress-next-line SecurityCheck-DoubleEscaped
1176  return CommentStore::getStore()
1177  ->getComment( 'rc_comment', $this->mAttribs, true )->text;
1178  }
1179 
1180  if ( $name === 'rc_user' || $name === 'rc_user_text' || $name === 'rc_actor' ) {
1181  $user = $this->getPerformerIdentity();
1182 
1183  if ( $name === 'rc_user' ) {
1184  return $user->getId();
1185  }
1186  if ( $name === 'rc_user_text' ) {
1187  return $user->getName();
1188  }
1189  if ( $name === 'rc_actor' ) {
1190  // NOTE: rc_actor exists in the database, but application logic should not use it.
1191  wfDeprecatedMsg( 'Accessing deprecated field rc_actor', '1.36' );
1192  $actorStore = MediaWikiServices::getInstance()->getActorStore();
1193  $db = wfGetDB( DB_REPLICA );
1194  return $actorStore->findActorId( $user, $db );
1195  }
1196  }
1197 
1198  return $this->mAttribs[$name] ?? null;
1199  }
1200 
1204  public function getAttributes() {
1205  return $this->mAttribs;
1206  }
1207 
1214  public function diffLinkTrail( $forceCur ) {
1215  if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
1216  $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) .
1217  "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] );
1218  if ( $forceCur ) {
1219  $trail .= '&diff=0';
1220  } else {
1221  $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] );
1222  }
1223  } else {
1224  $trail = '';
1225  }
1226 
1227  return $trail;
1228  }
1229 
1237  public function getCharacterDifference( $old = 0, $new = 0 ) {
1238  if ( $old === 0 ) {
1239  $old = $this->mAttribs['rc_old_len'];
1240  }
1241  if ( $new === 0 ) {
1242  $new = $this->mAttribs['rc_new_len'];
1243  }
1244  if ( $old === null || $new === null ) {
1245  return '';
1246  }
1247 
1248  return ChangesList::showCharacterDifference( $old, $new );
1249  }
1250 
1251  private static function checkIPAddress( $ip ) {
1252  global $wgRequest;
1253  if ( $ip ) {
1254  if ( !IPUtils::isIPAddress( $ip ) ) {
1255  throw new MWException( "Attempt to write \"" . $ip .
1256  "\" as an IP address into recent changes" );
1257  }
1258  } else {
1259  $ip = $wgRequest->getIP();
1260  if ( !$ip ) {
1261  $ip = '';
1262  }
1263  }
1264 
1265  return $ip;
1266  }
1267 
1277  public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
1278  $rcMaxAge = MediaWikiServices::getInstance()->getMainConfig()->get( 'RCMaxAge' );
1279 
1280  return (int)wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $rcMaxAge;
1281  }
1282 
1290  public function parseParams() {
1291  $rcParams = $this->getAttribute( 'rc_params' );
1292 
1293  Wikimedia\suppressWarnings();
1294  $unserializedParams = unserialize( $rcParams );
1295  Wikimedia\restoreWarnings();
1296 
1297  return $unserializedParams;
1298  }
1299 
1308  public function addTags( $tags ) {
1309  if ( is_string( $tags ) ) {
1310  $this->tags[] = $tags;
1311  } else {
1312  $this->tags = array_merge( $tags, $this->tags );
1313  }
1314  }
1315 
1323  public function setEditResult( ?EditResult $editResult ) {
1324  $this->editResult = $editResult;
1325  }
1326 
1334  private function getUserIdentityFromAnyId(
1335  $userId,
1336  $userName,
1337  $actorId = null
1338  ): UserIdentity {
1339  // XXX: Is this logic needed elsewhere? Should it be reusable?
1340 
1341  $userId = isset( $userId ) ? (int)$userId : null;
1342  $actorId = isset( $actorId ) ? (int)$actorId : 0;
1343 
1344  $actorStore = MediaWikiServices::getInstance()->getActorStore();
1345  if ( $userName && $actorId ) {
1346  // Likely the fields are coming from a join on actor table,
1347  // so can definitely build a UserIdentityValue.
1348  return $actorStore->newActorFromRowFields( $userId, $userName, $actorId );
1349  }
1350  if ( $userId !== null ) {
1351  if ( $userName !== null ) {
1352  // NOTE: For IPs and external users, $userId will be 0.
1353  $user = new UserIdentityValue( $userId, $userName );
1354  } else {
1355  $user = $actorStore->getUserIdentityByUserId( $userId );
1356 
1357  if ( !$user ) {
1358  throw new RuntimeException( "User not found by ID: $userId" );
1359  }
1360  }
1361  } elseif ( $actorId > 0 ) {
1362  $db = wfGetDB( DB_REPLICA );
1363  $user = $actorStore->getActorById( $actorId, $db );
1364 
1365  if ( !$user ) {
1366  throw new RuntimeException( "User not found by actor ID: $actorId" );
1367  }
1368  } elseif ( $userName !== null ) {
1369  $user = $actorStore->getUserIdentityByName( $userName );
1370 
1371  if ( !$user ) {
1372  throw new RuntimeException( "User not found by name: $userName" );
1373  }
1374  } else {
1375  throw new RuntimeException( 'At least one of user ID, actor ID or user name must be given' );
1376  }
1377 
1378  return $user;
1379  }
1380 }
RecentChange\getCharacterDifference
getCharacterDifference( $old=0, $new=0)
Returns the change size (HTML).
Definition: RecentChange.php:1237
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:406
RecentChange\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
Definition: RecentChange.php:258
RecentChange\$editResult
EditResult null $editResult
EditResult associated with the edit.
Definition: RecentChange.php:141
RecentChange\newLogEntry
static newLogEntry( $timestamp, $logPage, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='', $revId=0, $isPatrollable=false)
Definition: RecentChange.php:925
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:731
RecentChange\$tags
array $tags
List of tags to apply.
Definition: RecentChange.php:136
RC_EDIT
const RC_EDIT
Definition: Defines.php:115
RecentChange\setEditResult
setEditResult(?EditResult $editResult)
Sets the EditResult associated with the edit.
Definition: RecentChange.php:1323
RecentChange\setExtra
setExtra( $extra)
Definition: RecentChange.php:322
RecentChange\getUserIdentityFromAnyId
getUserIdentityFromAnyId( $userId, $userName, $actorId=null)
Definition: RecentChange.php:1334
RecentChange\$mPage
PageReference null $mPage
Definition: RecentChange.php:113
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:204
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:227
$wgRequest
$wgRequest
Definition: Setup.php:732
RecentChange\getAttributes
getAttributes()
Definition: RecentChange.php:1204
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:81
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1649
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:1131
RecentChange\getChangeTypes
static getChangeTypes()
Get an array of all change types.
Definition: RecentChange.php:204
Page\PageIdentity\getId
getId( $wikiId=self::LOCAL)
Returns the page ID.
RecentChange\reallyMarkPatrolled
reallyMarkPatrolled()
Mark this RecentChange patrolled, without error checking.
Definition: RecentChange.php:680
RecentChange\$counter
int $counter
Line number of recent change.
Definition: RecentChange.php:131
RecentChange\setAttribs
setAttribs( $attribs)
Definition: RecentChange.php:315
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:672
MediaWiki\User\UserIdentity\getId
getId( $wikiId=self::LOCAL)
RecentChange\getPerformerIdentity
getPerformerIdentity()
Get the UserIdentity of the client that performed this change.
Definition: RecentChange.php:385
MediaWiki\ChangeTags\Taggable
Interface that defines how to tag objects.
Definition: Taggable.php:32
RecentChange\SRC_CATEGORIZE
const SRC_CATEGORIZE
Definition: RecentChange.php:90
RecentChange\parseToRCType
static parseToRCType( $type)
Parsing text to RC_* constants.
Definition: RecentChange.php:171
RC_NEW
const RC_NEW
Definition: Defines.php:116
serialize
serialize()
Definition: ApiMessageTrait.php:138
MediaWiki\Permissions\Authority\getUser
getUser()
Returns the performer of the actions associated with this authority.
RecentChange\SRC_LOG
const SRC_LOG
Definition: RecentChange.php:88
RecentChangesUpdateJob\newCacheUpdateJob
static newCacheUpdateJob()
Definition: RecentChangesUpdateJob.php:55
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:330
RecentChange\parseParams
parseParams()
Parses and returns the rc_params attribute.
Definition: RecentChange.php:1290
RecentChange\CHANGE_TYPES
const CHANGE_TYPES
Definition: RecentChange.php:143
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:96
deprecatePublicPropertyFallback
deprecatePublicPropertyFallback(string $property, string $version, $getter, $setter=null, $class=null, $component=null)
Mark a removed public property as deprecated and provide fallback getter and setter callables.
Definition: DeprecationHelper.php:125
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
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
Page\PageReference\getNamespace
getNamespace()
Returns the page's namespace number.
RecentChange\$notificationtimestamp
$notificationtimestamp
Definition: RecentChange.php:121
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2186
RecentChange\$mPerformer
UserIdentity null $mPerformer
Definition: RecentChange.php:118
RecentChange\$watchlistExpiry
string null $watchlistExpiry
The expiry time, if this is a temporary watchlist item.
Definition: RecentChange.php:126
RecentChange\newFromRow
static newFromRow( $row)
Definition: RecentChange.php:157
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:86
RecentChange\getPage
getPage()
Definition: RecentChange.php:339
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:648
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:1214
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:87
RecentChange\notifyLog
static notifyLog( $timestamp, $logPage, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='')
Definition: RecentChange.php:889
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:37
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:93
RecentChange\newFromId
static newFromId( $rcid)
Obtain the recent change with a given rc_id value.
Definition: RecentChange.php:214
RecentChange\$mExtra
$mExtra
Definition: RecentChange.php:108
RC_EXTERNAL
const RC_EXTERNAL
Definition: Defines.php:118
Page\PageReference\getDBkey
getDBkey()
Get the page title in DB key form.
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:173
ChangesList\showCharacterDifference
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
Definition: ChangesList.php:347
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:94
RecentChange\addTags
addTags( $tags)
Tags to append to the recent change, and associated revision/log.
Definition: RecentChange.php:1308
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:35
RecentChange\getAttribute
getAttribute( $name)
Get an attribute value.
Definition: RecentChange.php:1173
RecentChange\checkIPAddress
static checkIPAddress( $ip)
Definition: RecentChange.php:1251
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:1035
Title
Represents a title within MediaWiki.
Definition: Title.php:47
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:193
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:812
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:92
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:1277
RecentChangesUpdateJob\newPurgeJob
static newPurgeJob()
Definition: RecentChangesUpdateJob.php:45
RecentChange\doMarkPatrolled
doMarkPatrolled(Authority $performer, $auto=false, $tags=null)
Mark this RecentChange as patrolled.
Definition: RecentChange.php:624
Title\castFromPageReference
static castFromPageReference(?PageReference $pageReference)
Return a Title for a given Reference.
Definition: Title.php:339
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:107
RecentChange\getPerformer
getPerformer()
Get the User object of the person who performed this change.
Definition: RecentChange.php:369
EmailNotification
This module processes the email notifications when the current page is changed.
Definition: EmailNotification.php:53
RecentChange\__construct
__construct()
Definition: RecentChange.php:297
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:151
RecentChange\getParam
getParam( $name)
Get a parameter value.
Definition: RecentChange.php:1121
RecentChange\$numberofWatchingusers
$numberofWatchingusers
Definition: RecentChange.php:120
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:333
RecentChange\SRC_EXTERNAL
const SRC_EXTERNAL
Definition: RecentChange.php:89
$type
$type
Definition: testCompression.php:52