Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
80.58% |
83 / 103 |
|
75.00% |
6 / 8 |
CRAP | |
0.00% |
0 / 1 |
StructuredMentorWriter | |
80.58% |
83 / 103 |
|
75.00% |
6 / 8 |
22.93 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
serializeMentor | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
saveMentorData | |
62.50% |
10 / 16 |
|
0.00% |
0 / 1 |
4.84 | |||
isBlocked | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
addMentor | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
4 | |||
removeMentor | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
changeMentor | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
touchList | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\Mentorship\Provider; |
4 | |
5 | use GrowthExperiments\Config\Validation\StructuredMentorListValidator; |
6 | use GrowthExperiments\Config\WikiPageConfigLoader; |
7 | use GrowthExperiments\Config\WikiPageConfigWriterFactory; |
8 | use GrowthExperiments\Mentorship\Mentor; |
9 | use GrowthExperiments\Mentorship\MentorshipSummaryCreator; |
10 | use IDBAccessObject; |
11 | use MediaWiki\Title\Title; |
12 | use MediaWiki\User\UserFactory; |
13 | use MediaWiki\User\UserIdentity; |
14 | use MediaWiki\User\UserIdentityLookup; |
15 | use StatusValue; |
16 | |
17 | /** |
18 | * This class writes to the structured mentor list and allows to add/remove |
19 | * mentors from the structured mentor list. |
20 | * |
21 | * Use StructuredMentorProvider to read the mentor list. |
22 | * |
23 | * This class uses WikiPageConfigWriter under the hood. |
24 | */ |
25 | class StructuredMentorWriter implements IMentorWriter { |
26 | use GetMentorDataTrait; |
27 | |
28 | /** @var string Change tag to tag structured mentor list edits with */ |
29 | public const CHANGE_TAG = 'mentor list change'; |
30 | |
31 | /** @var string */ |
32 | public const CONFIG_KEY = 'Mentors'; |
33 | |
34 | private MentorProvider $mentorProvider; |
35 | private UserIdentityLookup $userIdentityLookup; |
36 | private UserFactory $userFactory; |
37 | private WikiPageConfigWriterFactory $configWriterFactory; |
38 | private StructuredMentorListValidator $mentorListValidator; |
39 | |
40 | /** |
41 | * @param MentorProvider $mentorProvider |
42 | * @param UserIdentityLookup $userIdentityLookup |
43 | * @param UserFactory $userFactory |
44 | * @param WikiPageConfigLoader $configLoader |
45 | * @param WikiPageConfigWriterFactory $configWriterFactory |
46 | * @param StructuredMentorListValidator $mentorListValidator |
47 | * @param Title $mentorList |
48 | */ |
49 | public function __construct( |
50 | MentorProvider $mentorProvider, |
51 | UserIdentityLookup $userIdentityLookup, |
52 | UserFactory $userFactory, |
53 | WikiPageConfigLoader $configLoader, |
54 | WikiPageConfigWriterFactory $configWriterFactory, |
55 | StructuredMentorListValidator $mentorListValidator, |
56 | Title $mentorList |
57 | ) { |
58 | $this->mentorProvider = $mentorProvider; |
59 | $this->userIdentityLookup = $userIdentityLookup; |
60 | $this->userFactory = $userFactory; |
61 | $this->configLoader = $configLoader; |
62 | $this->configWriterFactory = $configWriterFactory; |
63 | $this->mentorListValidator = $mentorListValidator; |
64 | $this->mentorList = $mentorList; |
65 | } |
66 | |
67 | /** |
68 | * Serialize a Mentor object to an array |
69 | * |
70 | * @param Mentor $mentor |
71 | * @return array |
72 | */ |
73 | public static function serializeMentor( Mentor $mentor ): array { |
74 | return [ |
75 | 'message' => $mentor->hasCustomIntroText() ? $mentor->getIntroText() : null, |
76 | 'weight' => $mentor->getWeight(), |
77 | ]; |
78 | } |
79 | |
80 | /** |
81 | * Wrapper around WikiPageConfigWriter to save all mentor data |
82 | * |
83 | * @param array $mentorData |
84 | * @param string $summary |
85 | * @param UserIdentity $performer |
86 | * @param bool $bypassWarnings Should warnings raised by the validator stop the operation? |
87 | * @return StatusValue |
88 | */ |
89 | private function saveMentorData( |
90 | array $mentorData, |
91 | string $summary, |
92 | UserIdentity $performer, |
93 | bool $bypassWarnings |
94 | ): StatusValue { |
95 | // check if $performer is not blocked from the mentor list page |
96 | if ( $this->isBlocked( $performer, IDBAccessObject::READ_LATEST ) ) { |
97 | return StatusValue::newFatal( 'growthexperiments-mentor-writer-error-blocked' ); |
98 | } |
99 | |
100 | // add 'username' key for readability (T331444) |
101 | foreach ( $mentorData as $mentorId => $_ ) { |
102 | $mentorUser = $this->userIdentityLookup->getUserIdentityByUserId( $mentorId ); |
103 | if ( !$mentorUser ) { |
104 | $this->logger->warning( $this->mentorList->getText() . ' contains an invalid user for ID {userId}', [ |
105 | 'userId' => $mentorId, |
106 | 'namespace' => $this->mentorList->getNamespace(), |
107 | 'title' => $this->mentorList->getText() |
108 | ] ); |
109 | continue; |
110 | } |
111 | |
112 | $mentorData[$mentorId]['username'] = $mentorUser->getName(); |
113 | } |
114 | |
115 | $configWriter = $this->configWriterFactory |
116 | ->newWikiPageConfigWriter( $this->mentorList, $performer ); |
117 | $configWriter->setVariable( self::CONFIG_KEY, $mentorData ); |
118 | return $configWriter->save( $summary, false, self::CHANGE_TAG, $bypassWarnings ); |
119 | } |
120 | |
121 | /** |
122 | * @inheritDoc |
123 | */ |
124 | public function isBlocked( |
125 | UserIdentity $performer, |
126 | int $freshness = IDBAccessObject::READ_NORMAL |
127 | ): bool { |
128 | $block = $this->userFactory->newFromUserIdentity( $performer )->getBlock( $freshness ); |
129 | return $block && $block->appliesToTitle( $this->mentorList ); |
130 | } |
131 | |
132 | /** |
133 | * @inheritDoc |
134 | */ |
135 | public function addMentor( |
136 | Mentor $mentor, |
137 | UserIdentity $performer, |
138 | string $summary, |
139 | bool $bypassWarnings = false |
140 | ): StatusValue { |
141 | $mentorUserIdentity = $mentor->getUserIdentity(); |
142 | if ( !$mentorUserIdentity->isRegistered() |
143 | || !$this->userFactory->newFromUserIdentity( $mentorUserIdentity )->isNamed() |
144 | ) { |
145 | return StatusValue::newFatal( |
146 | 'growthexperiments-mentor-writer-error-anonymous-user', |
147 | $mentorUserIdentity->getName() |
148 | ); |
149 | } |
150 | |
151 | $mentorData = $this->getMentorData(); |
152 | if ( array_key_exists( $mentorUserIdentity->getId(), $mentorData ) ) { |
153 | // we're trying to add someone who's already added |
154 | return StatusValue::newFatal( |
155 | 'growthexperiments-mentor-writer-error-already-added', |
156 | $mentorUserIdentity->getName() |
157 | ); |
158 | } |
159 | $mentorData[$mentorUserIdentity->getId()] = $this->serializeMentor( $mentor ); |
160 | |
161 | return $this->saveMentorData( |
162 | $mentorData, |
163 | MentorshipSummaryCreator::createAddSummary( |
164 | $performer, |
165 | $mentorUserIdentity, |
166 | $summary |
167 | ), |
168 | $performer, |
169 | $bypassWarnings |
170 | ); |
171 | } |
172 | |
173 | /** |
174 | * @inheritDoc |
175 | */ |
176 | public function removeMentor( |
177 | Mentor $mentor, |
178 | UserIdentity $performer, |
179 | string $summary, |
180 | bool $bypassWarnings = false |
181 | ): StatusValue { |
182 | $mentorUserIdentity = $mentor->getUserIdentity(); |
183 | |
184 | $mentorData = $this->getMentorData(); |
185 | if ( !array_key_exists( $mentorUserIdentity->getId(), $mentorData ) ) { |
186 | // we're trying to remove someone who isn't added |
187 | return StatusValue::newFatal( |
188 | 'growthexperiments-mentor-writer-error-not-in-the-list', |
189 | $mentorUserIdentity->getName() |
190 | ); |
191 | } |
192 | unset( $mentorData[$mentorUserIdentity->getId()] ); |
193 | |
194 | return $this->saveMentorData( |
195 | $mentorData, |
196 | MentorshipSummaryCreator::createRemoveSummary( |
197 | $performer, |
198 | $mentorUserIdentity, |
199 | $summary |
200 | ), |
201 | $performer, |
202 | $bypassWarnings |
203 | ); |
204 | } |
205 | |
206 | /** |
207 | * @inheritDoc |
208 | */ |
209 | public function changeMentor( |
210 | Mentor $mentor, |
211 | UserIdentity $performer, |
212 | string $summary, |
213 | bool $bypassWarnings = false |
214 | ): StatusValue { |
215 | $mentorUserIdentity = $mentor->getUserIdentity(); |
216 | |
217 | $mentorData = $this->getMentorData(); |
218 | if ( !array_key_exists( $mentorUserIdentity->getId(), $mentorData ) ) { |
219 | // we're trying to change someone who isn't added |
220 | return StatusValue::newFatal( |
221 | 'growthexperiments-mentor-writer-error-not-in-the-list', |
222 | $mentorUserIdentity->getName() |
223 | ); |
224 | } |
225 | $mentorData[$mentorUserIdentity->getId()] = $this->serializeMentor( $mentor ); |
226 | |
227 | return $this->saveMentorData( |
228 | $mentorData, |
229 | MentorshipSummaryCreator::createChangeSummary( |
230 | $performer, |
231 | $mentorUserIdentity, |
232 | $summary |
233 | ), |
234 | $performer, |
235 | $bypassWarnings |
236 | ); |
237 | } |
238 | |
239 | /** |
240 | * @inheritDoc |
241 | */ |
242 | public function touchList( UserIdentity $performer, string $summary ): StatusValue { |
243 | $mentorData = $this->getMentorData(); |
244 | foreach ( $mentorData as $mentorId => $mentorArr ) { |
245 | $mentorUser = $this->userIdentityLookup->getUserIdentityByUserId( $mentorId ); |
246 | if ( !$mentorUser ) { |
247 | continue; |
248 | } |
249 | $mentorData[$mentorUser->getId()] = $this->serializeMentor( |
250 | $this->mentorProvider->newMentorFromUserIdentity( $mentorUser ) |
251 | ); |
252 | } |
253 | return $this->saveMentorData( |
254 | $mentorData, |
255 | $summary, |
256 | $performer, |
257 | true |
258 | ); |
259 | } |
260 | } |