Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.01% covered (warning)
87.01%
67 / 77
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReviewTranslationActionApi
87.01% covered (warning)
87.01%
67 / 77
50.00% covered (danger)
50.00%
4 / 8
18.71
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 execute
85.00% covered (warning)
85.00%
17 / 20
0.00% covered (danger)
0.00%
0 / 1
5.08
 doReview
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
2
 getReviewBlockers
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
6.13
 isWriteMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 needsToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedParams
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TranslatorInterface;
5
6use ApiBase;
7use ApiMain;
8use ManualLogEntry;
9use MediaWiki\Extension\Translate\HookRunner;
10use MediaWiki\Extension\Translate\MessageLoading\MessageHandle;
11use MediaWiki\Revision\RevisionLookup;
12use MediaWiki\Revision\RevisionRecord;
13use Status;
14use TitleFormatter;
15use User;
16use Wikimedia\ParamValidator\ParamValidator;
17use Wikimedia\Rdbms\ILoadBalancer;
18
19/**
20 * API module for marking translations as reviewed
21 * @author Niklas Laxström
22 * @license GPL-2.0-or-later
23 * @ingroup API TranslateAPI
24 */
25class ReviewTranslationActionApi extends ApiBase {
26    protected static $right = 'translate-messagereview';
27    /** @var RevisionLookup */
28    private $revisionLookup;
29    /** @var TitleFormatter */
30    private $titleFormatter;
31    /** @var ILoadBalancer */
32    private $loadBalancer;
33    /** @var HookRunner */
34    private $hookRunner;
35
36    public function __construct(
37        ApiMain $main,
38        string $moduleName,
39        RevisionLookup $revisionLookup,
40        TitleFormatter $titleFormatter,
41        ILoadBalancer $loadBalancer,
42        HookRunner $hookRunner
43    ) {
44        parent::__construct( $main, $moduleName );
45        $this->revisionLookup = $revisionLookup;
46        $this->titleFormatter = $titleFormatter;
47        $this->loadBalancer = $loadBalancer;
48        $this->hookRunner = $hookRunner;
49    }
50
51    public function execute() {
52        $this->checkUserRightsAny( self::$right );
53
54        $params = $this->extractRequestParams();
55
56        $revRecord = $this->revisionLookup->getRevisionById( $params['revision'] );
57        if ( !$revRecord ) {
58            $this->dieWithError( [ 'apierror-nosuchrevid', $params['revision'] ], 'invalidrevision' );
59        }
60
61        $status = $this->getReviewBlockers( $this->getUser(), $revRecord );
62        if ( !$status->isGood() ) {
63            if ( $status->hasMessage( 'blocked' ) ) {
64                $this->dieBlocked( $this->getUser()->getBlock() );
65            } else {
66                $this->dieStatus( $status );
67            }
68        }
69
70        $ok = $this->doReview( $this->getUser(), $revRecord );
71        if ( !$ok ) {
72            $this->addWarning( 'apiwarn-translate-alreadyreviewedbyyou' );
73        }
74
75        $prefixedText = $this->titleFormatter->getPrefixedText( $revRecord->getPageAsLinkTarget() );
76        $output = [ 'review' => [
77            'title' => $prefixedText,
78            'pageid' => $revRecord->getPageId(),
79            'revision' => $revRecord->getId()
80        ] ];
81
82        $this->getResult()->addValue( null, $this->getModuleName(), $output );
83    }
84
85    /**
86     * Executes the real stuff. No checks done!
87     * @return bool whether the action was recorded.
88     */
89    private function doReview( User $user, RevisionRecord $revRecord ): bool {
90        $dbw = $this->loadBalancer->getConnection( DB_PRIMARY );
91        $table = 'translate_reviews';
92        $row = [
93            'trr_user' => $user->getId(),
94            'trr_page' => $revRecord->getPageId(),
95            'trr_revision' => $revRecord->getId(),
96        ];
97        $options = [ 'IGNORE' ];
98        $dbw->insert( $table, $row, __METHOD__, $options );
99
100        if ( !$dbw->affectedRows() ) {
101            return false;
102        }
103
104        $title = $revRecord->getPageAsLinkTarget();
105
106        $entry = new ManualLogEntry( 'translationreview', 'message' );
107        $entry->setPerformer( $user );
108        $entry->setTarget( $title );
109        $entry->setParameters( [
110            '4::revision' => $revRecord->getId(),
111        ] );
112
113        $logid = $entry->insert();
114        $entry->publish( $logid );
115
116        $handle = new MessageHandle( $title );
117        $this->hookRunner->onTranslateEventTranslationReview( $handle );
118
119        return true;
120    }
121
122    /**
123     * Validates review action by checking permissions and other things.
124     * @return Status Contains error key that describes the review blocker.
125     */
126    private function getReviewBlockers( User $user, RevisionRecord $revRecord ): Status {
127        if ( !$user->isAllowed( self::$right ) ) {
128            return Status::newFatal( 'apierror-permissiondenied-generic' );
129        }
130
131        if ( $user->getBlock() ) {
132            return Status::newFatal( 'blocked' );
133        }
134
135        $title = $revRecord->getPageAsLinkTarget();
136        $handle = new MessageHandle( $title );
137        if ( !$handle->isValid() ) {
138            return Status::newFatal( 'apierror-translate-unknownmessage' );
139        }
140
141        if ( $user->equals( $revRecord->getUser() ) ) {
142            return Status::newFatal( 'apierror-translate-owntranslation' );
143        }
144
145        if ( $handle->isFuzzy() ) {
146            return Status::newFatal( 'apierror-translate-fuzzymessage' );
147        }
148
149        return Status::newGood();
150    }
151
152    public function isWriteMode(): bool {
153        return true;
154    }
155
156    public function needsToken(): string {
157        return 'csrf';
158    }
159
160    protected function getAllowedParams(): array {
161        return [
162            'revision' => [
163                ParamValidator::PARAM_TYPE => 'integer',
164                ParamValidator::PARAM_REQUIRED => true,
165            ],
166            'token' => [
167                ParamValidator::PARAM_TYPE => 'string',
168                ParamValidator::PARAM_REQUIRED => true,
169            ],
170        ];
171    }
172
173    protected function getExamplesMessages(): array {
174        return [
175            'action=translationreview&revision=1&token=foo'
176                => 'apihelp-translationreview-example-1',
177        ];
178    }
179}