mediawiki-extensions-Thanks/includes/Storage/LogStore.php
Marcin Kostrzewski e483b844c9 API: Refactor to match modern code standards
I've moved all API classes into a separate folder,
as I felt like grouping modules improves readability.
APIs themselves had storage logic which I extracted and put
in another folder. I was originally going with an interface
to the storage to allow for other storage methods than
log entries, but the code was too tightly coupled with it,
so I've left that for another day. Added dependency injection
for all services and used ServiceOptions for config vars.

Bug: T337002
Change-Id: Ie8a1f435d635e1d0e1286f673bfe96cc4fdfe4fe
2023-05-20 17:57:26 +03:00

115 lines
3.6 KiB
PHP

<?php
namespace MediaWiki\Extension\Thanks\Storage;
use DatabaseLogEntry;
use ExtensionRegistry;
use ManualLogEntry;
use MediaWiki\CheckUser\Hooks;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\Thanks\Storage\Exceptions\InvalidLogType;
use MediaWiki\Extension\Thanks\Storage\Exceptions\LogDeleted;
use MediaWiki\User\ActorNormalization;
use User;
use Wikimedia\Rdbms\IConnectionProvider;
/**
* Manages the storage for Thank events.
* Thanks are stored as logs with relations between users.
* Each thank action should have an unique ID generated by callers.
*/
class LogStore {
protected IConnectionProvider $conn;
protected ActorNormalization $actorNormalization;
public const CONSTRUCTOR_OPTIONS = [ 'ThanksLogging', 'ThanksAllowedLogTypes' ];
protected ServiceOptions $serviceOptions;
public function __construct(
IConnectionProvider $conn,
ActorNormalization $actorNormalization,
ServiceOptions $serviceOptions
) {
$this->conn = $conn;
$this->actorNormalization = $actorNormalization;
$serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
$this->serviceOptions = $serviceOptions;
}
/**
* @param User $user The user performing the thanks (and the log entry).
* @param User $recipient The target of the thanks (and the log entry).
* @param string $uniqueId A unique Id to identify the event being thanked for, to use
* when checking for duplicate thanks
*/
public function thank( User $user, User $recipient, string $uniqueId ): void {
if ( !$this->serviceOptions->get( 'ThanksLogging' ) ) {
return;
}
$logEntry = new ManualLogEntry( 'thanks', 'thank' );
$logEntry->setPerformer( $user );
$logEntry->setRelations( [ 'thankid' => $uniqueId ] );
$target = $recipient->getUserPage();
$logEntry->setTarget( $target );
$logId = $logEntry->insert();
$logEntry->publish( $logId, 'udp' );
if ( ExtensionRegistry::getInstance()->isLoaded( 'CheckUser' ) ) {
$recentChange = $logEntry->getRecentChange();
// TODO: This should be done in a separate hook handler
Hooks::updateCheckUserData( $recentChange );
}
}
/**
* This checks the log_search data.
*
* @param User $thanker The user sending the thanks.
* @param string $uniqueId The identifier for the thanks.
* @return bool Whether thanks has already been sent
*/
public function haveThanked( User $thanker, string $uniqueId ): bool {
// TODO: Figure out why it's not getting the data from a replica
$dbw = $this->conn->getPrimaryDatabase();
$thankerActor = $this->actorNormalization->acquireActorId( $thanker, $dbw );
return (bool)$dbw->newSelectQueryBuilder()
->select( 'ls_value' )
->from( 'log_search' )
->join( 'logging', null, [ 'ls_log_id=log_id' ] )
->where(
[
'log_actor' => $thankerActor,
'ls_field' => 'thankid',
'ls_value' => $uniqueId,
]
)
->caller( __METHOD__ )
->fetchRow();
}
/**
* @throws InvalidLogType
* @throws LogDeleted
*/
public function getLogEntryFromId( int $logId ): ?DatabaseLogEntry {
$logEntry = DatabaseLogEntry::newFromId( $logId, $this->conn->getPrimaryDatabase() );
if ( !$logEntry ) {
return null;
}
// Make sure this log type is allowed.
$allowedLogTypes = $this->serviceOptions->get( 'ThanksAllowedLogTypes' );
if ( !in_array( $logEntry->getType(), $allowedLogTypes )
&& !in_array( $logEntry->getType() . '/' . $logEntry->getSubtype(), $allowedLogTypes ) ) {
throw new InvalidLogType( $logEntry->getType() );
}
// Don't permit thanks if any part of the log entry is deleted.
if ( $logEntry->getDeleted() ) {
throw new LogDeleted();
}
return $logEntry;
}
}