MediaWiki master
UserEditTracker.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\User;
4
5use InvalidArgumentException;
6use LogicException;
15use Wikimedia\Timestamp\ConvertibleTimestamp;
16
25
26 private const FIRST_EDIT = 1;
27 private const LATEST_EDIT = 2;
28
29 private ActorNormalization $actorNormalization;
30 private IConnectionProvider $dbProvider;
31 private JobQueueGroup $jobQueueGroup;
32
41 private $userEditCountCache = [];
42
48 public function __construct(
49 ActorNormalization $actorNormalization,
50 IConnectionProvider $dbProvider,
51 JobQueueGroup $jobQueueGroup
52 ) {
53 $this->actorNormalization = $actorNormalization;
54 $this->dbProvider = $dbProvider;
55 $this->jobQueueGroup = $jobQueueGroup;
56 }
57
64 public function getUserEditCount( UserIdentity $user ): ?int {
65 if ( !$user->isRegistered() ) {
66 return null;
67 }
68
69 $cacheKey = $this->getCacheKey( $user );
70 if ( isset( $this->userEditCountCache[ $cacheKey ] ) ) {
71 return $this->userEditCountCache[ $cacheKey ];
72 }
73
74 $wikiId = $user->getWikiId();
75 $userId = $user->getId( $wikiId );
76 $count = $this->dbProvider->getReplicaDatabase( $wikiId )->newSelectQueryBuilder()
77 ->select( 'user_editcount' )
78 ->from( 'user' )
79 ->where( [ 'user_id' => $userId ] )
80 ->caller( __METHOD__ )->fetchField();
81
82 if ( $count === null ) {
83 // it has not been initialized. do so.
84 $count = $this->initializeUserEditCount( $user );
85 }
86
87 $this->userEditCountCache[ $cacheKey ] = $count;
88 return $count;
89 }
90
96 public function initializeUserEditCount( UserIdentity $user ): int {
97 if ( $user->getWikiId() !== UserIdentity::LOCAL ) {
98 // Don't record edits on remote wikis
99 throw new LogicException( __METHOD__ . ' only supports local users' );
100 }
101
102 $dbr = $this->dbProvider->getReplicaDatabase();
103 $count = (int)$dbr->newSelectQueryBuilder()
104 ->select( 'COUNT(*)' )
105 ->from( 'revision' )
106 ->where( [ 'rev_actor' => $this->actorNormalization->findActorId( $user, $dbr ) ] )
107 ->caller( __METHOD__ )
108 ->fetchField();
109
110 // Defer updating the edit count via a job (T259719)
111 $this->jobQueueGroup->push( new UserEditCountInitJob( [
112 'userId' => $user->getId(),
113 'editCount' => $count,
114 ] ) );
115
116 return $count;
117 }
118
125 public function incrementUserEditCount( UserIdentity $user ) {
126 if ( !$user->isRegistered() ) {
127 // Can't store editcount without user row (i.e. unregistered)
128 return;
129 }
130
131 DeferredUpdates::addUpdate(
132 new UserEditCountUpdate( $user, 1 ),
133 DeferredUpdates::POSTSEND
134 );
135 }
136
145 public function getFirstEditTimestamp( UserIdentity $user, int $flags = IDBAccessObject::READ_NORMAL ) {
146 return $this->getUserEditTimestamp( $user, self::FIRST_EDIT, $flags );
147 }
148
157 public function getLatestEditTimestamp( UserIdentity $user, int $flags = IDBAccessObject::READ_NORMAL ) {
158 return $this->getUserEditTimestamp( $user, self::LATEST_EDIT, $flags );
159 }
160
169 private function getUserEditTimestamp( UserIdentity $user, int $type, int $flags = IDBAccessObject::READ_NORMAL ) {
170 if ( !$user->isRegistered() ) {
171 return false;
172 }
173 if ( $flags & IDBAccessObject::READ_LATEST ) {
174 $db = $this->dbProvider->getPrimaryDatabase( $user->getWikiId() );
175 } else {
176 $db = $this->dbProvider->getReplicaDatabase( $user->getWikiId() );
177 }
178
179 $sortOrder = ( $type === self::FIRST_EDIT ) ? SelectQueryBuilder::SORT_ASC : SelectQueryBuilder::SORT_DESC;
180 $time = $db->newSelectQueryBuilder()
181 ->select( 'rev_timestamp' )
182 ->from( 'revision' )
183 ->where( [ 'rev_actor' => $this->actorNormalization->findActorId( $user, $db ) ] )
184 ->orderBy( 'rev_timestamp', $sortOrder )
185 ->caller( __METHOD__ )
186 ->fetchField();
187
188 if ( !$time ) {
189 return false; // no edits
190 }
191
192 return ConvertibleTimestamp::convert( TS_MW, $time );
193 }
194
199 public function clearUserEditCache( UserIdentity $user ) {
200 if ( !$user->isRegistered() ) {
201 return;
202 }
203
204 $cacheKey = $this->getCacheKey( $user );
205 unset( $this->userEditCountCache[ $cacheKey ] );
206 }
207
214 public function setCachedUserEditCount( UserIdentity $user, int $editCount ) {
215 if ( !$user->isRegistered() ) {
216 throw new InvalidArgumentException( __METHOD__ . ' with an anonymous user' );
217 }
218
219 $cacheKey = $this->getCacheKey( $user );
220 $this->userEditCountCache[ $cacheKey ] = $editCount;
221 }
222
223 private function getCacheKey( UserIdentity $user ): string {
224 if ( !$user->isRegistered() ) {
225 throw new InvalidArgumentException( 'Cannot prepare cache key for an anonymous user' );
226 }
227
228 $wikiId = $user->getWikiId();
229 $userId = $user->getId( $wikiId );
230 $isRemoteWiki = ( $wikiId !== UserIdentity::LOCAL ) && !WikiMap::isCurrentWikiId( $wikiId );
231 if ( $isRemoteWiki ) {
232 return $wikiId . ':u' . $userId;
233 }
234 return 'u' . $userId;
235 }
236}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Defer callable updates to run later in the PHP process.
Handles increment the edit count for a given set of users.
Handle enqueueing of background jobs.
Track info about user edit counts and timings.
getLatestEditTimestamp(UserIdentity $user, int $flags=IDBAccessObject::READ_NORMAL)
Get the user's latest edit timestamp.
getUserEditCount(UserIdentity $user)
Get a user's edit count from the user_editcount field, falling back to initialize.
getFirstEditTimestamp(UserIdentity $user, int $flags=IDBAccessObject::READ_NORMAL)
Get the user's first edit timestamp.
incrementUserEditCount(UserIdentity $user)
Schedule a job to increase a user's edit count.
initializeUserEditCount(UserIdentity $user)
clearUserEditCache(UserIdentity $user)
__construct(ActorNormalization $actorNormalization, IConnectionProvider $dbProvider, JobQueueGroup $jobQueueGroup)
setCachedUserEditCount(UserIdentity $user, int $editCount)
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
Job that initializes an user's edit count.
Build SELECT queries with a fluent interface.
getWikiId()
Get the ID of the wiki this page belongs to.
Service for dealing with the actor table.
Interface for objects representing user identity.
isRegistered()
This must be equivalent to getId() != 0 and is provided for code readability.
getId( $wikiId=self::LOCAL)
Provide primary and replica IDatabase connections.
Interface for database access objects.