Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 255 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
FlaggedRevsHTML | |
0.00% |
0 / 255 |
|
0.00% |
0 / 9 |
1560 | |
0.00% |
0 / 1 |
getNamespaceMenu | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
56 | |||
getDefaultFilterMenu | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
2 | |||
getRestrictionFilterMenu | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
20 | |||
addTagRatings | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
6 | |||
getEditTagFilterMenu | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
6 | |||
reviewDialog | |
0.00% |
0 / 100 |
|
0.00% |
0 / 1 |
380 | |||
addMessageBox | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
diffToggle | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
stabilityLogExcerpt | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | use MediaWiki\ChangeTags\ChangeTags; |
4 | use MediaWiki\Context\RequestContext; |
5 | use MediaWiki\Html\Html; |
6 | use MediaWiki\Logging\LogEventsList; |
7 | use MediaWiki\MediaWikiServices; |
8 | use MediaWiki\Title\Title; |
9 | |
10 | /** |
11 | * Class containing utility HTML functions for a FlaggedRevs. |
12 | * Includes functions for selectors, icons, notices, CSS, and form aspects. |
13 | */ |
14 | class FlaggedRevsHTML { |
15 | |
16 | /** |
17 | * Get a selector of reviewable namespaces |
18 | * @param int|null $selected namespace selected |
19 | * @param string|null $all Value of an item denoting all namespaces, or null to omit |
20 | */ |
21 | public static function getNamespaceMenu( ?int $selected = null, ?string $all = null ): string { |
22 | $s = Html::rawElement( 'div', [ 'class' => 'cdx-field__item' ], |
23 | Html::rawElement( 'div', [ 'class' => 'cdx-label' ], |
24 | Html::label( |
25 | wfMessage( 'namespace' )->text(), |
26 | 'namespace', |
27 | [ 'class' => 'cdx-label__label' ] |
28 | ) |
29 | ) |
30 | ); |
31 | |
32 | # No namespace selected; let exact match work without hitting Main |
33 | $selected ??= ''; |
34 | $s .= "\n<select id='namespace' name='namespace' class='cdx-select namespaceselector'>\n"; |
35 | $arr = MediaWikiServices::getInstance()->getContentLanguage()->getFormattedNamespaces(); |
36 | if ( $all !== null ) { |
37 | $arr = [ $all => wfMessage( 'namespacesall' )->text() ] + $arr; // should be first |
38 | } |
39 | foreach ( $arr as $index => $name ) { |
40 | # Content pages only (except 'all') |
41 | if ( $index !== $all && !FlaggedRevs::isReviewNamespace( $index ) ) { |
42 | continue; |
43 | } |
44 | $name = $index !== 0 ? $name : wfMessage( 'blanknamespace' )->text(); |
45 | if ( $index === $selected ) { |
46 | $s .= "\t" . Html::element( 'option', [ 'value' => $index, |
47 | "selected" => "selected" ], $name ) . "\n"; |
48 | } else { |
49 | $s .= "\t" . Html::element( 'option', [ 'value' => $index ], $name ) . "\n"; |
50 | } |
51 | } |
52 | $s .= "</select>\n"; |
53 | return $s; |
54 | } |
55 | |
56 | /** |
57 | * Get a <select> of default page version (stable or draft). Used for filters. |
58 | * @param int|null $selected (0=draft, 1=stable, null=either ) |
59 | */ |
60 | public static function getDefaultFilterMenu( ?int $selected = null ): string { |
61 | $s = Html::rawElement( 'div', [ 'class' => 'cdx-field__item' ], |
62 | Html::rawElement( 'div', [ 'class' => 'cdx-label' ], |
63 | Html::label( |
64 | wfMessage( 'revreview-defaultfilter' )->text(), |
65 | 'wpStable', |
66 | [ 'class' => 'cdx-label__label' ] |
67 | ) |
68 | ) |
69 | ); |
70 | |
71 | $selectOptions = Html::element( 'option', [ 'value' => '', 'selected' => $selected === null ], |
72 | wfMessage( 'revreview-def-all' )->text() ); |
73 | $selectOptions .= Html::element( 'option', [ 'value' => '1', 'selected' => $selected === 1 ], |
74 | wfMessage( 'revreview-def-stable' )->text() ); |
75 | $selectOptions .= Html::element( 'option', [ 'value' => '0', 'selected' => $selected === 0 ], |
76 | wfMessage( 'revreview-def-draft' )->text() ); |
77 | |
78 | $s .= Html::rawElement( 'select', [ |
79 | 'name' => 'stable', |
80 | 'id' => 'wpStable', |
81 | 'class' => 'cdx-select filterselector' |
82 | ], $selectOptions ); |
83 | |
84 | return $s; |
85 | } |
86 | |
87 | /** |
88 | * Get a <select> of options of 'autoreview' restriction levels. Used for filters. |
89 | * @param string|null $selected (null or empty string for "any", 'none' for none) |
90 | */ |
91 | public static function getRestrictionFilterMenu( ?string $selected = '' ): string { |
92 | $s = Html::rawElement( 'div', [ 'class' => 'cdx-field__item' ], |
93 | Html::rawElement( 'div', [ 'class' => 'cdx-label' ], |
94 | Html::label( |
95 | wfMessage( 'revreview-restrictfilter' )->text(), |
96 | 'wpRestriction', |
97 | [ 'class' => 'cdx-label__label' ] |
98 | ) |
99 | ) |
100 | ); |
101 | |
102 | $selectOptions = Html::element( 'option', |
103 | [ 'value' => '', 'selected' => ( $selected ?? '' ) === '' ], |
104 | wfMessage( 'revreview-restriction-any' )->text() |
105 | ); |
106 | |
107 | if ( !FlaggedRevs::useProtectionLevels() ) { |
108 | # All "protected" pages have a protection level, not "none" |
109 | $selectOptions .= Html::element( 'option', |
110 | [ 'value' => 'none', 'selected' => $selected === 'none' ], |
111 | wfMessage( 'revreview-restriction-none' )->text() |
112 | ); |
113 | } |
114 | |
115 | foreach ( FlaggedRevs::getRestrictionLevels() as $perm ) { |
116 | // Give grep a chance to find the usages: |
117 | // revreview-restriction-any, revreview-restriction-none |
118 | $key = "revreview-restriction-$perm"; |
119 | $msg = wfMessage( $key )->isDisabled() ? $perm : wfMessage( $key )->text(); |
120 | $selectOptions .= Html::element( 'option', |
121 | [ 'value' => $perm, 'selected' => $selected == $perm ], |
122 | $msg |
123 | ); |
124 | } |
125 | |
126 | $s .= Html::rawElement( 'select', [ |
127 | 'name' => 'restriction', |
128 | 'id' => 'wpRestriction', |
129 | 'class' => 'cdx-select restrictionselector' |
130 | ], $selectOptions ); |
131 | |
132 | return $s; |
133 | } |
134 | |
135 | /** |
136 | * Generates a review box/tag displaying the quality level based on flags. |
137 | * |
138 | * This method creates a simple HTML table with two cells: one for the quality label |
139 | * and the other for the corresponding rating. The table is only generated if the page |
140 | * is not protected by FlaggedRevs settings. |
141 | * |
142 | * @param array $flags An associative array containing the flag ratings. |
143 | * |
144 | * @return string The generated HTML string for the review box/tag. |
145 | */ |
146 | public static function addTagRatings( array $flags ): string { |
147 | if ( FlaggedRevs::useOnlyIfProtected() ) { |
148 | return ''; |
149 | } |
150 | |
151 | $quality = FlaggedRevs::getTagName(); |
152 | $level = $flags[$quality] ?? 0; |
153 | $encValueText = wfMessage( "revreview-$quality-$level" )->text(); |
154 | $levelClass = 'fr-value' . ( $level * 20 + 20 ); |
155 | |
156 | return Html::rawElement( 'table', [ |
157 | 'id' => 'mw-fr-revisionratings-box', |
158 | 'class' => 'flaggedrevs-color-1', |
159 | 'style' => 'margin: auto;', |
160 | 'cellpadding' => '0', |
161 | ], |
162 | Html::rawElement( 'tr', [], |
163 | Html::element( 'td', [ 'class' => 'fr-text', 'style' => 'vertical-align: middle;' ], |
164 | wfMessage( "revreview-$quality" )->text() |
165 | ) . |
166 | Html::element( 'td', [ 'class' => $levelClass, 'style' => 'vertical-align: middle;' ], |
167 | $encValueText |
168 | ) |
169 | ) |
170 | ); |
171 | } |
172 | |
173 | /** |
174 | * Generates a dropdown menu for edit tag filters |
175 | * |
176 | * @param string|null $selected (null or empty string for "any") |
177 | * @since 1.43 |
178 | */ |
179 | public static function getEditTagFilterMenu( ?string $selected = '' ): string { |
180 | $s = Html::rawElement( 'div', [ 'class' => 'cdx-field__item' ], |
181 | Html::rawElement( 'div', [ 'class' => 'cdx-label' ], |
182 | Html::label( |
183 | wfMessage( 'pendingchanges-edit-tag' )->text(), |
184 | 'wpTagFilter', |
185 | [ 'class' => 'cdx-label__label' ] |
186 | ) |
187 | ) |
188 | ); |
189 | |
190 | $selectOptions = Html::element( 'option', |
191 | [ 'value' => '', 'selected' => ( $selected ?? '' ) === '' ], |
192 | wfMessage( 'pendingchanges-edit-tag-any' )->text() |
193 | ); |
194 | |
195 | $tagDefs = ChangeTags::getChangeTagList( RequestContext::getMain(), RequestContext::getMain()->getLanguage() ); |
196 | foreach ( $tagDefs as $tagInfo ) { |
197 | $tagName = $tagInfo['name']; |
198 | $selectOptions .= Html::element( 'option', |
199 | [ 'value' => $tagName, 'selected' => $selected == $tagName ], |
200 | $tagName |
201 | ); |
202 | } |
203 | |
204 | $s .= Html::rawElement( 'select', [ |
205 | 'name' => 'tagFilter', |
206 | 'id' => 'wpTagFilter', |
207 | 'class' => 'cdx-select' |
208 | ], $selectOptions ); |
209 | |
210 | return $s; |
211 | } |
212 | |
213 | /** |
214 | * Generates a review box using a table using FlaggedRevsHTML::addTagRatings() |
215 | * |
216 | * @param FlaggedRevision|null $frev the reviewed version |
217 | * @param int $revisionId the revision ID |
218 | * @param int $revsSince revisions since review |
219 | * @param string $type (stable/draft/oldstable) |
220 | * @param bool $synced does stable=current and this is one of them? |
221 | * |
222 | * @return string |
223 | */ |
224 | public static function reviewDialog( |
225 | ?FlaggedRevision $frev, |
226 | int $revisionId, |
227 | int $revsSince, |
228 | string $type = 'oldstable', |
229 | bool $synced = false |
230 | ): string { |
231 | global $wgLang; |
232 | $href = ''; |
233 | $context = RequestContext::getMain(); |
234 | $user = $context->getAuthority(); |
235 | $skin = $context->getSkin(); |
236 | |
237 | // If $frev is null, show a dialog with a "no flagged revision" message |
238 | if ( $frev === null ) { |
239 | $subtitleMessageKey = 'revreview-unchecked-title'; |
240 | $msg = 'revreview-noflagged'; |
241 | $subtitle = $context->msg( $subtitleMessageKey )->text(); |
242 | $html = $context->msg( $msg )->parse(); |
243 | } else { |
244 | // Regular case when $frev is not null |
245 | $flags = $frev->getTags(); |
246 | $time = $wgLang->date( $frev->getTimestamp(), true ); |
247 | |
248 | $subtitleMessageKey = ( $type === 'stable' || $synced ) |
249 | ? 'revreview-basic-title' // This is a checked version of this page |
250 | : 'revreview-draft-title'; // Pending changes are displayed on this page |
251 | |
252 | $subtitle = $context->msg( $subtitleMessageKey )->text(); |
253 | |
254 | // Construct some tagging |
255 | if ( $synced && ( $type == 'stable' || $type == 'draft' ) ) { |
256 | $msg = 'revreview-basic-same'; |
257 | $html = $context->msg( $msg, $frev->getRevId(), $time )->numParams( $revsSince )->parse(); |
258 | } elseif ( $type == 'oldstable' ) { |
259 | $msg = 'revreview-basic-old'; |
260 | $html = $context->msg( $msg, $frev->getRevId(), $time )->parse(); |
261 | } else { |
262 | $msg = $type === 'stable' ? 'revreview-basic' : 'revreview-newest-basic'; |
263 | $msg .= !$revsSince ? '-i' : ''; |
264 | $html = $context->msg( $msg, $frev->getRevId(), $time )->numParams( $revsSince )->parse(); |
265 | } |
266 | |
267 | // Add any rating tags as needed... |
268 | if ( $flags && !FlaggedRevs::binaryFlagging() ) { |
269 | if ( $skin->getSkinName() !== 'minerva' ) { |
270 | // Don't show the ratings on draft views |
271 | if ( $type == 'stable' || $type == 'oldstable' ) { |
272 | $html .= '<p>' . self::addTagRatings( $flags ) . '</p>'; |
273 | } |
274 | } |
275 | } |
276 | |
277 | $title = $frev->getTitle(); |
278 | $href = $title->getFullURL( [ 'diff' => 'cur', 'oldid' => $revisionId ] ); |
279 | } |
280 | |
281 | if ( $skin && $skin->getSkinName() === 'minerva' ) { |
282 | return self::addMessageBox( 'inline', $html, [ |
283 | 'class' => 'mw-fr-mobile-message-inline', |
284 | ] ); |
285 | } else { |
286 | return Html::rawElement( |
287 | 'div', |
288 | [ |
289 | 'id' => 'mw-fr-revision-details', |
290 | 'class' => 'mw-fr-revision-details-dialog', |
291 | 'style' => 'display:none;' |
292 | ], |
293 | Html::rawElement( 'div', [ 'tabindex' => '0' ] ) . |
294 | Html::rawElement( |
295 | 'div', |
296 | [ 'class' => 'cdx-dialog cdx-dialog--horizontal-actions' ], |
297 | Html::rawElement( |
298 | 'header', |
299 | [ 'class' => 'cdx-dialog__header cdx-dialog__header--default' ], |
300 | Html::rawElement( |
301 | 'div', |
302 | [ 'class' => 'cdx-dialog__header__title-group' ], |
303 | Html::element( 'h2', [ 'class' => 'cdx-dialog__header__title' ], |
304 | wfMessage( 'revreview-dialog-title' ) ) . |
305 | Html::element( 'p', [ 'class' => 'cdx-dialog__header__subtitle' ], $subtitle ) |
306 | ) . |
307 | Html::rawElement( 'button', [ |
308 | 'class' => 'cdx-button cdx-button--action-default cdx-button--weight-quiet |
309 | cdx-button--size-medium cdx-button--icon-only cdx-dialog__header__close-button', |
310 | 'aria-label' => wfMessage( 'fr-revision-info-dialog-close-aria-label' ), |
311 | 'onclick' => 'document.getElementById("mw-fr-revision-details").style.display = "none";' |
312 | ], |
313 | Html::rawElement( 'span', [ 'class' => 'cdx-icon cdx-icon--medium |
314 | cdx-fr-css-icon--close' ] ) |
315 | ) |
316 | ) . |
317 | Html::rawElement( |
318 | 'div', |
319 | [ 'class' => 'cdx-dialog__body' ], |
320 | $html |
321 | ) . |
322 | ( $frev !== null && $user->isAllowed( 'review' ) ? |
323 | Html::rawElement( |
324 | 'footer', |
325 | [ 'class' => 'cdx-dialog__footer cdx-dialog__footer--default' ], |
326 | Html::rawElement( 'div', [ 'class' => 'cdx-dialog__footer__actions' ], |
327 | Html::element( |
328 | 'a', |
329 | [ |
330 | 'href' => $href, |
331 | 'class' => |
332 | 'cdx-button cdx-button--action-progressive cdx-button--weight-primary |
333 | cdx-button--size-medium cdx-dialog__footer__primary-action |
334 | cdx-button--fake-button cdx-button--fake-button--enabled' |
335 | ], |
336 | wfMessage( 'fr-revision-info-dialog-review-button' ) |
337 | ) . |
338 | Html::element( |
339 | 'button', |
340 | [ |
341 | 'class' => 'cdx-dialog__footer__default-action cdx-button cdx-button--default', |
342 | 'onclick' => |
343 | 'document.getElementById("mw-fr-revision-details").style.display = "none";' |
344 | ], |
345 | wfMessage( 'fr-revision-info-dialog-cancel-button' ) |
346 | ) |
347 | ) |
348 | ) : '' ) |
349 | ) . |
350 | Html::rawElement( 'div', [ 'tabindex' => '0' ] ) |
351 | ); |
352 | } |
353 | } |
354 | |
355 | /** |
356 | * Generates a custom message box using the `cdx-message` class. |
357 | * |
358 | * This method creates a message box with the `cdx-message` class, which can be configured |
359 | * as either `inline` or `block`. The method allows for additional attributes to be passed |
360 | * to further customize the appearance and behavior of the message box. |
361 | * |
362 | * The message box will include: |
363 | * - An outer `div` element with the appropriate `cdx-message` classes and any additional |
364 | * classes or attributes specified in the `$attrs` parameter. |
365 | * - A `span` element for the message icon, using the `cdx-message__icon` class. |
366 | * - A nested `div` element to contain the message content, using the `cdx-message__content` class. |
367 | * |
368 | * This method is useful for creating consistent, styled message boxes across the application. |
369 | * |
370 | * @param string $type The type of message box to create, either 'inline' or 'block'. |
371 | * This determines the overall structure and style of the message box. |
372 | * @param string $message The content to display inside the message box. This can include |
373 | * HTML or plain text, depending on the context. |
374 | * @param array $attrs Optional. An associative array of additional HTML attributes to |
375 | * apply to the outer `div` element of the message box. The `class` |
376 | * attribute can be extended by passing additional classes in this array. |
377 | * @return string The generated HTML string for the complete message box, ready to be |
378 | * rendered in the output. |
379 | */ |
380 | public static function addMessageBox( string $type, string $message, array $attrs = [] ): string { |
381 | // Base classes for the message box, including type and notice class |
382 | $baseClass = 'cdx-message mw-fr-message-box cdx-message--' . $type . ' cdx-message--notice'; |
383 | |
384 | // Merge custom attributes with the default class |
385 | $attrs['class'] = isset( $attrs['class'] ) ? $baseClass . ' ' . $attrs['class'] : $baseClass; |
386 | |
387 | // Generate and return the complete HTML for the message box |
388 | return Html::rawElement( |
389 | 'div', |
390 | $attrs, |
391 | Html::element( 'span', [ 'class' => 'cdx-message__icon' ] ) . |
392 | Html::rawElement( 'div', [ 'class' => 'cdx-message__content' ], $message ) |
393 | ); |
394 | } |
395 | |
396 | /** |
397 | * Generates the "(show/hide)" diff toggle. With JS disabled, it functions as a link to the diff. |
398 | * |
399 | * @param Title $title |
400 | * @param int $fromrev |
401 | * @param int $torev |
402 | * @param string|null $multiNotice Message about intermediate revisions |
403 | * |
404 | * @return string |
405 | */ |
406 | public static function diffToggle( Title $title, int $fromrev, int $torev, ?string $multiNotice = null ): string { |
407 | // Construct a link to the diff |
408 | $href = $title->getFullURL( [ 'diff' => $torev, 'oldid' => $fromrev ] ); |
409 | |
410 | $toggle = Html::element( 'a', [ |
411 | 'class' => 'fr-toggle-text', |
412 | 'title' => wfMessage( 'revreview-diff-toggle-title' )->text(), |
413 | 'href' => $href, |
414 | 'data-mw-fromrev' => $fromrev, |
415 | 'data-mw-torev' => $torev, |
416 | 'data-mw-multinotice' => $multiNotice, |
417 | ], wfMessage( 'revreview-diff-toggle-show' )->text() ); |
418 | |
419 | return '<span id="mw-fr-diff-toggle">' . |
420 | wfMessage( 'parentheses' )->rawParams( $toggle )->escaped() . '</span>'; |
421 | } |
422 | |
423 | /** |
424 | * Creates a stability log excerpt |
425 | */ |
426 | public static function stabilityLogExcerpt( Title $title ): string { |
427 | $logHtml = ''; |
428 | $params = [ |
429 | 'lim' => 1, |
430 | 'flags' => LogEventsList::NO_EXTRA_USER_LINKS |
431 | ]; |
432 | LogEventsList::showLogExtract( $logHtml, 'stable', |
433 | $title->getPrefixedText(), '', $params ); |
434 | return Html::rawElement( |
435 | 'div', |
436 | [ 'id' => 'mw-fr-logexcerpt' ], |
437 | $logHtml |
438 | ); |
439 | } |
440 | |
441 | } |