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 | } |