Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.77% covered (success)
95.77%
68 / 71
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
RegisterForEventHandler
95.77% covered (success)
95.77%
68 / 71
66.67% covered (warning)
66.67%
4 / 6
14
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
 validate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 run
95.45% covered (success)
95.45%
42 / 44
0.00% covered (danger)
0.00%
0 / 1
8
 getParamSettings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBodyValidator
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 getBodyParams
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\CampaignEvents\Rest;
6
7use MediaWiki\DAO\WikiAwareEntity;
8use MediaWiki\Extension\CampaignEvents\Event\Store\IEventLookup;
9use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsCentralUserLookup;
10use MediaWiki\Extension\CampaignEvents\MWEntity\MWAuthorityProxy;
11use MediaWiki\Extension\CampaignEvents\MWEntity\UserNotGlobalException;
12use MediaWiki\Extension\CampaignEvents\Participants\ParticipantsStore;
13use MediaWiki\Extension\CampaignEvents\Participants\RegisterParticipantCommand;
14use MediaWiki\Extension\CampaignEvents\Questions\EventQuestionsRegistry;
15use MediaWiki\Extension\CampaignEvents\Questions\InvalidAnswerDataException;
16use MediaWiki\Permissions\PermissionStatus;
17use MediaWiki\Rest\LocalizedHttpException;
18use MediaWiki\Rest\Response;
19use MediaWiki\Rest\SimpleHandler;
20use MediaWiki\Rest\TokenAwareHandlerTrait;
21use MediaWiki\Rest\Validator\JsonBodyValidator;
22use MediaWiki\Rest\Validator\UnsupportedContentTypeBodyValidator;
23use MediaWiki\Rest\Validator\Validator;
24use Wikimedia\Message\MessageValue;
25use Wikimedia\ParamValidator\ParamValidator;
26
27class RegisterForEventHandler extends SimpleHandler {
28    use EventIDParamTrait;
29    use TokenAwareHandlerTrait;
30    use FailStatusUtilTrait;
31
32    private IEventLookup $eventLookup;
33    private RegisterParticipantCommand $registerParticipantCommand;
34    private EventQuestionsRegistry $eventQuestionsRegistry;
35    private ParticipantsStore $participantsStore;
36    private CampaignsCentralUserLookup $centralUserLookup;
37
38    public function __construct(
39        IEventLookup $eventLookup,
40        RegisterParticipantCommand $registerParticipantCommand,
41        EventQuestionsRegistry $eventQuestionsRegistry,
42        ParticipantsStore $participantsStore,
43        CampaignsCentralUserLookup $centralUserLookup
44    ) {
45        $this->eventLookup = $eventLookup;
46        $this->registerParticipantCommand = $registerParticipantCommand;
47        $this->eventQuestionsRegistry = $eventQuestionsRegistry;
48        $this->participantsStore = $participantsStore;
49        $this->centralUserLookup = $centralUserLookup;
50    }
51
52    /**
53     * @inheritDoc
54     */
55    public function validate( Validator $restValidator ): void {
56        parent::validate( $restValidator );
57        $this->validateToken();
58    }
59
60    /**
61     * @param int $eventID
62     * @return Response
63     */
64    protected function run( int $eventID ): Response {
65        $body = $this->getValidatedBody() ?? [];
66        $performer = new MWAuthorityProxy( $this->getAuthority() );
67        $eventRegistration = $this->getRegistrationOrThrow( $this->eventLookup, $eventID );
68        try {
69            $centralUser = $this->centralUserLookup->newFromAuthority( $performer );
70            $curParticipantData = $this->participantsStore->getEventParticipant( $eventID, $centralUser );
71            $curAnswers = $curParticipantData ? $curParticipantData->getAnswers() : [];
72        } catch ( UserNotGlobalException $_ ) {
73            // Silently ignore it for now, it's going to be thrown when attempting to register
74            $curAnswers = [];
75        }
76        $allowedQuestions = EventQuestionsRegistry::getParticipantQuestionsToShow(
77            $eventRegistration->getParticipantQuestions(),
78            $curAnswers
79        );
80
81        $privateFlag = $body['is_private'] ?
82            RegisterParticipantCommand::REGISTRATION_PRIVATE :
83            RegisterParticipantCommand::REGISTRATION_PUBLIC;
84
85        $wikiID = $eventRegistration->getPage()->getWikiId();
86        if ( $wikiID !== WikiAwareEntity::LOCAL ) {
87            throw new LocalizedHttpException(
88                MessageValue::new( 'campaignevents-rest-register-for-event-nonlocal-error-message' )
89                    ->params( $wikiID ),
90                400
91            );
92        }
93        try {
94            $answers = $this->eventQuestionsRegistry->extractUserAnswersAPI(
95                $body['answers'] ?? [],
96                $allowedQuestions
97            );
98        } catch ( InvalidAnswerDataException $e ) {
99            throw new LocalizedHttpException(
100                MessageValue::new( 'campaignevents-rest-register-invalid-answer' )
101                    ->params( $e->getQuestionName() ),
102                400
103            );
104        }
105        $status = $this->registerParticipantCommand->registerIfAllowed(
106            $eventRegistration,
107            $performer,
108            $privateFlag,
109            $answers
110        );
111        if ( !$status->isGood() ) {
112            $httpStatus = $status instanceof PermissionStatus ? 403 : 400;
113            $this->exitWithStatus( $status, $httpStatus );
114        }
115        return $this->getResponseFactory()->createJson( [
116            'modified' => $status->getValue()
117        ] );
118    }
119
120    /**
121     * @inheritDoc
122     */
123    public function getParamSettings(): array {
124        return $this->getIDParamSetting();
125    }
126
127    /**
128     * @inheritDoc
129     */
130    public function getBodyValidator( $contentType ) {
131        if ( $contentType !== 'application/json' ) {
132            return new UnsupportedContentTypeBodyValidator( $contentType );
133        }
134
135        return new JsonBodyValidator(
136            array_merge(
137                $this->getTokenParamDefinition(),
138                $this->getBodyParams()
139            )
140        );
141    }
142
143    /**
144     * @return array
145     */
146    protected function getBodyParams(): array {
147        return [
148            'is_private' => [
149                static::PARAM_SOURCE => 'body',
150                ParamValidator::PARAM_TYPE => 'boolean',
151                ParamValidator::PARAM_REQUIRED => true,
152            ],
153            'answers' => [
154                static::PARAM_SOURCE => 'body',
155                ParamValidator::PARAM_TYPE => 'array',
156            ],
157        ];
158    }
159}