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