Create a generic UDP transport class
[lhc/web/wiklou.git] / includes / EditPage.php
index fa19c78..d05b996 100644 (file)
@@ -144,6 +144,18 @@ class EditPage {
         */
        const AS_IMAGE_REDIRECT_LOGGED = 234;
 
+       /**
+        * Status: user tried to modify the content model, but is not allowed to do that
+        * ( User::isAllowed('editcontentmodel') == false )
+        */
+       const AS_NO_CHANGE_CONTENT_MODEL = 235;
+
+       /**
+        * Status: user tried to create self-redirect (redirect to the same article) and
+        * wpIgnoreSelfRedirect == false
+        */
+       const AS_SELF_REDIRECT = 236;
+
        /**
         * Status: can't parse content
         */
@@ -250,6 +262,12 @@ class EditPage {
        /** @var bool */
        protected $allowBlankArticle = false;
 
+       /** @var bool */
+       protected $selfRedirect = false;
+
+       /** @var bool */
+       protected $allowSelfRedirect = false;
+
        /** @var string */
        public $autoSumm = '';
 
@@ -315,6 +333,9 @@ class EditPage {
        /** @var int */
        public $oldid = 0;
 
+       /** @var int */
+       public $parentRevId = 0;
+
        /** @var string */
        public $editintro = '';
 
@@ -442,7 +463,7 @@ class EditPage {
        function edit() {
                global $wgOut, $wgRequest, $wgUser;
                // Allow extensions to modify/prevent this form or submission
-               if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
+               if ( !Hooks::run( 'AlternateEdit', array( $this ) ) ) {
                        return;
                }
 
@@ -537,9 +558,9 @@ class EditPage {
                        }
 
                        if ( !$this->mTitle->getArticleID() ) {
-                               wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
+                               Hooks::run( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
                        } else {
-                               wfRunHooks( 'EditFormInitialText', array( $this ) );
+                               Hooks::run( 'EditFormInitialText', array( $this ) );
                        }
 
                }
@@ -606,7 +627,7 @@ class EditPage {
                        throw new PermissionsError( $action, $permErrors );
                }
 
-               wfRunHooks( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) );
+               Hooks::run( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) );
 
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
                $wgOut->setPageTitle( wfMessage(
@@ -839,6 +860,7 @@ class EditPage {
                        $this->autoSumm = $request->getText( 'wpAutoSummary' );
 
                        $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
+                       $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
                } else {
                        # Not a posted form? Start with nothing.
                        wfDebug( __METHOD__ . ": Not a posted form.\n" );
@@ -875,6 +897,7 @@ class EditPage {
                }
 
                $this->oldid = $request->getInt( 'oldid' );
+               $this->parentRevId = $request->getInt( 'parentRevId' );
 
                $this->bot = $request->getBool( 'bot', true );
                $this->nosummary = $request->getBool( 'nosummary' );
@@ -906,7 +929,7 @@ class EditPage {
                        $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
 
                // Allow extensions to modify form data
-               wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
+               Hooks::run( 'EditPage::importFormData', array( $this, $request ) );
 
                wfProfileOut( __METHOD__ );
        }
@@ -1324,6 +1347,7 @@ class EditPage {
                        case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
                        case self::AS_END:
                        case self::AS_BLANK_ARTICLE:
+                       case self::AS_SELF_REDIRECT:
                                return true;
 
                        case self::AS_HOOK_ERROR:
@@ -1344,7 +1368,7 @@ class EditPage {
                                $sectionanchor = $resultDetails['sectionanchor'];
 
                                // Give extensions a chance to modify URL query on update
-                               wfRunHooks(
+                               Hooks::run(
                                        'ArticleUpdateBeforeRedirect',
                                        array( $this->mArticle, &$sectionanchor, &$extraQuery )
                                );
@@ -1384,6 +1408,9 @@ class EditPage {
                                $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
                                throw new PermissionsError( $permission );
 
+                       case self::AS_NO_CHANGE_CONTENT_MODEL:
+                               throw new PermissionsError( 'editcontentmodel' );
+
                        default:
                                // We don't recognize $status->value. The only way that can happen
                                // is if an extension hook aborted from inside ArticleSave.
@@ -1407,8 +1434,8 @@ class EditPage {
        protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
                // Run old style post-section-merge edit filter
                if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
-                       array( $this, $content, &$this->hookError, $this->summary ) ) ) {
-
+                       array( $this, $content, &$this->hookError, $this->summary ) )
+               ) {
                        # Error messages etc. could be handled within the hook...
                        $status->fatal( 'hookaborted' );
                        $status->value = self::AS_HOOK_ERROR;
@@ -1421,15 +1448,24 @@ class EditPage {
                }
 
                // Run new style post-section-merge edit filter
-               if ( !wfRunHooks( 'EditFilterMergedContent',
-                       array( $this->mArticle->getContext(), $content, $status, $this->summary,
-                               $user, $this->minoredit ) ) ) {
-
+               if ( !Hooks::run( 'EditFilterMergedContent',
+                               array( $this->mArticle->getContext(), $content, $status, $this->summary,
+                               $user, $this->minoredit ) )
+               ) {
                        # Error messages etc. could be handled within the hook...
-                       // XXX: $status->value may already be something informative...
-                       $this->hookError = $status->getWikiText();
-                       $status->fatal( 'hookaborted' );
-                       $status->value = self::AS_HOOK_ERROR;
+                       if ( $status->isGood() ) {
+                               $status->fatal( 'hookaborted' );
+                               // Not setting $this->hookError here is a hack to allow the hook
+                               // to cause a return to the edit page without $this->hookError
+                               // being set. This is used by ConfirmEdit to display a captcha
+                               // without any error message cruft.
+                       } else {
+                               $this->hookError = $status->getWikiText();
+                       }
+                       // Use the existing $status->value if the hook set it
+                       if ( !$status->value ) {
+                               $status->value = self::AS_HOOK_ERROR;
+                       }
                        return false;
                } elseif ( !$status->isOK() ) {
                        # ...or the hook could be expecting us to produce an error
@@ -1505,7 +1541,7 @@ class EditPage {
                wfProfileIn( __METHOD__ );
                wfProfileIn( __METHOD__ . '-checks' );
 
-               if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
+               if ( !Hooks::run( 'EditPage::attemptSave', array( $this ) ) ) {
                        wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
                        $status->fatal( 'hookaborted' );
                        $status->value = self::AS_HOOK_ERROR;
@@ -1591,7 +1627,7 @@ class EditPage {
                        wfProfileOut( __METHOD__ );
                        return $status;
                }
-               if ( !wfRunHooks(
+               if ( !Hooks::run(
                        'EditFilter',
                        array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) )
                ) {
@@ -1645,6 +1681,15 @@ class EditPage {
                        }
                }
 
+               if ( $this->contentModel !== $this->mTitle->getContentModel()
+                       && !$wgUser->isAllowed( 'editcontentmodel' )
+               ) {
+                       $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
+                       wfProfileOut( __METHOD__ . '-checks' );
+                       wfProfileOut( __METHOD__ );
+                       return $status;
+               }
+
                if ( wfReadOnly() ) {
                        $status->fatal( 'readonlytext' );
                        $status->value = self::AS_READ_ONLY_PAGE;
@@ -1879,6 +1924,21 @@ class EditPage {
                        $status->value = self::AS_SUCCESS_UPDATE;
                }
 
+               if ( !$this->allowSelfRedirect
+                       && $content->isRedirect()
+                       && $content->getRedirectTarget()->equals( $this->getTitle() )
+               ) {
+                       // If the page already redirects to itself, don't warn.
+                       $currentTarget = $this->getCurrentContent()->getRedirectTarget();
+                       if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
+                               $this->selfRedirect = true;
+                               $status->fatal( 'selfredirect' );
+                               $status->value = self::AS_SELF_REDIRECT;
+                               wfProfileOut( __METHOD__ );
+                               return $status;
+                       }
+               }
+
                // Check for length errors again now that the section is merged in
                $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
                if ( $this->kblength > $wgMaxArticleSize ) {
@@ -2053,7 +2113,7 @@ class EditPage {
        }
 
        function setHeaders() {
-               global $wgOut, $wgUser;
+               global $wgOut, $wgUser, $wgAjaxEditStash;
 
                $wgOut->addModules( 'mediawiki.action.edit' );
                $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
@@ -2066,6 +2126,10 @@ class EditPage {
                        $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
                }
 
+               if ( $wgAjaxEditStash ) {
+                       $wgOut->addModules( 'mediawiki.action.edit.stash' );
+               }
+
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
 
                # Enabled article-related sidebar, toplinks, etc.
@@ -2294,6 +2358,9 @@ class EditPage {
         * Send the edit form and related headers to $wgOut
         * @param callable|null $formCallback That takes an OutputPage parameter; will be called
         *     during form output near the top, for captchas and the like.
+        *
+        * The $formCallback parameter is deprecated since MediaWiki 1.25. Please
+        * use the EditPage::showEditForm:fields hook instead.
         */
        function showEditForm( $formCallback = null ) {
                global $wgOut, $wgUser;
@@ -2309,7 +2376,7 @@ class EditPage {
                        $previewOutput = $this->getPreviewText();
                }
 
-               wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
+               Hooks::run( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
 
                $this->setHeaders();
 
@@ -2352,6 +2419,7 @@ class EditPage {
                ) );
 
                if ( is_callable( $formCallback ) ) {
+                       wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
                        call_user_func_array( $formCallback, array( &$wgOut ) );
                }
 
@@ -2375,7 +2443,7 @@ class EditPage {
                        . Xml::closeElement( 'div' )
                );
 
-               wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
+               Hooks::run( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
 
                // Put these up at the top to ensure they aren't lost on early form submission
                $this->showFormBeforeText();
@@ -2419,6 +2487,10 @@ class EditPage {
                        $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
                }
 
+               if ( $this->selfRedirect ) {
+                       $wgOut->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
+               }
+
                if ( $this->hasPresetSummary ) {
                        // If a summary has been preset using &summary= we don't want to prompt for
                        // a different summary. Only prompt for a summary if the summary is blanked.
@@ -2430,6 +2502,8 @@ class EditPage {
                $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
 
                $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+               $wgOut->addHTML( Html::hidden( 'parentRevId',
+                       $this->parentRevId ?: $this->mArticle->getRevIdFetched() ) );
 
                $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
                $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
@@ -2581,6 +2655,10 @@ class EditPage {
                                $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' );
                        }
 
+                       if ( $this->selfRedirect ) {
+                               $wgOut->wrapWikiMsg( "<div id='mw-selfredirect'>\n$1\n</div>", 'selfredirect' );
+                       }
+
                        if ( $this->hookError !== '' ) {
                                $wgOut->addWikiText( $this->hookError );
                        }
@@ -2838,7 +2916,7 @@ class EditPage {
                global $wgOut;
                $section = htmlspecialchars( $this->section );
                $wgOut->addHTML( <<<HTML
-<input type='hidden' value="{$section}" name="wpSection" />
+<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" />
@@ -2956,7 +3034,7 @@ HTML
                );
 
                $pageLang = $this->mTitle->getPageLanguage();
-               $attribs['lang'] = $pageLang->getCode();
+               $attribs['lang'] = $pageLang->getHtmlCode();
                $attribs['dir'] = $pageLang->getDir();
 
                $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
@@ -3011,7 +3089,7 @@ HTML
                }
                # This hook seems slightly odd here, but makes things more
                # consistent for extensions.
-               wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
+               Hooks::run( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
                $wgOut->addHTML( $text );
                if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
                        $this->mArticle->closeShowCategory();
@@ -3050,7 +3128,7 @@ HTML
 
                if ( $newContent ) {
                        ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
-                       wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
+                       Hooks::run( 'EditPageGetDiffContent', array( $this, &$newContent ) );
 
                        $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
                        $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
@@ -3102,7 +3180,7 @@ HTML
         */
        protected function showTosSummary() {
                $msg = 'editpage-tos-summary';
-               wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
+               Hooks::run( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
                if ( !wfMessage( $msg )->isDisabled() ) {
                        global $wgOut;
                        $wgOut->addHTML( '<div class="mw-tos-summary">' );
@@ -3146,7 +3224,7 @@ HTML
                                '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
                }
                // Allow for site and per-namespace customization of contribution/copyright notice.
-               wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
+               Hooks::run( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
 
                return "<div id=\"editpage-copywarn\">\n" .
                        call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
@@ -3179,7 +3257,7 @@ HTML
                        Html::openElement( 'tbody' );
 
                foreach ( $output->getLimitReportData() as $key => $value ) {
-                       if ( wfRunHooks( 'ParserLimitReportFormat',
+                       if ( Hooks::run( 'ParserLimitReportFormat',
                                array( $key, &$value, &$limitReport, true, true )
                        ) ) {
                                $keyMsg = wfMessage( $key );
@@ -3247,7 +3325,7 @@ HTML
                $wgOut->addHTML( "      <span class='editHelp'>{$edithelp}</span>\n" );
                $wgOut->addHTML( "</div><!-- editButtons -->\n" );
 
-               wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
+               Hooks::run( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
 
                $wgOut->addHTML( "</div><!-- editOptions -->\n" );
        }
@@ -3259,7 +3337,7 @@ HTML
        protected function showConflict() {
                global $wgOut;
 
-               if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
+               if ( Hooks::run( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 
                        $content1 = $this->toEditContent( $this->textbox1 );
@@ -3410,7 +3488,7 @@ HTML
                        $content = $this->toEditContent( $this->textbox1 );
 
                        $previewHTML = '';
-                       if ( !wfRunHooks(
+                       if ( !Hooks::run(
                                'AlternateEditPreview',
                                array( $this, &$content, &$previewHTML, &$this->mParserOutput ) )
                        ) {
@@ -3435,7 +3513,6 @@ HTML
                        }
 
                        $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
-                       $parserOptions->setEditSection( false );
                        $parserOptions->setIsPreview( true );
                        $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
 
@@ -3480,20 +3557,24 @@ HTML
 
                        $hook_args = array( $this, &$content );
                        ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
-                       wfRunHooks( 'EditPageGetPreviewContent', $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
 
-                       $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
-                       $parserOutput = $content->getParserOutput(
-                               $this->getArticle()->getTitle(),
-                               null,
-                               $parserOptions
+                       $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+                       $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
+                       ApiStashEdit::stashEditFromPreview(
+                               $this->getArticle(), $content, $pstContent,
+                               $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()
                        );
 
+                       $parserOutput->setEditSectionTokens( false ); // no section edit links
                        $previewHTML = $parserOutput->getText();
                        $this->mParserOutput = $parserOutput;
                        $wgOut->addParserOutputMetadata( $parserOutput );
@@ -3674,7 +3755,7 @@ HTML
 
                $toolbar = '<div id="toolbar"></div>';
 
-               wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
+               Hooks::run( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
 
                return $toolbar;
        }
@@ -3741,7 +3822,7 @@ HTML
                                $checkboxes['watch'] = $watchThisHtml;
                        }
                }
-               wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
+               Hooks::run( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
                return $checkboxes;
        }
 
@@ -3782,7 +3863,7 @@ HTML
                $buttons['diff'] = Html::submitButton( wfMessage( 'showdiff' )->text(),
                        $attribs );
 
-               wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
+               Hooks::run( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
                return $buttons;
        }
 
@@ -3826,7 +3907,7 @@ HTML
                $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
 
                $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
-               wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
+               Hooks::run( 'EditPageNoSuchSection', array( &$this, &$res ) );
                $wgOut->addHTML( $res );
 
                $wgOut->returnToMain( false, $this->mTitle );