* This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
*
* @param callable $callback
+ *
* @see DeferredUpdates::addCallableUpdate for callback signiture
*
+ * @return ScopedCallback to reset the overridden value
* @throws MWException
*/
public function overrideDeferredUpdatesAddCallableUpdateCallback( $callback ) {
);
}
Assert::parameterType( 'callable', $callback, '$callback' );
+
+ $previousValue = $this->deferredUpdatesAddCallableUpdateCallback;
$this->deferredUpdatesAddCallableUpdateCallback = $callback;
+ return new ScopedCallback( function() use ( $previousValue ) {
+ $this->deferredUpdatesAddCallableUpdateCallback = $previousValue;
+ } );
}
/**
* @param callable $callback
* @see Revision::getTimestampFromId for callback signiture
*
+ * @return ScopedCallback to reset the overridden value
* @throws MWException
*/
public function overrideRevisionGetTimestampFromIdCallback( $callback ) {
);
}
Assert::parameterType( 'callable', $callback, '$callback' );
+
+ $previousValue = $this->revisionGetTimestampFromIdCallback;
$this->revisionGetTimestampFromIdCallback = $callback;
+ return new ScopedCallback( function() use ( $previousValue ) {
+ $this->revisionGetTimestampFromIdCallback = $previousValue;
+ } );
}
/**
* Overrides the default instance of this class
* This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
*
- * @param WatchedItemStore $store
+ * If this method is used it MUST also be called with null after a test to ensure a new
+ * default instance is created next time getDefaultInstance is called.
*
+ * @param WatchedItemStore|null $store
+ *
+ * @return ScopedCallback to reset the overridden value
* @throws MWException
*/
- public static function overrideDefaultInstance( WatchedItemStore $store ) {
+ public static function overrideDefaultInstance( WatchedItemStore $store = null ) {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
throw new MWException(
'Cannot override ' . __CLASS__ . 'default instance in operation.'
);
}
+
+ $previousValue = self::$instance;
self::$instance = $store;
+ return new ScopedCallback( function() use ( $previousValue ) {
+ self::$instance = $previousValue;
+ } );
}
/**
];
}
+ /**
+ * @param int $slaveOrMaster DB_MASTER or DB_SLAVE
+ *
+ * @return DatabaseBase
+ * @throws MWException
+ */
+ private function getConnection( $slaveOrMaster ) {
+ return $this->loadBalancer->getConnection( $slaveOrMaster, [ 'watchlist' ] );
+ }
+
+ /**
+ * @param DatabaseBase $connection
+ *
+ * @throws MWException
+ */
+ private function reuseConnection( $connection ) {
+ $this->loadBalancer->reuseConnection( $connection );
+ }
+
+ /**
+ * Count the number of individual items that are watched by the user.
+ * If a subject and corresponding talk page are watched this will return 2.
+ *
+ * @param User $user
+ *
+ * @return int
+ */
+ public function countWatchedItems( User $user ) {
+ $dbr = $this->getConnection( DB_SLAVE );
+ $return = (int)$dbr->selectField(
+ 'watchlist',
+ 'COUNT(*)',
+ [
+ 'wl_user' => $user->getId()
+ ],
+ __METHOD__
+ );
+ $this->reuseConnection( $dbr );
+
+ return $return;
+ }
+
/**
* @param LinkTarget $target
*
* @return int
*/
public function countWatchers( LinkTarget $target ) {
- $dbr = $this->loadBalancer->getConnection( DB_SLAVE, [ 'watchlist' ] );
+ $dbr = $this->getConnection( DB_SLAVE );
$return = (int)$dbr->selectField(
'watchlist',
'COUNT(*)',
],
__METHOD__
);
- $this->loadBalancer->reuseConnection( $dbr );
+ $this->reuseConnection( $dbr );
return $return;
}
* @throws MWException
*/
public function countVisitingWatchers( LinkTarget $target, $threshold ) {
- $dbr = $this->loadBalancer->getConnection( DB_SLAVE, [ 'watchlist' ] );
+ $dbr = $this->getConnection( DB_SLAVE );
$visitingWatchers = (int)$dbr->selectField(
'watchlist',
'COUNT(*)',
],
__METHOD__
);
- $this->loadBalancer->reuseConnection( $dbr );
+ $this->reuseConnection( $dbr );
return $visitingWatchers;
}
public function countWatchersMultiple( array $targets, array $options = [] ) {
$dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ];
- $dbr = $this->loadBalancer->getConnection( DB_SLAVE, [ 'watchlist' ] );
+ $dbr = $this->getConnection( DB_SLAVE );
if ( array_key_exists( 'minimumWatchers', $options ) ) {
$dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$options['minimumWatchers'];
$dbOptions
);
- $this->loadBalancer->reuseConnection( $dbr );
+ $this->reuseConnection( $dbr );
$watchCounts = [];
foreach ( $targets as $linkTarget ) {
return $watchCounts;
}
+ /**
+ * Number of watchers of each page who have visited recent edits to that page
+ *
+ * @param array $targetsWithVisitThresholds array of pairs (LinkTarget $target, mixed $threshold),
+ * $threshold is:
+ * - a timestamp of the recent edit if $target exists (format accepted by wfTimestamp)
+ * - null if $target doesn't exist
+ * @param int|null $minimumWatchers
+ * @return array multi-dimensional like $return[$namespaceId][$titleString] = $watchers,
+ * where $watchers is an int:
+ * - if the page exists, number of users watching who have visited the page recently
+ * - if the page doesn't exist, number of users that have the page on their watchlist
+ * - 0 means there are no visiting watchers or their number is below the minimumWatchers
+ * option (if passed).
+ */
+ public function countVisitingWatchersMultiple(
+ array $targetsWithVisitThresholds,
+ $minimumWatchers = null
+ ) {
+ $dbr = $this->getConnection( DB_SLAVE );
+
+ $conds = $this->getVisitingWatchersCondition( $dbr, $targetsWithVisitThresholds );
+
+ $dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ];
+ if ( $minimumWatchers !== null ) {
+ $dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$minimumWatchers;
+ }
+ $res = $dbr->select(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
+ $conds,
+ __METHOD__,
+ $dbOptions
+ );
+
+ $this->reuseConnection( $dbr );
+
+ $watcherCounts = [];
+ foreach ( $targetsWithVisitThresholds as list( $target ) ) {
+ /* @var LinkTarget $target */
+ $watcherCounts[$target->getNamespace()][$target->getDBkey()] = 0;
+ }
+
+ foreach ( $res as $row ) {
+ $watcherCounts[$row->wl_namespace][$row->wl_title] = (int)$row->watchers;
+ }
+
+ return $watcherCounts;
+ }
+
+ /**
+ * Generates condition for the query used in a batch count visiting watchers.
+ *
+ * @param IDatabase $db
+ * @param array $targetsWithVisitThresholds array of pairs (LinkTarget, last visit threshold)
+ * @return string
+ */
+ private function getVisitingWatchersCondition(
+ IDatabase $db,
+ array $targetsWithVisitThresholds
+ ) {
+ $missingTargets = [];
+ $namespaceConds = [];
+ foreach ( $targetsWithVisitThresholds as list( $target, $threshold ) ) {
+ if ( $threshold === null ) {
+ $missingTargets[] = $target;
+ continue;
+ }
+ /* @var LinkTarget $target */
+ $namespaceConds[$target->getNamespace()][] = $db->makeList( [
+ 'wl_title = ' . $db->addQuotes( $target->getDBkey() ),
+ $db->makeList( [
+ 'wl_notificationtimestamp >= ' . $db->addQuotes( $db->timestamp( $threshold ) ),
+ 'wl_notificationtimestamp IS NULL'
+ ], LIST_OR )
+ ], LIST_AND );
+ }
+
+ $conds = [];
+ foreach ( $namespaceConds as $namespace => $pageConds ) {
+ $conds[] = $db->makeList( [
+ 'wl_namespace = ' . $namespace,
+ '(' . $db->makeList( $pageConds, LIST_OR ) . ')'
+ ], LIST_AND );
+ }
+
+ if ( $missingTargets ) {
+ $lb = new LinkBatch( $missingTargets );
+ $conds[] = $lb->constructSet( 'wl', $db );
+ }
+
+ return $db->makeList( $conds, LIST_OR );
+ }
+
/**
* Get an item (may be cached)
*
return false;
}
- $dbr = $this->loadBalancer->getConnection( DB_SLAVE, [ 'watchlist' ] );
+ $dbr = $this->getConnection( DB_SLAVE );
$row = $dbr->selectRow(
'watchlist',
'wl_notificationtimestamp',
$this->dbCond( $user, $target ),
__METHOD__
);
- $this->loadBalancer->reuseConnection( $dbr );
+ $this->reuseConnection( $dbr );
if ( !$row ) {
return false;
return false;
}
- $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] );
+ $dbw = $this->getConnection( DB_MASTER );
foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
// Use INSERT IGNORE to avoid overwriting the notification timestamp
// if there's already an entry for this page
$dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' );
}
- $this->loadBalancer->reuseConnection( $dbw );
+ $this->reuseConnection( $dbw );
return true;
}
$this->uncache( $user, $target );
- $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] );
+ $dbw = $this->getConnection( DB_MASTER );
$dbw->delete( 'watchlist',
[
'wl_user' => $user->getId(),
], __METHOD__
);
$success = (bool)$dbw->affectedRows();
- $this->loadBalancer->reuseConnection( $dbw );
+ $this->reuseConnection( $dbw );
return $success;
}
* @return int[] Array of user IDs the timestamp has been updated for
*/
public function updateNotificationTimestamp( User $editor, LinkTarget $target, $timestamp ) {
- $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] );
+ $dbw = $this->getConnection( DB_MASTER );
$res = $dbw->select( [ 'watchlist' ],
[ 'wl_user' ],
[
);
}
- $this->loadBalancer->reuseConnection( $dbw );
+ $this->reuseConnection( $dbw );
return $watchers;
}
return $notificationTimestamp;
}
+ /**
+ * @param User $user
+ * @param int $unreadLimit
+ *
+ * @return int|bool The number of unread notifications
+ * true if greater than or equal to $unreadLimit
+ */
+ public function countUnreadNotifications( User $user, $unreadLimit = null ) {
+ $queryOptions = [];
+ if ( $unreadLimit !== null ) {
+ $unreadLimit = (int)$unreadLimit;
+ $queryOptions['LIMIT'] = $unreadLimit;
+ }
+
+ $dbr = $this->getConnection( DB_SLAVE );
+ $rowCount = $dbr->selectRowCount(
+ 'watchlist',
+ '1',
+ [
+ 'wl_user' => $user->getId(),
+ 'wl_notificationtimestamp IS NOT NULL',
+ ],
+ __METHOD__,
+ $queryOptions
+ );
+ $this->reuseConnection( $dbr );
+
+ if ( !isset( $unreadLimit ) ) {
+ return $rowCount;
+ }
+
+ if ( $rowCount >= $unreadLimit ) {
+ return true;
+ }
+
+ return $rowCount;
+ }
+
/**
* Check if the given title already is watched by the user, and if so
* add a watch for the new title.
* @param LinkTarget $newTarget
*/
public function duplicateEntry( LinkTarget $oldTarget, LinkTarget $newTarget ) {
- $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] );
+ $dbw = $this->getConnection( DB_MASTER );
$result = $dbw->select(
'watchlist',
);
}
- $this->loadBalancer->reuseConnection( $dbw );
+ $this->reuseConnection( $dbw );
}
}