Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
43.69% covered (danger)
43.69%
45 / 103
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiManageMentorList
43.69% covered (danger)
43.69%
45 / 103
0.00% covered (danger)
0.00%
0 / 5
126.85
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
 execute
68.18% covered (warning)
68.18%
45 / 66
0.00% covered (danger)
0.00%
0 / 1
32.89
 isWriteMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 needsToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\Api;
4
5use GrowthExperiments\MentorDashboard\MentorTools\IMentorWeights;
6use GrowthExperiments\MentorDashboard\MentorTools\MentorStatusManager;
7use GrowthExperiments\Mentorship\Provider\IMentorWriter;
8use GrowthExperiments\Mentorship\Provider\MentorProvider;
9use LogicException;
10use MediaWiki\Api\ApiBase;
11use MediaWiki\Api\ApiMain;
12use MediaWiki\ParamValidator\TypeDef\UserDef;
13use Wikimedia\ParamValidator\ParamValidator;
14use Wikimedia\Rdbms\IDBAccessObject;
15
16class ApiManageMentorList extends ApiBase {
17
18    private MentorProvider $mentorProvider;
19    private IMentorWriter $mentorWriter;
20    private MentorStatusManager $mentorStatusManager;
21
22    public function __construct(
23        ApiMain $mainModule,
24        string $moduleName,
25        MentorProvider $mentorProvider,
26        IMentorWriter $mentorWriter,
27        MentorStatusManager $mentorStatusManager
28    ) {
29        parent::__construct( $mainModule, $moduleName );
30
31        $this->mentorProvider = $mentorProvider;
32        $this->mentorWriter = $mentorWriter;
33        $this->mentorStatusManager = $mentorStatusManager;
34    }
35
36    /**
37     * @inheritDoc
38     */
39    public function execute() {
40        $block = $this->getUser()->getBlock( IDBAccessObject::READ_LATEST );
41        if ( $block && $block->isSitewide() ) {
42            $this->dieBlocked( $block );
43        }
44
45        // if the user is not a mentor, require enrollasmentor or managementors; the if is here to
46        // allow users to remove themselves, even after they lost the ability to enroll themselves.
47        if ( !$this->mentorProvider->isMentor( $this->getUser() ) ) {
48            $this->checkUserRightsAny( [ 'enrollasmentor', 'managementors' ] );
49        }
50
51        $params = $this->extractRequestParams();
52
53        if ( $params['username'] !== null ) {
54            $this->checkUserRightsAny( 'managementors' );
55            // despite the name, $params['username'] is converted to an UserIdentity
56            // via UserDef::PARAM_RETURN_OBJECT in getAllowedParams().
57            $mentorUser = $params['username'];
58        } else {
59            $mentorUser = $this->getUser();
60        }
61
62        $mentor = $this->mentorProvider->newMentorFromUserIdentity( $mentorUser );
63
64        if ( $params['message'] !== null ) {
65            $mentor->setIntroText( $params['message'] !== '' ? $params['message'] : null );
66        }
67        if ( $params['weight'] !== null ) {
68            $mentor->setWeight( (int)$params['weight'] );
69        }
70
71        // ensure awaytimestamp is provided when isaway=true
72        if ( $params['isaway'] && $params['awaytimestamp'] === null ) {
73            $this->dieWithError(
74                'growthexperiments-api-managementors-error-no-away-timestamp',
75                'away-timestamp'
76            );
77        }
78
79        switch ( $params['geaction'] ) {
80            case 'add':
81                $statusValue = $this->mentorWriter->addMentor(
82                    $mentor,
83                    $this->getUser(),
84                    $params['summary']
85                );
86                break;
87            case 'change':
88                $statusValue = $this->mentorWriter->changeMentor(
89                    $mentor,
90                    $this->getUser(),
91                    $params['summary']
92                );
93                break;
94            case 'remove':
95                $statusValue = $this->mentorWriter->removeMentor(
96                    $mentor,
97                    $this->getUser(),
98                    $params['summary']
99                );
100                break;
101            default:
102                // this should never happen, unless getAllowedParams is wrong
103                throw new LogicException( 'Invalid geaction passed validation' );
104        }
105
106        if ( !$statusValue->isOK() ) {
107            $this->dieStatus( $statusValue );
108        }
109
110        if ( $params['geaction'] !== 'remove' ) {
111            if ( $params['isaway'] ) {
112                $result = $this->mentorStatusManager->markMentorAsAwayTimestamp(
113                    $mentorUser,
114                    $params['awaytimestamp']
115                );
116                if ( !$result->isOK() ) {
117                    $this->dieStatus( $result );
118                }
119            } else {
120                $this->mentorStatusManager->markMentorAsActive( $mentorUser );
121            }
122        }
123
124        $rawBackTs = $this->mentorStatusManager->getMentorBackTimestamp( $mentorUser );
125        $this->getResult()->addValue( null, $this->getModuleName(), [
126            'status' => 'ok',
127            'mentor' => [
128                'message' => $mentor->hasCustomIntroText() ? $mentor->getIntroText() : null,
129                'weight' => $mentor->getWeight(),
130                'awayTimestamp' => $rawBackTs,
131                'awayTimestampHuman' => $rawBackTs !== null ? $this->getContext()
132                    ->getLanguage()->date( $rawBackTs, true ) : null,
133
134                // NOTE: Legacy attribute, weight provides the same info.
135                'automaticallyAssigned' => $mentor->getWeight() !== IMentorWeights::WEIGHT_NONE,
136            ]
137        ] );
138    }
139
140    /**
141     * @inheritDoc
142     */
143    public function isWriteMode() {
144        return true;
145    }
146
147    /**
148     * @inheritDoc
149     */
150    public function needsToken() {
151        return 'csrf';
152    }
153
154    /**
155     * @inheritDoc
156     */
157    protected function getAllowedParams() {
158        return [
159            'geaction' => [
160                ParamValidator::PARAM_REQUIRED => true,
161                ParamValidator::PARAM_TYPE => [
162                    'add',
163                    'change',
164                    'remove',
165                ]
166            ],
167            'message' => [
168                ParamValidator::PARAM_TYPE => 'string'
169            ],
170            'weight' => [
171                ParamValidator::PARAM_TYPE => array_map( 'strval', IMentorWeights::WEIGHTS )
172            ],
173            'isaway' => [
174                ParamValidator::PARAM_TYPE => 'boolean',
175            ],
176            'awaytimestamp' => [
177                ParamValidator::PARAM_TYPE => 'timestamp',
178            ],
179            'summary' => [
180                ParamValidator::PARAM_TYPE => 'string',
181                ParamValidator::PARAM_DEFAULT => '',
182            ],
183            'username' => [
184                ParamValidator::PARAM_TYPE => 'user',
185                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name' ],
186                UserDef::PARAM_RETURN_OBJECT => true,
187            ],
188        ];
189    }
190}