src/TranslatorInterface/ReviewTranslationActionApi.php
<?php
declare( strict_types = 1 );
namespace MediaWiki\Extension\Translate\TranslatorInterface;
use ApiBase;
use ApiMain;
use ManualLogEntry;
use MediaWiki\Extension\Translate\HookRunner;
use MediaWiki\Extension\Translate\MessageLoading\MessageHandle;
use MediaWiki\Revision\RevisionLookup;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Status\Status;
use MediaWiki\Title\TitleFormatter;
use MediaWiki\User\User;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\Rdbms\ILoadBalancer;
/**
* API module for marking translations as reviewed
* @author Niklas Laxström
* @license GPL-2.0-or-later
* @ingroup API TranslateAPI
*/
class ReviewTranslationActionApi extends ApiBase {
/** @var string */
protected static $right = 'translate-messagereview';
private RevisionLookup $revisionLookup;
private TitleFormatter $titleFormatter;
private ILoadBalancer $loadBalancer;
private HookRunner $hookRunner;
public function __construct(
ApiMain $main,
string $moduleName,
RevisionLookup $revisionLookup,
TitleFormatter $titleFormatter,
ILoadBalancer $loadBalancer,
HookRunner $hookRunner
) {
parent::__construct( $main, $moduleName );
$this->revisionLookup = $revisionLookup;
$this->titleFormatter = $titleFormatter;
$this->loadBalancer = $loadBalancer;
$this->hookRunner = $hookRunner;
}
public function execute() {
$this->checkUserRightsAny( self::$right );
$params = $this->extractRequestParams();
$revRecord = $this->revisionLookup->getRevisionById( $params['revision'] );
if ( !$revRecord ) {
$this->dieWithError( [ 'apierror-nosuchrevid', $params['revision'] ], 'invalidrevision' );
}
$status = $this->getReviewBlockers( $this->getUser(), $revRecord );
if ( !$status->isGood() ) {
if ( $status->hasMessage( 'blocked' ) ) {
$this->dieBlocked( $this->getUser()->getBlock() );
} else {
$this->dieStatus( $status );
}
}
$ok = $this->doReview( $this->getUser(), $revRecord );
if ( !$ok ) {
$this->addWarning( 'apiwarn-translate-alreadyreviewedbyyou' );
}
$prefixedText = $this->titleFormatter->getPrefixedText( $revRecord->getPageAsLinkTarget() );
$output = [ 'review' => [
'title' => $prefixedText,
'pageid' => $revRecord->getPageId(),
'revision' => $revRecord->getId()
] ];
$this->getResult()->addValue( null, $this->getModuleName(), $output );
}
/**
* Executes the real stuff. No checks done!
* @return bool whether the action was recorded.
*/
private function doReview( User $user, RevisionRecord $revRecord ): bool {
$dbw = $this->loadBalancer->getConnection( DB_PRIMARY );
$dbw->newInsertQueryBuilder()
->insertInto( 'translate_reviews' )
->ignore()
->row( [
'trr_user' => $user->getId(),
'trr_page' => $revRecord->getPageId(),
'trr_revision' => $revRecord->getId(),
] )
->caller( __METHOD__ )
->execute();
if ( !$dbw->affectedRows() ) {
return false;
}
$title = $revRecord->getPageAsLinkTarget();
$entry = new ManualLogEntry( 'translationreview', 'message' );
$entry->setPerformer( $user );
$entry->setTarget( $title );
$entry->setParameters( [
'4::revision' => $revRecord->getId(),
] );
$logid = $entry->insert();
$entry->publish( $logid );
$handle = new MessageHandle( $title );
$this->hookRunner->onTranslateEventTranslationReview( $handle );
return true;
}
/**
* Validates review action by checking permissions and other things.
* @return Status Contains error key that describes the review blocker.
*/
private function getReviewBlockers( User $user, RevisionRecord $revRecord ): Status {
if ( !$user->isAllowed( self::$right ) ) {
return Status::newFatal( 'apierror-permissiondenied-generic' );
}
if ( $user->getBlock() ) {
return Status::newFatal( 'blocked' );
}
$title = $revRecord->getPageAsLinkTarget();
$handle = new MessageHandle( $title );
if ( !$handle->isValid() ) {
return Status::newFatal( 'apierror-translate-unknownmessage' );
}
if ( $user->equals( $revRecord->getUser() ) ) {
return Status::newFatal( 'apierror-translate-owntranslation' );
}
if ( $handle->isFuzzy() ) {
return Status::newFatal( 'apierror-translate-fuzzymessage' );
}
return Status::newGood();
}
public function isWriteMode(): bool {
return true;
}
public function needsToken(): string {
return 'csrf';
}
protected function getAllowedParams(): array {
return [
'revision' => [
ParamValidator::PARAM_TYPE => 'integer',
ParamValidator::PARAM_REQUIRED => true,
],
'token' => [
ParamValidator::PARAM_TYPE => 'string',
ParamValidator::PARAM_REQUIRED => true,
],
];
}
protected function getExamplesMessages(): array {
return [
'action=translationreview&revision=1&token=foo'
=> 'apihelp-translationreview-example-1',
];
}
}