Refactoring: * Break out the database access part of UserOptionsManager to a separate class hierarchy implementing interface UserOptionsStore. It's basically a key/key/string-value store, very simple. The complex parts of user options storage remain in UserOptionsManager. * Bundle the UserOptionsManager caches into a per-user cache object. I was adding a couple more and it was getting tedious. Start integrating GlobalPreferences with UserOptionsManager: * Have an array of stores. There's always a local store, and extensions can add stores via an attribute. * Add $global parameter to UserOptionsManager::setOption(), allowing this method to update or override global options. * Rename loadOptionsFromDb to loadOptionsFromStore. * Move the local override feature from GlobalPreferences to core. Bug: T323076 Change-Id: Ib3623b723557c819bc0ffdf21a4ffcb070eb298b
96 lines
2.5 KiB
PHP
96 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\User\Options;
|
|
|
|
use DBAccessObjectUtils;
|
|
use MediaWiki\User\UserIdentity;
|
|
use Wikimedia\Rdbms\IConnectionProvider;
|
|
|
|
class LocalUserOptionsStore implements UserOptionsStore {
|
|
private IConnectionProvider $dbProvider;
|
|
|
|
/** @var array[] Cached options for each user, by user ID */
|
|
private $optionsFromDb;
|
|
|
|
public function __construct( IConnectionProvider $dbProvider ) {
|
|
$this->dbProvider = $dbProvider;
|
|
}
|
|
|
|
public function fetch(
|
|
UserIdentity $user,
|
|
int $recency
|
|
): array {
|
|
$dbr = DBAccessObjectUtils::getDBFromRecency( $this->dbProvider, $recency );
|
|
$res = $dbr->newSelectQueryBuilder()
|
|
->select( [ 'up_property', 'up_value' ] )
|
|
->from( 'user_properties' )
|
|
->where( [ 'up_user' => $user->getId() ] )
|
|
->recency( $recency )
|
|
->caller( __METHOD__ )->fetchResultSet();
|
|
|
|
$options = [];
|
|
foreach ( $res as $row ) {
|
|
$options[$row->up_property] = (string)$row->up_value;
|
|
}
|
|
|
|
$this->optionsFromDb[$user->getId()] = $options;
|
|
return $options;
|
|
}
|
|
|
|
public function store( UserIdentity $user, array $updates ) {
|
|
$oldOptions = $this->optionsFromDb[ $user->getId() ]
|
|
?? $this->fetch( $user, \IDBAccessObject::READ_LATEST );
|
|
$newOptions = $oldOptions;
|
|
$keysToDelete = [];
|
|
$rowsToInsert = [];
|
|
foreach ( $updates as $key => $value ) {
|
|
if ( !UserOptionsManager::isValueEqual(
|
|
$value, $oldOptions[$key] ?? null )
|
|
) {
|
|
// Update by deleting and reinserting
|
|
if ( array_key_exists( $key, $oldOptions ) ) {
|
|
$keysToDelete[] = $key;
|
|
unset( $newOptions[$key] );
|
|
}
|
|
if ( $value !== null ) {
|
|
$truncValue = mb_strcut( $value, 0,
|
|
UserOptionsManager::MAX_BYTES_OPTION_VALUE );
|
|
$rowsToInsert[] = [
|
|
'up_user' => $user->getId(),
|
|
'up_property' => $key,
|
|
'up_value' => $truncValue,
|
|
];
|
|
$newOptions[$key] = $truncValue;
|
|
}
|
|
}
|
|
}
|
|
if ( !count( $keysToDelete ) && !count( $rowsToInsert ) ) {
|
|
// Nothing to do
|
|
return false;
|
|
}
|
|
|
|
// Do the DELETE
|
|
$dbw = $this->dbProvider->getPrimaryDatabase();
|
|
if ( $keysToDelete ) {
|
|
$dbw->newDeleteQueryBuilder()
|
|
->deleteFrom( 'user_properties' )
|
|
->where( [ 'up_user' => $user->getId() ] )
|
|
->andWhere( [ 'up_property' => $keysToDelete ] )
|
|
->caller( __METHOD__ )->execute();
|
|
}
|
|
if ( $rowsToInsert ) {
|
|
// Insert the new preference rows
|
|
$dbw->newInsertQueryBuilder()
|
|
->insertInto( 'user_properties' )
|
|
->ignore()
|
|
->rows( $rowsToInsert )
|
|
->caller( __METHOD__ )->execute();
|
|
}
|
|
|
|
// Update cache
|
|
$this->optionsFromDb[$user->getId()] = $newOptions;
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|