5use InvalidArgumentException;
16use Wikimedia\Timestamp\ConvertibleTimestamp;
17use Wikimedia\Timestamp\TimestampFormat as TS;
28 private const FIRST_EDIT = 1;
29 private const LATEST_EDIT = 2;
31 private const CACHE_FIRST_EDIT =
'firsteditts';
32 private const CACHE_EDIT_COUNT =
'editcount';
35 private array $userEditCountCache = [];
52 if ( !$user->isRegistered() ) {
56 $cacheKey = $this->getCacheKey( self::CACHE_EDIT_COUNT, $user );
57 if ( isset( $this->userEditCountCache[ $cacheKey ] ) ) {
58 return $this->userEditCountCache[ $cacheKey ];
62 $userId = $user->
getId( $wikiId );
63 $count = $this->dbProvider->getReplicaDatabase( $wikiId )->newSelectQueryBuilder()
64 ->select(
'user_editcount' )
66 ->where( [
'user_id' => $userId ] )
67 ->caller( __METHOD__ )->fetchField();
69 if ( $count ===
null ) {
74 $this->userEditCountCache[ $cacheKey ] = $count;
96 foreach ( $users as $user ) {
98 $user->isRegistered() &&
101 $userIds[] = $user->getId();
105 $userIds = array_unique( $userIds );
107 $dbr = $this->dbProvider->getReplicaDatabase();
109 foreach ( array_chunk( $userIds, 500 ) as $batch ) {
110 $rows = $dbr->newSelectQueryBuilder()
111 ->select( [
'user_id',
'user_editcount' ] )
113 ->where( [
'user_id' => $batch ] )
114 ->caller( __METHOD__ )
117 foreach ( $rows as $row ) {
118 if ( $row->user_editcount !==
null ) {
119 $key = $this->getCacheKeyByUserId( self::CACHE_EDIT_COUNT, (
int)$row->user_id );
121 $this->userEditCountCache[$key] = (int)$row->user_editcount;
135 throw new LogicException( __METHOD__ .
' only supports local users' );
138 $dbr = $this->dbProvider->getReplicaDatabase();
139 $count = (int)$dbr->newSelectQueryBuilder()
140 ->select(
'COUNT(*)' )
142 ->where( [
'rev_actor' => $this->actorNormalization->findActorId( $user, $dbr ) ] )
143 ->caller( __METHOD__ )
148 'userId' => $user->getId(),
149 'editCount' => $count,
167 DeferredUpdates::addUpdate(
169 DeferredUpdates::POSTSEND
184 int $flags = IDBAccessObject::READ_NORMAL
186 if ( !$user->isRegistered() ) {
190 if ( $flags !== IDBAccessObject::READ_NORMAL ) {
191 return $this->getUserEditTimestamp( $user, self::FIRST_EDIT, $flags );
196 $timestamp = $this->wanObjectCache->getWithSetCallback(
197 $this->getCacheKey( self::CACHE_FIRST_EDIT, $user ),
198 WANObjectCache::TTL_MONTH,
199 function () use ( $user ) {
200 $timestamp = $this->getUserEditTimestamp( $user, self::FIRST_EDIT );
201 if ( $timestamp ===
false ) {
208 'staleTTL' => WANObjectCache::TTL_DAY,
216 if ( $timestamp === 0 ) {
231 return $this->getUserEditTimestamp( $user, self::LATEST_EDIT, $flags );
242 private function getUserEditTimestamp(
UserIdentity $user,
int $type,
int $flags = IDBAccessObject::READ_NORMAL ) {
246 if ( $flags & IDBAccessObject::READ_LATEST ) {
247 $db = $this->dbProvider->getPrimaryDatabase( $user->
getWikiId() );
249 $db = $this->dbProvider->getReplicaDatabase( $user->
getWikiId() );
252 $sortOrder = ( $type === self::FIRST_EDIT ) ? SelectQueryBuilder::SORT_ASC : SelectQueryBuilder::SORT_DESC;
253 $time = $db->newSelectQueryBuilder()
254 ->select(
'rev_timestamp' )
256 ->where( [
'rev_actor' => $this->actorNormalization->findActorId( $user, $db ) ] )
257 ->orderBy(
'rev_timestamp', $sortOrder )
258 ->caller( __METHOD__ )
265 return ConvertibleTimestamp::convert( TS::MW, $time );
277 $cacheKey = $this->getCacheKey( self::CACHE_EDIT_COUNT, $user );
278 unset( $this->userEditCountCache[ $cacheKey ] );
289 throw new InvalidArgumentException( __METHOD__ .
' with an anonymous user' );
292 $cacheKey = $this->getCacheKey( self::CACHE_EDIT_COUNT, $user );
293 $this->userEditCountCache[ $cacheKey ] = $editCount;
302 foreach ( $users as [ $user, $timestamp ] ) {
307 if ( $timestamp ===
false ) {
312 $key = $this->getCacheKey( self::CACHE_FIRST_EDIT, $user );
313 $cachedTimestamp = $this->wanObjectCache->get( $key );
315 if ( $timestamp === $cachedTimestamp ) {
316 $this->wanObjectCache->delete( $key );
330 private function getCacheKey(
string $keygroup, UserIdentity $user ): string {
331 if ( !$user->isRegistered() ) {
332 throw new InvalidArgumentException(
'Cannot prepare cache key for an anonymous user' );
337 return $this->getCacheKeyByUserId( $keygroup, $user->
getId( $wikiId ), $wikiId );
350 private function getCacheKeyByUserId(
353 string|
bool $wikiId = WikiAwareEntity::LOCAL
355 if ( $wikiId === WikiAwareEntity::LOCAL ) {
356 $wikiId = WikiMap::getCurrentWikiId();
358 return $this->wanObjectCache->makeKey( $keygroup, $userId, $wikiId );
if(!defined('MW_SETUP_CALLBACK'))