Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.04% covered (warning)
89.04%
65 / 73
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
UnregisterParticipantCommand
89.04% covered (warning)
89.04%
65 / 73
75.00% covered (warning)
75.00%
6 / 8
22.64
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
 unregisterIfAllowed
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 authorizeUnregistration
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 checkIsUnregistrationAllowed
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 unregisterUnsafe
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 removeParticipantsIfAllowed
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 authorizeRemoveParticipants
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 removeParticipantsUnsafe
73.08% covered (warning)
73.08%
19 / 26
0.00% covered (danger)
0.00%
0 / 1
5.49
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\CampaignEvents\Participants;
6
7use MediaWiki\Extension\CampaignEvents\Event\ExistingEventRegistration;
8use MediaWiki\Extension\CampaignEvents\EventPage\EventPageCacheUpdater;
9use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsCentralUserLookup;
10use MediaWiki\Extension\CampaignEvents\MWEntity\CentralUser;
11use MediaWiki\Extension\CampaignEvents\MWEntity\ICampaignsAuthority;
12use MediaWiki\Extension\CampaignEvents\MWEntity\UserNotGlobalException;
13use MediaWiki\Extension\CampaignEvents\Permissions\PermissionChecker;
14use MediaWiki\Extension\CampaignEvents\TrackingTool\TrackingToolEventWatcher;
15use MediaWiki\Permissions\PermissionStatus;
16use StatusValue;
17use UnexpectedValueException;
18
19class UnregisterParticipantCommand {
20    public const SERVICE_NAME = 'CampaignEventsUnregisterParticipantCommand';
21
22    public const CAN_UNREGISTER = 0;
23    public const CANNOT_UNREGISTER_DELETED = 1;
24    public const DO_NOT_INVERT_USERS = false;
25    public const INVERT_USERS = true;
26
27    private ParticipantsStore $participantsStore;
28    private PermissionChecker $permissionChecker;
29    private CampaignsCentralUserLookup $centralUserLookup;
30    private EventPageCacheUpdater $eventPageCacheUpdater;
31    private TrackingToolEventWatcher $trackingToolEventWatcher;
32
33    /**
34     * @param ParticipantsStore $participantsStore
35     * @param PermissionChecker $permissionChecker
36     * @param CampaignsCentralUserLookup $centralUserLookup
37     * @param EventPageCacheUpdater $eventPageCacheUpdater
38     * @param TrackingToolEventWatcher $trackingToolEventWatcher
39     */
40    public function __construct(
41        ParticipantsStore $participantsStore,
42        PermissionChecker $permissionChecker,
43        CampaignsCentralUserLookup $centralUserLookup,
44        EventPageCacheUpdater $eventPageCacheUpdater,
45        TrackingToolEventWatcher $trackingToolEventWatcher
46    ) {
47        $this->participantsStore = $participantsStore;
48        $this->permissionChecker = $permissionChecker;
49        $this->centralUserLookup = $centralUserLookup;
50        $this->eventPageCacheUpdater = $eventPageCacheUpdater;
51        $this->trackingToolEventWatcher = $trackingToolEventWatcher;
52    }
53
54    /**
55     * @param ExistingEventRegistration $registration
56     * @param ICampaignsAuthority $performer
57     * @return StatusValue Good if everything went fine, fatal with errors otherwise. If good, the value shall be
58     *   true if the user was actively registered, and false if they unregistered or had never registered.
59     *   Will be a PermissionStatus for permissions-related errors.
60     */
61    public function unregisterIfAllowed(
62        ExistingEventRegistration $registration,
63        ICampaignsAuthority $performer
64    ): StatusValue {
65        $permStatus = $this->authorizeUnregistration( $performer );
66        if ( !$permStatus->isGood() ) {
67            return $permStatus;
68        }
69        return $this->unregisterUnsafe( $registration, $performer );
70    }
71
72    /**
73     * @param ICampaignsAuthority $performer
74     * @return PermissionStatus
75     */
76    private function authorizeUnregistration( ICampaignsAuthority $performer ): PermissionStatus {
77        if ( !$this->permissionChecker->userCanCancelRegistration( $performer ) ) {
78            return PermissionStatus::newFatal( 'campaignevents-unregister-not-allowed' );
79        }
80        return PermissionStatus::newGood();
81    }
82
83    /**
84     * @param ExistingEventRegistration $registration
85     * @return int self::CAN_UNREGISTER or one of the self::CANNOT_UNREGISTER_* contants.
86     */
87    public static function checkIsUnregistrationAllowed( ExistingEventRegistration $registration ): int {
88        if ( $registration->getDeletionTimestamp() !== null ) {
89            return self::CANNOT_UNREGISTER_DELETED;
90        }
91        return self::CAN_UNREGISTER;
92    }
93
94    /**
95     * @param ExistingEventRegistration $registration
96     * @param ICampaignsAuthority $performer
97     * @return StatusValue
98     */
99    public function unregisterUnsafe(
100        ExistingEventRegistration $registration,
101        ICampaignsAuthority $performer
102    ): StatusValue {
103        $unregistrationAllowedVal = self::checkIsUnregistrationAllowed( $registration );
104        if ( $unregistrationAllowedVal !== self::CAN_UNREGISTER ) {
105            if ( $unregistrationAllowedVal === self::CANNOT_UNREGISTER_DELETED ) {
106                return StatusValue::newFatal( 'campaignevents-unregister-registration-deleted' );
107            }
108            throw new UnexpectedValueException( "Unexpected val $unregistrationAllowedVal" );
109        }
110
111        try {
112            $centralUser = $this->centralUserLookup->newFromAuthority( $performer );
113        } catch ( UserNotGlobalException $_ ) {
114            return StatusValue::newFatal( 'campaignevents-unregister-need-central-account' );
115        }
116
117        $trackingToolValidationStatus = $this->trackingToolEventWatcher->validateParticipantsRemoved(
118            $registration,
119            [ $centralUser ],
120            false
121        );
122        if ( !$trackingToolValidationStatus->isGood() ) {
123            return $trackingToolValidationStatus;
124        }
125
126        $modified = $this->participantsStore->removeParticipantFromEvent( $registration->getID(), $centralUser );
127
128        if ( $modified ) {
129            $this->trackingToolEventWatcher->onParticipantsRemoved(
130                $registration,
131                [ $centralUser ],
132                false
133            );
134            $this->eventPageCacheUpdater->purgeEventPageCache( $registration );
135        }
136
137        return StatusValue::newGood( $modified );
138    }
139
140    /**
141     * @param ExistingEventRegistration $registration
142     * @param CentralUser[]|null $users Array of users, if null remove all
143     * @param ICampaignsAuthority $performer
144     * @param bool $invertUsers self::DO_NOT_INVERT_USERS or self::INVERT_USERS
145     * @return StatusValue The StatusValue's "value" property has the number of participants removed
146     */
147    public function removeParticipantsIfAllowed(
148        ExistingEventRegistration $registration,
149        ?array $users,
150        ICampaignsAuthority $performer,
151        bool $invertUsers
152    ): StatusValue {
153        $permStatus = $this->authorizeRemoveParticipants( $registration, $performer );
154        if ( !$permStatus->isGood() ) {
155            return $permStatus;
156        }
157
158        return $this->removeParticipantsUnsafe( $registration, $users, $invertUsers );
159    }
160
161    /**
162     * @param ExistingEventRegistration $registration
163     * @param ICampaignsAuthority $performer
164     * @return PermissionStatus
165     */
166    private function authorizeRemoveParticipants(
167        ExistingEventRegistration $registration,
168        ICampaignsAuthority $performer
169    ): PermissionStatus {
170        $registrationID = $registration->getID();
171        if ( !$this->permissionChecker->userCanRemoveParticipants( $performer, $registrationID ) ) {
172            return PermissionStatus::newFatal( 'campaignevents-unregister-participants-permission-denied' );
173        }
174        return PermissionStatus::newGood();
175    }
176
177    /**
178     * @param ExistingEventRegistration $registration
179     * @param CentralUser[]|null $users Array of users, if null remove all
180     * @param bool $invertUsers self::DO_NOT_INVERT_USERS or self::INVERT_USERS
181     * @return StatusValue The StatusValue's "value" property has the number of participants removed
182     */
183    public function removeParticipantsUnsafe(
184        ExistingEventRegistration $registration,
185        ?array $users,
186        bool $invertUsers
187    ): StatusValue {
188        $unregistrationAllowedVal = self::checkIsUnregistrationAllowed( $registration );
189
190        if ( $unregistrationAllowedVal === self::CANNOT_UNREGISTER_DELETED ) {
191            return StatusValue::newFatal( 'campaignevents-unregister-participants-registration-deleted' );
192        }
193        if ( $unregistrationAllowedVal !== self::CAN_UNREGISTER ) {
194            throw new UnexpectedValueException( "Unexpected val $unregistrationAllowedVal" );
195        }
196
197        $trackingToolValidationStatus = $this->trackingToolEventWatcher->validateParticipantsRemoved(
198            $registration,
199            $users,
200            $invertUsers
201        );
202        if ( !$trackingToolValidationStatus->isGood() ) {
203            return $trackingToolValidationStatus;
204        }
205
206        $eventID = $registration->getID();
207        $removedParticipants = $this->participantsStore->removeParticipantsFromEvent(
208            $eventID,
209            $users,
210            $invertUsers
211        );
212
213        if ( $removedParticipants > 0 ) {
214            $this->trackingToolEventWatcher->onParticipantsRemoved(
215                $registration,
216                $users,
217                $invertUsers
218            );
219            $this->eventPageCacheUpdater->purgeEventPageCache( $registration );
220        }
221
222        return StatusValue::newGood( $removedParticipants );
223    }
224}