Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 183 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
SpecialDisplayNotificationsConfiguration | |
0.00% |
0 / 183 |
|
0.00% |
0 / 10 |
992 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
12 | |||
outputConfiguration | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
outputCheckMatrix | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
outputNotificationsInCategories | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
6 | |||
outputNotificationsInSections | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
20 | |||
outputAvailability | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
20 | |||
getNewUserPreferenceOverrides | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
outputEnabledDefault | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
110 | |||
outputMandatory | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Notifications\Special; |
4 | |
5 | use MediaWiki\Extension\Notifications\AttributeManager; |
6 | use MediaWiki\Extension\Notifications\Hooks as EchoHooks; |
7 | use MediaWiki\Html\Html; |
8 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
9 | use MediaWiki\User\Options\UserOptionsManager; |
10 | use MediaWiki\User\User; |
11 | use OOUIHTMLForm; |
12 | |
13 | class SpecialDisplayNotificationsConfiguration extends UnlistedSpecialPage { |
14 | /** |
15 | * AttributeManager to access notification configuration |
16 | * |
17 | * @var AttributeManager |
18 | */ |
19 | protected $attributeManager; |
20 | |
21 | /** |
22 | * Category names, mapping internal name to HTML-formatted name |
23 | * |
24 | * @var string[] |
25 | */ |
26 | protected $categoryNames; |
27 | |
28 | // Should be one mapping text (friendly) name to internal name, but there |
29 | // is no friendly name |
30 | /** |
31 | * Notification type names. Mapping HTML-formatted internal name to internal name |
32 | * |
33 | * @var string[] |
34 | */ |
35 | protected $notificationTypeNames; |
36 | |
37 | /** |
38 | * Notify types, mapping internal name to HTML-formatted name |
39 | * |
40 | * @var string[] |
41 | */ |
42 | protected $notifyTypes; |
43 | |
44 | // Due to how HTMLForm works, it's convenient to have both directions |
45 | /** |
46 | * Category names, mapping HTML-formatted name to internal name |
47 | * |
48 | * @var string[] |
49 | */ |
50 | protected $flippedCategoryNames; |
51 | |
52 | /** |
53 | * Notify types, mapping HTML-formatted name to internal name |
54 | * |
55 | * @var string[] |
56 | */ |
57 | protected $flippedNotifyTypes; |
58 | |
59 | /** |
60 | * @var UserOptionsManager |
61 | */ |
62 | private $userOptionsManager; |
63 | |
64 | /** |
65 | * @param AttributeManager $attributeManager |
66 | * @param UserOptionsManager $userOptionsManager |
67 | */ |
68 | public function __construct( |
69 | AttributeManager $attributeManager, |
70 | UserOptionsManager $userOptionsManager |
71 | ) { |
72 | parent::__construct( 'DisplayNotificationsConfiguration' ); |
73 | |
74 | $this->attributeManager = $attributeManager; |
75 | $this->userOptionsManager = $userOptionsManager; |
76 | } |
77 | |
78 | public function execute( $subPage ) { |
79 | $this->setHeaders(); |
80 | $this->checkPermissions(); |
81 | |
82 | $config = $this->getConfig(); |
83 | |
84 | $internalCategoryNames = $this->attributeManager->getInternalCategoryNames(); |
85 | $this->categoryNames = []; |
86 | |
87 | foreach ( $internalCategoryNames as $internalCategoryName ) { |
88 | $formattedFriendlyCategoryName = Html::element( |
89 | 'strong', |
90 | [], |
91 | $this->msg( 'echo-category-title-' . $internalCategoryName )->numParams( 1 )->text() |
92 | ); |
93 | |
94 | $formattedInternalCategoryName = $this->msg( 'parentheses' )->rawParams( |
95 | Html::element( |
96 | 'em', |
97 | [], |
98 | $internalCategoryName |
99 | ) |
100 | )->parse(); |
101 | |
102 | $this->categoryNames[$internalCategoryName] = $formattedFriendlyCategoryName . ' ' |
103 | . $formattedInternalCategoryName; |
104 | } |
105 | |
106 | $this->flippedCategoryNames = array_flip( $this->categoryNames ); |
107 | |
108 | $this->notifyTypes = []; |
109 | foreach ( $config->get( 'EchoNotifiers' ) as $notifyType => $notifier ) { |
110 | $this->notifyTypes[$notifyType] = $this->msg( 'echo-pref-' . $notifyType )->escaped(); |
111 | } |
112 | |
113 | $this->flippedNotifyTypes = array_flip( $this->notifyTypes ); |
114 | |
115 | $notificationTypes = array_keys( $config->get( 'EchoNotifications' ) ); |
116 | $this->notificationTypeNames = array_combine( |
117 | array_map( 'htmlspecialchars', $notificationTypes ), |
118 | $notificationTypes |
119 | ); |
120 | |
121 | $this->getOutput()->setPageTitleMsg( $this->msg( 'echo-displaynotificationsconfiguration' ) ); |
122 | $this->outputHeader( 'echo-displaynotificationsconfiguration-summary' ); |
123 | $this->outputConfiguration(); |
124 | } |
125 | |
126 | /** |
127 | * Outputs the Echo configuration |
128 | */ |
129 | protected function outputConfiguration() { |
130 | $this->outputNotificationsInCategories(); |
131 | $this->outputNotificationsInSections(); |
132 | $this->outputAvailability(); |
133 | $this->outputMandatory(); |
134 | $this->outputEnabledDefault(); |
135 | } |
136 | |
137 | /** |
138 | * Displays a checkbox matrix, using an HTMLForm |
139 | * |
140 | * @param string $id Arbitrary ID |
141 | * @param string $legendMsgKey Message key for an explanatory legend. For example, |
142 | * "We wrote this feature because in the days of yore, there was but one notification badge" |
143 | * @param array $rowLabelMapping Associative array mapping label to tag |
144 | * @param array $columnLabelMapping Associative array mapping label to tag |
145 | * @param array $value Array consisting of strings in the format '$columnTag-$rowTag' |
146 | */ |
147 | protected function outputCheckMatrix( |
148 | $id, |
149 | $legendMsgKey, |
150 | array $rowLabelMapping, |
151 | array $columnLabelMapping, |
152 | array $value |
153 | ) { |
154 | $form = new OOUIHTMLForm( |
155 | [ |
156 | $id => [ |
157 | 'type' => 'checkmatrix', |
158 | 'rows' => $rowLabelMapping, |
159 | 'columns' => $columnLabelMapping, |
160 | 'default' => $value, |
161 | 'disabled' => true, |
162 | ] |
163 | ], |
164 | $this->getContext() |
165 | ); |
166 | |
167 | $form->setTitle( $this->getPageTitle() ) |
168 | ->prepareForm() |
169 | ->suppressDefaultSubmit() |
170 | ->setWrapperLegendMsg( $legendMsgKey ) |
171 | ->displayForm( false ); |
172 | } |
173 | |
174 | /** |
175 | * Outputs the notification types in each category |
176 | */ |
177 | protected function outputNotificationsInCategories() { |
178 | $notificationsByCategory = $this->attributeManager->getEventsByCategory(); |
179 | |
180 | $out = $this->getOutput(); |
181 | $out->addHTML( Html::element( |
182 | 'h2', |
183 | [ 'id' => 'mw-echo-displaynotificationsconfiguration-notifications-by-category' ], |
184 | $this->msg( 'echo-displaynotificationsconfiguration-notifications-by-category-header' )->text() |
185 | ) ); |
186 | |
187 | $out->addHTML( Html::openElement( 'ul' ) ); |
188 | foreach ( $notificationsByCategory as $categoryName => $notificationTypes ) { |
189 | $implodedTypes = Html::element( |
190 | 'span', |
191 | [], |
192 | implode( $this->msg( 'comma-separator' )->text(), $notificationTypes ) |
193 | ); |
194 | |
195 | $out->addHTML( |
196 | Html::rawElement( |
197 | 'li', |
198 | [], |
199 | $this->categoryNames[$categoryName] . $this->msg( 'colon-separator' )->escaped() . ' ' |
200 | . $implodedTypes |
201 | ) |
202 | ); |
203 | } |
204 | $out->addHTML( Html::closeElement( 'ul' ) ); |
205 | } |
206 | |
207 | /** |
208 | * Output the notification types in each section (alert/message) |
209 | */ |
210 | protected function outputNotificationsInSections() { |
211 | $this->getOutput()->addHTML( Html::element( |
212 | 'h2', |
213 | [ 'id' => 'mw-echo-displaynotificationsconfiguration-sorting-by-section' ], |
214 | $this->msg( 'echo-displaynotificationsconfiguration-sorting-by-section-header' )->text() |
215 | ) ); |
216 | |
217 | $bySectionValue = []; |
218 | |
219 | $flippedSectionNames = []; |
220 | |
221 | foreach ( AttributeManager::$sections as $section ) { |
222 | $types = $this->attributeManager->getEventsForSection( $section ); |
223 | // echo-notification-alert-text-only, echo-notification-notice-text-only |
224 | $msgSection = $section == 'message' ? 'notice' : $section; |
225 | $flippedSectionNames[$this->msg( 'echo-notification-' . $msgSection . '-text-only' )->escaped()] |
226 | = $section; |
227 | foreach ( $types as $type ) { |
228 | $bySectionValue[] = "$section-$type"; |
229 | } |
230 | } |
231 | |
232 | $this->outputCheckMatrix( |
233 | 'type-by-section', |
234 | 'echo-displaynotificationsconfiguration-sorting-by-section-legend', |
235 | $this->notificationTypeNames, |
236 | $flippedSectionNames, |
237 | $bySectionValue |
238 | ); |
239 | } |
240 | |
241 | /** |
242 | * Output which notify types are available for each category |
243 | */ |
244 | protected function outputAvailability() { |
245 | $this->getOutput()->addHTML( Html::element( |
246 | 'h2', |
247 | [ 'id' => 'mw-echo-displaynotificationsconfiguration-available-notification-methods' ], |
248 | $this->msg( 'echo-displaynotificationsconfiguration-available-notification-methods-header' )->text() |
249 | ) ); |
250 | |
251 | $byCategoryValue = []; |
252 | |
253 | foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { |
254 | foreach ( $this->categoryNames as $category => $displayCategory ) { |
255 | if ( $this->attributeManager->isNotifyTypeAvailableForCategory( $category, $notifyType ) ) { |
256 | $byCategoryValue[] = "$notifyType-$category"; |
257 | } |
258 | } |
259 | } |
260 | |
261 | $this->outputCheckMatrix( |
262 | 'availability-by-category', |
263 | 'echo-displaynotificationsconfiguration-available-notification-methods-by-category-legend', |
264 | $this->flippedCategoryNames, |
265 | $this->flippedNotifyTypes, |
266 | $byCategoryValue |
267 | ); |
268 | } |
269 | |
270 | /** |
271 | * View-only overrides of notification preferences for new users |
272 | * |
273 | * @todo (Likely) remove the underlying functionality, see |
274 | * https://phabricator.wikimedia.org/T357219. |
275 | * @return bool[] |
276 | */ |
277 | private static function getNewUserPreferenceOverrides(): array { |
278 | return [ |
279 | 'echo-subscriptions-web-reverted' => false, |
280 | 'echo-subscriptions-web-article-linked' => true, |
281 | 'echo-subscriptions-email-mention' => true, |
282 | 'echo-subscriptions-email-article-linked' => true, |
283 | ]; |
284 | } |
285 | |
286 | /** |
287 | * Output which notification categories are turned on by default, for each notify type |
288 | */ |
289 | protected function outputEnabledDefault() { |
290 | $this->getOutput()->addHTML( Html::element( |
291 | 'h2', |
292 | [ 'id' => 'mw-echo-displaynotificationsconfiguration-enabled-default' ], |
293 | $this->msg( 'echo-displaynotificationsconfiguration-enabled-default-header' )->text() |
294 | ) ); |
295 | |
296 | // Some of the preferences are mapped to existing ones defined in core MediaWiki |
297 | $virtualOptions = EchoHooks::getVirtualUserOptions(); |
298 | |
299 | // In reality, anon users are not relevant to Echo, but this lets us easily query default options. |
300 | $anonUser = new User; |
301 | |
302 | $byCategoryValueExisting = []; |
303 | foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { |
304 | foreach ( $this->categoryNames as $category => $displayCategory ) { |
305 | $prefKey = "echo-subscriptions-$notifyType-$category"; |
306 | if ( isset( $virtualOptions[ $prefKey ] ) ) { |
307 | $prefKey = $virtualOptions[ $prefKey ]; |
308 | } |
309 | if ( $this->userOptionsManager->getOption( $anonUser, $prefKey ) ) { |
310 | $byCategoryValueExisting[] = "$notifyType-$category"; |
311 | } |
312 | } |
313 | } |
314 | |
315 | $this->outputCheckMatrix( |
316 | 'enabled-by-default-generic', |
317 | 'echo-displaynotificationsconfiguration-enabled-default-existing-users-legend', |
318 | $this->flippedCategoryNames, |
319 | $this->flippedNotifyTypes, |
320 | $byCategoryValueExisting |
321 | ); |
322 | |
323 | $loggedInUser = new User; |
324 | |
325 | // NOTE: This is not reliable, and will break if other variables than those hardcoded in |
326 | // the method below are changed for new users, either via a hook or via conditional |
327 | // defaults. See T357219 for details. |
328 | $overrides = $this->getNewUserPreferenceOverrides(); |
329 | foreach ( $overrides as $prefKey => $value ) { |
330 | $this->userOptionsManager->setOption( $loggedInUser, $prefKey, $value ); |
331 | } |
332 | |
333 | $byCategoryValueNew = []; |
334 | foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { |
335 | foreach ( $this->categoryNames as $category => $displayCategory ) { |
336 | $prefKey = "echo-subscriptions-$notifyType-$category"; |
337 | if ( isset( $virtualOptions[ $prefKey ] ) ) { |
338 | $prefKey = $virtualOptions[ $prefKey ]; |
339 | } |
340 | if ( $this->userOptionsManager->getOption( $loggedInUser, $prefKey ) ) { |
341 | $byCategoryValueNew[] = "$notifyType-$category"; |
342 | } |
343 | } |
344 | } |
345 | |
346 | $this->outputCheckMatrix( |
347 | 'enabled-by-default-new', |
348 | 'echo-displaynotificationsconfiguration-enabled-default-new-users-legend', |
349 | $this->flippedCategoryNames, |
350 | $this->flippedNotifyTypes, |
351 | $byCategoryValueNew |
352 | ); |
353 | } |
354 | |
355 | /** |
356 | * Output which notify types are mandatory for each category |
357 | */ |
358 | protected function outputMandatory() { |
359 | $byCategoryValue = []; |
360 | |
361 | $this->getOutput()->addHTML( Html::element( |
362 | 'h2', |
363 | [ 'id' => 'mw-echo-displaynotificationsconfiguration-mandatory-notification-methods' ], |
364 | $this->msg( 'echo-displaynotificationsconfiguration-mandatory-notification-methods-header' )->text() |
365 | ) ); |
366 | |
367 | foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { |
368 | foreach ( $this->categoryNames as $category => $displayCategory ) { |
369 | if ( !$this->attributeManager->isNotifyTypeDismissableForCategory( $category, $notifyType ) ) { |
370 | $byCategoryValue[] = "$notifyType-$category"; |
371 | } |
372 | } |
373 | } |
374 | |
375 | $this->outputCheckMatrix( |
376 | 'mandatory', |
377 | 'echo-displaynotificationsconfiguration-mandatory-notification-methods-by-category-legend', |
378 | $this->flippedCategoryNames, |
379 | $this->flippedNotifyTypes, |
380 | $byCategoryValue |
381 | ); |
382 | } |
383 | } |