Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -1338,6 +1338,8 @@ |
1339 | 1339 | 'nocreatetext' => '{{SITENAME}} has restricted the ability to create new pages. |
1340 | 1340 | You can go back and edit an existing page, or [[Special:UserLogin|log in or create an account]].', |
1341 | 1341 | 'nocreate-loggedin' => 'You do not have permission to create new pages.', |
| 1342 | +'sectioneditnotsupported-title' => 'Section editing not supported', |
| 1343 | +'sectioneditnotsupported-text' => 'Section editing is not supported in this edit page.', |
1342 | 1344 | 'permissionserrors' => 'Permissions Errors', |
1343 | 1345 | 'permissionserrorstext' => 'You do not have permission to do that, for the following {{PLURAL:$1|reason|reasons}}:', |
1344 | 1346 | 'permissionserrorstext-withaction' => 'You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:', |
Index: trunk/phase3/includes/Html.php |
— | — | @@ -486,4 +486,29 @@ |
487 | 487 | public static function hidden( $name, $value, $attribs = array() ) { |
488 | 488 | return self::input( $name, $value, 'hidden', $attribs ); |
489 | 489 | } |
| 490 | + |
| 491 | + /** |
| 492 | + * Convenience function to produce an <input> element. This supports leaving |
| 493 | + * out the cols= and rows= which Xml requires and are required by HTML4/XHTML |
| 494 | + * but not required by HTML5 and will silently set cols="" and rows="" if |
| 495 | + * $wgHtml5 is false and cols and rows are omitted (HTML4 validates present |
| 496 | + * but empty cols="" and rows="" as valid). |
| 497 | + * |
| 498 | + * @param $name string name attribute |
| 499 | + * @param $value string value attribute |
| 500 | + * @param $attribs array Associative array of miscellaneous extra |
| 501 | + * attributes, passed to Html::element() |
| 502 | + * @return string Raw HTML |
| 503 | + */ |
| 504 | + public static function textarea( $name, $value = '', $attribs = array() ) { |
| 505 | + global $wgHtml5; |
| 506 | + $attribs['name'] = $name; |
| 507 | + if ( !$wgHtml5 ) { |
| 508 | + if ( !array_key_exists('cols', $attribs) ) |
| 509 | + $attribs['cols'] = ""; |
| 510 | + if ( !array_key_exists('rows', $attribs) ) |
| 511 | + $attribs['rows'] = ""; |
| 512 | + } |
| 513 | + return self::element( 'textarea', $attribs, $value ); |
| 514 | + } |
490 | 515 | } |
Index: trunk/phase3/includes/EditPage.php |
— | — | @@ -556,6 +556,29 @@ |
557 | 557 | } |
558 | 558 | |
559 | 559 | /** |
| 560 | + * Does this EditPage class support section editing? |
| 561 | + * This is used by EditPage subclasses to indicate their ui cannot handle section edits |
| 562 | + * |
| 563 | + * @return bool |
| 564 | + */ |
| 565 | + protected function isSectionEditSupported() { |
| 566 | + return true; |
| 567 | + } |
| 568 | + |
| 569 | + /** |
| 570 | + * Returns the URL to use in the form's action attribute. |
| 571 | + * This is used by EditPage subclasses when simply customizing the action |
| 572 | + * variable in the constructor is not enough. This can be used when the |
| 573 | + * EditPage lives inside of a Special page rather than a custom page action. |
| 574 | + * |
| 575 | + * @param Title $title The title for which is being edited (where we go to for &action= links) |
| 576 | + * @return string |
| 577 | + */ |
| 578 | + protected function getActionURL( Title $title ) { |
| 579 | + return $title->getLocalURL( array( 'action' => $this->action ) ); |
| 580 | + } |
| 581 | + |
| 582 | + /** |
560 | 583 | * @todo document |
561 | 584 | * @param $request |
562 | 585 | */ |
— | — | @@ -572,7 +595,16 @@ |
573 | 596 | # Also remove trailing whitespace, but don't remove _initial_ |
574 | 597 | # whitespace from the text boxes. This may be significant formatting. |
575 | 598 | $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' ); |
576 | | - $this->textbox2 = $this->safeUnicodeInput( $request, 'wpTextbox2' ); |
| 599 | + if ( !$request->getCheck('wpTextbox2') ) { |
| 600 | + // Skip this if wpTextbox2 has input, it indicates that we came |
| 601 | + // from a conflict page with raw page text, not a custom form |
| 602 | + // modified by subclasses |
| 603 | + wfProfileIn( get_class($this)."::importContentFormData" ); |
| 604 | + $textbox1 = $this->importContentFormData( $request ); |
| 605 | + if ( isset($textbox1) ) |
| 606 | + $this->textbox1 = $textbox1; |
| 607 | + wfProfileOut( get_class($this)."::importContentFormData" ); |
| 608 | + } |
577 | 609 | $this->mMetaData = rtrim( $request->getText( 'metadata' ) ); |
578 | 610 | # Truncate for whole multibyte characters. +5 bytes for ellipsis |
579 | 611 | $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250, '' ); |
— | — | @@ -643,7 +675,6 @@ |
644 | 676 | # Not a posted form? Start with nothing. |
645 | 677 | wfDebug( __METHOD__ . ": Not a posted form.\n" ); |
646 | 678 | $this->textbox1 = ''; |
647 | | - $this->textbox2 = ''; |
648 | 679 | $this->mMetaData = ''; |
649 | 680 | $this->summary = ''; |
650 | 681 | $this->edittime = ''; |
— | — | @@ -681,6 +712,18 @@ |
682 | 713 | } |
683 | 714 | |
684 | 715 | /** |
| 716 | + * Subpage overridable method for extracting the page content data from the |
| 717 | + * posted form to be placed in $this->textbox1, if using customized input |
| 718 | + * this method should be overrided and return the page text that will be used |
| 719 | + * for saving, preview parsing and so on... |
| 720 | + * |
| 721 | + * @praram WebRequest $request |
| 722 | + */ |
| 723 | + protected function importContentFormData( &$request ) { |
| 724 | + return; // Don't do anything, EditPage already extracted wpTextbox1 |
| 725 | + } |
| 726 | + |
| 727 | + /** |
685 | 728 | * Make sure the form isn't faking a user's credentials. |
686 | 729 | * |
687 | 730 | * @param $request WebRequest |
— | — | @@ -1168,7 +1211,7 @@ |
1169 | 1212 | * near the top, for captchas and the like. |
1170 | 1213 | */ |
1171 | 1214 | function showEditForm( $formCallback=null ) { |
1172 | | - global $wgOut, $wgUser, $wgLang, $wgContLang, $wgMaxArticleSize, $wgTitle, $wgRequest; |
| 1215 | + global $wgOut, $wgUser, $wgTitle, $wgRequest; |
1173 | 1216 | |
1174 | 1217 | # If $wgTitle is null, that means we're in API mode. |
1175 | 1218 | # Some hook probably called this function without checking |
— | — | @@ -1197,15 +1240,175 @@ |
1198 | 1241 | # Enabled article-related sidebar, toplinks, etc. |
1199 | 1242 | $wgOut->setArticleRelated( true ); |
1200 | 1243 | |
1201 | | - $cancelParams = array(); |
| 1244 | + if ( $this->editFormHeadInit() === false ) |
| 1245 | + return; |
1202 | 1246 | |
| 1247 | + $action = htmlspecialchars($this->getActionURL($wgTitle)); |
| 1248 | + |
| 1249 | + if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) { |
| 1250 | + # prepare toolbar for edit buttons |
| 1251 | + $toolbar = EditPage::getEditToolbar(); |
| 1252 | + } else { |
| 1253 | + $toolbar = ''; |
| 1254 | + } |
| 1255 | + |
| 1256 | + |
| 1257 | + // activate checkboxes if user wants them to be always active |
| 1258 | + if ( !$this->preview && !$this->diff ) { |
| 1259 | + # Sort out the "watch" checkbox |
| 1260 | + if ( $wgUser->getOption( 'watchdefault' ) ) { |
| 1261 | + # Watch all edits |
| 1262 | + $this->watchthis = true; |
| 1263 | + } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) { |
| 1264 | + # Watch creations |
| 1265 | + $this->watchthis = true; |
| 1266 | + } elseif ( $this->mTitle->userIsWatching() ) { |
| 1267 | + # Already watched |
| 1268 | + $this->watchthis = true; |
| 1269 | + } |
| 1270 | + |
| 1271 | + # May be overriden by request parameters |
| 1272 | + if( $wgRequest->getBool( 'watchthis' ) ) { |
| 1273 | + $this->watchthis = true; |
| 1274 | + } |
| 1275 | + |
| 1276 | + if ( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true; |
| 1277 | + } |
| 1278 | + |
| 1279 | + $wgOut->addHTML( $this->editFormPageTop ); |
| 1280 | + |
| 1281 | + if ( $wgUser->getOption( 'previewontop' ) ) { |
| 1282 | + $this->displayPreviewArea( $previewOutput, true ); |
| 1283 | + } |
| 1284 | + |
| 1285 | + $wgOut->addHTML( $this->editFormTextTop ); |
| 1286 | + |
| 1287 | + $templates = $this->getTemplates(); |
| 1288 | + $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != ''); |
| 1289 | + |
| 1290 | + $hiddencats = $this->mArticle->getHiddenCategories(); |
| 1291 | + $formattedhiddencats = $sk->formatHiddenCategories( $hiddencats ); |
| 1292 | + |
| 1293 | + if ( $this->wasDeletedSinceLastEdit() && 'save' != $this->formtype ) { |
| 1294 | + $wgOut->wrapWikiMsg( |
| 1295 | + "<div class='error mw-deleted-while-editing'>\n$1</div>", |
| 1296 | + 'deletedwhileediting' ); |
| 1297 | + } elseif ( $this->wasDeletedSinceLastEdit() ) { |
| 1298 | + // Hide the toolbar and edit area, user can click preview to get it back |
| 1299 | + // Add an confirmation checkbox and explanation. |
| 1300 | + $toolbar = ''; |
| 1301 | + // @todo move this to a cleaner conditional instead of blanking a variable |
| 1302 | + } |
| 1303 | + $wgOut->addHTML( <<<END |
| 1304 | +{$toolbar} |
| 1305 | +<form id="editform" name="editform" method="post" action="$action" enctype="multipart/form-data"> |
| 1306 | +END |
| 1307 | +); |
| 1308 | + |
| 1309 | + if ( is_callable( $formCallback ) ) { |
| 1310 | + call_user_func_array( $formCallback, array( &$wgOut ) ); |
| 1311 | + } |
| 1312 | + |
| 1313 | + wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) ); |
| 1314 | + |
| 1315 | + // Put these up at the top to ensure they aren't lost on early form submission |
| 1316 | + $this->showFormBeforeText(); |
| 1317 | + |
| 1318 | + if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) { |
| 1319 | + $wgOut->addHTML( |
| 1320 | + '<div class="mw-confirm-recreate">' . |
| 1321 | + $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment ) ) . |
| 1322 | + Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false, |
| 1323 | + array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ) |
| 1324 | + ) . |
| 1325 | + '</div>' |
| 1326 | + ); |
| 1327 | + } |
| 1328 | + |
| 1329 | + # If a blank edit summary was previously provided, and the appropriate |
| 1330 | + # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the |
| 1331 | + # user being bounced back more than once in the event that a summary |
| 1332 | + # is not required. |
| 1333 | + ##### |
| 1334 | + # For a bit more sophisticated detection of blank summaries, hash the |
| 1335 | + # automatic one and pass that in the hidden field wpAutoSummary. |
| 1336 | + if ( $this->missingSummary || |
| 1337 | + ( $this->section == 'new' && $wgRequest->getBool( 'nosummary' ) ) ) |
| 1338 | + $wgOut->addHTML( Xml::hidden( 'wpIgnoreBlankSummary', true ) ); |
| 1339 | + $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary ); |
| 1340 | + $wgOut->addHTML( Xml::hidden( 'wpAutoSummary', $autosumm ) ); |
| 1341 | + |
| 1342 | + if ( $this->section == 'new' ) { |
| 1343 | + $this->showSummaryInput( true, $this->summary ); |
| 1344 | + $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) ); |
| 1345 | + } |
| 1346 | + |
| 1347 | + $wgOut->addHTML( $this->editFormTextBeforeContent ); |
| 1348 | + |
1203 | 1349 | if ( $this->isConflict ) { |
| 1350 | + // In an edit conflict bypass the overrideable content form method |
| 1351 | + // and fallback to the raw wpTextbox1 since editconflicts can't be |
| 1352 | + // resolved between page source edits and custom ui edits using the |
| 1353 | + // custom edit ui. |
| 1354 | + $this->showTextbox1(); |
| 1355 | + } else { |
| 1356 | + $this->showContentForm(); |
| 1357 | + } |
| 1358 | + |
| 1359 | + $wgOut->addHTML( $this->editFormTextAfterContent ); |
| 1360 | + |
| 1361 | + $wgOut->addWikiText( $this->getCopywarn() ); |
| 1362 | + if ( isset($this->editFormTextAfterWarn) && $this->editFormTextAfterWarn !== '' ) |
| 1363 | + $wgOut->addHTML( $this->editFormTextAfterWarn ); |
| 1364 | + |
| 1365 | + global $wgUseMetadataEdit; |
| 1366 | + if ( $wgUseMetadataEdit ) |
| 1367 | + $this->showMetaData(); |
| 1368 | + |
| 1369 | + $this->showStandardInputs(); |
| 1370 | + |
| 1371 | + $this->showFormAfterText(); |
| 1372 | + |
| 1373 | + $this->showTosSummary(); |
| 1374 | + $this->showEditTools(); |
| 1375 | + |
| 1376 | + $wgOut->addHTML( <<<END |
| 1377 | +{$this->editFormTextAfterTools} |
| 1378 | +<div class='templatesUsed'> |
| 1379 | +{$formattedtemplates} |
| 1380 | +</div> |
| 1381 | +<div class='hiddencats'> |
| 1382 | +{$formattedhiddencats} |
| 1383 | +</div> |
| 1384 | +END |
| 1385 | +); |
| 1386 | + |
| 1387 | + if ( $this->isConflict ) |
| 1388 | + $this->showConflict(); |
| 1389 | + |
| 1390 | + $wgOut->addHTML( $this->editFormTextBottom ); |
| 1391 | + $wgOut->addHTML( "</form>\n" ); |
| 1392 | + if ( !$wgUser->getOption( 'previewontop' ) ) { |
| 1393 | + $this->displayPreviewArea( $previewOutput, false ); |
| 1394 | + } |
| 1395 | + |
| 1396 | + wfProfileOut( __METHOD__ ); |
| 1397 | + } |
| 1398 | + |
| 1399 | + protected function editFormHeadInit() { |
| 1400 | + global $wgOut, $wgParser, $wgUser, $wgMaxArticleSize, $wgLang; |
| 1401 | + if ( $this->isConflict ) { |
1204 | 1402 | $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1</div>", 'explainconflict' ); |
1205 | | - |
1206 | | - $this->textbox2 = $this->textbox1; |
1207 | | - $this->textbox1 = $this->getContent(); |
1208 | 1403 | $this->edittime = $this->mArticle->getTimestamp(); |
1209 | 1404 | } else { |
| 1405 | + if ( $this->section != '' && !$this->isSectionEditSupported() ) { |
| 1406 | + // We use $this->section to much before this and getVal('wgSection') directly in other places |
| 1407 | + // at this point we can't reset $this->section to '' to fallback to non-section editing. |
| 1408 | + // Someone is welcome to try refactoring though |
| 1409 | + $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' ); |
| 1410 | + return false; |
| 1411 | + } |
| 1412 | + |
1210 | 1413 | if ( $this->section != '' && $this->section != 'new' ) { |
1211 | 1414 | $matches = array(); |
1212 | 1415 | if ( !$this->summary && !$this->preview && !$this->diff ) { |
— | — | @@ -1219,25 +1422,21 @@ |
1220 | 1423 | } |
1221 | 1424 | } |
1222 | 1425 | |
1223 | | - if ( $this->missingComment ) { |
| 1426 | + if ( $this->missingComment ) |
1224 | 1427 | $wgOut->wrapWikiMsg( '<div id="mw-missingcommenttext">$1</div>', 'missingcommenttext' ); |
1225 | | - } |
1226 | 1428 | |
1227 | | - if ( $this->missingSummary && $this->section != 'new' ) { |
| 1429 | + if ( $this->missingSummary && $this->section != 'new' ) |
1228 | 1430 | $wgOut->wrapWikiMsg( '<div id="mw-missingsummary">$1</div>', 'missingsummary' ); |
1229 | | - } |
1230 | 1431 | |
1231 | | - if ( $this->missingSummary && $this->section == 'new' ) { |
| 1432 | + if ( $this->missingSummary && $this->section == 'new' ) |
1232 | 1433 | $wgOut->wrapWikiMsg( '<div id="mw-missingcommentheader">$1</div>', 'missingcommentheader' ); |
1233 | | - } |
1234 | 1434 | |
1235 | | - if ( $this->hookError !== '' ) { |
| 1435 | + if ( $this->hookError !== '' ) |
1236 | 1436 | $wgOut->addWikiText( $this->hookError ); |
1237 | | - } |
1238 | 1437 | |
1239 | | - if ( !$this->checkUnicodeCompliantBrowser() ) { |
| 1438 | + if ( !$this->checkUnicodeCompliantBrowser() ) |
1240 | 1439 | $wgOut->addWikiMsg( 'nonunicodebrowser' ); |
1241 | | - } |
| 1440 | + |
1242 | 1441 | if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) { |
1243 | 1442 | // Let sysop know that this will make private content public if saved |
1244 | 1443 | |
— | — | @@ -1249,7 +1448,6 @@ |
1250 | 1449 | |
1251 | 1450 | if ( !$this->mArticle->mRevision->isCurrent() ) { |
1252 | 1451 | $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() ); |
1253 | | - $cancelParams['oldid'] = $this->mArticle->mRevision->getId(); |
1254 | 1452 | $wgOut->addWikiMsg( 'editingold' ); |
1255 | 1453 | } |
1256 | 1454 | } |
— | — | @@ -1274,17 +1472,13 @@ |
1275 | 1473 | } |
1276 | 1474 | } |
1277 | 1475 | |
1278 | | - $classes = array(); // Textarea CSS |
1279 | | - if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { |
1280 | | - } elseif ( $this->mTitle->isProtected( 'edit' ) ) { |
| 1476 | + if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) { |
1281 | 1477 | # Is the title semi-protected? |
1282 | 1478 | if ( $this->mTitle->isSemiProtected() ) { |
1283 | 1479 | $noticeMsg = 'semiprotectedpagewarning'; |
1284 | | - $classes[] = 'mw-textarea-sprotected'; |
1285 | 1480 | } else { |
1286 | 1481 | # Then it must be protected based on static groups (regular) |
1287 | 1482 | $noticeMsg = 'protectedpagewarning'; |
1288 | | - $classes[] = 'mw-textarea-protected'; |
1289 | 1483 | } |
1290 | 1484 | LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle->getPrefixedText(), '', |
1291 | 1485 | array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) ); |
— | — | @@ -1307,9 +1501,9 @@ |
1308 | 1502 | $wgOut->wrapWikiMsg( '<div class="mw-titleprotectedwarning">$1</div>', 'titleprotectedwarning' ); |
1309 | 1503 | } |
1310 | 1504 | |
1311 | | - if ( $this->kblength === false ) { |
| 1505 | + if ( $this->kblength === false ) |
1312 | 1506 | $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 ); |
1313 | | - } |
| 1507 | + |
1314 | 1508 | if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) { |
1315 | 1509 | $wgOut->addHTML( "<div class='error' id='mw-edit-longpageerror'>\n" ); |
1316 | 1510 | $wgOut->addWikiMsg( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ); |
— | — | @@ -1319,261 +1513,113 @@ |
1320 | 1514 | $wgOut->addWikiMsg( 'longpagewarning', $wgLang->formatNum( $this->kblength ) ); |
1321 | 1515 | $wgOut->addHTML( "</div>\n" ); |
1322 | 1516 | } |
| 1517 | + } |
1323 | 1518 | |
1324 | | - $action = $wgTitle->escapeLocalURL( array( 'action' => $this->action ) ); |
1325 | | - |
1326 | | - $summary = wfMsgExt( 'summary', 'parseinline' ); |
1327 | | - $subject = wfMsgExt( 'subject', 'parseinline' ); |
1328 | | - |
1329 | | - $cancel = $sk->link( |
1330 | | - $wgTitle, |
1331 | | - wfMsgExt( 'cancel', array( 'parseinline' ) ), |
1332 | | - array( 'id' => 'mw-editform-cancel' ), |
1333 | | - $cancelParams, |
1334 | | - array( 'known', 'noclasses' ) |
| 1519 | + /** |
| 1520 | + * Standard summary input and label (wgSummary), abstracted so EditPage |
| 1521 | + * subclasses may reorganize the form. |
| 1522 | + * Note that you do not need to worry about the label's for=, it will be |
| 1523 | + * inferred by the id given to the input. You can remove them both by |
| 1524 | + * passing array( 'id' => false ) to $userInputAttrs. |
| 1525 | + * |
| 1526 | + * @param $summary The value of the summary input |
| 1527 | + * @param $labelText The html to place inside the label |
| 1528 | + * @param $userInputAttrs An array of attrs to use on the input |
| 1529 | + * @param $userSpanAttrs An array of attrs to use on the span inside the label |
| 1530 | + * |
| 1531 | + * @return array An array in the format array( $label, $input ) |
| 1532 | + */ |
| 1533 | + function getSummaryInput($summary = "", $labelText = null, $userInputAttrs = null, $userSpanLabelAttrs = null) { |
| 1534 | + $inputAttrs = array( |
| 1535 | + 'id' => 'wpSummary', |
| 1536 | + 'maxlength' => '200', |
| 1537 | + 'tabindex' => '1', |
| 1538 | + 'size' => 60, |
| 1539 | + 'spellcheck' => 'true', |
| 1540 | + 'onfocus' => "currentFocused = this;", |
1335 | 1541 | ); |
1336 | | - $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' ); |
1337 | | - $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) ); |
1338 | | - $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'. |
1339 | | - htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '. |
1340 | | - htmlspecialchars( wfMsg( 'newwindow' ) ); |
| 1542 | + if ( $userInputAttrs ) |
| 1543 | + $inputAttrs += $userInputAttrs; |
| 1544 | + $spanLabelAttrs = array( |
| 1545 | + 'class' => $summaryClass, |
| 1546 | + 'id' => "wpSummaryLabel" |
| 1547 | + ); |
| 1548 | + if ( is_array($userSpanLabelAttrs) ) |
| 1549 | + $spanLabelAttrs += $userSpanLabelAttrs; |
1341 | 1550 | |
1342 | | - global $wgRightsText; |
1343 | | - if ( $wgRightsText ) { |
1344 | | - $copywarnMsg = array( 'copyrightwarning', |
1345 | | - '[[' . wfMsgForContent( 'copyrightpage' ) . ']]', |
1346 | | - $wgRightsText ); |
1347 | | - } else { |
1348 | | - $copywarnMsg = array( 'copyrightwarning2', |
1349 | | - '[[' . wfMsgForContent( 'copyrightpage' ) . ']]' ); |
| 1551 | + $label = null; |
| 1552 | + if ( $labelText ) { |
| 1553 | + $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText ); |
| 1554 | + $label = Xml::tags( 'span', $spanLabelAttrs, $label ); |
1350 | 1555 | } |
1351 | | - // Allow for site and per-namespace customization of contribution/copyright notice. |
1352 | | - wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) ); |
1353 | 1556 | |
1354 | | - if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) { |
1355 | | - # prepare toolbar for edit buttons |
1356 | | - $toolbar = EditPage::getEditToolbar(); |
1357 | | - } else { |
1358 | | - $toolbar = ''; |
1359 | | - } |
| 1557 | + $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs ); |
1360 | 1558 | |
| 1559 | + return array( $label, $input ); |
| 1560 | + } |
1361 | 1561 | |
1362 | | - // activate checkboxes if user wants them to be always active |
1363 | | - if ( !$this->preview && !$this->diff ) { |
1364 | | - # Sort out the "watch" checkbox |
1365 | | - if ( $wgUser->getOption( 'watchdefault' ) ) { |
1366 | | - # Watch all edits |
1367 | | - $this->watchthis = true; |
1368 | | - } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) { |
1369 | | - # Watch creations |
1370 | | - $this->watchthis = true; |
1371 | | - } elseif ( $this->mTitle->userIsWatching() ) { |
1372 | | - # Already watched |
1373 | | - $this->watchthis = true; |
1374 | | - } |
1375 | | - |
1376 | | - # May be overriden by request parameters |
1377 | | - if( $wgRequest->getBool( 'watchthis' ) ) { |
1378 | | - $this->watchthis = true; |
1379 | | - } |
1380 | | - |
1381 | | - if ( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true; |
1382 | | - } |
1383 | | - |
1384 | | - $wgOut->addHTML( $this->editFormPageTop ); |
1385 | | - |
1386 | | - if ( $wgUser->getOption( 'previewontop' ) ) { |
1387 | | - $this->displayPreviewArea( $previewOutput, true ); |
1388 | | - } |
1389 | | - |
1390 | | - |
1391 | | - $wgOut->addHTML( $this->editFormTextTop ); |
1392 | | - |
1393 | | - # if this is a comment, show a subject line at the top, which is also the edit summary. |
1394 | | - # Otherwise, show a summary field at the bottom |
1395 | | - $summarytext = $wgContLang->recodeForEdit( $this->summary ); |
1396 | | - |
1397 | | - # If a blank edit summary was previously provided, and the appropriate |
1398 | | - # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the |
1399 | | - # user being bounced back more than once in the event that a summary |
1400 | | - # is not required. |
1401 | | - ##### |
1402 | | - # For a bit more sophisticated detection of blank summaries, hash the |
1403 | | - # automatic one and pass that in the hidden field wpAutoSummary. |
1404 | | - $summaryhiddens = ''; |
1405 | | - if ( $this->missingSummary ) $summaryhiddens .= Xml::hidden( 'wpIgnoreBlankSummary', true ); |
1406 | | - $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary ); |
1407 | | - $summaryhiddens .= Xml::hidden( 'wpAutoSummary', $autosumm ); |
1408 | | - if ( $this->section == 'new' ) { |
1409 | | - $commentsubject = ''; |
1410 | | - if ( !$wgRequest->getBool( 'nosummary' ) ) { |
1411 | | - # Add a class if 'missingsummary' is triggered to allow styling of the summary line |
1412 | | - $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary'; |
1413 | | - |
1414 | | - $commentsubject = |
1415 | | - Xml::tags( 'label', array( 'for' => 'wpSummary' ), $subject ); |
1416 | | - $commentsubject = |
1417 | | - Xml::tags( 'span', array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ), |
1418 | | - $commentsubject ); |
1419 | | - $commentsubject .= ' '; |
1420 | | - $commentsubject .= Html::input( 'wpSummary', |
1421 | | - $summarytext, |
1422 | | - 'text', |
1423 | | - array( |
1424 | | - 'id' => 'wpSummary', |
1425 | | - 'maxlength' => '200', |
1426 | | - 'tabindex' => '1', |
1427 | | - 'size' => '60', |
1428 | | - 'spellcheck' => 'true' |
1429 | | - ) ); |
1430 | | - } else { |
1431 | | - $summaryhiddens .= Xml::hidden( 'wpIgnoreBlankSummary', true ); # bug 18699 |
1432 | | - } |
1433 | | - $editsummary = "<div class='editOptions'>\n"; |
1434 | | - global $wgParser; |
1435 | | - $formattedSummary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $this->summary ) ); |
1436 | | - $subjectpreview = $summarytext && ( $this->preview || $this->diff ) ? |
1437 | | - "<div class=\"mw-summary-preview\">". wfMsgExt( 'subject-preview', 'parseinline' ) . $sk->commentBlock( $formattedSummary, $this->mTitle, true )."</div>\n" : ''; |
1438 | | - $summarypreview = ''; |
| 1562 | + /** |
| 1563 | + * @param bool $isSubjectPreview true if this is the section subject/title |
| 1564 | + * up top, or false if this is the comment |
| 1565 | + * summary down below the textarea |
| 1566 | + * @param string $summary The text of the summary to display |
| 1567 | + * @return string |
| 1568 | + */ |
| 1569 | + protected function showSummaryInput( $isSubjectPreview, $summary = "" ) { |
| 1570 | + global $wgOut, $wgContLang; |
| 1571 | + # Add a class if 'missingsummary' is triggered to allow styling of the summary line |
| 1572 | + $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary'; |
| 1573 | + if ( $isSubjectPreview ) { |
| 1574 | + if ( $wgRequest->getBool( 'nosummary' ) ) |
| 1575 | + return; |
1439 | 1576 | } else { |
1440 | | - $commentsubject = ''; |
1441 | | - |
1442 | | - # Add a class if 'missingsummary' is triggered to allow styling of the summary line |
1443 | | - $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary'; |
1444 | | - |
1445 | | - $editsummary = Xml::tags( 'label', array( 'for' => 'wpSummary' ), $summary ); |
1446 | | - $editsummary = Xml::tags( 'span', array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ), |
1447 | | - $editsummary ) . ' '; |
1448 | | - |
1449 | | - $editsummary .= Html::input( 'wpSummary', |
1450 | | - $summarytext, |
1451 | | - 'text', |
1452 | | - array( |
1453 | | - 'id' => 'wpSummary', |
1454 | | - 'maxlength' => '200', |
1455 | | - 'tabindex' => '1', |
1456 | | - 'size' => '60', |
1457 | | - 'spellcheck' => 'true' |
1458 | | - ) ); |
1459 | | - |
1460 | | - // No idea where this is closed. |
1461 | | - $editsummary .= '<br/>'; |
1462 | | - $editsummary = Xml::openElement( 'div', array( 'class' => 'editOptions' ) ) |
1463 | | - . ($this->mShowSummaryField ? $editsummary : ''); |
1464 | | - |
1465 | | - $summarypreview = ''; |
1466 | | - if ( $summarytext && ( $this->preview || $this->diff ) ) { |
1467 | | - $summarypreview = |
1468 | | - Xml::tags( 'div', |
1469 | | - array( 'class' => 'mw-summary-preview' ), |
1470 | | - wfMsgExt( 'summary-preview', 'parseinline' ) . |
1471 | | - $sk->commentBlock( $this->summary, $this->mTitle ) |
1472 | | - ); |
1473 | | - } |
1474 | | - $subjectpreview = ''; |
| 1577 | + if ( !$this->mShowSummaryField ) |
| 1578 | + return; |
1475 | 1579 | } |
1476 | | - $commentsubject .= $summaryhiddens; |
| 1580 | + $summary = $wgContLang->recodeForEdit( $summary ); |
| 1581 | + $labelText = wfMsgExt( $isSubjectPreview ? 'subject' : 'summary', 'parseinline' ); |
| 1582 | + list($label, $input) = $this->getSummaryInput($summary, $labelText, array( 'class' => $summaryClass ), array()); |
| 1583 | + $wgOut->addHTML("{$label} {$input}"); |
| 1584 | + } |
1477 | 1585 | |
1478 | | - # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display |
1479 | | - if ( !$this->preview && !$this->diff ) { |
1480 | | - $wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus()' ); |
1481 | | - } |
1482 | | - $templates = $this->getTemplates(); |
1483 | | - $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != ''); |
| 1586 | + /** |
| 1587 | + * @param bool $isSubjectPreview true if this is the section subject/title |
| 1588 | + * up top, or false if this is the comment |
| 1589 | + * summary down below the textarea |
| 1590 | + * @param string $summary The text of the summary to display |
| 1591 | + * @return string |
| 1592 | + */ |
| 1593 | + protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) { |
| 1594 | + if ( !$summary || ( !$this->preview && !$this->diff ) ) |
| 1595 | + return ""; |
| 1596 | + |
| 1597 | + global $wgParser, $wgUser; |
| 1598 | + $sk = $wgUser->getSkin(); |
| 1599 | + |
| 1600 | + if ( $isSubjectPreview ) |
| 1601 | + $summary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $summary ) ); |
1484 | 1602 | |
1485 | | - $hiddencats = $this->mArticle->getHiddenCategories(); |
1486 | | - $formattedhiddencats = $sk->formatHiddenCategories( $hiddencats ); |
| 1603 | + $summary = wfMsgExt( 'subject-preview', 'parseinline' ) . $sk->commentBlock( $summary, $this->mTitle, !!$isSubjectPreview ); |
| 1604 | + return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary ); |
| 1605 | + } |
1487 | 1606 | |
1488 | | - global $wgUseMetadataEdit ; |
1489 | | - if ( $wgUseMetadataEdit ) { |
1490 | | - $metadata = $this->mMetaData ; |
1491 | | - $metadata = htmlspecialchars( $wgContLang->recodeForEdit( $metadata ) ) ; |
1492 | | - $top = wfMsgWikiHtml( 'metadata_help' ); |
1493 | | - /* ToDo: Replace with clean code */ |
1494 | | - $ew = $wgUser->getOption( 'editwidth' ); |
1495 | | - if ( $ew ) $ew = " style=\"width:100%\""; |
1496 | | - else $ew = ''; |
1497 | | - $cols = $wgUser->getIntOption( 'cols' ); |
1498 | | - /* /ToDo */ |
1499 | | - $metadata = $top . "<textarea name='metadata' rows='3' cols='{$cols}'{$ew}>{$metadata}</textarea>" ; |
1500 | | - } |
1501 | | - else $metadata = "" ; |
| 1607 | + protected function showFormBeforeText() { |
| 1608 | + global $wgOut; |
| 1609 | + $section = htmlspecialchars( $this->section ); |
| 1610 | + $wgOut->addHTML( <<<INPUTS |
| 1611 | +<input type='hidden' value="{$section}" name="wpSection" /> |
| 1612 | +<input type='hidden' value="{$this->starttime}" name="wpStarttime" /> |
| 1613 | +<input type='hidden' value="{$this->edittime}" name="wpEdittime" /> |
| 1614 | +<input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" /> |
1502 | 1615 | |
1503 | | - $recreate = ''; |
1504 | | - if ( $this->wasDeletedSinceLastEdit() ) { |
1505 | | - if ( 'save' != $this->formtype ) { |
1506 | | - $wgOut->wrapWikiMsg( |
1507 | | - "<div class='error mw-deleted-while-editing'>\n$1</div>", |
1508 | | - 'deletedwhileediting' ); |
1509 | | - } else { |
1510 | | - // Hide the toolbar and edit area, user can click preview to get it back |
1511 | | - // Add an confirmation checkbox and explanation. |
1512 | | - $toolbar = ''; |
1513 | | - $recreate = '<div class="mw-confirm-recreate">' . |
1514 | | - $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment ) ) . |
1515 | | - Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false, |
1516 | | - array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ) |
1517 | | - ) . '</div>'; |
1518 | | - } |
1519 | | - } |
1520 | | - |
1521 | | - $tabindex = 2; |
1522 | | - |
1523 | | - $checkboxes = $this->getCheckboxes( $tabindex, $sk, |
1524 | | - array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) ); |
1525 | | - |
1526 | | - $checkboxhtml = implode( $checkboxes, "\n" ); |
1527 | | - |
1528 | | - $buttons = $this->getEditButtons( $tabindex ); |
1529 | | - $buttonshtml = implode( $buttons, "\n" ); |
1530 | | - |
1531 | | - $safemodehtml = $this->checkUnicodeCompliantBrowser() |
1532 | | - ? '' : Xml::hidden( 'safemode', '1' ); |
1533 | | - |
1534 | | - $wgOut->addHTML( <<<END |
1535 | | -{$toolbar} |
1536 | | -<form id="editform" name="editform" method="post" action="$action" enctype="multipart/form-data"> |
1537 | | -END |
1538 | | -); |
1539 | | - |
1540 | | - if ( is_callable( $formCallback ) ) { |
1541 | | - call_user_func_array( $formCallback, array( &$wgOut ) ); |
1542 | | - } |
1543 | | - |
1544 | | - wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) ); |
1545 | | - |
1546 | | - // Put these up at the top to ensure they aren't lost on early form submission |
1547 | | - $this->showFormBeforeText(); |
1548 | | - |
1549 | | - $wgOut->addHTML( <<<END |
1550 | | -{$recreate} |
1551 | | -{$commentsubject} |
1552 | | -{$subjectpreview} |
1553 | | -{$this->editFormTextBeforeContent} |
1554 | | -END |
1555 | | -); |
1556 | | - $this->showTextbox1( $classes ); |
1557 | | - |
1558 | | - $wgOut->addHTML( $this->editFormTextAfterContent ); |
1559 | | - |
1560 | | - $wgOut->wrapWikiMsg( "<div id=\"editpage-copywarn\">\n$1\n</div>", $copywarnMsg ); |
1561 | | - $wgOut->addHTML( <<<END |
1562 | | -{$this->editFormTextAfterWarn} |
1563 | | -{$metadata} |
1564 | | -{$editsummary} |
1565 | | -{$summarypreview} |
1566 | | -{$checkboxhtml} |
1567 | | -{$safemodehtml} |
1568 | | -END |
1569 | | -); |
1570 | | - |
1571 | | - $wgOut->addHTML( |
1572 | | -"<div class='editButtons'> |
1573 | | -{$buttonshtml} |
1574 | | - <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span> |
1575 | | -</div><!-- editButtons --> |
1576 | | -</div><!-- editOptions -->"); |
1577 | | - |
| 1616 | +INPUTS |
| 1617 | + ); |
| 1618 | + if ( !$this->checkUnicodeCompliantBrowser() ) |
| 1619 | + $wgOut->addHTML(Xml::hidden( 'safemode', '1' )); |
| 1620 | + } |
| 1621 | + |
| 1622 | + protected function showFormAfterText() { |
| 1623 | + global $wgOut, $wgUser; |
1578 | 1624 | /** |
1579 | 1625 | * To make it harder for someone to slip a user a page |
1580 | 1626 | * which submits an edit form to the wiki without their |
— | — | @@ -1586,67 +1632,68 @@ |
1587 | 1633 | * include the constant suffix to prevent editing from |
1588 | 1634 | * broken text-mangling proxies. |
1589 | 1635 | */ |
1590 | | - $token = htmlspecialchars( $wgUser->editToken() ); |
1591 | | - $wgOut->addHTML( "\n<input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" ); |
1592 | | - |
1593 | | - $this->showTosSummary(); |
1594 | | - $this->showEditTools(); |
1595 | | - |
1596 | | - $wgOut->addHTML( <<<END |
1597 | | -{$this->editFormTextAfterTools} |
1598 | | -<div class='templatesUsed'> |
1599 | | -{$formattedtemplates} |
1600 | | -</div> |
1601 | | -<div class='hiddencats'> |
1602 | | -{$formattedhiddencats} |
1603 | | -</div> |
1604 | | -END |
1605 | | -); |
1606 | | - |
1607 | | - if ( $this->isConflict && wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) { |
1608 | | - $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" ); |
1609 | | - |
1610 | | - $de = new DifferenceEngine( $this->mTitle ); |
1611 | | - $de->setText( $this->textbox2, $this->textbox1 ); |
1612 | | - $de->showDiff( wfMsg( "yourtext" ), wfMsg( "storedversion" ) ); |
1613 | | - |
1614 | | - $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" ); |
1615 | | - $this->showTextbox2(); |
1616 | | - } |
1617 | | - $wgOut->addHTML( $this->editFormTextBottom ); |
1618 | | - $wgOut->addHTML( "</form>\n" ); |
1619 | | - if ( !$wgUser->getOption( 'previewontop' ) ) { |
1620 | | - $this->displayPreviewArea( $previewOutput, false ); |
1621 | | - } |
1622 | | - |
1623 | | - wfProfileOut( __METHOD__ ); |
| 1636 | + $wgOut->addHTML( "\n" . Xml::hidden( "wpEditToken", $wgUser->editToken() ) . "\n" ); |
1624 | 1637 | } |
1625 | 1638 | |
1626 | | - protected function showFormBeforeText() { |
1627 | | - global $wgOut; |
1628 | | - $wgOut->addHTML( " |
1629 | | -<input type='hidden' value=\"" . htmlspecialchars( $this->section ) . "\" name=\"wpSection\" /> |
1630 | | -<input type='hidden' value=\"{$this->starttime}\" name=\"wpStarttime\" />\n |
1631 | | -<input type='hidden' value=\"{$this->edittime}\" name=\"wpEdittime\" />\n |
1632 | | -<input type='hidden' value=\"{$this->scrolltop}\" name=\"wpScrolltop\" id=\"wpScrolltop\" />\n" ); |
| 1639 | + /** |
| 1640 | + * Subpage overridable method for printing the form for page content editing |
| 1641 | + * By default this simply outputs wpTextbox1 |
| 1642 | + * Subclasses can override this to provide a custom UI for editing; |
| 1643 | + * be it a form, or simply wpTextbox1 with a modified content that will be |
| 1644 | + * reverse modified when extracted from the post data. |
| 1645 | + * Note that this is basically the inverse for importContentFormData |
| 1646 | + * |
| 1647 | + * @praram WebRequest $request |
| 1648 | + */ |
| 1649 | + protected function showContentForm() { |
| 1650 | + $this->showTextbox1(); |
1633 | 1651 | } |
1634 | 1652 | |
1635 | | - protected function showTextbox1( $classes ) { |
| 1653 | + /** |
| 1654 | + * Method to output wpTextbox1 |
| 1655 | + * The $textoverride method can be used by subclasses overriding showContentForm |
| 1656 | + * to pass back to this method. |
| 1657 | + * |
| 1658 | + * @param array $customAttribs An array of html attributes to use in the textarea |
| 1659 | + * @param string $textoverride Optional text to override $this->textarea1 with |
| 1660 | + */ |
| 1661 | + protected function showTextbox1($customAttribs = null, $textoverride = null) { |
| 1662 | + $classes = array(); // Textarea CSS |
| 1663 | + if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) { |
| 1664 | + # Is the title semi-protected? |
| 1665 | + if ( $this->mTitle->isSemiProtected() ) { |
| 1666 | + $classes[] = 'mw-textarea-sprotected'; |
| 1667 | + } else { |
| 1668 | + # Then it must be protected based on static groups (regular) |
| 1669 | + $classes[] = 'mw-textarea-protected'; |
| 1670 | + } |
| 1671 | + } |
1636 | 1672 | $attribs = array( 'tabindex' => 1 ); |
| 1673 | + if ( is_array($customAttribs) ) |
| 1674 | + $attribs += $customAttribs; |
1637 | 1675 | |
1638 | 1676 | if ( $this->wasDeletedSinceLastEdit() ) |
1639 | 1677 | $attribs['type'] = 'hidden'; |
1640 | | - if ( !empty( $classes ) ) |
| 1678 | + if ( !empty( $classes ) ) { |
| 1679 | + if ( isset($attribs['class']) ) |
| 1680 | + $classes[] = $attribs['class']; |
1641 | 1681 | $attribs['class'] = implode( ' ', $classes ); |
| 1682 | + } |
1642 | 1683 | |
1643 | | - $this->showTextbox( $this->textbox1, 'wpTextbox1', $attribs ); |
| 1684 | + # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display |
| 1685 | + if ( !$this->preview && !$this->diff ) { |
| 1686 | + global $wgOut; |
| 1687 | + $wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus();' ); |
| 1688 | + } |
| 1689 | + |
| 1690 | + $this->showTextbox( isset($textoverride) ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs ); |
1644 | 1691 | } |
1645 | 1692 | |
1646 | 1693 | protected function showTextbox2() { |
1647 | 1694 | $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6 ) ); |
1648 | 1695 | } |
1649 | 1696 | |
1650 | | - protected function showTextbox( $content, $name, $attribs = array() ) { |
| 1697 | + protected function showTextbox( $content, $name, $customAttribs = array() ) { |
1651 | 1698 | global $wgOut, $wgUser; |
1652 | 1699 | |
1653 | 1700 | $wikitext = $this->safeUnicodeOutput( $content ); |
— | — | @@ -1658,18 +1705,28 @@ |
1659 | 1706 | $wikitext .= "\n"; |
1660 | 1707 | } |
1661 | 1708 | |
1662 | | - $attribs['accesskey'] = ','; |
1663 | | - $attribs['id'] = $name; |
| 1709 | + $attribs = $customAttribs + array( |
| 1710 | + 'accesskey' => ',', |
| 1711 | + 'id' => $name, |
| 1712 | + 'cols' => $wgUser->getIntOption( 'cols' ), |
| 1713 | + 'rows' => $wgUser->getIntOption( 'rows' ), |
| 1714 | + 'onfocus' => "currentFocused = this;", |
| 1715 | + ); |
1664 | 1716 | |
1665 | 1717 | if ( $wgUser->getOption( 'editwidth' ) ) |
1666 | | - $attribs['style'] = 'width: 100%'; |
| 1718 | + $attribs['style'] .= 'width: 100%'; |
1667 | 1719 | |
1668 | | - $wgOut->addHTML( Xml::textarea( |
1669 | | - $name, |
1670 | | - $wikitext, |
1671 | | - $wgUser->getIntOption( 'cols' ), $wgUser->getIntOption( 'rows' ), |
1672 | | - $attribs ) ); |
| 1720 | + $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) ); |
1673 | 1721 | } |
| 1722 | + |
| 1723 | + protected function showMetaData() { |
| 1724 | + global $wgOut, $wgContLang, $wgUser; |
| 1725 | + $metadata = htmlspecialchars( $wgContLang->recodeForEdit( $this->mMetaData ) ); |
| 1726 | + $ew = $wgUser->getOption( 'editwidth' ) ? ' style="width:100%"' : ''; |
| 1727 | + $cols = $wgUser->getIntOption( 'cols' ); |
| 1728 | + $metadata = wfMsgWikiHtml( 'metadata_help' ) . "<textarea name='metadata' rows='3' cols='{$cols}'{$ew}>{$metadata}</textarea>" ; |
| 1729 | + $wgOut->addHTML( $metadata ); |
| 1730 | + } |
1674 | 1731 | |
1675 | 1732 | protected function displayPreviewArea( $previewOutput, $isOnTop = false ) { |
1676 | 1733 | global $wgOut; |
— | — | @@ -1739,7 +1796,64 @@ |
1740 | 1797 | $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) ); |
1741 | 1798 | $wgOut->addHTML( '</div>' ); |
1742 | 1799 | } |
| 1800 | + |
| 1801 | + protected function getCopywarn() { |
| 1802 | + global $wgRightsText; |
| 1803 | + if ( $wgRightsText ) { |
| 1804 | + $copywarnMsg = array( 'copyrightwarning', |
| 1805 | + '[[' . wfMsgForContent( 'copyrightpage' ) . ']]', |
| 1806 | + $wgRightsText ); |
| 1807 | + } else { |
| 1808 | + $copywarnMsg = array( 'copyrightwarning2', |
| 1809 | + '[[' . wfMsgForContent( 'copyrightpage' ) . ']]' ); |
| 1810 | + } |
| 1811 | + // Allow for site and per-namespace customization of contribution/copyright notice. |
| 1812 | + wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) ); |
| 1813 | + |
| 1814 | + return "<div id=\"editpage-copywarn\">\n" . call_user_func_array("wfMsgNoTrans", $copywarnMsg) . "\n</div>"; |
| 1815 | + } |
| 1816 | + |
| 1817 | + protected function showStandardInputs( &$tabindex = 2 ) { |
| 1818 | + global $wgOut, $wgUser; |
| 1819 | + $wgOut->addHTML( "<div class='editOptions'>\n" ); |
1743 | 1820 | |
| 1821 | + if ( $this->section != 'new' ) { |
| 1822 | + $this->showSummaryInput( false, $this->summary ); |
| 1823 | + $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) ); |
| 1824 | + } |
| 1825 | + |
| 1826 | + $checkboxes = $this->getCheckboxes( $tabindex, $wgUser->getSkin(), |
| 1827 | + array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) ); |
| 1828 | + $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" ); |
| 1829 | + $wgOut->addHTML( "<div class='editButtons'>\n" ); |
| 1830 | + $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" ); |
| 1831 | + |
| 1832 | + $cancel = $this->getCancelLink(); |
| 1833 | + $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' ); |
| 1834 | + $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) ); |
| 1835 | + $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'. |
| 1836 | + htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '. |
| 1837 | + htmlspecialchars( wfMsg( 'newwindow' ) ); |
| 1838 | + $wgOut->addHTML( " <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span>\n" ); |
| 1839 | + $wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" ); |
| 1840 | + } |
| 1841 | + |
| 1842 | + protected function showConflict() { |
| 1843 | + global $wgOut; |
| 1844 | + $this->textbox2 = $this->textbox1; |
| 1845 | + $this->textbox1 = $this->getContent(); |
| 1846 | + if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) { |
| 1847 | + $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" ); |
| 1848 | + |
| 1849 | + $de = new DifferenceEngine( $this->mTitle ); |
| 1850 | + $de->setText( $this->textbox2, $this->textbox1 ); |
| 1851 | + $de->showDiff( wfMsg( "yourtext" ), wfMsg( "storedversion" ) ); |
| 1852 | + |
| 1853 | + $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" ); |
| 1854 | + $this->showTextbox2(); |
| 1855 | + } |
| 1856 | + } |
| 1857 | + |
1744 | 1858 | protected function getLastDelete() { |
1745 | 1859 | $dbr = wfGetDB( DB_SLAVE ); |
1746 | 1860 | $data = $dbr->selectRow( |
— | — | @@ -2057,6 +2171,7 @@ |
2058 | 2172 | global $wgStylePath, $wgContLang, $wgLang; |
2059 | 2173 | |
2060 | 2174 | /** |
| 2175 | + |
2061 | 2176 | * toolarray an array of arrays which each include the filename of |
2062 | 2177 | * the button image (without path), the opening tag, the closing tag, |
2063 | 2178 | * and optionally a sample text that is inserted between the two when no |
— | — | @@ -2324,6 +2439,22 @@ |
2325 | 2440 | } |
2326 | 2441 | |
2327 | 2442 | |
| 2443 | + public function getCancelLink() { |
| 2444 | + global $wgUser, $wgTitle; |
| 2445 | + $cancelParams = array(); |
| 2446 | + if ( !$this->isConflict && isset( $this->mArticle ) && |
| 2447 | + isset( $this->mArticle->mRevision ) && |
| 2448 | + !$this->mArticle->mRevision->isCurrent() ) |
| 2449 | + $cancelParams['oldid'] = $this->mArticle->mRevision->getId(); |
| 2450 | + return $wgUser->getSkin()->link( |
| 2451 | + $wgTitle, |
| 2452 | + wfMsgExt( 'cancel', array( 'parseinline' ) ), |
| 2453 | + array( 'id' => 'mw-editform-cancel' ), |
| 2454 | + $cancelParams, |
| 2455 | + array( 'known', 'noclasses' ) |
| 2456 | + ); |
| 2457 | + } |
| 2458 | + |
2328 | 2459 | /** |
2329 | 2460 | * Get a diff between the current contents of the edit box and the |
2330 | 2461 | * version of the page we're editing from. |
— | — | @@ -2367,6 +2498,13 @@ |
2368 | 2499 | : $text; |
2369 | 2500 | } |
2370 | 2501 | |
| 2502 | + function safeUnicodeText( $request, $text ) { |
| 2503 | + $text = rtrim( $text ); |
| 2504 | + return $request->getBool( 'safemode' ) |
| 2505 | + ? $this->unmakesafe( $text ) |
| 2506 | + : $text; |
| 2507 | + } |
| 2508 | + |
2371 | 2509 | /** |
2372 | 2510 | * Filter an output field through a Unicode armoring process if it is |
2373 | 2511 | * going to an old browser with known broken Unicode editing issues. |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -292,6 +292,7 @@ |
293 | 293 | * Allow \pagecolor and \definecolor in texvc |
294 | 294 | * $wgTexvcBackgroundColor contains background color for texvc call |
295 | 295 | * (bug 21574) Redirects can now have "303 See Other" HTTP status |
| 296 | +* EditPage refactored to allow extensions to derive new edit modes much easier. |
296 | 297 | |
297 | 298 | === Bug fixes in 1.16 === |
298 | 299 | |
Index: trunk/phase3/skins/common/edit.js |
— | — | @@ -5,13 +5,13 @@ |
6 | 6 | function addButton(imageFile, speedTip, tagOpen, tagClose, sampleText, imageId) { |
7 | 7 | // Don't generate buttons for browsers which don't fully |
8 | 8 | // support it. |
9 | | - mwEditButtons[mwEditButtons.length] = |
| 9 | + mwEditButtons.push( |
10 | 10 | {"imageId": imageId, |
11 | 11 | "imageFile": imageFile, |
12 | 12 | "speedTip": speedTip, |
13 | 13 | "tagOpen": tagOpen, |
14 | 14 | "tagClose": tagClose, |
15 | | - "sampleText": sampleText}; |
| 15 | + "sampleText": sampleText}); |
16 | 16 | } |
17 | 17 | |
18 | 18 | // this function generates the actual toolbar buttons with localized text |
— | — | @@ -44,11 +44,9 @@ |
45 | 45 | var toolbar = document.getElementById('toolbar'); |
46 | 46 | if (!toolbar) { return false; } |
47 | 47 | |
48 | | - var textbox = document.getElementById('wpTextbox1'); |
49 | | - if (!textbox) { return false; } |
50 | | - |
51 | 48 | // Don't generate buttons for browsers which don't fully |
52 | 49 | // support it. |
| 50 | + var textbox = document.createElement('textarea'); // abstract, don't assume wpTextbox1 is always there |
53 | 51 | if (!(document.selection && document.selection.createRange) |
54 | 52 | && textbox.selectionStart === null) { |
55 | 53 | return false; |
— | — | @@ -154,17 +152,13 @@ |
155 | 153 | if( scrollTop.value ) |
156 | 154 | editBox.scrollTop = scrollTop.value; |
157 | 155 | addHandler( editForm, 'submit', function() { |
158 | | - document.getElementById( 'wpScrolltop' ).value = document.getElementById( 'wpTextbox1' ).scrollTop; |
| 156 | + scrollTop.value = editBox.scrollTop; |
159 | 157 | } ); |
160 | 158 | } |
161 | 159 | } |
162 | 160 | hookEvent( 'load', scrollEditBox ); |
163 | 161 | hookEvent( 'load', mwSetupToolbar ); |
164 | 162 | hookEvent( 'load', function() { |
165 | | - if ( document.editform ) { |
166 | | - currentFocused = document.editform.wpTextbox1; |
167 | | - document.editform.wpTextbox1.onfocus = function() { currentFocused = document.editform.wpTextbox1; }; |
168 | | - document.editform.wpSummary.onfocus = function() { currentFocused = document.editform.wpSummary; }; |
169 | | - } |
| 163 | + currentFocused = document.getElementById( 'wpTextbox1' ); |
170 | 164 | } ); |
171 | 165 | |