* Database interaction & caching
* TODO caching should be factored out into a CachingWatchedItemStore class
*
- * Uses database because this uses User::isAnon
- *
- * @group Database
- *
* @author Addshore
* @since 1.27
*/
*/
private $revisionGetTimestampFromIdCallback;
+ /**
+ * @var int
+ */
+ private $updateRowsPerQuery;
+
/**
* @var StatsdDataFactoryInterface
*/
* @param LoadBalancer $loadBalancer
* @param HashBagOStuff $cache
* @param ReadOnlyMode $readOnlyMode
+ * @param int $updateRowsPerQuery
*/
public function __construct(
LoadBalancer $loadBalancer,
HashBagOStuff $cache,
- ReadOnlyMode $readOnlyMode
+ ReadOnlyMode $readOnlyMode,
+ $updateRowsPerQuery
) {
$this->loadBalancer = $loadBalancer;
$this->cache = $cache;
$this->readOnlyMode = $readOnlyMode;
$this->stats = new NullStatsdDataFactory();
- $this->deferredUpdatesAddCallableUpdateCallback = [ 'DeferredUpdates', 'addCallableUpdate' ];
- $this->revisionGetTimestampFromIdCallback = [ 'Revision', 'getTimestampFromId' ];
+ $this->deferredUpdatesAddCallableUpdateCallback =
+ [ DeferredUpdates::class, 'addCallableUpdate' ];
+ $this->revisionGetTimestampFromIdCallback =
+ [ Revision::class, 'getTimestampFromId' ];
+ $this->updateRowsPerQuery = $updateRowsPerQuery;
}
+ /**
+ * @param StatsdDataFactoryInterface $stats
+ */
public function setStatsdDataFactory( StatsdDataFactoryInterface $stats ) {
$this->stats = $stats;
}
return $this->loadBalancer->getConnectionRef( $dbIndex, [ 'watchlist' ] );
}
+ /**
+ * Deletes ALL watched items for the given user when under
+ * $updateRowsPerQuery entries exist.
+ *
+ * @since 1.30
+ *
+ * @param User $user
+ *
+ * @return bool true on success, false when too many items are watched
+ */
+ public function clearUserWatchedItems( User $user ) {
+ if ( $this->countWatchedItems( $user ) > $this->updateRowsPerQuery ) {
+ return false;
+ }
+
+ $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
+ $dbw->delete(
+ 'watchlist',
+ [ 'wl_user' => $user->getId() ],
+ __METHOD__
+ );
+ $this->uncacheAllItemsForUser( $user );
+
+ return true;
+ }
+
+ private function uncacheAllItemsForUser( User $user ) {
+ $userId = $user->getId();
+ foreach ( $this->cacheIndex as $ns => $dbKeyIndex ) {
+ foreach ( $dbKeyIndex as $dbKey => $userIndex ) {
+ if ( array_key_exists( $userId, $userIndex ) ) {
+ $this->cache->delete( $userIndex[$userId] );
+ unset( $this->cacheIndex[$ns][$dbKey][$userId] );
+ }
+ }
+ }
+
+ // Cleanup empty cache keys
+ foreach ( $this->cacheIndex as $ns => $dbKeyIndex ) {
+ foreach ( $dbKeyIndex as $dbKey => $userIndex ) {
+ if ( empty( $this->cacheIndex[$ns][$dbKey] ) ) {
+ unset( $this->cacheIndex[$ns][$dbKey] );
+ }
+ }
+ if ( empty( $this->cacheIndex[$ns] ) ) {
+ unset( $this->cacheIndex[$ns] );
+ }
+ }
+ }
+
/**
* Queues a job that will clear the users watchlist using the Job Queue.
*
/**
* @since 1.31
+ * @param User $user
+ * @return int
*/
public function countWatchedItems( User $user ) {
$dbr = $this->getConnectionRef( DB_REPLICA );
/**
* @since 1.27
+ * @param LinkTarget $target
+ * @return int
*/
public function countWatchers( LinkTarget $target ) {
$dbr = $this->getConnectionRef( DB_REPLICA );
/**
* @since 1.27
+ * @param LinkTarget $target
+ * @param string|int $threshold
+ * @return int
*/
public function countVisitingWatchers( LinkTarget $target, $threshold ) {
$dbr = $this->getConnectionRef( DB_REPLICA );
/**
* @since 1.27
+ * @param LinkTarget[] $targets
+ * @param array $options
+ * @return array
*/
public function countWatchersMultiple( array $targets, array $options = [] ) {
$dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ];
/**
* @since 1.27
+ * @param array $targetsWithVisitThresholds
+ * @param int|null $minimumWatchers
+ * @return array
*/
public function countVisitingWatchersMultiple(
array $targetsWithVisitThresholds,
/**
* @since 1.27
+ * @param User $user
+ * @param LinkTarget $target
+ * @return bool
*/
public function getWatchedItem( User $user, LinkTarget $target ) {
if ( $user->isAnon() ) {
/**
* @since 1.27
+ * @param User $user
+ * @param LinkTarget $target
+ * @return bool
*/
public function loadWatchedItem( User $user, LinkTarget $target ) {
// Only loggedin user can have a watchlist
/**
* @since 1.27
+ * @param User $user
+ * @param array $options
+ * @return WatchedItem[]
*/
public function getWatchedItemsForUser( User $user, array $options = [] ) {
$options += [ 'forWrite' => false ];
/**
* @since 1.27
+ * @param User $user
+ * @param LinkTarget $target
+ * @return bool
*/
public function isWatched( User $user, LinkTarget $target ) {
return (bool)$this->getWatchedItem( $user, $target );
/**
* @since 1.27
+ * @param User $user
+ * @param LinkTarget[] $targets
+ * @return array
*/
public function getNotificationTimestampsBatch( User $user, array $targets ) {
$timestamps = [];
/**
* @since 1.27
+ * @param User $user
+ * @param LinkTarget $target
*/
public function addWatch( User $user, LinkTarget $target ) {
$this->addWatchBatchForUser( $user, [ $target ] );
/**
* @since 1.27
+ * @param User $user
+ * @param LinkTarget[] $targets
+ * @return bool
*/
public function addWatchBatchForUser( User $user, array $targets ) {
if ( $this->readOnlyMode->isReadOnly() ) {
}
// Update process cache to ensure skin doesn't claim that the current
// page is unwatched in the response of action=watch itself (T28292).
- // This would otherwise be re-queried from a slave by isWatched().
+ // This would otherwise be re-queried from a replica by isWatched().
foreach ( $items as $item ) {
$this->cache( $item );
}
/**
* @since 1.27
+ * @param User $user
+ * @param LinkTarget $target
+ * @return bool
*/
public function removeWatch( User $user, LinkTarget $target ) {
// Only logged in user can have a watchlist
/**
* @since 1.27
+ * @param User $user
+ * @param string|int $timestamp
+ * @param LinkTarget[] $targets
+ * @return bool
*/
public function setNotificationTimestampsForUser( User $user, $timestamp, array $targets = [] ) {
// Only loggedin user can have a watchlist
/**
* @since 1.27
+ * @param User $editor
+ * @param LinkTarget $target
+ * @param string|int $timestamp
+ * @return int
*/
public function updateNotificationTimestamp( User $editor, LinkTarget $target, $timestamp ) {
$dbw = $this->getConnectionRef( DB_MASTER );
/**
* @since 1.27
+ * @param User $user
+ * @param Title $title
+ * @param string $force
+ * @param int $oldid
+ * @return bool
*/
public function resetNotificationTimestamp( User $user, Title $title, $force = '', $oldid = 0 ) {
// Only loggedin user can have a watchlist
/**
* @since 1.27
+ * @param User $user
+ * @param int|null $unreadLimit
+ * @return int|bool
*/
public function countUnreadNotifications( User $user, $unreadLimit = null ) {
$queryOptions = [];
/**
* @since 1.27
+ * @param LinkTarget $oldTarget
+ * @param LinkTarget $newTarget
*/
public function duplicateAllAssociatedEntries( LinkTarget $oldTarget, LinkTarget $newTarget ) {
$oldTarget = Title::newFromLinkTarget( $oldTarget );
/**
* @since 1.27
+ * @param LinkTarget $oldTarget
+ * @param LinkTarget $newTarget
*/
public function duplicateEntry( LinkTarget $oldTarget, LinkTarget $newTarget ) {
$dbw = $this->getConnectionRef( DB_MASTER );