MediaWiki  1.27.2
WatchedItemStore.php
Go to the documentation of this file.
1 <?php
2 
6 
16 
17  const SORT_DESC = 'DESC';
18  const SORT_ASC = 'ASC';
19 
23  private $loadBalancer;
24 
28  private $cache;
29 
36  private $cacheIndex = [];
37 
42 
47 
51  private $stats;
52 
56  private static $instance;
57 
62  public function __construct(
65  ) {
66  $this->loadBalancer = $loadBalancer;
67  $this->cache = $cache;
68  $this->stats = new NullStatsdDataFactory();
69  $this->deferredUpdatesAddCallableUpdateCallback = [ 'DeferredUpdates', 'addCallableUpdate' ];
70  $this->revisionGetTimestampFromIdCallback = [ 'Revision', 'getTimestampFromId' ];
71  }
72 
73  public function setStatsdDataFactory( StatsdDataFactoryInterface $stats ) {
74  $this->stats = $stats;
75  }
76 
88  public function overrideDeferredUpdatesAddCallableUpdateCallback( $callback ) {
89  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
90  throw new MWException(
91  'Cannot override DeferredUpdates::addCallableUpdate callback in operation.'
92  );
93  }
94  Assert::parameterType( 'callable', $callback, '$callback' );
95 
97  $this->deferredUpdatesAddCallableUpdateCallback = $callback;
98  return new ScopedCallback( function() use ( $previousValue ) {
99  $this->deferredUpdatesAddCallableUpdateCallback = $previousValue;
100  } );
101  }
102 
113  public function overrideRevisionGetTimestampFromIdCallback( $callback ) {
114  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
115  throw new MWException(
116  'Cannot override Revision::getTimestampFromId callback in operation.'
117  );
118  }
119  Assert::parameterType( 'callable', $callback, '$callback' );
120 
122  $this->revisionGetTimestampFromIdCallback = $callback;
123  return new ScopedCallback( function() use ( $previousValue ) {
124  $this->revisionGetTimestampFromIdCallback = $previousValue;
125  } );
126  }
127 
140  public static function overrideDefaultInstance( WatchedItemStore $store = null ) {
141  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
142  throw new MWException(
143  'Cannot override ' . __CLASS__ . 'default instance in operation.'
144  );
145  }
146 
147  $previousValue = self::$instance;
148  self::$instance = $store;
149  return new ScopedCallback( function() use ( $previousValue ) {
150  self::$instance = $previousValue;
151  } );
152  }
153 
157  public static function getDefaultInstance() {
158  if ( !self::$instance ) {
159  self::$instance = new self(
160  wfGetLB(),
161  new HashBagOStuff( [ 'maxKeys' => 100 ] )
162  );
163  self::$instance->setStatsdDataFactory( RequestContext::getMain()->getStats() );
164  }
165  return self::$instance;
166  }
167 
168  private function getCacheKey( User $user, LinkTarget $target ) {
169  return $this->cache->makeKey(
170  (string)$target->getNamespace(),
171  $target->getDBkey(),
172  (string)$user->getId()
173  );
174  }
175 
176  private function cache( WatchedItem $item ) {
177  $user = $item->getUser();
178  $target = $item->getLinkTarget();
179  $key = $this->getCacheKey( $user, $target );
180  $this->cache->set( $key, $item );
181  $this->cacheIndex[$target->getNamespace()][$target->getDBkey()][$user->getId()] = $key;
182  $this->stats->increment( 'WatchedItemStore.cache' );
183  }
184 
185  private function uncache( User $user, LinkTarget $target ) {
186  $this->cache->delete( $this->getCacheKey( $user, $target ) );
187  unset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()][$user->getId()] );
188  $this->stats->increment( 'WatchedItemStore.uncache' );
189  }
190 
191  private function uncacheLinkTarget( LinkTarget $target ) {
192  if ( !isset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] ) ) {
193  return;
194  }
195  $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
196  foreach ( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] as $key ) {
197  $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget.items' );
198  $this->cache->delete( $key );
199  }
200  }
201 
208  private function getCached( User $user, LinkTarget $target ) {
209  return $this->cache->get( $this->getCacheKey( $user, $target ) );
210  }
211 
221  private function dbCond( User $user, LinkTarget $target ) {
222  return [
223  'wl_user' => $user->getId(),
224  'wl_namespace' => $target->getNamespace(),
225  'wl_title' => $target->getDBkey(),
226  ];
227  }
228 
235  private function getConnection( $slaveOrMaster ) {
236  return $this->loadBalancer->getConnection( $slaveOrMaster, [ 'watchlist' ] );
237  }
238 
244  private function reuseConnection( $connection ) {
245  $this->loadBalancer->reuseConnection( $connection );
246  }
247 
256  public function countWatchedItems( User $user ) {
257  $dbr = $this->getConnection( DB_SLAVE );
258  $return = (int)$dbr->selectField(
259  'watchlist',
260  'COUNT(*)',
261  [
262  'wl_user' => $user->getId()
263  ],
264  __METHOD__
265  );
266  $this->reuseConnection( $dbr );
267 
268  return $return;
269  }
270 
276  public function countWatchers( LinkTarget $target ) {
277  $dbr = $this->getConnection( DB_SLAVE );
278  $return = (int)$dbr->selectField(
279  'watchlist',
280  'COUNT(*)',
281  [
282  'wl_namespace' => $target->getNamespace(),
283  'wl_title' => $target->getDBkey(),
284  ],
285  __METHOD__
286  );
287  $this->reuseConnection( $dbr );
288 
289  return $return;
290  }
291 
302  public function countVisitingWatchers( LinkTarget $target, $threshold ) {
303  $dbr = $this->getConnection( DB_SLAVE );
304  $visitingWatchers = (int)$dbr->selectField(
305  'watchlist',
306  'COUNT(*)',
307  [
308  'wl_namespace' => $target->getNamespace(),
309  'wl_title' => $target->getDBkey(),
310  'wl_notificationtimestamp >= ' .
311  $dbr->addQuotes( $dbr->timestamp( $threshold ) ) .
312  ' OR wl_notificationtimestamp IS NULL'
313  ],
314  __METHOD__
315  );
316  $this->reuseConnection( $dbr );
317 
318  return $visitingWatchers;
319  }
320 
330  public function countWatchersMultiple( array $targets, array $options = [] ) {
331  $dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ];
332 
333  $dbr = $this->getConnection( DB_SLAVE );
334 
335  if ( array_key_exists( 'minimumWatchers', $options ) ) {
336  $dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$options['minimumWatchers'];
337  }
338 
339  $lb = new LinkBatch( $targets );
340  $res = $dbr->select(
341  'watchlist',
342  [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
343  [ $lb->constructSet( 'wl', $dbr ) ],
344  __METHOD__,
345  $dbOptions
346  );
347 
348  $this->reuseConnection( $dbr );
349 
350  $watchCounts = [];
351  foreach ( $targets as $linkTarget ) {
352  $watchCounts[$linkTarget->getNamespace()][$linkTarget->getDBkey()] = 0;
353  }
354 
355  foreach ( $res as $row ) {
356  $watchCounts[$row->wl_namespace][$row->wl_title] = (int)$row->watchers;
357  }
358 
359  return $watchCounts;
360  }
361 
378  array $targetsWithVisitThresholds,
379  $minimumWatchers = null
380  ) {
381  $dbr = $this->getConnection( DB_SLAVE );
382 
383  $conds = $this->getVisitingWatchersCondition( $dbr, $targetsWithVisitThresholds );
384 
385  $dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ];
386  if ( $minimumWatchers !== null ) {
387  $dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$minimumWatchers;
388  }
389  $res = $dbr->select(
390  'watchlist',
391  [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
392  $conds,
393  __METHOD__,
394  $dbOptions
395  );
396 
397  $this->reuseConnection( $dbr );
398 
399  $watcherCounts = [];
400  foreach ( $targetsWithVisitThresholds as list( $target ) ) {
401  /* @var LinkTarget $target */
402  $watcherCounts[$target->getNamespace()][$target->getDBkey()] = 0;
403  }
404 
405  foreach ( $res as $row ) {
406  $watcherCounts[$row->wl_namespace][$row->wl_title] = (int)$row->watchers;
407  }
408 
409  return $watcherCounts;
410  }
411 
420  IDatabase $db,
421  array $targetsWithVisitThresholds
422  ) {
423  $missingTargets = [];
424  $namespaceConds = [];
425  foreach ( $targetsWithVisitThresholds as list( $target, $threshold ) ) {
426  if ( $threshold === null ) {
427  $missingTargets[] = $target;
428  continue;
429  }
430  /* @var LinkTarget $target */
431  $namespaceConds[$target->getNamespace()][] = $db->makeList( [
432  'wl_title = ' . $db->addQuotes( $target->getDBkey() ),
433  $db->makeList( [
434  'wl_notificationtimestamp >= ' . $db->addQuotes( $db->timestamp( $threshold ) ),
435  'wl_notificationtimestamp IS NULL'
436  ], LIST_OR )
437  ], LIST_AND );
438  }
439 
440  $conds = [];
441  foreach ( $namespaceConds as $namespace => $pageConds ) {
442  $conds[] = $db->makeList( [
443  'wl_namespace = ' . $namespace,
444  '(' . $db->makeList( $pageConds, LIST_OR ) . ')'
445  ], LIST_AND );
446  }
447 
448  if ( $missingTargets ) {
449  $lb = new LinkBatch( $missingTargets );
450  $conds[] = $lb->constructSet( 'wl', $db );
451  }
452 
453  return $db->makeList( $conds, LIST_OR );
454  }
455 
464  public function getWatchedItem( User $user, LinkTarget $target ) {
465  if ( $user->isAnon() ) {
466  return false;
467  }
468 
469  $cached = $this->getCached( $user, $target );
470  if ( $cached ) {
471  $this->stats->increment( 'WatchedItemStore.getWatchedItem.cached' );
472  return $cached;
473  }
474  $this->stats->increment( 'WatchedItemStore.getWatchedItem.load' );
475  return $this->loadWatchedItem( $user, $target );
476  }
477 
486  public function loadWatchedItem( User $user, LinkTarget $target ) {
487  // Only loggedin user can have a watchlist
488  if ( $user->isAnon() ) {
489  return false;
490  }
491 
492  $dbr = $this->getConnection( DB_SLAVE );
493  $row = $dbr->selectRow(
494  'watchlist',
495  'wl_notificationtimestamp',
496  $this->dbCond( $user, $target ),
497  __METHOD__
498  );
499  $this->reuseConnection( $dbr );
500 
501  if ( !$row ) {
502  return false;
503  }
504 
505  $item = new WatchedItem(
506  $user,
507  $target,
508  $row->wl_notificationtimestamp
509  );
510  $this->cache( $item );
511 
512  return $item;
513  }
514 
524  public function getWatchedItemsForUser( User $user, array $options = [] ) {
525  $options += [ 'forWrite' => false ];
526 
527  $dbOptions = [];
528  if ( array_key_exists( 'sort', $options ) ) {
529  Assert::parameter(
530  ( in_array( $options['sort'], [ self::SORT_ASC, self::SORT_DESC ] ) ),
531  '$options[\'sort\']',
532  'must be SORT_ASC or SORT_DESC'
533  );
534  $dbOptions['ORDER BY'] = [
535  "wl_namespace {$options['sort']}",
536  "wl_title {$options['sort']}"
537  ];
538  }
539  $db = $this->getConnection( $options['forWrite'] ? DB_MASTER : DB_SLAVE );
540 
541  $res = $db->select(
542  'watchlist',
543  [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
544  [ 'wl_user' => $user->getId() ],
545  __METHOD__,
546  $dbOptions
547  );
548  $this->reuseConnection( $db );
549 
550  $watchedItems = [];
551  foreach ( $res as $row ) {
552  // todo these could all be cached at some point?
553  $watchedItems[] = new WatchedItem(
554  $user,
555  new TitleValue( (int)$row->wl_namespace, $row->wl_title ),
556  $row->wl_notificationtimestamp
557  );
558  }
559 
560  return $watchedItems;
561  }
562 
571  public function isWatched( User $user, LinkTarget $target ) {
572  return (bool)$this->getWatchedItem( $user, $target );
573  }
574 
584  public function getNotificationTimestampsBatch( User $user, array $targets ) {
585  $timestamps = [];
586  foreach ( $targets as $target ) {
587  $timestamps[$target->getNamespace()][$target->getDBkey()] = false;
588  }
589 
590  if ( $user->isAnon() ) {
591  return $timestamps;
592  }
593 
594  $targetsToLoad = [];
595  foreach ( $targets as $target ) {
596  $cachedItem = $this->getCached( $user, $target );
597  if ( $cachedItem ) {
598  $timestamps[$target->getNamespace()][$target->getDBkey()] =
599  $cachedItem->getNotificationTimestamp();
600  } else {
601  $targetsToLoad[] = $target;
602  }
603  }
604 
605  if ( !$targetsToLoad ) {
606  return $timestamps;
607  }
608 
609  $dbr = $this->getConnection( DB_SLAVE );
610 
611  $lb = new LinkBatch( $targetsToLoad );
612  $res = $dbr->select(
613  'watchlist',
614  [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
615  [
616  $lb->constructSet( 'wl', $dbr ),
617  'wl_user' => $user->getId(),
618  ],
619  __METHOD__
620  );
621  $this->reuseConnection( $dbr );
622 
623  foreach ( $res as $row ) {
624  $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
625  }
626 
627  return $timestamps;
628  }
629 
636  public function addWatch( User $user, LinkTarget $target ) {
637  $this->addWatchBatchForUser( $user, [ $target ] );
638  }
639 
646  public function addWatchBatchForUser( User $user, array $targets ) {
647  if ( $this->loadBalancer->getReadOnlyReason() !== false ) {
648  return false;
649  }
650  // Only loggedin user can have a watchlist
651  if ( $user->isAnon() ) {
652  return false;
653  }
654 
655  if ( !$targets ) {
656  return true;
657  }
658 
659  $rows = [];
660  foreach ( $targets as $target ) {
661  $rows[] = [
662  'wl_user' => $user->getId(),
663  'wl_namespace' => $target->getNamespace(),
664  'wl_title' => $target->getDBkey(),
665  'wl_notificationtimestamp' => null,
666  ];
667  $this->uncache( $user, $target );
668  }
669 
670  $dbw = $this->getConnection( DB_MASTER );
671  foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
672  // Use INSERT IGNORE to avoid overwriting the notification timestamp
673  // if there's already an entry for this page
674  $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' );
675  }
676  $this->reuseConnection( $dbw );
677 
678  return true;
679  }
680 
692  public function removeWatch( User $user, LinkTarget $target ) {
693  // Only logged in user can have a watchlist
694  if ( $this->loadBalancer->getReadOnlyReason() !== false || $user->isAnon() ) {
695  return false;
696  }
697 
698  $this->uncache( $user, $target );
699 
700  $dbw = $this->getConnection( DB_MASTER );
701  $dbw->delete( 'watchlist',
702  [
703  'wl_user' => $user->getId(),
704  'wl_namespace' => $target->getNamespace(),
705  'wl_title' => $target->getDBkey(),
706  ], __METHOD__
707  );
708  $success = (bool)$dbw->affectedRows();
709  $this->reuseConnection( $dbw );
710 
711  return $success;
712  }
713 
723  $dbw = $this->getConnection( DB_MASTER );
724  $res = $dbw->select( [ 'watchlist' ],
725  [ 'wl_user' ],
726  [
727  'wl_user != ' . intval( $editor->getId() ),
728  'wl_namespace' => $target->getNamespace(),
729  'wl_title' => $target->getDBkey(),
730  'wl_notificationtimestamp IS NULL',
731  ], __METHOD__
732  );
733 
734  $watchers = [];
735  foreach ( $res as $row ) {
736  $watchers[] = intval( $row->wl_user );
737  }
738 
739  if ( $watchers ) {
740  // Update wl_notificationtimestamp for all watching users except the editor
741  $fname = __METHOD__;
742  $dbw->onTransactionIdle(
743  function () use ( $dbw, $timestamp, $watchers, $target, $fname ) {
744  $dbw->update( 'watchlist',
745  [ /* SET */
746  'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
747  ], [ /* WHERE */
748  'wl_user' => $watchers,
749  'wl_namespace' => $target->getNamespace(),
750  'wl_title' => $target->getDBkey(),
751  ], $fname
752  );
753  $this->uncacheLinkTarget( $target );
754  }
755  );
756  }
757 
758  $this->reuseConnection( $dbw );
759 
760  return $watchers;
761  }
762 
775  public function resetNotificationTimestamp( User $user, Title $title, $force = '', $oldid = 0 ) {
776  // Only loggedin user can have a watchlist
777  if ( $this->loadBalancer->getReadOnlyReason() !== false || $user->isAnon() ) {
778  return false;
779  }
780 
781  $item = null;
782  if ( $force != 'force' ) {
783  $item = $this->loadWatchedItem( $user, $title );
784  if ( !$item || $item->getNotificationTimestamp() === null ) {
785  return false;
786  }
787  }
788 
789  // If the page is watched by the user (or may be watched), update the timestamp
790  $job = new ActivityUpdateJob(
791  $title,
792  [
793  'type' => 'updateWatchlistNotification',
794  'userid' => $user->getId(),
795  'notifTime' => $this->getNotificationTimestamp( $user, $title, $item, $force, $oldid ),
796  'curTime' => time()
797  ]
798  );
799 
800  // Try to run this post-send
801  // Calls DeferredUpdates::addCallableUpdate in normal operation
802  call_user_func(
803  $this->deferredUpdatesAddCallableUpdateCallback,
804  function() use ( $job ) {
805  $job->run();
806  }
807  );
808 
809  $this->uncache( $user, $title );
810 
811  return true;
812  }
813 
814  private function getNotificationTimestamp( User $user, Title $title, $item, $force, $oldid ) {
815  if ( !$oldid ) {
816  // No oldid given, assuming latest revision; clear the timestamp.
817  return null;
818  }
819 
820  if ( !$title->getNextRevisionID( $oldid ) ) {
821  // Oldid given and is the latest revision for this title; clear the timestamp.
822  return null;
823  }
824 
825  if ( $item === null ) {
826  $item = $this->loadWatchedItem( $user, $title );
827  }
828 
829  if ( !$item ) {
830  // This can only happen if $force is enabled.
831  return null;
832  }
833 
834  // Oldid given and isn't the latest; update the timestamp.
835  // This will result in no further notification emails being sent!
836  // Calls Revision::getTimestampFromId in normal operation
837  $notificationTimestamp = call_user_func(
838  $this->revisionGetTimestampFromIdCallback,
839  $title,
840  $oldid
841  );
842 
843  // We need to go one second to the future because of various strict comparisons
844  // throughout the codebase
845  $ts = new MWTimestamp( $notificationTimestamp );
846  $ts->timestamp->add( new DateInterval( 'PT1S' ) );
847  $notificationTimestamp = $ts->getTimestamp( TS_MW );
848 
849  if ( $notificationTimestamp < $item->getNotificationTimestamp() ) {
850  if ( $force != 'force' ) {
851  return false;
852  } else {
853  // This is a little silly…
854  return $item->getNotificationTimestamp();
855  }
856  }
857 
858  return $notificationTimestamp;
859  }
860 
868  public function countUnreadNotifications( User $user, $unreadLimit = null ) {
869  $queryOptions = [];
870  if ( $unreadLimit !== null ) {
871  $unreadLimit = (int)$unreadLimit;
872  $queryOptions['LIMIT'] = $unreadLimit;
873  }
874 
875  $dbr = $this->getConnection( DB_SLAVE );
876  $rowCount = $dbr->selectRowCount(
877  'watchlist',
878  '1',
879  [
880  'wl_user' => $user->getId(),
881  'wl_notificationtimestamp IS NOT NULL',
882  ],
883  __METHOD__,
884  $queryOptions
885  );
886  $this->reuseConnection( $dbr );
887 
888  if ( !isset( $unreadLimit ) ) {
889  return $rowCount;
890  }
891 
892  if ( $rowCount >= $unreadLimit ) {
893  return true;
894  }
895 
896  return $rowCount;
897  }
898 
908  public function duplicateAllAssociatedEntries( LinkTarget $oldTarget, LinkTarget $newTarget ) {
909  $oldTarget = Title::newFromLinkTarget( $oldTarget );
910  $newTarget = Title::newFromLinkTarget( $newTarget );
911 
912  $this->duplicateEntry( $oldTarget->getSubjectPage(), $newTarget->getSubjectPage() );
913  $this->duplicateEntry( $oldTarget->getTalkPage(), $newTarget->getTalkPage() );
914  }
915 
926  public function duplicateEntry( LinkTarget $oldTarget, LinkTarget $newTarget ) {
927  $dbw = $this->getConnection( DB_MASTER );
928 
929  $result = $dbw->select(
930  'watchlist',
931  [ 'wl_user', 'wl_notificationtimestamp' ],
932  [
933  'wl_namespace' => $oldTarget->getNamespace(),
934  'wl_title' => $oldTarget->getDBkey(),
935  ],
936  __METHOD__,
937  [ 'FOR UPDATE' ]
938  );
939 
940  $newNamespace = $newTarget->getNamespace();
941  $newDBkey = $newTarget->getDBkey();
942 
943  # Construct array to replace into the watchlist
944  $values = [];
945  foreach ( $result as $row ) {
946  $values[] = [
947  'wl_user' => $row->wl_user,
948  'wl_namespace' => $newNamespace,
949  'wl_title' => $newDBkey,
950  'wl_notificationtimestamp' => $row->wl_notificationtimestamp,
951  ];
952  }
953 
954  if ( !empty( $values ) ) {
955  # Perform replace
956  # Note that multi-row replace is very efficient for MySQL but may be inefficient for
957  # some other DBMSes, mostly due to poor simulation by us
958  $dbw->replace(
959  'watchlist',
960  [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
961  $values,
962  __METHOD__
963  );
964  }
965 
966  $this->reuseConnection( $dbw );
967  }
968 
969 }
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
the array() calling protocol came about after MediaWiki 1.4rc1.
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
$success
getCacheKey(User $user, LinkTarget $target)
countWatchedItems(User $user)
Count the number of individual items that are watched by the user.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
makeList($a, $mode=LIST_COMMA)
Makes an encoded list of strings from an array.
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
getCached(User $user, LinkTarget $target)
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:36
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:177
reuseConnection($connection)
overrideRevisionGetTimestampFromIdCallback($callback)
Overrides the Revision::getTimestampFromId callback This is intended for use while testing and will f...
addWatchBatchForUser(User $user, array $targets)
Represents a title within MediaWiki.
Definition: Title.php:34
getNotificationTimestamp(User $user, Title $title, $item, $force, $oldid)
Describes a Statsd aware interface.
timestamp($ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
overrideDeferredUpdatesAddCallableUpdateCallback($callback)
Overrides the DeferredUpdates::addCallableUpdate callback This is intended for use while testing and ...
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition: Title.php:251
__construct(LoadBalancer $loadBalancer, HashBagOStuff $cache)
getNamespace()
Get the namespace index.
Storage layer class for WatchedItems.
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message.Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload.Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports.&$fullInterwikiPrefix:Interwiki prefix, may contain colons.&$pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable.Can be used to lazy-load the import sources list.&$importSources:The value of $wgImportSources.Modify as necessary.See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page.$context:IContextSource object &$pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect.&$title:Title object for the current page &$request:WebRequest &$ignoreRedirect:boolean to skip redirect check &$target:Title/string of redirect target &$article:Article object 'InternalParseBeforeLinks':during Parser's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InternalParseBeforeSanitize':during Parser's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings.Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not.Return true without providing an interwiki to continue interwiki search.$prefix:interwiki prefix we are looking for.&$iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user's email has been invalidated successfully.$user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification.Callee may modify $url and $query, URL will be constructed as $url.$query &$url:URL to index.php &$query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) &$article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() &$ip:IP being check &$result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from &$allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn't match your organization.$addr:The e-mail address entered by the user &$result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user &$result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we're looking for a messages file for &$file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED!Use $magicWords in a file listed in $wgExtensionMessagesFiles instead.Use this to define synonyms of magic words depending of the language &$magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces.Do not use this hook to add namespaces.Use CanonicalNamespaces for that.&$namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED!Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead.Use to define aliases of special pages names depending of the language &$specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names.&$names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page's language links.This is called in various places to allow extensions to define the effective language links for a page.$title:The page's Title.&$links:Associative array mapping language codes to prefixed links of the form"language:title".&$linkFlags:Associative array mapping prefixed links to arrays of flags.Currently unused, but planned to provide support for marking individual language links in the UI, e.g.for featured articles. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1796
callable null $revisionGetTimestampFromIdCallback
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:31
Class for asserting that a callback happens when an dummy object leaves scope.
Job for updating user activity like "last viewed" timestamps.
Database load balancing object.
getNextRevisionID($revId, $flags=0)
Get the revision ID of the next revision.
Definition: Title.php:3994
wfGetLB($wiki=false)
Get a load balancer object.
addWatch(User $user, LinkTarget $target)
Must be called separately for Subject & Talk namespaces.
duplicateAllAssociatedEntries(LinkTarget $oldTarget, LinkTarget $newTarget)
Check if the given title already is watched by the user, and if so add a watch for the new title...
static getMain()
Static methods.
countWatchers(LinkTarget $target)
resetNotificationTimestamp(User $user, Title $title, $force= '', $oldid=0)
Reset the notification timestamp of this entry.
const LIST_AND
Definition: Defines.php:193
StatsdDataFactoryInterface $stats
isAnon()
Get whether the user is anonymous.
Definition: User.php:3368
if($limit) $timestamp
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1004
array[] $cacheIndex
Looks like $cacheIndex[Namespace ID][Target DB Key][User Id] => 'key' The index is needed so that on ...
$res
Definition: database.txt:21
countUnreadNotifications(User $user, $unreadLimit=null)
static overrideDefaultInstance(WatchedItemStore $store=null)
Overrides the default instance of this class This is intended for use while testing and will fail if ...
getDBkey()
Get the main part with underscores.
getWatchedItemsForUser(User $user, array $options=[])
loadWatchedItem(User $user, LinkTarget $target)
Loads an item from the db.
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:31
const DB_SLAVE
Definition: Defines.php:46
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
LoadBalancer $loadBalancer
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
const LIST_OR
Definition: Defines.php:196
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
static self null $instance
setStatsdDataFactory(StatsdDataFactoryInterface $stats)
Sets a StatsdDataFactory instance on the object.
getConnection($slaveOrMaster)
countVisitingWatchersMultiple(array $targetsWithVisitThresholds, $minimumWatchers=null)
Number of watchers of each page who have visited recent edits to that page.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
countWatchersMultiple(array $targets, array $options=[])
updateNotificationTimestamp(User $editor, LinkTarget $target, $timestamp)
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:35
removeWatch(User $user, LinkTarget $target)
Removes the an entry for the User watching the LinkTarget Must be called separately for Subject & Tal...
callable null $deferredUpdatesAddCallableUpdateCallback
getId()
Get the user's ID.
Definition: User.php:2061
isWatched(User $user, LinkTarget $target)
Must be called separately for Subject & Talk namespaces.
cache(WatchedItem $item)
if(count($args)< 1) $job
HashBagOStuff $cache
uncacheLinkTarget(LinkTarget $target)
getWatchedItem(User $user, LinkTarget $target)
Get an item (may be cached)
duplicateEntry(LinkTarget $oldTarget, LinkTarget $newTarget)
Check if the given title already is watched by the user, and if so add a watch for the new title...
getNotificationTimestampsBatch(User $user, array $targets)
const DB_MASTER
Definition: Defines.php:47
addQuotes($s)
Adds quotes and backslashes.
dbCond(User $user, LinkTarget $target)
Return an array of conditions to select or update the appropriate database row.
getVisitingWatchersCondition(IDatabase $db, array $targetsWithVisitThresholds)
Generates condition for the query used in a batch count visiting watchers.
uncache(User $user, LinkTarget $target)
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:31
countVisitingWatchers(LinkTarget $target, $threshold)
Number of page watchers who also visited a "recent" edit.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor'you ll need to handle error etc yourself modifying $error and returning true will cause the contents of $error to be echoed at the top of the edit form as wikitext Return true without altering $error to allow the edit to proceed & $editor
Definition: hooks.txt:1099
Basic database interface for live and lazy-loaded DB handles.
Definition: IDatabase.php:35