wikimedia/mediawiki-extensions-Translate

View on GitHub
src/TranslatorInterface/ReviewTranslationActionApi.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?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',
        ];
    }
}