Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 103
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
AddImageFeedbackHandler
0.00% covered (danger)
0.00%
0 / 103
0.00% covered (danger)
0.00%
0 / 5
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
306
 getParamSettings
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
2
 validate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 makeException
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\Rest\Handler;
4
5use ApiMessage;
6use GrowthExperiments\NewcomerTasks\AddImage\AddImageSubmissionHandler;
7use GrowthExperiments\NewcomerTasks\ConfigurationLoader\ConfigurationLoader;
8use GrowthExperiments\NewcomerTasks\TaskType\ImageRecommendationTaskType;
9use GrowthExperiments\Util;
10use MediaWiki\ParamValidator\TypeDef\TitleDef;
11use MediaWiki\Rest\HttpException;
12use MediaWiki\Rest\LocalizedHttpException;
13use MediaWiki\Rest\SimpleHandler;
14use MediaWiki\Rest\TokenAwareHandlerTrait;
15use MediaWiki\Rest\Validator\Validator;
16use MediaWiki\Revision\RevisionLookup;
17use MediaWiki\Status\Status;
18use MediaWiki\Title\TitleFactory;
19use Wikimedia\Message\MessageValue;
20use Wikimedia\ParamValidator\ParamValidator;
21
22/**
23 * Accept image recommendation feedback. Basically just a wrapper for AddImageSubmissionHandler.
24 */
25class AddImageFeedbackHandler extends SimpleHandler {
26
27    use TokenAwareHandlerTrait;
28
29    private TitleFactory $titleFactory;
30    private RevisionLookup $revisionLookup;
31    private ConfigurationLoader $configurationLoader;
32    private AddImageSubmissionHandler $addImageSubmissionHandler;
33
34    /**
35     * @param TitleFactory $titleFactory
36     * @param RevisionLookup $revisionLookup
37     * @param ConfigurationLoader $configurationLoader
38     * @param AddImageSubmissionHandler $addImageSubmissionHandler
39     */
40    public function __construct(
41        TitleFactory $titleFactory,
42        RevisionLookup $revisionLookup,
43        ConfigurationLoader $configurationLoader,
44        AddImageSubmissionHandler $addImageSubmissionHandler
45    ) {
46        $this->titleFactory = $titleFactory;
47        $this->revisionLookup = $revisionLookup;
48        $this->configurationLoader = $configurationLoader;
49        $this->addImageSubmissionHandler = $addImageSubmissionHandler;
50    }
51
52    public function run() {
53        $authority = $this->getAuthority();
54        $user = $authority->getUser();
55        $title = $this->titleFactory->newFromLinkTarget( $this->getValidatedParams()['title'] );
56        $data = $this->getValidatedBody() ?? [];
57        $editRevId = $data['editRevId'];
58
59        if ( !$authority->isNamed() ) {
60            throw $this->makeException( 'rest-permission-denied-anon', [], 401 );
61        }
62        if ( $data['accepted'] && !$editRevId ) {
63            throw $this->makeException( 'growthexperiments-addimage-feedback-accepted-editrevid' );
64        } elseif ( !$data['accepted'] && $editRevId ) {
65            throw $this->makeException( 'growthexperiments-addimage-feedback-rejected-editrevid' );
66        }
67        if ( $data['accepted'] && ( $data['caption'] ?? null ) === null ) {
68            throw $this->makeException( 'growthexperiments-addimage-feedback-accepted-caption' );
69        }
70
71        $editRev = null;
72        if ( $editRevId !== null ) {
73            $editRev = $this->revisionLookup->getRevisionById( $editRevId );
74            if ( !$editRev ) {
75                throw $this->makeException( 'growthexperiments-addimage-feedback-revid-nonexistent', [ $editRev ] );
76            } elseif ( $editRev->getPageId() !== $title->getArticleID() ) {
77                throw $this->makeException(
78                    'growthexperiments-addimage-feedback-invalid-revid-wrong-page',
79                    [ $editRevId, $title->getPrefixedText() ]
80                );
81            }
82        }
83        // The handler doesn't use the base revid so make things simple and just fake it.
84        if ( $editRev ) {
85            $baseRev = $this->revisionLookup->getPreviousRevision( $editRev );
86            $baseRevId = $baseRev ? $baseRev->getId() : null;
87        } else {
88            $baseRevId = $title->getLatestRevID();
89        }
90
91        // TODO support section images
92        $allTaskTypes = $this->configurationLoader->getTaskTypes()
93            + $this->configurationLoader->getDisabledTaskTypes();
94        $taskType = $allTaskTypes['image-recommendation'] ?? null;
95        if ( !( $taskType instanceof ImageRecommendationTaskType ) ) {
96            throw new LocalizedHttpException(
97                new MessageValue( 'growthexperiments-newcomertasks-invalid-tasktype', [ 'image-recommendation' ] )
98            );
99        }
100
101        $status = $this->addImageSubmissionHandler->validate(
102            $taskType, $title->toPageIdentity(), $user, $baseRevId, $data
103        );
104        if ( $status->isGood() ) {
105            $status->merge( $this->addImageSubmissionHandler->handle(
106                $taskType, $title->toPageIdentity(), $user, $baseRevId, $editRevId, $data
107            ), true );
108        }
109        if ( !$status->isGood() ) {
110            Util::logStatus( $status );
111            // There isn't any good way to convert a Message into a MessageValue.
112            $errorKey = ( new ApiMessage( Status::wrap( $status )->getMessage() ) )->getApiCode();
113            throw new HttpException(
114                Status::wrap( $status )->getMessage( false, false, 'en' )->text(),
115                $status->isOK() ? 400 : 500,
116                [ 'errorKey' => $errorKey ]
117            );
118        }
119
120        return [ 'success' => true ] + $status->getValue();
121    }
122
123    /** @inheritDoc */
124    public function getParamSettings() {
125        return [
126            'title' => [
127                self::PARAM_SOURCE => 'path',
128                ParamValidator::PARAM_TYPE => 'title',
129                ParamValidator::PARAM_REQUIRED => true,
130                TitleDef::PARAM_RETURN_OBJECT => true,
131                TitleDef::PARAM_MUST_EXIST => true,
132            ],
133            'editRevId' => [
134                self::PARAM_SOURCE => 'body',
135                ParamValidator::PARAM_TYPE => 'integer',
136                ParamValidator::PARAM_REQUIRED => false,
137            ],
138            'filename' => [
139                self::PARAM_SOURCE => 'body',
140                ParamValidator::PARAM_TYPE => 'string',
141                ParamValidator::PARAM_REQUIRED => true,
142            ],
143            'accepted' => [
144                self::PARAM_SOURCE => 'body',
145                ParamValidator::PARAM_TYPE => 'boolean',
146                ParamValidator::PARAM_REQUIRED => true,
147            ],
148            'reasons' => [
149                self::PARAM_SOURCE => 'body',
150                ParamValidator::PARAM_TYPE => AddImageSubmissionHandler::REJECTION_REASONS,
151                ParamValidator::PARAM_ISMULTI => true,
152                ParamValidator::PARAM_REQUIRED => false,
153            ],
154            'caption' => [
155                self::PARAM_SOURCE => 'body',
156                ParamValidator::PARAM_TYPE => 'string',
157                ParamValidator::PARAM_REQUIRED => false,
158            ],
159            'sectionTitle' => [
160                self::PARAM_SOURCE => 'body',
161                ParamValidator::PARAM_TYPE => 'string',
162                ParamValidator::PARAM_REQUIRED => false,
163            ],
164            'sectionNumber' => [
165                self::PARAM_SOURCE => 'body',
166                ParamValidator::PARAM_TYPE => 'integer',
167                ParamValidator::PARAM_REQUIRED => false,
168            ],
169        ] + $this->getTokenParamDefinition();
170    }
171
172    /**
173     * @inheritDoc
174     */
175    public function validate( Validator $restValidator ) {
176        parent::validate( $restValidator );
177        $this->validateToken();
178    }
179
180    private function makeException( string $messageKey, array $params = [], int $errorCode = 400 ) {
181        return new LocalizedHttpException( new MessageValue( $messageKey, $params ), $errorCode );
182    }
183
184}