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