wiki.techinc.nl/includes/user/UserEditTracker.php
libraryupgrader 5357695270 build: Updating dependencies
composer:
* mediawiki/mediawiki-codesniffer: 36.0.0 → 37.0.0
  The following sniffs now pass and were enabled:
  * Generic.ControlStructures.InlineControlStructure
  * MediaWiki.PHPUnit.AssertCount.NotUsed

npm:
* svgo: 2.3.0 → 2.3.1
  * https://npmjs.com/advisories/1754 (CVE-2021-33587)

Change-Id: I2a9bbee2fecbf7259876d335f565ece4b3622426
2021-07-22 03:36:05 +00:00

227 lines
5.5 KiB
PHP

<?php
namespace MediaWiki\User;
use ActorMigration;
use DeferredUpdates;
use InvalidArgumentException;
use JobQueueGroup;
use UserEditCountInitJob;
use UserEditCountUpdate;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Timestamp\ConvertibleTimestamp;
/**
* Track info about user edit counts and timings
*
* @since 1.35
*
* @author DannyS712
*/
class UserEditTracker {
private const FIRST_EDIT = 1;
private const LATEST_EDIT = 2;
/** @var ActorMigration */
private $actorMigration;
/** @var ILoadBalancer */
private $loadBalancer;
/** @var JobQueueGroup */
private $jobQueueGroup;
/**
* @var array
*
* Mapping of user id to edit count for caching
* To avoid using non-sequential numerical keys, keys are in the form: `u⧼user id⧽`
*/
private $userEditCountCache = [];
/**
* @param ActorMigration $actorMigration
* @param ILoadBalancer $loadBalancer
* @param JobQueueGroup $jobQueueGroup
*/
public function __construct(
ActorMigration $actorMigration,
ILoadBalancer $loadBalancer,
JobQueueGroup $jobQueueGroup
) {
$this->actorMigration = $actorMigration;
$this->loadBalancer = $loadBalancer;
$this->jobQueueGroup = $jobQueueGroup;
}
/**
* Get a user's edit count from the user_editcount field, falling back to initialize
*
* @param UserIdentity $user
* @return int|null Null for anonymous users
*/
public function getUserEditCount( UserIdentity $user ): ?int {
if ( !$user->isRegistered() ) {
return null;
}
$userId = $user->getId();
$cacheKey = 'u' . (string)$userId;
if ( isset( $this->userEditCountCache[ $cacheKey ] ) ) {
return $this->userEditCountCache[ $cacheKey ];
}
$dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
$count = $dbr->selectField(
'user',
'user_editcount',
[ 'user_id' => $userId ],
__METHOD__
);
if ( $count === null ) {
// it has not been initialized. do so.
$count = $this->initializeUserEditCount( $user );
}
$this->userEditCountCache[ $cacheKey ] = $count;
return $count;
}
/**
* @internal For use in UserEditCountUpdate class
* @param UserIdentity $user
* @return int
*/
public function initializeUserEditCount( UserIdentity $user ): int {
$dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
$actorWhere = $this->actorMigration->getWhere( $dbr, 'rev_user', $user );
$count = (int)$dbr->selectField(
[ 'revision' ] + $actorWhere['tables'],
'COUNT(*)',
[ $actorWhere['conds'] ],
__METHOD__,
[],
$actorWhere['joins']
);
// Defer updating the edit count via a job (T259719)
$this->jobQueueGroup->push( new UserEditCountInitJob( [
'userId' => $user->getId(),
'editCount' => $count,
] ) );
return $count;
}
/**
* Schedule a job to increase a user's edit count
*
* @since 1.37
* @param UserIdentity $user
*/
public function incrementUserEditCount( UserIdentity $user ) {
if ( !$user->isRegistered() ) {
// Anonymous users don't have edit counts
return;
}
DeferredUpdates::addUpdate(
new UserEditCountUpdate( $user, 1 ),
DeferredUpdates::POSTSEND
);
}
/**
* Get the user's first edit timestamp
*
* @param UserIdentity $user
* @return string|false Timestamp of first edit, or false for non-existent/anonymous user
* accounts.
*/
public function getFirstEditTimestamp( UserIdentity $user ) {
return $this->getUserEditTimestamp( $user, self::FIRST_EDIT );
}
/**
* Get the user's latest edit timestamp
*
* @param UserIdentity $user
* @return string|false Timestamp of latest edit, or false for non-existent/anonymous user
* accounts.
*/
public function getLatestEditTimestamp( UserIdentity $user ) {
return $this->getUserEditTimestamp( $user, self::LATEST_EDIT );
}
/**
* Get the timestamp of a user's edit, either their first or latest
*
* @param UserIdentity $user
* @param int $type either self::FIRST_EDIT or ::LATEST_EDIT
* @return string|false Timestamp of edit, or false for non-existent/anonymous user accounts.
*/
private function getUserEditTimestamp( UserIdentity $user, int $type ) {
if ( $user->getId() === 0 ) {
return false; // anonymous user
}
$dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
$actorWhere = $this->actorMigration->getWhere( $dbr, 'rev_user', $user );
$tsField = isset( $actorWhere['tables']['temp_rev_user'] ) // SCHEMA_COMPAT_READ_TEMP
? 'revactor_timestamp' : 'rev_timestamp';
$sortOrder = ( $type === self::FIRST_EDIT ) ? 'ASC' : 'DESC';
$time = $dbr->selectField(
[ 'revision' ] + $actorWhere['tables'],
$tsField,
[ $actorWhere['conds'] ],
__METHOD__,
[ 'ORDER BY' => "$tsField $sortOrder" ],
$actorWhere['joins']
);
if ( !$time ) {
return false; // no edits
}
return ConvertibleTimestamp::convert( TS_MW, $time );
}
/**
* @internal For use by User::clearInstanceCache()
* @param UserIdentity $user
*/
public function clearUserEditCache( UserIdentity $user ) {
if ( !$user->isRegistered() ) {
return;
}
$userId = $user->getId();
$cacheKey = 'u' . (string)$userId;
unset( $this->userEditCountCache[ $cacheKey ] );
}
/**
* @internal For use by User::loadFromRow() and tests
* @param UserIdentity $user
* @param int $editCount
* @throws InvalidArgumentException If the user is not registered
*/
public function setCachedUserEditCount( UserIdentity $user, int $editCount ) {
if ( !$user->isRegistered() ) {
throw new InvalidArgumentException( __METHOD__ . ' with an anonymous user' );
}
$userId = $user->getId();
$cacheKey = 'u' . (string)$userId;
$this->userEditCountCache[ $cacheKey ] = $editCount;
}
}