resourceloader: Update expected length of module version hash
[lhc/web/wiklou.git] / includes / EditPage.php
index f2403fe..9c7ccdf 100644 (file)
@@ -335,6 +335,9 @@ class EditPage {
        /** @var string */
        public $edittime = '';
 
+       /** @var integer */
+       private $editRevId = null;
+
        /** @var string */
        public $section = '';
 
@@ -839,6 +842,7 @@ class EditPage {
                        $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
 
                        $this->edittime = $request->getVal( 'wpEdittime' );
+                       $this->editRevId = $request->getIntOrNull( 'editRevId' );
                        $this->starttime = $request->getVal( 'wpStarttime' );
 
                        $undidRev = $request->getInt( 'wpUndidRevision' );
@@ -935,6 +939,7 @@ class EditPage {
                        $this->summary = '';
                        $this->sectiontitle = '';
                        $this->edittime = '';
+                       $this->editRevId = null;
                        $this->starttime = wfTimestampNow();
                        $this->edit = false;
                        $this->preview = false;
@@ -1020,6 +1025,7 @@ class EditPage {
        function initialiseForm() {
                global $wgUser;
                $this->edittime = $this->page->getTimestamp();
+               $this->editRevId = $this->page->getLatest();
 
                $content = $this->getContentObject( false ); # TODO: track content object?!
                if ( $content === false ) {
@@ -1862,10 +1868,14 @@ class EditPage {
 
                        $this->page->clear(); # Force reload of dates, etc.
                        $timestamp = $this->page->getTimestamp();
+                       $latest = $this->page->getLatest();
 
                        wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
 
-                       if ( $timestamp != $this->edittime ) {
+                       // Check editRevId if set, which handles same-second timestamp collisions
+                       if ( $timestamp != $this->edittime
+                               || ( $this->editRevId !== null && $this->editRevId != $latest )
+                       ) {
                                $this->isConflict = true;
                                if ( $this->section == 'new' ) {
                                        if ( $this->page->getUserText() == $wgUser->getName() &&
@@ -1905,14 +1915,24 @@ class EditPage {
                        if ( $this->isConflict ) {
                                wfDebug( __METHOD__
                                        . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
-                                       . " (article time '{$timestamp}')\n" );
-
-                               $content = $this->page->replaceSectionContent(
-                                       $this->section,
-                                       $textbox_content,
-                                       $sectionTitle,
-                                       $this->edittime
-                               );
+                                       . " (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
+                               // @TODO: replaceSectionAtRev() with base ID (not prior current) for ?oldid=X case
+                               // ...or disable section editing for non-current revisions (not exposed anyway).
+                               if ( $this->editRevId !== null ) {
+                                       $content = $this->page->replaceSectionAtRev(
+                                               $this->section,
+                                               $textbox_content,
+                                               $sectionTitle,
+                                               $this->editRevId
+                                       );
+                               } else {
+                                       $content = $this->page->replaceSectionContent(
+                                               $this->section,
+                                               $textbox_content,
+                                               $sectionTitle,
+                                               $this->edittime
+                                       );
+                               }
                        } else {
                                wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
                                $content = $this->page->replaceSectionContent(
@@ -2172,8 +2192,9 @@ class EditPage {
        function getBaseRevision() {
                if ( !$this->mBaseRevision ) {
                        $db = wfGetDB( DB_MASTER );
-                       $this->mBaseRevision = Revision::loadFromTimestamp(
-                               $db, $this->mTitle, $this->edittime );
+                       $this->mBaseRevision = $this->editRevId
+                               ? Revision::newFromId( $this->editRevId, Revision::READ_LATEST )
+                               : Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime );
                }
                return $this->mBaseRevision;
        }
@@ -2242,10 +2263,6 @@ class EditPage {
                        $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
                }
 
-               if ( $wgAjaxEditStash ) {
-                       $wgOut->addModules( 'mediawiki.action.edit.stash' );
-               }
-
                # Enabled article-related sidebar, toplinks, etc.
                $wgOut->setArticleRelated( true );
 
@@ -2273,7 +2290,10 @@ class EditPage {
                $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
                # Transmit the name of the message to JavaScript for live preview
                # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
-               $wgOut->addJsConfigVars( 'wgEditMessage', $msg );
+               $wgOut->addJsConfigVars( [
+                       'wgEditMessage' => $msg,
+                       'wgAjaxEditStash' => $wgAjaxEditStash,
+               ] );
        }
 
        /**
@@ -2690,6 +2710,18 @@ class EditPage {
                        }
                }
 
+               // Set a hidden field so JS knows what edit form mode we are in
+               if ( $this->isConflict ) {
+                       $mode = 'conflict';
+               } elseif ( $this->preview ) {
+                       $mode = 'preview';
+               } elseif ( $this->diff ) {
+                       $mode = 'diff';
+               } else {
+                       $mode = 'text';
+               }
+               $wgOut->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
+
                // Marker for detecting truncated form data.  This must be the last
                // parameter sent in order to be of use, so do not move me.
                $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) );
@@ -2745,7 +2777,7 @@ class EditPage {
 
                if ( $this->isConflict ) {
                        $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
-                       $this->edittime = $this->page->getTimestamp();
+                       $this->editRevId = $this->page->getLatest();
                } else {
                        if ( $this->section != '' && !$this->isSectionEditSupported() ) {
                                // We use $this->section to much before this and getVal('wgSection') directly in other places
@@ -3052,6 +3084,7 @@ class EditPage {
 <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->editRevId}" name="editRevId" />
 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
 
 HTML
@@ -3260,10 +3293,15 @@ HTML
                }
 
                $textboxContent = $this->toEditContent( $this->textbox1 );
-
-               $newContent = $this->page->replaceSectionContent(
-                                                       $this->section, $textboxContent,
-                                                       $this->summary, $this->edittime );
+               if ( $this->editRevId !== null ) {
+                       $newContent = $this->page->replaceSectionAtRev(
+                               $this->section, $textboxContent, $this->summary, $this->editRevId
+                       );
+               } else {
+                       $newContent = $this->page->replaceSectionContent(
+                               $this->section, $textboxContent, $this->summary, $this->edittime
+                       );
+               }
 
                if ( $newContent ) {
                        ContentHandler::runLegacyHooks( 'EditPageGetDiffText', [ $this, &$newContent ] );
@@ -3475,6 +3513,14 @@ HTML
                if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$this, &$wgOut ] ) ) {
                        $stats = $wgOut->getContext()->getStats();
                        $stats->increment( 'edit.failures.conflict' );
+                       if ( $this->mTitle->isTalkPage() ) {
+                               $stats->increment( 'edit.failures.conflict.byType.talk' );
+                       } else {
+                               $stats->increment( 'edit.failures.conflict.byType.subject' );
+                       }
+                       if ( $this->mTitle->getNamespace() === NS_PROJECT ) {
+                               $stats->increment( 'edit.failures.conflict.byNamespace.project' );
+                       }
 
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 
@@ -3602,8 +3648,8 @@ HTML
         * @return string
         */
        function getPreviewText() {
-               global $wgOut, $wgUser, $wgRawHtml, $wgLang;
-               global $wgAllowUserCss, $wgAllowUserJs, $wgAjaxEditStash;
+               global $wgOut, $wgRawHtml, $wgLang;
+               global $wgAllowUserCss, $wgAllowUserJs;
 
                $stats = $wgOut->getContext()->getStats();
 
@@ -3656,10 +3702,6 @@ HTML
                                $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
                        }
 
-                       $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() );
-                       $parserOptions->setIsPreview( true );
-                       $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
-
                        # don't parse non-wikitext pages, show message about preview
                        if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
                                if ( $this->mTitle->isCssJsSubpage() ) {
@@ -3703,27 +3745,9 @@ HTML
                        ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
                        Hooks::run( 'EditPageGetPreviewContent', $hook_args );
 
-                       $parserOptions->enableLimitReport();
-
-                       # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
-                       # But it's now deprecated, so never mind
-
-                       $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
-                       $scopedCallback = $parserOptions->setupFakeRevision(
-                               $this->mTitle, $pstContent, $wgUser );
-                       $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
-
-                       # Try to stash the edit for the final submission step
-                       # @todo: different date format preferences cause cache misses
-                       if ( $wgAjaxEditStash ) {
-                               ApiStashEdit::stashEditFromPreview(
-                                       $this->getArticle(), $content, $pstContent,
-                                       $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()
-                               );
-                       }
-
-                       $parserOutput->setEditSectionTokens( false ); // no section edit links
-                       $previewHTML = $parserOutput->getText();
+                       $parserResult = $this->doPreviewParse( $content );
+                       $parserOutput = $parserResult['parserOutput'];
+                       $previewHTML = $parserResult['html'];
                        $this->mParserOutput = $parserOutput;
                        $wgOut->addParserOutputMetadata( $parserOutput );
 
@@ -3731,7 +3755,6 @@ HTML
                                $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
                        }
 
-                       ScopedCallback::consume( $scopedCallback );
                } catch ( MWContentSerializationException $ex ) {
                        $m = wfMessage(
                                'content-failed-to-parse',
@@ -3762,6 +3785,41 @@ HTML
                return $previewhead . $previewHTML . $this->previewTextAfterContent;
        }
 
+       /**
+        * Get parser options for a preview
+        * @return ParserOptions
+        */
+       protected function getPreviewParserOptions() {
+               $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() );
+               $parserOptions->setIsPreview( true );
+               $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
+               $parserOptions->enableLimitReport();
+               return $parserOptions;
+       }
+
+       /**
+        * Parse the page for a preview. Subclasses may override this class, in order
+        * to parse with different options, or to otherwise modify the preview HTML.
+        *
+        * @param Content @content The page content
+        * @return Associative array with keys:
+        *   - parserOutput: The ParserOutput object
+        *   - html: The HTML to be displayed
+        */
+       protected function doPreviewParse( Content $content ) {
+               global $wgUser;
+               $parserOptions = $this->getPreviewParserOptions();
+               $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+               $scopedCallback = $parserOptions->setupFakeRevision(
+                       $this->mTitle, $pstContent, $wgUser );
+               $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
+               ScopedCallback::consume( $scopedCallback );
+               $parserOutput->setEditSectionTokens( false ); // no section edit links
+               return [
+                       'parserOutput' => $parserOutput,
+                       'html' => $parserOutput->getText() ];
+       }
+
        /**
         * @return array
         */