DatabaseUpdater: Add modifyExtensionTable()
[lhc/web/wiklou.git] / includes / EditPage.php
index b40a054..ff224c5 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use MediaWiki\EditPage\TextboxBuilder;
+use MediaWiki\EditPage\TextConflictHelper;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
 use Wikimedia\ScopedCallback;
@@ -213,12 +215,18 @@ class EditPage {
         */
        const POST_EDIT_COOKIE_DURATION = 1200;
 
-       /** @var Article */
+       /**
+        * @deprecated for public usage since 1.30 use EditPage::getArticle()
+        * @var Article
+        */
        public $mArticle;
        /** @var WikiPage */
        private $page;
 
-       /** @var Title */
+       /**
+        * @deprecated for public usage since 1.30 use EditPage::getTitle()
+        * @var Title
+        */
        public $mTitle;
 
        /** @var null|Title */
@@ -230,16 +238,28 @@ class EditPage {
        /** @var bool */
        public $isConflict = false;
 
-       /** @var bool */
+       /**
+        * @deprecated since 1.30 use Title::isCssJsSubpage()
+        * @var bool
+        */
        public $isCssJsSubpage = false;
 
-       /** @var bool */
+       /**
+        * @deprecated since 1.30 use Title::isCssSubpage()
+        * @var bool
+        */
        public $isCssSubpage = false;
 
-       /** @var bool */
+       /**
+        * @deprecated since 1.30 use Title::isJsSubpage()
+        * @var bool
+        */
        public $isJsSubpage = false;
 
-       /** @var bool */
+       /**
+        * @deprecated since 1.30
+        * @var bool
+        */
        public $isWrongCaseCssJsPage = false;
 
        /** @var bool New page or new section */
@@ -428,6 +448,18 @@ class EditPage {
         */
        private $unicodeCheck;
 
+       /**
+        * Factory function to create an edit conflict helper
+        *
+        * @var callable
+        */
+       private $editConflictHelperFactory;
+
+       /**
+        * @var TextConflictHelper|null
+        */
+       private $editConflictHelper;
+
        /**
         * @param Article $article
         */
@@ -441,6 +473,7 @@ class EditPage {
 
                $handler = ContentHandler::getForModelID( $this->contentModel );
                $this->contentFormat = $handler->getDefaultFormat();
+               $this->editConflictHelperFactory = [ $this, 'newTextConflictHelper' ];
        }
 
        /**
@@ -501,6 +534,7 @@ class EditPage {
         * @deprecated since 1.30
         */
        public function isOouiEnabled() {
+               wfDeprecated( __METHOD__, '1.30' );
                return true;
        }
 
@@ -627,10 +661,11 @@ class EditPage {
 
                $this->isConflict = false;
                // css / js subpages of user pages get a special treatment
+               // The following member variables are deprecated since 1.30,
+               // the functions should be used instead.
                $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
                $this->isCssSubpage = $this->mTitle->isCssSubpage();
                $this->isJsSubpage = $this->mTitle->isJsSubpage();
-               // @todo FIXME: Silly assignment.
                $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
 
                # Show applicable editing introductions
@@ -802,8 +837,15 @@ class EditPage {
         * @return bool
         */
        protected function previewOnOpen() {
-               global $wgPreviewOnOpenNamespaces;
+               $config = $this->context->getConfig();
+               $previewOnOpenNamespaces = $config->get( 'PreviewOnOpenNamespaces' );
                $request = $this->context->getRequest();
+               if ( $config->get( 'RawHtml' ) ) {
+                       // If raw HTML is enabled, disable preview on open
+                       // since it has to be posted with a token for
+                       // security reasons
+                       return false;
+               }
                if ( $request->getVal( 'preview' ) == 'yes' ) {
                        // Explicit override from request
                        return true;
@@ -819,8 +861,8 @@ class EditPage {
                        // Standard preference behavior
                        return true;
                } elseif ( !$this->mTitle->exists()
-                       && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
-                       && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
+                       && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
+                       && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
                ) {
                        // Categories are special
                        return true;
@@ -1507,8 +1549,7 @@ class EditPage {
                        return;
                }
 
-               $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
-               $stats->increment( 'edit.failures.conflict.resolved' );
+               $this->getEditConflictHelper()->incrementResolvedStats();
        }
 
        /**
@@ -1665,7 +1706,7 @@ class EditPage {
 
                // Run new style post-section-merge edit filter
                if ( !Hooks::run( 'EditFilterMergedContent',
-                               [ $this->mArticle->getContext(), $content, $status, $this->summary,
+                               [ $this->context, $content, $status, $this->summary,
                                $user, $this->minoredit ] )
                ) {
                        # Error messages etc. could be handled within the hook...
@@ -1750,9 +1791,6 @@ class EditPage {
         * time.
         */
        public function internalAttemptSave( &$result, $bot = false ) {
-               global $wgMaxArticleSize;
-               global $wgContentHandlerUseDB;
-
                $status = Status::newGood();
                $user = $this->context->getUser();
 
@@ -1864,7 +1902,9 @@ class EditPage {
                }
 
                $this->contentLength = strlen( $this->textbox1 );
-               if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
+               $config = $this->context->getConfig();
+               $maxArticleSize = $config->get( 'MaxArticleSize' );
+               if ( $this->contentLength > $maxArticleSize * 1024 ) {
                        // Error will be displayed by showEditForm()
                        $this->tooBig = true;
                        $status->setResult( false, self::AS_CONTENT_TOO_BIG );
@@ -1884,7 +1924,7 @@ class EditPage {
 
                $changingContentModel = false;
                if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
-                       if ( !$wgContentHandlerUseDB ) {
+                       if ( !$config->get( 'ContentHandlerUseDB' ) ) {
                                $status->fatal( 'editpage-cannot-use-custom-model' );
                                $status->value = self::AS_CANNOT_USE_CUSTOM_MODEL;
                                return $status;
@@ -2163,7 +2203,7 @@ class EditPage {
 
                // Check for length errors again now that the section is merged in
                $this->contentLength = strlen( $this->toEditText( $content ) );
-               if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
+               if ( $this->contentLength > $maxArticleSize * 1024 ) {
                        $this->tooBig = true;
                        $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
                        return $status;
@@ -2364,12 +2404,11 @@ class EditPage {
        }
 
        public function setHeaders() {
-               global $wgAjaxEditStash;
-
                $out = $this->context->getOutput();
 
                $out->addModules( 'mediawiki.action.edit' );
                $out->addModuleStyles( 'mediawiki.action.edit.styles' );
+               $out->addModuleStyles( 'mediawiki.editfont.styles' );
 
                $user = $this->context->getUser();
                if ( $user->getOption( 'showtoolbar' ) ) {
@@ -2417,7 +2456,7 @@ class EditPage {
                # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
                $out->addJsConfigVars( [
                        'wgEditMessage' => $msg,
-                       'wgAjaxEditStash' => $wgAjaxEditStash,
+                       'wgAjaxEditStash' => $this->context->getConfig()->get( 'AjaxEditStash' ),
                ] );
        }
 
@@ -2794,8 +2833,22 @@ class EditPage {
                }
 
                $out->addHTML( $this->editFormTextBeforeContent );
+               if ( $this->isConflict ) {
+                       // In an edit conflict, we turn textbox2 into the user's text,
+                       // and textbox1 into the stored version
+                       $this->textbox2 = $this->textbox1;
 
-               if ( !$this->isCssJsSubpage && $showToolbar && $user->getOption( 'showtoolbar' ) ) {
+                       $content = $this->getCurrentContent();
+                       $this->textbox1 = $this->toEditText( $content );
+
+                       $editConflictHelper = $this->getEditConflictHelper();
+                       $editConflictHelper->setTextboxes( $this->textbox2, $this->textbox1 );
+                       $editConflictHelper->setContentModel( $this->contentModel );
+                       $editConflictHelper->setContentFormat( $this->contentFormat );
+                       $out->addHTML( $editConflictHelper->getEditFormHtmlBeforeContent() );
+               }
+
+               if ( !$this->mTitle->isCssJsSubpage() && $showToolbar && $user->getOption( 'showtoolbar' ) ) {
                        $out->addHTML( self::getEditToolbar( $this->mTitle ) );
                }
 
@@ -2808,12 +2861,8 @@ class EditPage {
                        // 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->textbox2 = $this->textbox1;
-
-                       $content = $this->getCurrentContent();
-                       $this->textbox1 = $this->toEditText( $content );
-
                        $this->showTextbox1();
+                       $out->addHTML( $editConflictHelper->getEditFormHtmlAfterContent() );
                } else {
                        $this->showContentForm();
                }
@@ -2919,8 +2968,6 @@ class EditPage {
        }
 
        protected function showHeader() {
-               global $wgAllowUserCss, $wgAllowUserJs;
-
                $out = $this->context->getOutput();
                $user = $this->context->getUser();
                if ( $this->isConflict ) {
@@ -3031,27 +3078,29 @@ class EditPage {
                                );
                        }
                } else {
-                       if ( $this->isCssJsSubpage ) {
+                       if ( $this->mTitle->isCssJsSubpage() ) {
                                # Check the skin exists
-                               if ( $this->isWrongCaseCssJsPage ) {
+                               if ( $this->isWrongCaseCssJsPage() ) {
                                        $out->wrapWikiMsg(
                                                "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
                                                [ 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
                                        );
                                }
                                if ( $this->getTitle()->isSubpageOf( $user->getUserPage() ) ) {
+                                       $isCssSubpage = $this->mTitle->isCssSubpage();
                                        $out->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
-                                               $this->isCssSubpage ? 'usercssispublic' : 'userjsispublic'
+                                               $isCssSubpage ? 'usercssispublic' : 'userjsispublic'
                                        );
                                        if ( $this->formtype !== 'preview' ) {
-                                               if ( $this->isCssSubpage && $wgAllowUserCss ) {
+                                               $config = $this->context->getConfig();
+                                               if ( $isCssSubpage && $config->get( 'AllowUserCss' ) ) {
                                                        $out->wrapWikiMsg(
                                                                "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
                                                                [ 'usercssyoucanpreview' ]
                                                        );
                                                }
 
-                                               if ( $this->isJsSubpage && $wgAllowUserJs ) {
+                                               if ( $this->mTitle->isJsSubpage() && $config->get( 'AllowUserJs' ) ) {
                                                        $out->wrapWikiMsg(
                                                                "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
                                                                [ 'userjsyoucanpreview' ]
@@ -3142,7 +3191,7 @@ class EditPage {
         */
        function getSummaryInputOOUI( $summary = "", $labelText = null, $inputAttrs = null ) {
                wfDeprecated( __METHOD__, '1.30' );
-               $this->getSummaryInputWidget( $summary, $labelText, $inputAttrs );
+               return $this->getSummaryInputWidget( $summary, $labelText, $inputAttrs );
        }
 
        /**
@@ -3239,7 +3288,7 @@ class EditPage {
 
        protected function showFormBeforeText() {
                $out = $this->context->getOutput();
-               $out->addHTML( Html::hidden( 'wpSection', htmlspecialchars( $this->section ) ) );
+               $out->addHTML( Html::hidden( 'wpSection', $this->section ) );
                $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
                $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
                $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
@@ -3337,11 +3386,17 @@ class EditPage {
        }
 
        protected function showTextbox( $text, $name, $customAttribs = [] ) {
-               $wikitext = $this->addNewLineAtEnd( $text );
-
-               $attribs = $this->buildTextboxAttribs( $name, $customAttribs, $this->context->getUser() );
+               $builder = new TextboxBuilder();
+               $attribs = $builder->buildTextboxAttribs(
+                       $name,
+                       $customAttribs,
+                       $this->context->getUser(),
+                       $this->mTitle
+               );
 
-               $this->context->getOutput()->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
+               $this->context->getOutput()->addHTML(
+                       Html::textarea( $name, $builder->addNewLineAtEnd( $text ), $attribs )
+               );
        }
 
        protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
@@ -3461,7 +3516,7 @@ class EditPage {
                                $newContent = $oldContent->getContentHandler()->makeEmptyContent();
                        }
 
-                       $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
+                       $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
                        $de->setContent( $oldContent, $newContent );
 
                        $difftext = $de->getDiff( $oldtitle, $newtitle );
@@ -3663,34 +3718,12 @@ class EditPage {
                if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
                        $this->incrementConflictStats();
 
-                       $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
-
-                       $content1 = $this->toEditContent( $this->textbox1 );
-                       $content2 = $this->toEditContent( $this->textbox2 );
-
-                       $handler = ContentHandler::getForModelID( $this->contentModel );
-                       $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
-                       $de->setContent( $content2, $content1 );
-                       $de->showDiff(
-                               $this->context->msg( 'yourtext' )->parse(),
-                               $this->context->msg( 'storedversion' )->text()
-                       );
-
-                       $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
-                       $this->showTextbox2();
+                       $this->getEditConflictHelper()->showEditFormTextAfterFooters();
                }
        }
 
        protected function incrementConflictStats() {
-               $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
-               $stats->increment( 'edit.failures.conflict' );
-               // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics
-               if (
-                       $this->mTitle->getNamespace() >= NS_MAIN &&
-                       $this->mTitle->getNamespace() <= NS_CATEGORY_TALK
-               ) {
-                       $stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
-               }
+               $this->getEditConflictHelper()->incrementConflictStats();
        }
 
        /**
@@ -3806,12 +3839,10 @@ class EditPage {
         * @return string
         */
        public function getPreviewText() {
-               global $wgRawHtml;
-               global $wgAllowUserCss, $wgAllowUserJs;
-
                $out = $this->context->getOutput();
+               $config = $this->context->getConfig();
 
-               if ( $wgRawHtml && !$this->mTokenOk ) {
+               if ( $config->get( 'RawHtml' ) && !$this->mTokenOk ) {
                        // Could be an offsite preview attempt. This is very unsafe if
                        // HTML is enabled, as it could be an attack.
                        $parsedNote = '';
@@ -3874,12 +3905,12 @@ class EditPage {
 
                                if ( $content->getModel() == CONTENT_MODEL_CSS ) {
                                        $format = 'css';
-                                       if ( $level === 'user' && !$wgAllowUserCss ) {
+                                       if ( $level === 'user' && !$config->get( 'AllowUserCss' ) ) {
                                                $format = false;
                                        }
                                } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
                                        $format = 'js';
-                                       if ( $level === 'user' && !$wgAllowUserJs ) {
+                                       if ( $level === 'user' && !$config->get( 'AllowUserJs' ) ) {
                                                $format = false;
                                        }
                                } else {
@@ -3954,7 +3985,7 @@ class EditPage {
         * @return ParserOptions
         */
        protected function getPreviewParserOptions() {
-               $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() );
+               $parserOptions = $this->page->makeParserOptions( $this->context );
                $parserOptions->setIsPreview( true );
                $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
                $parserOptions->enableLimitReport();
@@ -4206,6 +4237,7 @@ class EditPage {
         * @return array
         */
        public function getCheckboxes( &$tabindex, $checked ) {
+               wfDeprecated( __METHOD__, '1.30' );
                $checkboxes = [];
                $checkboxesDef = $this->getCheckboxesDefinition( $checked );
 
@@ -4261,6 +4293,7 @@ class EditPage {
         * @return array Associative array of string keys to OOUI\FieldLayout instances
         */
        public function getCheckboxesOOUI( &$tabindex, $checked ) {
+               wfDeprecated( __METHOD__, '1.30' );
                return $this->getCheckboxesWidget( $tabindex, $checked );
        }
 
@@ -4338,6 +4371,7 @@ class EditPage {
        /**
         * Get the message key of the label for the button to save the page
         *
+        * @since 1.30
         * @return string
         */
        protected function getSubmitButtonLabel() {
@@ -4539,20 +4573,19 @@ class EditPage {
         * @since 1.29
         */
        protected function addLongPageWarningHeader() {
-               global $wgMaxArticleSize;
-
                if ( $this->contentLength === false ) {
                        $this->contentLength = strlen( $this->textbox1 );
                }
 
                $out = $this->context->getOutput();
                $lang = $this->context->getLanguage();
-               if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) {
+               $maxArticleSize = $this->context->getConfig()->get( 'MaxArticleSize' );
+               if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
                        $out->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
                                [
                                        'longpageerror',
                                        $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
-                                       $lang->formatNum( $wgMaxArticleSize )
+                                       $lang->formatNum( $maxArticleSize )
                                ]
                        );
                } else {
@@ -4615,9 +4648,8 @@ class EditPage {
         * @since 1.29
         */
        protected function addExplainConflictHeader( OutputPage $out ) {
-               $out->wrapWikiMsg(
-                       "<div class='mw-explainconflict'>\n$1\n</div>",
-                       [ 'explainconflict', $this->context->msg( $this->getSubmitButtonLabel() )->text() ]
+               $out->addHTML(
+                       $this->getEditConflictHelper()->getExplainHeader()
                );
        }
 
@@ -4629,38 +4661,9 @@ class EditPage {
         * @since 1.29
         */
        protected function buildTextboxAttribs( $name, array $customAttribs, User $user ) {
-               $attribs = $customAttribs + [
-                               'accesskey' => ',',
-                               'id' => $name,
-                               'cols' => 80,
-                               'rows' => 25,
-                               // Avoid PHP notices when appending preferences
-                               // (appending allows customAttribs['style'] to still work).
-                               'style' => ''
-                       ];
-
-               // The following classes can be used here:
-               // * mw-editfont-default
-               // * mw-editfont-monospace
-               // * mw-editfont-sans-serif
-               // * mw-editfont-serif
-               $class = 'mw-editfont-' . $user->getOption( 'editfont' );
-
-               if ( isset( $attribs['class'] ) ) {
-                       if ( is_string( $attribs['class'] ) ) {
-                               $attribs['class'] .= ' ' . $class;
-                       } elseif ( is_array( $attribs['class'] ) ) {
-                               $attribs['class'][] = $class;
-                       }
-               } else {
-                       $attribs['class'] = $class;
-               }
-
-               $pageLang = $this->mTitle->getPageLanguage();
-               $attribs['lang'] = $pageLang->getHtmlCode();
-               $attribs['dir'] = $pageLang->getDir();
-
-               return $attribs;
+               return ( new TextboxBuilder() )->buildTextboxAttribs(
+                       $name, $customAttribs, $user, $this->mTitle
+               );
        }
 
        /**
@@ -4669,15 +4672,7 @@ class EditPage {
         * @since 1.29
         */
        protected function addNewLineAtEnd( $wikitext ) {
-               if ( strval( $wikitext ) !== '' ) {
-                       // Ensure there's a newline at the end, otherwise adding lines
-                       // is awkward.
-                       // But don't add a newline if the text is empty, or Firefox in XHTML
-                       // mode will show an extra newline. A bit annoying.
-                       $wikitext .= "\n";
-                       return $wikitext;
-               }
-               return $wikitext;
+               return ( new TextboxBuilder() )->addNewLineAtEnd( $wikitext );
        }
 
        /**
@@ -4702,4 +4697,42 @@ class EditPage {
                // Meanwhile, real browsers get real anchors
                return $wgParser->guessSectionNameFromWikiText( $text );
        }
+
+       /**
+        * Set a factory function to create an EditConflictHelper
+        *
+        * @param callable $factory Factory function
+        * @since 1.31
+        */
+       public function setEditConflictHelperFactory( callable $factory ) {
+               $this->editConflictHelperFactory = $factory;
+               $this->editConflictHelper = null;
+       }
+
+       /**
+        * @return TextConflictHelper
+        */
+       private function getEditConflictHelper() {
+               if ( !$this->editConflictHelper ) {
+                       $this->editConflictHelper = call_user_func(
+                               $this->editConflictHelperFactory,
+                               $this->getSubmitButtonLabel()
+                       );
+               }
+
+               return $this->editConflictHelper;
+       }
+
+       /**
+        * @param string $submitButtonLabel
+        * @return TextConflictHelper
+        */
+       private function newTextConflictHelper( $submitButtonLabel ) {
+               return new TextConflictHelper(
+                       $this->getTitle(),
+                       $this->getContext()->getOutput(),
+                       MediaWikiServices::getInstance()->getStatsdDataFactory(),
+                       $submitButtonLabel
+               );
+       }
 }