Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.44% covered (warning)
81.44%
79 / 97
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractStructuredMentorWriter
81.44% covered (warning)
81.44%
79 / 97
71.43% covered (warning)
71.43%
5 / 7
20.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getMentorData
n/a
0 / 0
n/a
0 / 0
0
 serializeMentor
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 doSaveMentorData
n/a
0 / 0
n/a
0 / 0
0
 saveMentorData
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
4.25
 addMentor
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
4
 removeMentor
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 changeMentor
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 touchList
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace GrowthExperiments\Mentorship\Provider;
4
5use GrowthExperiments\Mentorship\Mentor;
6use GrowthExperiments\Mentorship\MentorshipSummaryCreator;
7use MediaWiki\User\UserFactory;
8use MediaWiki\User\UserIdentity;
9use MediaWiki\User\UserIdentityLookup;
10use Psr\Log\LoggerAwareTrait;
11use StatusValue;
12use Wikimedia\Rdbms\IDBAccessObject;
13
14/**
15 * This class writes to the structured mentor list and allows to add/remove
16 * mentors from the structured mentor list.
17 *
18 * Use StructuredMentorProvider to read the mentor list.
19 *
20 * This class uses WikiPageConfigWriter under the hood.
21 */
22abstract class AbstractStructuredMentorWriter implements IMentorWriter {
23    use LoggerAwareTrait;
24
25    /**
26     * Change tag to tag structured mentor list edits with
27     *
28     * @note Keep in sync with extension.json (GrowthMentorList provider of
29     * CommunityConfiguration).
30     */
31    public const CHANGE_TAG = 'mentor list change';
32    public const CONFIG_KEY = 'Mentors';
33
34    protected MentorProvider $mentorProvider;
35    protected UserIdentityLookup $userIdentityLookup;
36    protected UserFactory $userFactory;
37
38    /**
39     * @param MentorProvider $mentorProvider
40     * @param UserIdentityLookup $userIdentityLookup
41     * @param UserFactory $userFactory
42     */
43    public function __construct(
44        MentorProvider $mentorProvider,
45        UserIdentityLookup $userIdentityLookup,
46        UserFactory $userFactory
47    ) {
48        $this->mentorProvider = $mentorProvider;
49        $this->userIdentityLookup = $userIdentityLookup;
50        $this->userFactory = $userFactory;
51    }
52
53    /**
54     * Get list of mentors
55     *
56     * @return array
57     */
58    abstract protected function getMentorData(): array;
59
60    /**
61     * Serialize a Mentor object to an array
62     *
63     * @param Mentor $mentor
64     * @return array
65     */
66    public static function serializeMentor( Mentor $mentor ): array {
67        return [
68            'message' => $mentor->hasCustomIntroText() ? $mentor->getIntroText() : null,
69            'weight' => $mentor->getWeight(),
70        ];
71    }
72
73    abstract protected function doSaveMentorData(
74        array $mentorData,
75        string $summary,
76        UserIdentity $performer,
77        bool $bypassWarnings
78    ): StatusValue;
79
80    /**
81     * Save mentor data
82     *
83     * @param array $mentorData
84     * @param string $summary
85     * @param UserIdentity $performer
86     * @param bool $bypassWarnings
87     * @return StatusValue
88     */
89    protected 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( 'Mentor list contains an invalid user for ID {userId}', [
105                    'userId' => $mentorId,
106                ] );
107                continue;
108            }
109
110            $mentorData[$mentorId]['username'] = $mentorUser->getName();
111        }
112
113        return $this->doSaveMentorData(
114            $mentorData,
115            $summary,
116            $performer,
117            $bypassWarnings
118        );
119    }
120
121    /**
122     * @inheritDoc
123     */
124    public function addMentor(
125        Mentor $mentor,
126        UserIdentity $performer,
127        string $summary,
128        bool $bypassWarnings = false
129    ): StatusValue {
130        $mentorUserIdentity = $mentor->getUserIdentity();
131        if ( !$mentorUserIdentity->isRegistered()
132            || !$this->userFactory->newFromUserIdentity( $mentorUserIdentity )->isNamed()
133        ) {
134            return StatusValue::newFatal(
135                'growthexperiments-mentor-writer-error-anonymous-user',
136                $mentorUserIdentity->getName()
137            );
138        }
139
140        $mentorData = $this->getMentorData();
141        if ( array_key_exists( $mentorUserIdentity->getId(), $mentorData ) ) {
142            // we're trying to add someone who's already added
143            return StatusValue::newFatal(
144                'growthexperiments-mentor-writer-error-already-added',
145                $mentorUserIdentity->getName()
146            );
147        }
148        $mentorData[$mentorUserIdentity->getId()] = $this->serializeMentor( $mentor );
149
150        return $this->saveMentorData(
151            $mentorData,
152            MentorshipSummaryCreator::createAddSummary(
153                $performer,
154                $mentorUserIdentity,
155                $summary
156            ),
157            $performer,
158            $bypassWarnings
159        );
160    }
161
162    /**
163     * @inheritDoc
164     */
165    public function removeMentor(
166        Mentor $mentor,
167        UserIdentity $performer,
168        string $summary,
169        bool $bypassWarnings = false
170    ): StatusValue {
171        $mentorUserIdentity = $mentor->getUserIdentity();
172
173        $mentorData = $this->getMentorData();
174        if ( !array_key_exists( $mentorUserIdentity->getId(), $mentorData ) ) {
175            // we're trying to remove someone who isn't added
176            return StatusValue::newFatal(
177                'growthexperiments-mentor-writer-error-not-in-the-list',
178                $mentorUserIdentity->getName()
179            );
180        }
181        unset( $mentorData[$mentorUserIdentity->getId()] );
182
183        return $this->saveMentorData(
184            $mentorData,
185            MentorshipSummaryCreator::createRemoveSummary(
186                $performer,
187                $mentorUserIdentity,
188                $summary
189            ),
190            $performer,
191            $bypassWarnings
192        );
193    }
194
195    /**
196     * @inheritDoc
197     */
198    public function changeMentor(
199        Mentor $mentor,
200        UserIdentity $performer,
201        string $summary,
202        bool $bypassWarnings = false
203    ): StatusValue {
204        $mentorUserIdentity = $mentor->getUserIdentity();
205
206        $mentorData = $this->getMentorData();
207        if ( !array_key_exists( $mentorUserIdentity->getId(), $mentorData ) ) {
208            // we're trying to change someone who isn't added
209            return StatusValue::newFatal(
210                'growthexperiments-mentor-writer-error-not-in-the-list',
211                $mentorUserIdentity->getName()
212            );
213        }
214        $mentorData[$mentorUserIdentity->getId()] = $this->serializeMentor( $mentor );
215
216        return $this->saveMentorData(
217            $mentorData,
218            MentorshipSummaryCreator::createChangeSummary(
219                $performer,
220                $mentorUserIdentity,
221                $summary
222            ),
223            $performer,
224            $bypassWarnings
225        );
226    }
227
228    /**
229     * @inheritDoc
230     */
231    public function touchList( UserIdentity $performer, string $summary ): StatusValue {
232        $mentorData = $this->getMentorData();
233        foreach ( $mentorData as $mentorId => $mentorArr ) {
234            $mentorUser = $this->userIdentityLookup->getUserIdentityByUserId( $mentorId );
235            if ( !$mentorUser ) {
236                continue;
237            }
238            $mentorData[$mentorUser->getId()] = $this->serializeMentor(
239                $this->mentorProvider->newMentorFromUserIdentity( $mentorUser )
240            );
241        }
242        return $this->saveMentorData(
243            $mentorData,
244            $summary,
245            $performer,
246            true
247        );
248    }
249}