Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
22.45% |
11 / 49 |
|
10.00% |
1 / 10 |
CRAP | |
0.00% |
0 / 1 |
PersonalizedPraiseSettings | |
22.45% |
11 / 49 |
|
10.00% |
1 / 10 |
105.42 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
loadSettings | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
toArray | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
castToNullableInt | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getPraiseworthyConditions | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
getPraisingMessageDefaultSubject | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getPraisingMessageUserTitle | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getPraisingMessageTitle | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getPraisingMessageContent | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getNotificationsFrequency | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\MentorDashboard\PersonalizedPraise; |
4 | |
5 | use MediaWiki\Config\Config; |
6 | use MediaWiki\Content\WikitextContent; |
7 | use MediaWiki\Json\FormatJson; |
8 | use MediaWiki\Revision\RevisionLookup; |
9 | use MediaWiki\Revision\SlotRecord; |
10 | use MediaWiki\Title\Title; |
11 | use MediaWiki\Title\TitleFactory; |
12 | use MediaWiki\User\Options\UserOptionsLookup; |
13 | use MediaWiki\User\UserFactory; |
14 | use MediaWiki\User\UserIdentity; |
15 | use MessageLocalizer; |
16 | |
17 | /** |
18 | * Accessor for mentor's Personalized praise settings |
19 | * |
20 | * The settings are modified on the frontend (via action=options); this is why there are no |
21 | * setters available. |
22 | */ |
23 | class PersonalizedPraiseSettings { |
24 | |
25 | /** @var int */ |
26 | public const NOTIFY_NEVER = -1; |
27 | /** @var int */ |
28 | public const NOTIFY_IMMEDIATELY = 0; |
29 | |
30 | /** @var string Note: This is hardcoded on the client side as well */ |
31 | public const PREF_NAME = 'growthexperiments-personalized-praise-settings'; |
32 | /** @var string */ |
33 | public const USER_MESSAGE_PRELOAD_SUBPAGE_NAME = 'Personalized praise message'; |
34 | |
35 | private const SETTING_MESSAGE_SUBJECT = 'messageSubject'; |
36 | private const SETTING_MESSAGE_TEXT = 'messageText'; |
37 | private const SETTING_NOTIFICATION_FREQUENCY = 'notificationFrequency'; |
38 | |
39 | private Config $wikiConfig; |
40 | private MessageLocalizer $messageLocalizer; |
41 | private UserOptionsLookup $userOptionsLookup; |
42 | private UserFactory $userFactory; |
43 | private TitleFactory $titleFactory; |
44 | private RevisionLookup $revisionLookup; |
45 | |
46 | /** |
47 | * @param Config $wikiConfig |
48 | * @param MessageLocalizer $messageLocalizer |
49 | * @param UserOptionsLookup $userOptionsLookup |
50 | * @param UserFactory $userFactory |
51 | * @param TitleFactory $titleFactory |
52 | * @param RevisionLookup $revisionLookup |
53 | */ |
54 | public function __construct( |
55 | Config $wikiConfig, |
56 | MessageLocalizer $messageLocalizer, |
57 | UserOptionsLookup $userOptionsLookup, |
58 | UserFactory $userFactory, |
59 | TitleFactory $titleFactory, |
60 | RevisionLookup $revisionLookup |
61 | ) { |
62 | $this->wikiConfig = $wikiConfig; |
63 | $this->messageLocalizer = $messageLocalizer; |
64 | $this->userOptionsLookup = $userOptionsLookup; |
65 | $this->userFactory = $userFactory; |
66 | $this->titleFactory = $titleFactory; |
67 | $this->revisionLookup = $revisionLookup; |
68 | } |
69 | |
70 | /** |
71 | * @param UserIdentity $user |
72 | * @return array |
73 | */ |
74 | private function loadSettings( UserIdentity $user ): array { |
75 | return FormatJson::decode( $this->userOptionsLookup->getOption( |
76 | $user, self::PREF_NAME |
77 | ), true ) ?? []; |
78 | } |
79 | |
80 | /** |
81 | * @param UserIdentity $user |
82 | * @return array |
83 | */ |
84 | public function toArray( UserIdentity $user ): array { |
85 | $conditions = $this->getPraiseworthyConditions( $user ); |
86 | return array_merge( [ |
87 | self::SETTING_MESSAGE_SUBJECT => $this->getPraisingMessageDefaultSubject( $user ), |
88 | self::SETTING_MESSAGE_TEXT => $this->getPraisingMessageContent( $user ), |
89 | self::SETTING_NOTIFICATION_FREQUENCY => $this->getNotificationsFrequency( $user ), |
90 | ], $conditions->jsonSerialize() ); |
91 | } |
92 | |
93 | /** |
94 | * Simulation of (?int)$value |
95 | * |
96 | * @note Could be replaced with https://wiki.php.net/rfc/nullable-casting, if it ever becomes |
97 | * a part of PHP. |
98 | * @param mixed $value |
99 | * @return int|null Null if $value is null, otherwise (int)$value. |
100 | */ |
101 | private function castToNullableInt( $value ): ?int { |
102 | if ( $value === null ) { |
103 | return null; |
104 | } |
105 | |
106 | return (int)$value; |
107 | } |
108 | |
109 | /** |
110 | * Get praiseworthy conditions for a mentor |
111 | * |
112 | * Defaults are provided by Community configuration as: |
113 | * |
114 | * 1) GEPersonalizedPraiseMaxEdits: the maximum number of edits a mentee must have to be |
115 | * praiseworthy |
116 | * 2) GEPersonalizedPraiseMinEdits: the minimum number of edits a mentee must have to be |
117 | * praiseworthy |
118 | * 3) GEPersonalizedPraiseDays: to be considered praiseworthy, a mentee needs to make a |
119 | * certain number of edits (see above) in this amount of days to be praiseworthy. |
120 | * |
121 | * @param UserIdentity $user |
122 | * @return PraiseworthyConditions |
123 | */ |
124 | public function getPraiseworthyConditions( UserIdentity $user ): PraiseworthyConditions { |
125 | $settings = $this->loadSettings( $user ); |
126 | |
127 | return new PraiseworthyConditions( |
128 | (int)( $settings[PraiseworthyConditions::SETTING_MAX_EDITS] ?? |
129 | $this->wikiConfig->get( 'GEPersonalizedPraiseMaxEdits' ) ), |
130 | (int)( $settings[PraiseworthyConditions::SETTING_MIN_EDITS] ?? |
131 | $this->wikiConfig->get( 'GEPersonalizedPraiseMinEdits' ) ), |
132 | $this->castToNullableInt( $settings[PraiseworthyConditions::SETTING_MAX_REVERTS] ?? |
133 | $this->wikiConfig->get( 'GEPersonalizedPraiseMaxReverts' ) ), |
134 | (int)( $settings[PraiseworthyConditions::SETTING_DAYS] ?? |
135 | $this->wikiConfig->get( 'GEPersonalizedPraiseDays' ) ), |
136 | ); |
137 | } |
138 | |
139 | /** |
140 | * Get default subject for the praising message |
141 | * |
142 | * @param UserIdentity $user |
143 | * @return string |
144 | */ |
145 | public function getPraisingMessageDefaultSubject( UserIdentity $user ): string { |
146 | return $this->loadSettings( $user )[ self::SETTING_MESSAGE_SUBJECT ] ?? $this->messageLocalizer->msg( |
147 | 'growthexperiments-mentor-dashboard-personalized-praise-praise-message-title' |
148 | )->inContentLanguage()->text(); |
149 | } |
150 | |
151 | /** |
152 | * Get user subpage title where the user-specific praising message is stored |
153 | * |
154 | * Unlike all other options in PersonalizedPraiseSettings, the praising message is stored on |
155 | * a user subpage, to make use of native MediaWiki preloading. |
156 | * |
157 | * Title returned by this method is not guaranteed to be known. |
158 | * |
159 | * @param UserIdentity $user |
160 | * @return Title |
161 | */ |
162 | public function getPraisingMessageUserTitle( UserIdentity $user ): Title { |
163 | return $this->userFactory->newFromUserIdentity( $user ) |
164 | ->getUserPage() |
165 | ->getSubpage( self::USER_MESSAGE_PRELOAD_SUBPAGE_NAME ); |
166 | } |
167 | |
168 | /** |
169 | * Get title that currently defines where the praising message is defined |
170 | * |
171 | * If it exists, a subpage of the $user is returned. Otherwise, a page in NS_MEDIAWIKI is |
172 | * returned instead. |
173 | * |
174 | * @param UserIdentity $user |
175 | * @return Title |
176 | */ |
177 | public function getPraisingMessageTitle( UserIdentity $user ): Title { |
178 | $userSubpage = $this->getPraisingMessageUserTitle( $user ); |
179 | return $userSubpage->exists() ? $userSubpage : $this->titleFactory->newFromTextThrow( |
180 | 'growthexperiments-mentor-dashboard-personalized-praise-praise-message-message', |
181 | NS_MEDIAWIKI |
182 | ); |
183 | } |
184 | |
185 | /** |
186 | * @param UserIdentity $user |
187 | * @return string |
188 | */ |
189 | public function getPraisingMessageContent( UserIdentity $user ): string { |
190 | $revision = $this->revisionLookup->getRevisionByTitle( $this->getPraisingMessageTitle( $user ) ); |
191 | if ( !$revision ) { |
192 | return ''; |
193 | } |
194 | $content = $revision->getContent( SlotRecord::MAIN ); |
195 | if ( !$content instanceof WikitextContent ) { |
196 | return ''; |
197 | } |
198 | |
199 | return $content->getText(); |
200 | } |
201 | |
202 | /** |
203 | * How frequently should the user be notified about new praiseworthy mentees? |
204 | * |
205 | * @param UserIdentity $user |
206 | * @return int Minimum number of hours that needs to pass since the last notification |
207 | * (values specified by PersonalizedPraiseSettings::NOTIFY_* constants have special meaning) |
208 | */ |
209 | public function getNotificationsFrequency( UserIdentity $user ): int { |
210 | return $this->loadSettings( $user )[self::SETTING_NOTIFICATION_FREQUENCY] |
211 | ?? (int)$this->wikiConfig->get( 'GEPersonalizedPraiseDefaultNotificationsFrequency' ); |
212 | } |
213 | } |