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