r59655 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r59654‎ | r59655 | r59656 >
Date:07:22, 2 December 2009
Author:dantman
Status:resolved (Comments)
Tags:
Comment:
EditPage refactor and improvements.
- EditPage::showEditForm broken up into task specific methods
- Subclasses can indicate they can't support section mode
- Standard inputs should all be now in methods they can be grabbed from by subclasses that want to re-arange things
- Many more places to override and hook into to change behavior
- showTextbox1 parameters changed from $classes to $customAttribs and $textoverride
- showContentForm and importContentFormData added; New workflow to override the wpTextbox1 behavior to use an alternate edit form ui or handle wpTextbox1 content in an alternate way.
- getActionURL added for EditPage subclasses used in places where $this->action isn't enough (ie: EditPage on special pages)
Html::textarea added
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/EditPage.php (modified) (history)
  • /trunk/phase3/includes/Html.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/skins/common/edit.js (modified) (history)

Diff [purge]

Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -1338,6 +1338,8 @@
13391339 'nocreatetext' => '{{SITENAME}} has restricted the ability to create new pages.
13401340 You can go back and edit an existing page, or [[Special:UserLogin|log in or create an account]].',
13411341 '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.',
13421344 'permissionserrors' => 'Permissions Errors',
13431345 'permissionserrorstext' => 'You do not have permission to do that, for the following {{PLURAL:$1|reason|reasons}}:',
13441346 '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 @@
487487 public static function hidden( $name, $value, $attribs = array() ) {
488488 return self::input( $name, $value, 'hidden', $attribs );
489489 }
 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+ }
490515 }
Index: trunk/phase3/includes/EditPage.php
@@ -556,6 +556,29 @@
557557 }
558558
559559 /**
 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+ /**
560583 * @todo document
561584 * @param $request
562585 */
@@ -572,7 +595,16 @@
573596 # Also remove trailing whitespace, but don't remove _initial_
574597 # whitespace from the text boxes. This may be significant formatting.
575598 $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+ }
577609 $this->mMetaData = rtrim( $request->getText( 'metadata' ) );
578610 # Truncate for whole multibyte characters. +5 bytes for ellipsis
579611 $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250, '' );
@@ -643,7 +675,6 @@
644676 # Not a posted form? Start with nothing.
645677 wfDebug( __METHOD__ . ": Not a posted form.\n" );
646678 $this->textbox1 = '';
647 - $this->textbox2 = '';
648679 $this->mMetaData = '';
649680 $this->summary = '';
650681 $this->edittime = '';
@@ -681,6 +712,18 @@
682713 }
683714
684715 /**
 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+ /**
685728 * Make sure the form isn't faking a user's credentials.
686729 *
687730 * @param $request WebRequest
@@ -1168,7 +1211,7 @@
11691212 * near the top, for captchas and the like.
11701213 */
11711214 function showEditForm( $formCallback=null ) {
1172 - global $wgOut, $wgUser, $wgLang, $wgContLang, $wgMaxArticleSize, $wgTitle, $wgRequest;
 1215+ global $wgOut, $wgUser, $wgTitle, $wgRequest;
11731216
11741217 # If $wgTitle is null, that means we're in API mode.
11751218 # Some hook probably called this function without checking
@@ -1197,15 +1240,175 @@
11981241 # Enabled article-related sidebar, toplinks, etc.
11991242 $wgOut->setArticleRelated( true );
12001243
1201 - $cancelParams = array();
 1244+ if ( $this->editFormHeadInit() === false )
 1245+ return;
12021246
 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+
12031349 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 ) {
12041402 $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1</div>", 'explainconflict' );
1205 -
1206 - $this->textbox2 = $this->textbox1;
1207 - $this->textbox1 = $this->getContent();
12081403 $this->edittime = $this->mArticle->getTimestamp();
12091404 } 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+
12101413 if ( $this->section != '' && $this->section != 'new' ) {
12111414 $matches = array();
12121415 if ( !$this->summary && !$this->preview && !$this->diff ) {
@@ -1219,25 +1422,21 @@
12201423 }
12211424 }
12221425
1223 - if ( $this->missingComment ) {
 1426+ if ( $this->missingComment )
12241427 $wgOut->wrapWikiMsg( '<div id="mw-missingcommenttext">$1</div>', 'missingcommenttext' );
1225 - }
12261428
1227 - if ( $this->missingSummary && $this->section != 'new' ) {
 1429+ if ( $this->missingSummary && $this->section != 'new' )
12281430 $wgOut->wrapWikiMsg( '<div id="mw-missingsummary">$1</div>', 'missingsummary' );
1229 - }
12301431
1231 - if ( $this->missingSummary && $this->section == 'new' ) {
 1432+ if ( $this->missingSummary && $this->section == 'new' )
12321433 $wgOut->wrapWikiMsg( '<div id="mw-missingcommentheader">$1</div>', 'missingcommentheader' );
1233 - }
12341434
1235 - if ( $this->hookError !== '' ) {
 1435+ if ( $this->hookError !== '' )
12361436 $wgOut->addWikiText( $this->hookError );
1237 - }
12381437
1239 - if ( !$this->checkUnicodeCompliantBrowser() ) {
 1438+ if ( !$this->checkUnicodeCompliantBrowser() )
12401439 $wgOut->addWikiMsg( 'nonunicodebrowser' );
1241 - }
 1440+
12421441 if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) {
12431442 // Let sysop know that this will make private content public if saved
12441443
@@ -1249,7 +1448,6 @@
12501449
12511450 if ( !$this->mArticle->mRevision->isCurrent() ) {
12521451 $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() );
1253 - $cancelParams['oldid'] = $this->mArticle->mRevision->getId();
12541452 $wgOut->addWikiMsg( 'editingold' );
12551453 }
12561454 }
@@ -1274,17 +1472,13 @@
12751473 }
12761474 }
12771475
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' ) ) {
12811477 # Is the title semi-protected?
12821478 if ( $this->mTitle->isSemiProtected() ) {
12831479 $noticeMsg = 'semiprotectedpagewarning';
1284 - $classes[] = 'mw-textarea-sprotected';
12851480 } else {
12861481 # Then it must be protected based on static groups (regular)
12871482 $noticeMsg = 'protectedpagewarning';
1288 - $classes[] = 'mw-textarea-protected';
12891483 }
12901484 LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle->getPrefixedText(), '',
12911485 array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
@@ -1307,9 +1501,9 @@
13081502 $wgOut->wrapWikiMsg( '<div class="mw-titleprotectedwarning">$1</div>', 'titleprotectedwarning' );
13091503 }
13101504
1311 - if ( $this->kblength === false ) {
 1505+ if ( $this->kblength === false )
13121506 $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
1313 - }
 1507+
13141508 if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
13151509 $wgOut->addHTML( "<div class='error' id='mw-edit-longpageerror'>\n" );
13161510 $wgOut->addWikiMsg( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) );
@@ -1319,261 +1513,113 @@
13201514 $wgOut->addWikiMsg( 'longpagewarning', $wgLang->formatNum( $this->kblength ) );
13211515 $wgOut->addHTML( "</div>\n" );
13221516 }
 1517+ }
13231518
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;",
13351541 );
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;
13411550
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 );
13501555 }
1351 - // Allow for site and per-namespace customization of contribution/copyright notice.
1352 - wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) );
13531556
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 );
13601558
 1559+ return array( $label, $input );
 1560+ }
13611561
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 .= '&nbsp;';
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;
14391576 } 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;
14751579 }
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+ }
14771585
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 ) );
14841602
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+ }
14871606
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" />
15021615
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;
15781624 /**
15791625 * To make it harder for someone to slip a user a page
15801626 * which submits an edit form to the wiki without their
@@ -1586,67 +1632,68 @@
15871633 * include the constant suffix to prevent editing from
15881634 * broken text-mangling proxies.
15891635 */
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" );
16241637 }
16251638
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();
16331651 }
16341652
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+ }
16361672 $attribs = array( 'tabindex' => 1 );
 1673+ if ( is_array($customAttribs) )
 1674+ $attribs += $customAttribs;
16371675
16381676 if ( $this->wasDeletedSinceLastEdit() )
16391677 $attribs['type'] = 'hidden';
1640 - if ( !empty( $classes ) )
 1678+ if ( !empty( $classes ) ) {
 1679+ if ( isset($attribs['class']) )
 1680+ $classes[] = $attribs['class'];
16411681 $attribs['class'] = implode( ' ', $classes );
 1682+ }
16421683
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 );
16441691 }
16451692
16461693 protected function showTextbox2() {
16471694 $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6 ) );
16481695 }
16491696
1650 - protected function showTextbox( $content, $name, $attribs = array() ) {
 1697+ protected function showTextbox( $content, $name, $customAttribs = array() ) {
16511698 global $wgOut, $wgUser;
16521699
16531700 $wikitext = $this->safeUnicodeOutput( $content );
@@ -1658,18 +1705,28 @@
16591706 $wikitext .= "\n";
16601707 }
16611708
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+ );
16641716
16651717 if ( $wgUser->getOption( 'editwidth' ) )
1666 - $attribs['style'] = 'width: 100%';
 1718+ $attribs['style'] .= 'width: 100%';
16671719
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 ) );
16731721 }
 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+ }
16741731
16751732 protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
16761733 global $wgOut;
@@ -1739,7 +1796,64 @@
17401797 $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
17411798 $wgOut->addHTML( '</div>' );
17421799 }
 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" );
17431820
 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+
17441858 protected function getLastDelete() {
17451859 $dbr = wfGetDB( DB_SLAVE );
17461860 $data = $dbr->selectRow(
@@ -2057,6 +2171,7 @@
20582172 global $wgStylePath, $wgContLang, $wgLang;
20592173
20602174 /**
 2175+
20612176 * toolarray an array of arrays which each include the filename of
20622177 * the button image (without path), the opening tag, the closing tag,
20632178 * and optionally a sample text that is inserted between the two when no
@@ -2324,6 +2439,22 @@
23252440 }
23262441
23272442
 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+
23282459 /**
23292460 * Get a diff between the current contents of the edit box and the
23302461 * version of the page we're editing from.
@@ -2367,6 +2498,13 @@
23682499 : $text;
23692500 }
23702501
 2502+ function safeUnicodeText( $request, $text ) {
 2503+ $text = rtrim( $text );
 2504+ return $request->getBool( 'safemode' )
 2505+ ? $this->unmakesafe( $text )
 2506+ : $text;
 2507+ }
 2508+
23712509 /**
23722510 * Filter an output field through a Unicode armoring process if it is
23732511 * going to an old browser with known broken Unicode editing issues.
Index: trunk/phase3/RELEASE-NOTES
@@ -292,6 +292,7 @@
293293 * Allow \pagecolor and \definecolor in texvc
294294 * $wgTexvcBackgroundColor contains background color for texvc call
295295 * (bug 21574) Redirects can now have "303 See Other" HTTP status
 296+* EditPage refactored to allow extensions to derive new edit modes much easier.
296297
297298 === Bug fixes in 1.16 ===
298299
Index: trunk/phase3/skins/common/edit.js
@@ -5,13 +5,13 @@
66 function addButton(imageFile, speedTip, tagOpen, tagClose, sampleText, imageId) {
77 // Don't generate buttons for browsers which don't fully
88 // support it.
9 - mwEditButtons[mwEditButtons.length] =
 9+ mwEditButtons.push(
1010 {"imageId": imageId,
1111 "imageFile": imageFile,
1212 "speedTip": speedTip,
1313 "tagOpen": tagOpen,
1414 "tagClose": tagClose,
15 - "sampleText": sampleText};
 15+ "sampleText": sampleText});
1616 }
1717
1818 // this function generates the actual toolbar buttons with localized text
@@ -44,11 +44,9 @@
4545 var toolbar = document.getElementById('toolbar');
4646 if (!toolbar) { return false; }
4747
48 - var textbox = document.getElementById('wpTextbox1');
49 - if (!textbox) { return false; }
50 -
5148 // Don't generate buttons for browsers which don't fully
5249 // support it.
 50+ var textbox = document.createElement('textarea'); // abstract, don't assume wpTextbox1 is always there
5351 if (!(document.selection && document.selection.createRange)
5452 && textbox.selectionStart === null) {
5553 return false;
@@ -154,17 +152,13 @@
155153 if( scrollTop.value )
156154 editBox.scrollTop = scrollTop.value;
157155 addHandler( editForm, 'submit', function() {
158 - document.getElementById( 'wpScrolltop' ).value = document.getElementById( 'wpTextbox1' ).scrollTop;
 156+ scrollTop.value = editBox.scrollTop;
159157 } );
160158 }
161159 }
162160 hookEvent( 'load', scrollEditBox );
163161 hookEvent( 'load', mwSetupToolbar );
164162 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' );
170164 } );
171165

Follow-up revisions

RevisionCommit summaryAuthorDate
r59672Fixes for fixme comments on my r59655dantman18:51, 2 December 2009
r59832Fixes for comments on r59655...dantman17:17, 8 December 2009
r64881fixing bug 23139 (introduced in r59655) which broke edit conflict resolutionchurchofemacs14:11, 10 April 2010
r66096* (bug 23448) MediaWiki:Summary-preview is now displayed instead of MediaWiki...ialex12:41, 9 May 2010

Comments

#Comment by Raymond (talk | contribs)   13:53, 2 December 2009

Seen at translatewiki:

PHP Notice: Undefined variable: summaryClass in /var/www/w/includes/EditPage.php on line 1550

Please update /maintenance/language/messages.inc too

#Comment by Raymond (talk | contribs)   18:54, 2 December 2009

Fixed with r59672.

#Comment by Raymond (talk | contribs)   21:51, 2 December 2009

Seen at translatewiki:

PHP Notice: Undefined index: style in /var/www/w/includes/EditPage.php on line 1723

#Comment by Raymond (talk | contribs)   12:19, 3 December 2009

Fixed with r59695. Thanks.

#Comment by 😂 (talk | contribs)   22:24, 2 December 2009

How extensively was this tested prior to committing? I know we're trying to branch 1.16; a refactor might not be best now.

#Comment by Dantman (talk | contribs)   08:22, 3 December 2009

Tested preview functionality, tested edit conflict, tested extensibility functionality, tested sections, tested section edit not supported feature, etc... I should have all the php notices squashed now, I need to use more strict error settings more often. One error was hidden behind a user pref. The only real danger in this refactor would be unnoticed variables that were missed when code was moved into another function. Depending on what they are used for they should generate some kind of php error in that case, and I think I've caught all of those.

#Comment by 😂 (talk | contribs)   13:17, 3 December 2009

Fair enough. Still want another set of eyes, leaving this 'new' still.

#Comment by Tim Starling (talk | contribs)   22:08, 7 December 2009

The JavaScript is a bit broken.

-	var textbox = document.getElementById('wpTextbox1');
-	if (!textbox) { return false; }
-
 	// Don't generate buttons for browsers which don't fully
 	// support it.
+	var textbox = document.createElement('textarea'); // abstract, don't assume wpTextbox1 is always there

In FF 3.5 this causes:

Error: uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMNSHTMLTextAreaElement.selectionStart]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: .../skins/common/edit.js?257 :: mwSetupToolbar :: line 51" data: no]

The script stops and the toolbar does not appear.

+			'onfocus' => "currentFocused = this;",

If you're going to make this a public interface for communicating between PHP and JS, fixed for all time and encouraged for use by extensions, you should think of a better way to do it than setting an unprefixed, ambiguously-named global variable.

+	protected function editFormHeadInit() {

Call this function "showHeader". Your name has the words in the wrong order, "edit form" is implied since that's what class we're in, and initialisation is not what it's doing, rather it's sending HTML to $wgOut and functions which do that usually start with "show". Also it needs a doc comment.

+		// activate checkboxes if user wants them to be always active
+		if ( !$this->preview && !$this->diff ) {
+			# Sort out the "watch" checkbox
+			if ( $wgUser->getOption( 'watchdefault' ) ) {
+				# Watch all edits
+				$this->watchthis = true;

Not vital since it was wrong in the original, but: I would suggest moving this section to an accessor function getWatchThis() instead of relying on initialisation order. Relying on things being initialised in the correct order is fragile, especially when there are subclasses involved. Same with $this->minoredit.

+	function getSummaryInput($summary = "", $labelText = null, $userInputAttrs = null, $userSpanLabelAttrs = null) {

Since the parameter names provide self-documentation, they should get the short, obvious names ($inputAttrs and $spanLabelAttrs) and the local variables should get the prefixed names (e.g. $defaultInputAttrs)

-		if ( $this->kblength === false ) {
+		if ( $this->kblength === false )

I prefer the former style for the reason explained at Manual:Coding conventions.

Otherwise looks good, definitely a useful refactor.

#Comment by Dantman (talk | contribs)   23:42, 7 December 2009

Ok, I'll find a better abstract way of doing the toolbar part.

Do you have another idea for the focused bit?

Originally I wanted to refactor edit.js to use a nice event delegating setup so that all text inputs would be available to the edit buttons. However I realized that focus events didn't bubble, so onfocus needed to be on each individual input.

I thought about some sort of register method, but something like that would cause problems for inputs that don't use id's. I thought about having js go through all the inputs and make them focusable. But that would cause trouble for dynamically generated inputs (a form adding another block of inputs).

So I guess I gave up and just used the global that edit.js was already using. I could use a method that would go through inputs and make them focusable, and later on if inputs were dynamically added the extension could call it again to update. But that might have to end up with a restriction that the onfocus attribute can't be used by extensions, depending on how good our current event system is.

However I ran into a quirksmode article with a quick search now: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html jQuery 1.4 will have support for it in .live, and a monkeypatch makes it available on .live in current code. I'll look into that so that the toolbar can work cleanly without any need to register edit form inputs.

#Comment by Tim Starling (talk | contribs)   00:22, 8 December 2009

I think having a class on the input elements which is converted to an onfocus handler by the DOM ready event would be a reasonable way to do it. JS that dynamically creates input boxes would have to call an update function, but I don't think that's unreasonable. Something like initEditToolbarInput(inputElement). Correct me if I'm wrong.

#Comment by Dantman (talk | contribs)   17:11, 8 December 2009

Instead of moving that watchthis stuff to a getWatchThis method, I moved the form related part of it into importFormData and the other parts to initialiseForm. That block was pretty much a bit of slight condition abuse. "If we're in the edit form (not save), not in preview, not in diff" which was really a bad way of checking for the initial form. Which is what initialiseForm's purpose is.

And I fixed that currentFocused use with some nice event delegation.

Status & tagging log