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