EditPage refactor and improvements.
authorDaniel Friesen <dantman@users.mediawiki.org>
Wed, 2 Dec 2009 07:22:29 +0000 (07:22 +0000)
committerDaniel Friesen <dantman@users.mediawiki.org>
Wed, 2 Dec 2009 07:22:29 +0000 (07:22 +0000)
- 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

RELEASE-NOTES
includes/EditPage.php
includes/Html.php
languages/messages/MessagesEn.php
skins/common/edit.js

index d9346e9..b73b43e 100644 (file)
@@ -292,6 +292,7 @@ Hopefully we will remove this configuration var soon)
 * Allow \pagecolor and \definecolor in texvc
 * $wgTexvcBackgroundColor contains background color for texvc call
 * (bug 21574) Redirects can now have "303 See Other" HTTP status
+* EditPage refactored to allow extensions to derive new edit modes much easier.
 
 === Bug fixes in 1.16 ===
 
index 3d41d86..e8867a5 100644 (file)
@@ -555,6 +555,29 @@ class EditPage {
                }
        }
 
+       /**
+        * Does this EditPage class support section editing?
+        * This is used by EditPage subclasses to indicate their ui cannot handle section edits
+        * 
+        * @return bool
+        */
+       protected function isSectionEditSupported() {
+               return true;
+       }
+
+       /**
+        * Returns the URL to use in the form's action attribute.
+        * This is used by EditPage subclasses when simply customizing the action
+        * variable in the constructor is not enough. This can be used when the
+        * EditPage lives inside of a Special page rather than a custom page action.
+        * 
+        * @param Title $title The title for which is being edited (where we go to for &action= links)
+        * @return string
+        */
+       protected function getActionURL( Title $title ) {
+               return $title->getLocalURL( array( 'action' => $this->action ) );
+       }
+
        /**
         * @todo document
         * @param $request
@@ -572,7 +595,16 @@ class EditPage {
                        # Also remove trailing whitespace, but don't remove _initial_
                        # whitespace from the text boxes. This may be significant formatting.
                        $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
-                       $this->textbox2 = $this->safeUnicodeInput( $request, 'wpTextbox2' );
+                       if ( !$request->getCheck('wpTextbox2') ) {
+                               // Skip this if wpTextbox2 has input, it indicates that we came
+                               // from a conflict page with raw page text, not a custom form
+                               // modified by subclasses
+                               wfProfileIn( get_class($this)."::importContentFormData" );
+                               $textbox1 = $this->importContentFormData( $request );
+                               if ( isset($textbox1) )
+                                       $this->textbox1 = $textbox1;
+                               wfProfileOut( get_class($this)."::importContentFormData" );
+                       }
                        $this->mMetaData = rtrim( $request->getText( 'metadata' ) );
                        # Truncate for whole multibyte characters. +5 bytes for ellipsis
                        $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250, '' );
@@ -643,7 +675,6 @@ class EditPage {
                        # Not a posted form? Start with nothing.
                        wfDebug( __METHOD__ . ": Not a posted form.\n" );
                        $this->textbox1  = '';
-                       $this->textbox2  = '';
                        $this->mMetaData = '';
                        $this->summary   = '';
                        $this->edittime  = '';
@@ -680,6 +711,18 @@ class EditPage {
                wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
        }
 
+       /**
+        * Subpage overridable method for extracting the page content data from the
+        * posted form to be placed in $this->textbox1, if using customized input
+        * this method should be overrided and return the page text that will be used
+        * for saving, preview parsing and so on...
+        * 
+        * @praram WebRequest $request
+        */
+       protected function importContentFormData( &$request ) {
+               return; // Don't do anything, EditPage already extracted wpTextbox1
+       }
+
        /**
         * Make sure the form isn't faking a user's credentials.
         *
@@ -1168,7 +1211,7 @@ class EditPage {
         *                      near the top, for captchas and the like.
         */
        function showEditForm( $formCallback=null ) {
-               global $wgOut, $wgUser, $wgLang, $wgContLang, $wgMaxArticleSize, $wgTitle, $wgRequest;
+               global $wgOut, $wgUser, $wgTitle, $wgRequest;
 
                # If $wgTitle is null, that means we're in API mode.
                # Some hook probably called this function  without checking
@@ -1197,15 +1240,175 @@ class EditPage {
                # Enabled article-related sidebar, toplinks, etc.
                $wgOut->setArticleRelated( true );
 
-               $cancelParams = array();
+               if ( $this->editFormHeadInit() === false )
+                       return;
+
+               $action = htmlspecialchars($this->getActionURL($wgTitle));
+
+               if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) {
+                       # prepare toolbar for edit buttons
+                       $toolbar = EditPage::getEditToolbar();
+               } else {
+                       $toolbar = '';
+               }
+
+
+               // 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;
+                       } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
+                               # Watch creations
+                               $this->watchthis = true;
+                       } elseif ( $this->mTitle->userIsWatching() ) {
+                               # Already watched
+                               $this->watchthis = true;
+                       }
+
+                       # May be overriden by request parameters
+                       if( $wgRequest->getBool( 'watchthis' ) ) {
+                               $this->watchthis = true;
+                       }
+
+                       if ( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true;
+               }
+
+               $wgOut->addHTML( $this->editFormPageTop );
+
+               if ( $wgUser->getOption( 'previewontop' ) ) {
+                       $this->displayPreviewArea( $previewOutput, true );
+               }
+
+               $wgOut->addHTML( $this->editFormTextTop );
+
+               $templates = $this->getTemplates();
+               $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != '');
+
+               $hiddencats = $this->mArticle->getHiddenCategories();
+               $formattedhiddencats = $sk->formatHiddenCategories( $hiddencats );
+
+               if ( $this->wasDeletedSinceLastEdit() && 'save' != $this->formtype ) {
+                       $wgOut->wrapWikiMsg(
+                               "<div class='error mw-deleted-while-editing'>\n$1</div>",
+                               'deletedwhileediting' );
+               } elseif ( $this->wasDeletedSinceLastEdit() ) {
+                       // Hide the toolbar and edit area, user can click preview to get it back
+                       // Add an confirmation checkbox and explanation.
+                       $toolbar = '';
+                       // @todo move this to a cleaner conditional instead of blanking a variable
+               }
+               $wgOut->addHTML( <<<END
+{$toolbar}
+<form id="editform" name="editform" method="post" action="$action" enctype="multipart/form-data">
+END
+);
+
+               if ( is_callable( $formCallback ) ) {
+                       call_user_func_array( $formCallback, array( &$wgOut ) );
+               }
+
+               wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
+
+               // Put these up at the top to ensure they aren't lost on early form submission
+               $this->showFormBeforeText();
+
+               if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
+                       $wgOut->addHTML(
+                               '<div class="mw-confirm-recreate">' .
+                               $wgOut->parse( wfMsg( 'confirmrecreate',  $this->lastDelete->user_name , $this->lastDelete->log_comment ) ) .
+                               Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false,
+                                       array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
+                               ) .
+                               '</div>'
+                       );
+               }
+
+               # If a blank edit summary was previously provided, and the appropriate
+               # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
+               # user being bounced back more than once in the event that a summary
+               # is not required.
+               #####
+               # For a bit more sophisticated detection of blank summaries, hash the
+               # automatic one and pass that in the hidden field wpAutoSummary.
+               if ( $this->missingSummary ||
+                       ( $this->section == 'new' && $wgRequest->getBool( 'nosummary' ) ) )
+                               $wgOut->addHTML( Xml::hidden( 'wpIgnoreBlankSummary', true ) );
+               $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
+               $wgOut->addHTML( Xml::hidden( 'wpAutoSummary', $autosumm ) );
+
+               if ( $this->section == 'new' ) {
+                       $this->showSummaryInput( true, $this->summary );
+                       $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
+               }
 
+               $wgOut->addHTML( $this->editFormTextBeforeContent );
+               
                if ( $this->isConflict ) {
-                       $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1</div>", 'explainconflict' );
+                       // In an edit conflict bypass the overrideable content form method
+                       // and fallback to the raw wpTextbox1 since editconflicts can't be
+                       // resolved between page source edits and custom ui edits using the
+                       // custom edit ui.
+                       $this->showTextbox1();
+               } else {
+                       $this->showContentForm();
+               }
+
+               $wgOut->addHTML( $this->editFormTextAfterContent );
+
+               $wgOut->addWikiText( $this->getCopywarn() );
+               if ( isset($this->editFormTextAfterWarn) && $this->editFormTextAfterWarn !== '' )
+                       $wgOut->addHTML( $this->editFormTextAfterWarn );
+
+               global $wgUseMetadataEdit;
+               if ( $wgUseMetadataEdit )
+                       $this->showMetaData();
+
+               $this->showStandardInputs();
+
+               $this->showFormAfterText();
+
+               $this->showTosSummary();
+               $this->showEditTools();
+
+               $wgOut->addHTML( <<<END
+{$this->editFormTextAfterTools}
+<div class='templatesUsed'>
+{$formattedtemplates}
+</div>
+<div class='hiddencats'>
+{$formattedhiddencats}
+</div>
+END
+);
+
+               if ( $this->isConflict )
+                       $this->showConflict();
+               
+               $wgOut->addHTML( $this->editFormTextBottom );
+               $wgOut->addHTML( "</form>\n" );
+               if ( !$wgUser->getOption( 'previewontop' ) ) {
+                       $this->displayPreviewArea( $previewOutput, false );
+               }
 
-                       $this->textbox2 = $this->textbox1;
-                       $this->textbox1 = $this->getContent();
+               wfProfileOut( __METHOD__ );
+       }
+       
+       protected function editFormHeadInit() {
+               global $wgOut, $wgParser, $wgUser, $wgMaxArticleSize, $wgLang;
+               if ( $this->isConflict ) {
+                       $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1</div>", 'explainconflict' );
                        $this->edittime = $this->mArticle->getTimestamp();
                } else {
+                       if ( $this->section != '' && !$this->isSectionEditSupported() ) {
+                               // We use $this->section to much before this and getVal('wgSection') directly in other places
+                               // at this point we can't reset $this->section to '' to fallback to non-section editing.
+                               // Someone is welcome to try refactoring though
+                               $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
+                               return false;
+                       }
+
                        if ( $this->section != '' && $this->section != 'new' ) {
                                $matches = array();
                                if ( !$this->summary && !$this->preview && !$this->diff ) {
@@ -1219,25 +1422,21 @@ class EditPage {
                                }
                        }
 
-                       if ( $this->missingComment ) {
+                       if ( $this->missingComment )
                                $wgOut->wrapWikiMsg( '<div id="mw-missingcommenttext">$1</div>', 'missingcommenttext' );
-                       }
 
-                       if ( $this->missingSummary && $this->section != 'new' ) {
+                       if ( $this->missingSummary && $this->section != 'new' )
                                $wgOut->wrapWikiMsg( '<div id="mw-missingsummary">$1</div>', 'missingsummary' );
-                       }
 
-                       if ( $this->missingSummary && $this->section == 'new' ) {
+                       if ( $this->missingSummary && $this->section == 'new' )
                                $wgOut->wrapWikiMsg( '<div id="mw-missingcommentheader">$1</div>', 'missingcommentheader' );
-                       }
 
-                       if ( $this->hookError !== '' ) {
+                       if ( $this->hookError !== '' )
                                $wgOut->addWikiText( $this->hookError );
-                       }
 
-                       if ( !$this->checkUnicodeCompliantBrowser() ) {
+                       if ( !$this->checkUnicodeCompliantBrowser() )
                                $wgOut->addWikiMsg( 'nonunicodebrowser' );
-                       }
+
                        if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) {
                        // Let sysop know that this will make private content public if saved
 
@@ -1249,7 +1448,6 @@ class EditPage {
 
                                if ( !$this->mArticle->mRevision->isCurrent() ) {
                                        $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() );
-                                       $cancelParams['oldid'] = $this->mArticle->mRevision->getId();
                                        $wgOut->addWikiMsg( 'editingold' );
                                }
                        }
@@ -1274,17 +1472,13 @@ class EditPage {
                        }
                }
 
-               $classes = array(); // Textarea CSS
-               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-               } elseif ( $this->mTitle->isProtected( 'edit' ) ) {
+               if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
                        # Is the title semi-protected?
                        if ( $this->mTitle->isSemiProtected() ) {
                                $noticeMsg = 'semiprotectedpagewarning';
-                               $classes[] = 'mw-textarea-sprotected';
                        } else {
                                # Then it must be protected based on static groups (regular)
                                $noticeMsg = 'protectedpagewarning';
-                               $classes[] = 'mw-textarea-protected';
                        }
                        LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle->getPrefixedText(), '',
                                array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
@@ -1307,9 +1501,9 @@ class EditPage {
                        $wgOut->wrapWikiMsg( '<div class="mw-titleprotectedwarning">$1</div>', 'titleprotectedwarning' );
                }
 
-               if ( $this->kblength === false ) {
+               if ( $this->kblength === false )
                        $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
-               }
+
                if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
                        $wgOut->addHTML( "<div class='error' id='mw-edit-longpageerror'>\n" );
                        $wgOut->addWikiMsg( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) );
@@ -1319,261 +1513,113 @@ class EditPage {
                        $wgOut->addWikiMsg( 'longpagewarning', $wgLang->formatNum( $this->kblength ) );
                        $wgOut->addHTML( "</div>\n" );
                }
+       }
 
-               $action = $wgTitle->escapeLocalURL( array( 'action' => $this->action ) );
-
-               $summary = wfMsgExt( 'summary', 'parseinline' );
-               $subject = wfMsgExt( 'subject', 'parseinline' );
-
-               $cancel = $sk->link(
-                       $wgTitle,
-                       wfMsgExt( 'cancel', array( 'parseinline' ) ),
-                       array( 'id' => 'mw-editform-cancel' ),
-                       $cancelParams,
-                       array( 'known', 'noclasses' )
+       /**
+        * Standard summary input and label (wgSummary), abstracted so EditPage
+        * subclasses may reorganize the form.
+        * Note that you do not need to worry about the label's for=, it will be
+        * inferred by the id given to the input. You can remove them both by
+        * passing array( 'id' => false ) to $userInputAttrs.
+        * 
+        * @param $summary The value of the summary input
+        * @param $labelText The html to place inside the label
+        * @param $userInputAttrs An array of attrs to use on the input
+        * @param $userSpanAttrs An array of attrs to use on the span inside the label
+        * 
+        * @return array An array in the format array( $label, $input )
+        */
+       function getSummaryInput($summary = "", $labelText = null, $userInputAttrs = null, $userSpanLabelAttrs = null) {
+               $inputAttrs = array(
+                       'id' => 'wpSummary',
+                       'maxlength' => '200',
+                       'tabindex' => '1',
+                       'size' => 60,
+                       'spellcheck' => 'true',
+                       'onfocus' => "currentFocused = this;",
                );
-               $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
-               $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) );
-               $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
-                       htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
-                       htmlspecialchars( wfMsg( 'newwindow' ) );
-
-               global $wgRightsText;
-               if ( $wgRightsText ) {
-                       $copywarnMsg = array( 'copyrightwarning',
-                               '[[' . wfMsgForContent( 'copyrightpage' ) . ']]',
-                               $wgRightsText );
-               } else {
-                       $copywarnMsg = array( 'copyrightwarning2',
-                               '[[' . wfMsgForContent( 'copyrightpage' ) . ']]' );
-               }
-               // Allow for site and per-namespace customization of contribution/copyright notice.
-               wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) );
-
-               if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) {
-                       # prepare toolbar for edit buttons
-                       $toolbar = EditPage::getEditToolbar();
-               } else {
-                       $toolbar = '';
-               }
-
-
-               // 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;
-                       } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
-                               # Watch creations
-                               $this->watchthis = true;
-                       } elseif ( $this->mTitle->userIsWatching() ) {
-                               # Already watched
-                               $this->watchthis = true;
-                       }
-
-                       # May be overriden by request parameters
-                       if( $wgRequest->getBool( 'watchthis' ) ) {
-                               $this->watchthis = true;
-                       }
-
-                       if ( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true;
-               }
-
-               $wgOut->addHTML( $this->editFormPageTop );
+               if ( $userInputAttrs )
+                       $inputAttrs += $userInputAttrs;
+               $spanLabelAttrs = array(
+                       'class' => $summaryClass,
+                       'id' => "wpSummaryLabel"
+               );
+               if ( is_array($userSpanLabelAttrs) )
+                       $spanLabelAttrs += $userSpanLabelAttrs;
 
-               if ( $wgUser->getOption( 'previewontop' ) ) {
-                       $this->displayPreviewArea( $previewOutput, true );
+               $label = null;
+               if ( $labelText ) {
+                       $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
+                       $label = Xml::tags( 'span', $spanLabelAttrs, $label );
                }
 
+               $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
 
-               $wgOut->addHTML( $this->editFormTextTop );
-
-               # if this is a comment, show a subject line at the top, which is also the edit summary.
-               # Otherwise, show a summary field at the bottom
-               $summarytext = $wgContLang->recodeForEdit( $this->summary );
+               return array( $label, $input );
+       }
 
-               # If a blank edit summary was previously provided, and the appropriate
-               # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
-               # user being bounced back more than once in the event that a summary
-               # is not required.
-               #####
-               # For a bit more sophisticated detection of blank summaries, hash the
-               # automatic one and pass that in the hidden field wpAutoSummary.
-               $summaryhiddens =  '';
-               if ( $this->missingSummary ) $summaryhiddens .= Xml::hidden( 'wpIgnoreBlankSummary', true );
-               $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
-               $summaryhiddens .= Xml::hidden( 'wpAutoSummary', $autosumm );
-               if ( $this->section == 'new' ) {
-                       $commentsubject = '';
-                       if ( !$wgRequest->getBool( 'nosummary' ) ) {
-                               # Add a class if 'missingsummary' is triggered to allow styling of the summary line
-                               $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
-
-                               $commentsubject =
-                                       Xml::tags( 'label', array( 'for' => 'wpSummary' ), $subject );
-                               $commentsubject =
-                                       Xml::tags( 'span', array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ),
-                                               $commentsubject );
-                               $commentsubject .= '&nbsp;';
-                               $commentsubject .= Html::input( 'wpSummary',
-                                                                       $summarytext,
-                                                                       'text',
-                                                                       array(
-                                                                               'id' => 'wpSummary',
-                                                                               'maxlength' => '200',
-                                                                               'tabindex' => '1',
-                                                                               'size' => '60',
-                                                                               'spellcheck' => 'true'
-                                                                       ) );
-                       } else {
-                               $summaryhiddens .= Xml::hidden( 'wpIgnoreBlankSummary', true ); # bug 18699
-                       }
-                       $editsummary = "<div class='editOptions'>\n";
-                       global $wgParser;
-                       $formattedSummary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $this->summary ) );
-                       $subjectpreview = $summarytext && ( $this->preview || $this->diff ) ?
-                               "<div class=\"mw-summary-preview\">". wfMsgExt( 'subject-preview', 'parseinline' ) . $sk->commentBlock( $formattedSummary, $this->mTitle, true )."</div>\n" : '';
-                       $summarypreview = '';
+       /**
+        * @param bool $isSubjectPreview true if this is the section subject/title
+        *                               up top, or false if this is the comment
+        *                               summary down below the textarea
+        * @param string $summary The text of the summary to display
+        * @return string
+        */
+       protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
+               global $wgOut, $wgContLang;
+               # Add a class if 'missingsummary' is triggered to allow styling of the summary line
+               $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
+               if ( $isSubjectPreview ) {
+                       if ( $wgRequest->getBool( 'nosummary' ) )
+                               return;
                } else {
-                       $commentsubject = '';
-
-                       # Add a class if 'missingsummary' is triggered to allow styling of the summary line
-                       $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
-
-                       $editsummary = Xml::tags( 'label', array( 'for' => 'wpSummary' ), $summary );
-                       $editsummary = Xml::tags( 'span',  array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ),
-                                       $editsummary ) . ' ';
-
-                       $editsummary .= Html::input( 'wpSummary',
-                               $summarytext,
-                               'text',
-                               array(
-                                       'id' => 'wpSummary',
-                                       'maxlength' => '200',
-                                       'tabindex' => '1',
-                                       'size' => '60',
-                                       'spellcheck' => 'true'
-                               ) );
-
-                       // No idea where this is closed.
-                       $editsummary .= '<br/>';
-                       $editsummary = Xml::openElement( 'div', array( 'class' => 'editOptions' ) )
-                                       . ($this->mShowSummaryField ? $editsummary : '');
-
-                       $summarypreview = '';
-                       if ( $summarytext && ( $this->preview || $this->diff ) ) {
-                               $summarypreview =
-                                       Xml::tags( 'div',
-                                               array( 'class' => 'mw-summary-preview' ),
-                                               wfMsgExt( 'summary-preview', 'parseinline' ) .
-                                                       $sk->commentBlock( $this->summary, $this->mTitle )
-                                       );
-                       }
-                       $subjectpreview = '';
-               }
-               $commentsubject .= $summaryhiddens;
-
-               # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display
-               if ( !$this->preview && !$this->diff ) {
-                       $wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus()' );
-               }
-               $templates = $this->getTemplates();
-               $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != '');
-
-               $hiddencats = $this->mArticle->getHiddenCategories();
-               $formattedhiddencats = $sk->formatHiddenCategories( $hiddencats );
-
-               global $wgUseMetadataEdit ;
-               if ( $wgUseMetadataEdit ) {
-                       $metadata = $this->mMetaData ;
-                       $metadata = htmlspecialchars( $wgContLang->recodeForEdit( $metadata ) ) ;
-                       $top = wfMsgWikiHtml( 'metadata_help' );
-                       /* ToDo: Replace with clean code */
-                       $ew = $wgUser->getOption( 'editwidth' );
-                       if ( $ew ) $ew = " style=\"width:100%\"";
-                       else $ew = '';
-                       $cols = $wgUser->getIntOption( 'cols' );
-                       /* /ToDo */
-                       $metadata = $top . "<textarea name='metadata' rows='3' cols='{$cols}'{$ew}>{$metadata}</textarea>" ;
-               }
-               else $metadata = "" ;
-
-               $recreate = '';
-               if ( $this->wasDeletedSinceLastEdit() ) {
-                       if ( 'save' != $this->formtype ) {
-                               $wgOut->wrapWikiMsg(
-                                       "<div class='error mw-deleted-while-editing'>\n$1</div>",
-                                       'deletedwhileediting' );
-                       } else {
-                               // Hide the toolbar and edit area, user can click preview to get it back
-                               // Add an confirmation checkbox and explanation.
-                               $toolbar = '';
-                               $recreate = '<div class="mw-confirm-recreate">' .
-                                               $wgOut->parse( wfMsg( 'confirmrecreate',  $this->lastDelete->user_name , $this->lastDelete->log_comment ) ) .
-                                               Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false,
-                                                       array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
-                                               ) . '</div>';
-                       }
-               }
-
-               $tabindex = 2;
-
-               $checkboxes = $this->getCheckboxes( $tabindex, $sk,
-                       array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
-
-               $checkboxhtml = implode( $checkboxes, "\n" );
-
-               $buttons = $this->getEditButtons( $tabindex );
-               $buttonshtml = implode( $buttons, "\n" );
-
-               $safemodehtml = $this->checkUnicodeCompliantBrowser()
-                       ? '' : Xml::hidden( 'safemode', '1' );
-
-               $wgOut->addHTML( <<<END
-{$toolbar}
-<form id="editform" name="editform" method="post" action="$action" enctype="multipart/form-data">
-END
-);
-
-               if ( is_callable( $formCallback ) ) {
-                       call_user_func_array( $formCallback, array( &$wgOut ) );
+                       if ( !$this->mShowSummaryField )
+                               return;
                }
+               $summary = $wgContLang->recodeForEdit( $summary );
+               $labelText = wfMsgExt( $isSubjectPreview ? 'subject' : 'summary', 'parseinline' );
+               list($label, $input) = $this->getSummaryInput($summary, $labelText, array( 'class' => $summaryClass ), array());
+               $wgOut->addHTML("{$label} {$input}");
+       }
 
-               wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
-
-               // Put these up at the top to ensure they aren't lost on early form submission
-               $this->showFormBeforeText();
-
-               $wgOut->addHTML( <<<END
-{$recreate}
-{$commentsubject}
-{$subjectpreview}
-{$this->editFormTextBeforeContent}
-END
-);
-               $this->showTextbox1( $classes );
-
-               $wgOut->addHTML( $this->editFormTextAfterContent );
-
-               $wgOut->wrapWikiMsg( "<div id=\"editpage-copywarn\">\n$1\n</div>", $copywarnMsg );
-               $wgOut->addHTML( <<<END
-{$this->editFormTextAfterWarn}
-{$metadata}
-{$editsummary}
-{$summarypreview}
-{$checkboxhtml}
-{$safemodehtml}
-END
-);
+       /**
+        * @param bool $isSubjectPreview true if this is the section subject/title
+        *                               up top, or false if this is the comment
+        *                               summary down below the textarea
+        * @param string $summary The text of the summary to display
+        * @return string
+        */
+       protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
+               if ( !$summary || ( !$this->preview && !$this->diff ) )
+                       return "";
+               
+               global $wgParser, $wgUser;
+               $sk = $wgUser->getSkin();
+               
+               if ( $isSubjectPreview )
+                       $summary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $summary ) );
 
-               $wgOut->addHTML(
-"<div class='editButtons'>
-{$buttonshtml}
-       <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span>
-</div><!-- editButtons -->
-</div><!-- editOptions -->");
+               $summary = wfMsgExt( 'subject-preview', 'parseinline' ) . $sk->commentBlock( $summary, $this->mTitle, !!$isSubjectPreview );
+               return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
+       }
 
+       protected function showFormBeforeText() {
+               global $wgOut;
+               $section = htmlspecialchars( $this->section );
+               $wgOut->addHTML( <<<INPUTS
+<input type='hidden' value="{$section}" name="wpSection" />
+<input type='hidden' value="{$this->starttime}" name="wpStarttime" />
+<input type='hidden' value="{$this->edittime}" name="wpEdittime" />
+<input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
+
+INPUTS
+               );
+               if ( !$this->checkUnicodeCompliantBrowser() )
+                       $wgOut->addHTML(Xml::hidden( 'safemode', '1' ));
+       }
+       
+       protected function showFormAfterText() {
+               global $wgOut, $wgUser;
                /**
                 * To make it harder for someone to slip a user a page
                 * which submits an edit form to the wiki without their
@@ -1586,67 +1632,68 @@ END
                 * include the constant suffix to prevent editing from
                 * broken text-mangling proxies.
                 */
-               $token = htmlspecialchars( $wgUser->editToken() );
-               $wgOut->addHTML( "\n<input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" );
-
-               $this->showTosSummary();
-               $this->showEditTools();
-
-               $wgOut->addHTML( <<<END
-{$this->editFormTextAfterTools}
-<div class='templatesUsed'>
-{$formattedtemplates}
-</div>
-<div class='hiddencats'>
-{$formattedhiddencats}
-</div>
-END
-);
-
-               if ( $this->isConflict && wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
-                       $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
-
-                       $de = new DifferenceEngine( $this->mTitle );
-                       $de->setText( $this->textbox2, $this->textbox1 );
-                       $de->showDiff( wfMsg( "yourtext" ), wfMsg( "storedversion" ) );
-
-                       $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
-                       $this->showTextbox2();
-               }
-               $wgOut->addHTML( $this->editFormTextBottom );
-               $wgOut->addHTML( "</form>\n" );
-               if ( !$wgUser->getOption( 'previewontop' ) ) {
-                       $this->displayPreviewArea( $previewOutput, false );
-               }
-
-               wfProfileOut( __METHOD__ );
+               $wgOut->addHTML( "\n" . Xml::hidden( "wpEditToken", $wgUser->editToken() ) . "\n" );
        }
 
-       protected function showFormBeforeText() {
-               global $wgOut;
-               $wgOut->addHTML( "
-<input type='hidden' value=\"" . htmlspecialchars( $this->section ) . "\" name=\"wpSection\" />
-<input type='hidden' value=\"{$this->starttime}\" name=\"wpStarttime\" />\n
-<input type='hidden' value=\"{$this->edittime}\" name=\"wpEdittime\" />\n
-<input type='hidden' value=\"{$this->scrolltop}\" name=\"wpScrolltop\" id=\"wpScrolltop\" />\n" );
+       /**
+        * Subpage overridable method for printing the form for page content editing
+        * By default this simply outputs wpTextbox1
+        * Subclasses can override this to provide a custom UI for editing;
+        * be it a form, or simply wpTextbox1 with a modified content that will be
+        * reverse modified when extracted from the post data.
+        * Note that this is basically the inverse for importContentFormData
+        * 
+        * @praram WebRequest $request
+        */
+       protected function showContentForm() {
+               $this->showTextbox1();
        }
 
-       protected function showTextbox1( $classes ) {
+       /**
+        * Method to output wpTextbox1
+        * The $textoverride method can be used by subclasses overriding showContentForm
+        * to pass back to this method.
+        * 
+        * @param array $customAttribs An array of html attributes to use in the textarea
+        * @param string $textoverride Optional text to override $this->textarea1 with
+        */
+       protected function showTextbox1($customAttribs = null, $textoverride = null) {
+               $classes = array(); // Textarea CSS
+               if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+                       # Is the title semi-protected?
+                       if ( $this->mTitle->isSemiProtected() ) {
+                               $classes[] = 'mw-textarea-sprotected';
+                       } else {
+                               # Then it must be protected based on static groups (regular)
+                               $classes[] = 'mw-textarea-protected';
+                       }
+               }
                $attribs = array( 'tabindex' => 1 );
+               if ( is_array($customAttribs) )
+                       $attribs += $customAttribs;
 
                if ( $this->wasDeletedSinceLastEdit() )
                        $attribs['type'] = 'hidden';
-               if ( !empty( $classes ) )
+               if ( !empty( $classes ) ) {
+                       if ( isset($attribs['class']) )
+                               $classes[] = $attribs['class'];
                        $attribs['class'] = implode( ' ', $classes );
+               }
 
-               $this->showTextbox( $this->textbox1, 'wpTextbox1', $attribs );
+               # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display
+               if ( !$this->preview && !$this->diff ) {
+                       global $wgOut;
+                       $wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus();' );
+               }
+               
+               $this->showTextbox( isset($textoverride) ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
        }
 
        protected function showTextbox2() {
                $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6 ) );
        }
 
-       protected function showTextbox( $content, $name, $attribs = array() ) {
+       protected function showTextbox( $content, $name, $customAttribs = array() ) {
                global $wgOut, $wgUser;
 
                $wikitext = $this->safeUnicodeOutput( $content );
@@ -1658,17 +1705,27 @@ END
                        $wikitext .= "\n";
                }
 
-               $attribs['accesskey'] = ',';
-               $attribs['id'] = $name;
+               $attribs = $customAttribs + array(
+                       'accesskey' => ',',
+                       'id'   => $name,
+                       'cols' => $wgUser->getIntOption( 'cols' ), 
+                       'rows' => $wgUser->getIntOption( 'rows' ),
+                       'onfocus' => "currentFocused = this;",
+               );
 
                if ( $wgUser->getOption( 'editwidth' ) )
-                       $attribs['style'] = 'width: 100%';
+                       $attribs['style'] .= 'width: 100%';
 
-               $wgOut->addHTML( Xml::textarea(
-                       $name,
-                       $wikitext,
-                       $wgUser->getIntOption( 'cols' ), $wgUser->getIntOption( 'rows' ),
-                       $attribs ) );
+               $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
+       }
+       
+       protected function showMetaData() {
+               global $wgOut, $wgContLang, $wgUser;
+               $metadata = htmlspecialchars( $wgContLang->recodeForEdit( $this->mMetaData ) );
+               $ew = $wgUser->getOption( 'editwidth' ) ? ' style="width:100%"' : '';
+               $cols = $wgUser->getIntOption( 'cols' );
+               $metadata = wfMsgWikiHtml( 'metadata_help' ) . "<textarea name='metadata' rows='3' cols='{$cols}'{$ew}>{$metadata}</textarea>" ;
+               $wgOut->addHTML( $metadata );
        }
 
        protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
@@ -1739,6 +1796,63 @@ END
                $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
                $wgOut->addHTML( '</div>' );
        }
+       
+       protected function getCopywarn() {
+               global $wgRightsText;
+               if ( $wgRightsText ) {
+                       $copywarnMsg = array( 'copyrightwarning',
+                               '[[' . wfMsgForContent( 'copyrightpage' ) . ']]',
+                               $wgRightsText );
+               } else {
+                       $copywarnMsg = array( 'copyrightwarning2',
+                               '[[' . wfMsgForContent( 'copyrightpage' ) . ']]' );
+               }
+               // Allow for site and per-namespace customization of contribution/copyright notice.
+               wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) );
+               
+               return "<div id=\"editpage-copywarn\">\n" . call_user_func_array("wfMsgNoTrans", $copywarnMsg) . "\n</div>";
+       }
+       
+       protected function showStandardInputs( &$tabindex = 2 ) {
+               global $wgOut, $wgUser;
+               $wgOut->addHTML( "<div class='editOptions'>\n" );
+
+               if ( $this->section != 'new' ) {
+                       $this->showSummaryInput( false, $this->summary );
+                       $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
+               }
+
+               $checkboxes = $this->getCheckboxes( $tabindex, $wgUser->getSkin(),
+                       array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
+               $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
+               $wgOut->addHTML( "<div class='editButtons'>\n" );
+               $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
+
+               $cancel = $this->getCancelLink();
+               $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
+               $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) );
+               $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
+                       htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
+                       htmlspecialchars( wfMsg( 'newwindow' ) );
+               $wgOut->addHTML( "      <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span>\n" );
+               $wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
+       }
+       
+       protected function showConflict() {
+               global $wgOut;
+               $this->textbox2 = $this->textbox1;
+               $this->textbox1 = $this->getContent();
+               if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
+                       $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
+
+                       $de = new DifferenceEngine( $this->mTitle );
+                       $de->setText( $this->textbox2, $this->textbox1 );
+                       $de->showDiff( wfMsg( "yourtext" ), wfMsg( "storedversion" ) );
+
+                       $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
+                       $this->showTextbox2();
+               }
+       }
 
        protected function getLastDelete() {
                $dbr = wfGetDB( DB_SLAVE );
@@ -2057,6 +2171,7 @@ END
                global $wgStylePath, $wgContLang, $wgLang;
 
                /**
+
                 * toolarray an array of arrays which each include the filename of
                 * the button image (without path), the opening tag, the closing tag,
                 * and optionally a sample text that is inserted between the two when no
@@ -2324,6 +2439,22 @@ END
        }
 
 
+       public function getCancelLink() {
+               global $wgUser, $wgTitle;
+               $cancelParams = array();
+               if ( !$this->isConflict && isset( $this->mArticle ) &&
+                       isset( $this->mArticle->mRevision ) &&
+                       !$this->mArticle->mRevision->isCurrent() )
+                               $cancelParams['oldid'] = $this->mArticle->mRevision->getId();
+               return $wgUser->getSkin()->link(
+                       $wgTitle,
+                       wfMsgExt( 'cancel', array( 'parseinline' ) ),
+                       array( 'id' => 'mw-editform-cancel' ),
+                       $cancelParams,
+                       array( 'known', 'noclasses' )
+               );
+       }
+
        /**
         * Get a diff between the current contents of the edit box and the
         * version of the page we're editing from.
@@ -2367,6 +2498,13 @@ END
                        : $text;
        }
 
+       function safeUnicodeText( $request, $text ) {
+               $text = rtrim( $text );
+               return $request->getBool( 'safemode' )
+                       ? $this->unmakesafe( $text )
+                       : $text;
+       }
+
        /**
         * Filter an output field through a Unicode armoring process if it is
         * going to an old browser with known broken Unicode editing issues.
index 4ecfcba..7357eb9 100644 (file)
@@ -486,4 +486,29 @@ class Html {
        public static function hidden( $name, $value, $attribs = array() ) {
                return self::input( $name, $value, 'hidden', $attribs );
        }
+
+       /**
+        * Convenience function to produce an <input> element.  This supports leaving
+        * out the cols= and rows= which Xml requires and are required by HTML4/XHTML
+        * but not required by HTML5 and will silently set cols="" and rows="" if
+        * $wgHtml5 is false and cols and rows are omitted (HTML4 validates present
+        * but empty cols="" and rows="" as valid).
+        *
+        * @param $name    string name attribute
+        * @param $value   string value attribute
+        * @param $attribs array  Associative array of miscellaneous extra
+        *   attributes, passed to Html::element()
+        * @return string Raw HTML
+        */
+       public static function textarea( $name, $value = '', $attribs = array() ) {
+               global $wgHtml5;
+               $attribs['name'] = $name;
+               if ( !$wgHtml5 ) {
+                       if ( !array_key_exists('cols', $attribs) )
+                               $attribs['cols'] = "";
+                       if ( !array_key_exists('rows', $attribs) )
+                               $attribs['rows'] = "";
+               }
+               return self::element( 'textarea', $attribs, $value );
+       }
 }
index 5dd57f0..295f723 100644 (file)
@@ -1338,6 +1338,8 @@ The administrator who locked it offered this explanation: $1",
 'nocreatetext'                     => '{{SITENAME}} has restricted the ability to create new pages.
 You can go back and edit an existing page, or [[Special:UserLogin|log in or create an account]].',
 'nocreate-loggedin'                => 'You do not have permission to create new pages.',
+'sectioneditnotsupported-title'    => 'Section editing not supported',
+'sectioneditnotsupported-text'     => 'Section editing is not supported in this edit page.',
 'permissionserrors'                => 'Permissions Errors',
 'permissionserrorstext'            => 'You do not have permission to do that, for the following {{PLURAL:$1|reason|reasons}}:',
 'permissionserrorstext-withaction' => 'You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:',
index a36e5db..17bf113 100644 (file)
@@ -5,13 +5,13 @@ var currentFocused;
 function addButton(imageFile, speedTip, tagOpen, tagClose, sampleText, imageId) {
        // Don't generate buttons for browsers which don't fully
        // support it.
-       mwEditButtons[mwEditButtons.length] =
+       mwEditButtons.push(
                {"imageId": imageId,
                 "imageFile": imageFile,
                 "speedTip": speedTip,
                 "tagOpen": tagOpen,
                 "tagClose": tagClose,
-                "sampleText": sampleText};
+                "sampleText": sampleText});
 }
 
 // this function generates the actual toolbar buttons with localized text
@@ -44,11 +44,9 @@ function mwSetupToolbar() {
        var toolbar = document.getElementById('toolbar');
        if (!toolbar) { return false; }
 
-       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
        if (!(document.selection && document.selection.createRange)
                && textbox.selectionStart === null) {
                return false;
@@ -154,17 +152,13 @@ function scrollEditBox() {
                if( scrollTop.value )
                        editBox.scrollTop = scrollTop.value;
                addHandler( editForm, 'submit', function() {
-                       document.getElementById( 'wpScrolltop' ).value = document.getElementById( 'wpTextbox1' ).scrollTop;
+                       scrollTop.value = editBox.scrollTop;
                } );
        }
 }
 hookEvent( 'load', scrollEditBox );
 hookEvent( 'load', mwSetupToolbar );
 hookEvent( 'load', function() {
-       if ( document.editform ) {
-               currentFocused = document.editform.wpTextbox1;
-               document.editform.wpTextbox1.onfocus = function() { currentFocused = document.editform.wpTextbox1; };
-               document.editform.wpSummary.onfocus = function() { currentFocused = document.editform.wpSummary; };
-       }
+       currentFocused = document.getElementById( 'wpTextbox1' );
 } );