Merge "Remove 'Browser default' editfont option"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 3 Oct 2017 18:46:52 +0000 (18:46 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 3 Oct 2017 18:46:52 +0000 (18:46 +0000)
1  2 
includes/EditPage.php
includes/Preferences.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/src/mediawiki.legacy/shared.css

diff --combined includes/EditPage.php
@@@ -40,11 -40,6 +40,11 @@@ use Wikimedia\ScopedCallback
   * headaches, which may be fatal.
   */
  class EditPage {
 +      /**
 +       * Used for Unicode support checks
 +       */
 +      const UNICODE_CHECK = 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ';
 +
        /**
         * Status: Article successfully updated
         */
         */
        const AS_CANNOT_USE_CUSTOM_MODEL = 241;
  
 +      /**
 +       * Status: edit rejected because browser doesn't support Unicode.
 +       */
 +      const AS_UNICODE_NOT_SUPPORTED = 242;
 +
        /**
         * HTML id and name for the beginning of the edit form.
         */
         */
        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 */
        /** @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 */
         */
        private $isOldRev = false;
  
 +      /**
 +       * @var string|null What the user submitted in the 'wpUnicodeCheck' field
 +       */
 +      private $unicodeCheck;
 +
        /**
         * @param Article $article
         */
         */
        public function getContextTitle() {
                if ( is_null( $this->mContextTitle ) ) {
 +                      wfDebugLog(
 +                              'GlobalTitleFail',
 +                              __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.'
 +                      );
                        global $wgTitle;
                        return $wgTitle;
                } else {
         * @deprecated since 1.30
         */
        public function isOouiEnabled() {
 +              wfDeprecated( __METHOD__, '1.30' );
                return true;
        }
  
         * @deprecated since 1.29, call edit directly
         */
        public function submit() {
 +              wfDeprecated( __METHOD__, '1.29' );
                $this->edit();
        }
  
         * the newly-edited page.
         */
        public function edit() {
 -              global $wgOut, $wgRequest, $wgUser;
                // Allow extensions to modify/prevent this form or submission
                if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
                        return;
  
                wfDebug( __METHOD__ . ": enter\n" );
  
 +              $request = $this->context->getRequest();
                // If they used redlink=1 and the page exists, redirect to the main article
 -              if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
 -                      $wgOut->redirect( $this->mTitle->getFullURL() );
 +              if ( $request->getBool( 'redlink' ) && $this->mTitle->exists() ) {
 +                      $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
                        return;
                }
  
 -              $this->importFormData( $wgRequest );
 +              $this->importFormData( $request );
                $this->firsttime = false;
  
                if ( wfReadOnly() && $this->save ) {
                        wfDebug( __METHOD__ . ": User can't edit\n" );
                        // Auto-block user's IP if the account was "hard" blocked
                        if ( !wfReadOnly() ) {
 -                              $user = $wgUser;
 -                              DeferredUpdates::addCallableUpdate( function () use ( $user ) {
 -                                      $user->spreadAnyEditBlock();
 +                              DeferredUpdates::addCallableUpdate( function () {
 +                                      $this->context->getUser()->spreadAnyEditBlock();
                                } );
                        }
                        $this->displayPermissionsError( $permErrors );
  
                $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
         * @return array
         */
        protected function getEditPermissionErrors( $rigor = 'secure' ) {
 -              global $wgUser;
 -
 -              $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser, $rigor );
 +              $user = $this->context->getUser();
 +              $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user, $rigor );
                # Can this title be created?
                if ( !$this->mTitle->exists() ) {
                        $permErrors = array_merge(
                                $permErrors,
                                wfArrayDiff2(
 -                                      $this->mTitle->getUserPermissionsErrors( 'create', $wgUser, $rigor ),
 +                                      $this->mTitle->getUserPermissionsErrors( 'create', $user, $rigor ),
                                        $permErrors
                                )
                        );
         * @throws PermissionsError
         */
        protected function displayPermissionsError( array $permErrors ) {
 -              global $wgRequest, $wgOut;
 -
 -              if ( $wgRequest->getBool( 'redlink' ) ) {
 +              $out = $this->context->getOutput();
 +              if ( $this->context->getRequest()->getBool( 'redlink' ) ) {
                        // The edit page was reached via a red link.
                        // Redirect to the article page and let them click the edit tab if
                        // they really want a permission error.
 -                      $wgOut->redirect( $this->mTitle->getFullURL() );
 +                      $out->redirect( $this->mTitle->getFullURL() );
                        return;
                }
  
  
                $this->displayViewSourcePage(
                        $content,
 -                      $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' )
 +                      $out->formatPermissionsErrorMessage( $permErrors, 'edit' )
                );
        }
  
         * @param string $errorMessage additional wikitext error message to display
         */
        protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
 -              global $wgOut;
 -
 -              Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$wgOut ] );
 +              $out = $this->context->getOutput();
 +              Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$out ] );
  
 -              $wgOut->setRobotPolicy( 'noindex,nofollow' );
 -              $wgOut->setPageTitle( $this->context->msg(
 +              $out->setRobotPolicy( 'noindex,nofollow' );
 +              $out->setPageTitle( $this->context->msg(
                        'viewsource-title',
                        $this->getContextTitle()->getPrefixedText()
                ) );
 -              $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
 -              $wgOut->addHTML( $this->editFormPageTop );
 -              $wgOut->addHTML( $this->editFormTextTop );
 +              $out->addBacklinkSubtitle( $this->getContextTitle() );
 +              $out->addHTML( $this->editFormPageTop );
 +              $out->addHTML( $this->editFormTextTop );
  
                if ( $errorMessage !== '' ) {
 -                      $wgOut->addWikiText( $errorMessage );
 -                      $wgOut->addHTML( "<hr />\n" );
 +                      $out->addWikiText( $errorMessage );
 +                      $out->addHTML( "<hr />\n" );
                }
  
                # If the user made changes, preserve them when showing the markup
                # (This happens when a user is blocked during edit, for instance)
                if ( !$this->firsttime ) {
                        $text = $this->textbox1;
 -                      $wgOut->addWikiMsg( 'viewyourtext' );
 +                      $out->addWikiMsg( 'viewyourtext' );
                } else {
                        try {
                                $text = $this->toEditText( $content );
                                # (e.g. for an old revision with a different model)
                                $text = $content->serialize();
                        }
 -                      $wgOut->addWikiMsg( 'viewsourcetext' );
 +                      $out->addWikiMsg( 'viewsourcetext' );
                }
  
 -              $wgOut->addHTML( $this->editFormTextBeforeContent );
 +              $out->addHTML( $this->editFormTextBeforeContent );
                $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
 -              $wgOut->addHTML( $this->editFormTextAfterContent );
 +              $out->addHTML( $this->editFormTextAfterContent );
  
 -              $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
 +              $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
  
 -              $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
 +              $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
  
 -              $wgOut->addHTML( $this->editFormTextBottom );
 +              $out->addHTML( $this->editFormTextBottom );
                if ( $this->mTitle->exists() ) {
 -                      $wgOut->returnToMain( null, $this->mTitle );
 +                      $out->returnToMain( null, $this->mTitle );
                }
        }
  
         * @return bool
         */
        protected function previewOnOpen() {
 -              global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
 -              if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
 +              $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;
 -              } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
 +              } elseif ( $request->getVal( 'preview' ) == 'no' ) {
                        // Explicit override from request
                        return false;
                } elseif ( $this->section == 'new' ) {
                        // Nothing *to* preview for new sections
                        return false;
 -              } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() )
 -                      && $wgUser->getOption( 'previewonfirst' )
 +              } elseif ( ( $request->getVal( 'preload' ) !== null || $this->mTitle->exists() )
 +                      && $this->context->getUser()->getOption( 'previewonfirst' )
                ) {
                        // 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;
         * @throws ErrorPageError
         */
        public function importFormData( &$request ) {
 -              global $wgContLang, $wgUser;
 -
                # Section edit can come from either the form or a link
                $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
  
                        # These fields need to be checked for encoding.
                        # 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->textbox1 = rtrim( $request->getText( 'wpTextbox1' ) );
                        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
                                }
                        }
  
 -                      # Truncate for whole multibyte characters
 -                      $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
 +                      $this->unicodeCheck = $request->getText( 'wpUnicodeCheck' );
 +
 +                      $this->summary = $request->getText( 'wpSummary' );
  
                        # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
                        # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
                        # currently doing double duty as both edit summary and section title. Right now this
                        # is just to allow API edits to work around this limitation, but this should be
                        # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
 -                      $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
 +                      $this->sectiontitle = $request->getText( 'wpSectionTitle' );
                        $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
  
                        $this->edittime = $request->getVal( 'wpEdittime' );
                        $this->minoredit = $request->getCheck( 'wpMinoredit' );
                        $this->watchthis = $request->getCheck( 'wpWatchthis' );
  
 +                      $user = $this->context->getUser();
                        # Don't force edit summaries when a user is editing their own user or talk page
                        if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
 -                              && $this->mTitle->getText() == $wgUser->getName()
 +                              && $this->mTitle->getText() == $user->getName()
                        ) {
                                $this->allowBlankSummary = true;
                        } else {
                                $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
 -                                      || !$wgUser->getOption( 'forceeditsummary' );
 +                                      || !$user->getOption( 'forceeditsummary' );
                        }
  
                        $this->autoSumm = $request->getText( 'wpAutoSummary' );
         * @return bool If the requested section is valid
         */
        public function initialiseForm() {
 -              global $wgUser;
                $this->edittime = $this->page->getTimestamp();
                $this->editRevId = $this->page->getLatest();
  
                }
                $this->textbox1 = $this->toEditText( $content );
  
 +              $user = $this->context->getUser();
                // activate checkboxes if user wants them to be always active
                # Sort out the "watch" checkbox
 -              if ( $wgUser->getOption( 'watchdefault' ) ) {
 +              if ( $user->getOption( 'watchdefault' ) ) {
                        # Watch all edits
                        $this->watchthis = true;
 -              } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
 +              } elseif ( $user->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
                        # Watch creations
                        $this->watchthis = true;
 -              } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
 +              } elseif ( $user->isWatched( $this->mTitle ) ) {
                        # Already watched
                        $this->watchthis = true;
                }
 -              if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
 +              if ( $user->getOption( 'minordefault' ) && !$this->isNew ) {
                        $this->minoredit = true;
                }
                if ( $this->textbox1 === false ) {
         * @since 1.21
         */
        protected function getContentObject( $def_content = null ) {
 -              global $wgOut, $wgRequest, $wgUser, $wgContLang;
 +              global $wgContLang;
  
                $content = false;
  
 +              $user = $this->context->getUser();
 +              $request = $this->context->getRequest();
                // For message page not locally set, use the i18n message.
                // For other non-existent articles, use preload text if any.
                if ( !$this->mTitle->exists() || $this->section == 'new' ) {
                        }
                        if ( $content === false ) {
                                # If requested, preload some text.
 -                              $preload = $wgRequest->getVal( 'preload',
 +                              $preload = $request->getVal( 'preload',
                                        // Custom preload text for new sections
                                        $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
 -                              $params = $wgRequest->getArray( 'preloadparams', [] );
 +                              $params = $request->getArray( 'preloadparams', [] );
  
                                $content = $this->getPreloadedContent( $preload, $params );
                        }
                } else {
                        if ( $this->section != '' ) {
                                // Get section edit text (returns $def_text for invalid sections)
 -                              $orig = $this->getOriginalContent( $wgUser );
 +                              $orig = $this->getOriginalContent( $user );
                                $content = $orig ? $orig->getSection( $this->section ) : null;
  
                                if ( !$content ) {
                                        $content = $def_content;
                                }
                        } else {
 -                              $undoafter = $wgRequest->getInt( 'undoafter' );
 -                              $undo = $wgRequest->getInt( 'undo' );
 +                              $undoafter = $request->getInt( 'undoafter' );
 +                              $undo = $request->getInt( 'undo' );
  
                                if ( $undo > 0 && $undoafter > 0 ) {
                                        $undorev = Revision::newFromId( $undo );
                                                        $undoMsg = 'failure';
                                                } else {
                                                        $oldContent = $this->page->getContent( Revision::RAW );
 -                                                      $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
 -                                                      $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
 +                                                      $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
 +                                                      $newContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
                                                        if ( $newContent->getModel() !== $oldContent->getModel() ) {
                                                                // The undo may change content
                                                                // model if its reverting the top
                                                $undoMsg = 'norev';
                                        }
  
 +                                      $out = $this->context->getOutput();
                                        // Messages: undo-success, undo-failure, undo-norev, undo-nochange
                                        $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
 -                                      $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
 +                                      $this->editFormPageTop .= $out->parse( "<div class=\"{$class}\">" .
                                                $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
                                }
  
                                if ( $content === false ) {
 -                                      $content = $this->getOriginalContent( $wgUser );
 +                                      $content = $this->getOriginalContent( $user );
                                }
                        }
                }
         * @since 1.21
         */
        protected function getPreloadedContent( $preload, $params = [] ) {
 -              global $wgUser;
 -
                if ( !empty( $this->mPreloadContent ) ) {
                        return $this->mPreloadContent;
                }
                        return $handler->makeEmptyContent();
                }
  
 +              $user = $this->context->getUser();
                $title = Title::newFromText( $preload );
                # Check for existence to avoid getting MediaWiki:Noarticletext
 -              if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
 +              if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
                        // TODO: somehow show a warning to the user!
                        return $handler->makeEmptyContent();
                }
                if ( $page->isRedirect() ) {
                        $title = $page->getRedirectTarget();
                        # Same as before
 -                      if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
 +                      if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
                                // TODO: somehow show a warning to the user!
                                return $handler->makeEmptyContent();
                        }
                        $page = WikiPage::factory( $title );
                }
  
 -              $parserOptions = ParserOptions::newFromUser( $wgUser );
 +              $parserOptions = ParserOptions::newFromUser( $user );
                $content = $page->getContent( Revision::RAW );
  
                if ( !$content ) {
         * @private
         */
        public function tokenOk( &$request ) {
 -              global $wgUser;
                $token = $request->getVal( 'wpEditToken' );
 -              $this->mTokenOk = $wgUser->matchEditToken( $token );
 -              $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
 +              $user = $this->context->getUser();
 +              $this->mTokenOk = $user->matchEditToken( $token );
 +              $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
                return $this->mTokenOk;
        }
  
                        $val = 'restored';
                }
  
 -              $response = RequestContext::getMain()->getRequest()->response();
 +              $response = $this->context->getRequest()->response();
                $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
        }
  
         * @return Status The resulting status object.
         */
        public function attemptSave( &$resultDetails = false ) {
 -              global $wgUser;
 -
                # Allow bots to exempt some edits from bot flagging
 -              $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
 +              $bot = $this->context->getUser()->isAllowed( 'bot' ) && $this->bot;
                $status = $this->internalAttemptSave( $resultDetails, $bot );
  
                Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
         * Log when a page was successfully saved after the edit conflict view
         */
        private function incrementResolvedConflicts() {
 -              global $wgRequest;
 -
 -              if ( $wgRequest->getText( 'mode' ) !== 'conflict' ) {
 +              if ( $this->context->getRequest()->getText( 'mode' ) !== 'conflict' ) {
                        return;
                }
  
         * @return bool False, if output is done, true if rest of the form should be displayed
         */
        private function handleStatus( Status $status, $resultDetails ) {
 -              global $wgUser, $wgOut;
 -
                /**
                 * @todo FIXME: once the interface for internalAttemptSave() is made
                 *   nicer, this should use the message in $status
                        }
                }
  
 +              $out = $this->context->getOutput();
 +
                // "wpExtraQueryRedirect" is a hidden input to modify
                // after save URL and is not used by actual edit form
 -              $request = RequestContext::getMain()->getRequest();
 +              $request = $this->context->getRequest();
                $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
  
                switch ( $status->value ) {
  
                        case self::AS_CANNOT_USE_CUSTOM_MODEL:
                        case self::AS_PARSE_ERROR:
 -                              $wgOut->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
 +                      case self::AS_UNICODE_NOT_SUPPORTED:
 +                              $out->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
                                return true;
  
                        case self::AS_SUCCESS_NEW_ARTICLE:
                                        }
                                }
                                $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
 -                              $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
 +                              $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
                                return false;
  
                        case self::AS_SUCCESS_UPDATE:
                                        }
                                }
  
 -                              $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
 +                              $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
                                return false;
  
                        case self::AS_SPAM_ERROR:
                                return false;
  
                        case self::AS_BLOCKED_PAGE_FOR_USER:
 -                              throw new UserBlockedError( $wgUser->getBlock() );
 +                              throw new UserBlockedError( $this->context->getUser()->getBlock() );
  
                        case self::AS_IMAGE_REDIRECT_ANON:
                        case self::AS_IMAGE_REDIRECT_LOGGED:
  
                // 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...
         * time.
         */
        public function internalAttemptSave( &$result, $bot = false ) {
 -              global $wgUser, $wgRequest, $wgMaxArticleSize;
 -              global $wgContentHandlerUseDB;
 -
                $status = Status::newGood();
 +              $user = $this->context->getUser();
  
                if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
                        wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
                        return $status;
                }
  
 -              $spam = $wgRequest->getText( 'wpAntispam' );
 +              if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
 +                      $status->fatal( 'unicode-support-fail' );
 +                      $status->value = self::AS_UNICODE_NOT_SUPPORTED;
 +                      return $status;
 +              }
 +
 +              $request = $this->context->getRequest();
 +              $spam = $request->getText( 'wpAntispam' );
                if ( $spam !== '' ) {
                        wfDebugLog(
                                'SimpleAntiSpam',
 -                              $wgUser->getName() .
 +                              $user->getName() .
                                ' editing "' .
                                $this->mTitle->getPrefixedText() .
                                '" submitted bogus field "' .
                # Check image redirect
                if ( $this->mTitle->getNamespace() == NS_FILE &&
                        $textbox_content->isRedirect() &&
 -                      !$wgUser->isAllowed( 'upload' )
 +                      !$user->isAllowed( 'upload' )
                ) {
 -                              $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
 +                              $code = $user->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
                                $status->setResult( false, $code );
  
                                return $status;
                }
                if ( $match !== false ) {
                        $result['spam'] = $match;
 -                      $ip = $wgRequest->getIP();
 +                      $ip = $request->getIP();
                        $pdbk = $this->mTitle->getPrefixedDBkey();
                        $match = str_replace( "\n", '', $match );
                        wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
                        return $status;
                }
  
 -              if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
 +              if ( $user->isBlockedFrom( $this->mTitle, false ) ) {
                        // Auto-block user's IP if the account was "hard" blocked
                        if ( !wfReadOnly() ) {
 -                              $wgUser->spreadAnyEditBlock();
 +                              $user->spreadAnyEditBlock();
                        }
                        # Check block state against master, thus 'false'.
                        $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
                }
  
                $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 );
                        return $status;
                }
  
 -              if ( !$wgUser->isAllowed( 'edit' ) ) {
 -                      if ( $wgUser->isAnon() ) {
 +              if ( !$user->isAllowed( 'edit' ) ) {
 +                      if ( $user->isAnon() ) {
                                $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
                                return $status;
                        } else {
  
                $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;
 -                      } elseif ( !$wgUser->isAllowed( 'editcontentmodel' ) ) {
 +                      } elseif ( !$user->isAllowed( 'editcontentmodel' ) ) {
                                $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
                                return $status;
                        }
                        // Make sure the user can edit the page under the new content model too
                        $titleWithNewContentModel = clone $this->mTitle;
                        $titleWithNewContentModel->setContentModel( $this->contentModel );
 -                      if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $wgUser )
 -                              || !$titleWithNewContentModel->userCan( 'edit', $wgUser )
 +                      if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $user )
 +                              || !$titleWithNewContentModel->userCan( 'edit', $user )
                        ) {
                                $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
                                return $status;
  
                if ( $this->changeTags ) {
                        $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
 -                              $this->changeTags, $wgUser );
 +                              $this->changeTags, $user );
                        if ( !$changeTagsStatus->isOK() ) {
                                $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
                                return $changeTagsStatus;
                        $status->value = self::AS_READ_ONLY_PAGE;
                        return $status;
                }
 -              if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 )
 -                      || ( $changingContentModel && $wgUser->pingLimiter( 'editcontentmodel' ) )
 +              if ( $user->pingLimiter() || $user->pingLimiter( 'linkpurge', 0 )
 +                      || ( $changingContentModel && $user->pingLimiter( 'editcontentmodel' ) )
                ) {
                        $status->fatal( 'actionthrottledtext' );
                        $status->value = self::AS_RATE_LIMITED;
  
                if ( $new ) {
                        // Late check for create permission, just in case *PARANOIA*
 -                      if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
 +                      if ( !$this->mTitle->userCan( 'create', $user ) ) {
                                $status->fatal( 'nocreatetext' );
                                $status->value = self::AS_NO_CREATE_PERMISSION;
                                wfDebug( __METHOD__ . ": no create permission\n" );
                                return $status;
                        }
  
 -                      if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
 +                      if ( !$this->runPostMergeFilters( $textbox_content, $status, $user ) ) {
                                return $status;
                        }
  
                        ) {
                                $this->isConflict = true;
                                if ( $this->section == 'new' ) {
 -                                      if ( $this->page->getUserText() == $wgUser->getName() &&
 +                                      if ( $this->page->getUserText() == $user->getName() &&
                                                $this->page->getComment() == $this->newSectionSummary()
                                        ) {
                                                // Probably a duplicate submission of a new comment.
                                } elseif ( $this->section == ''
                                        && Revision::userWasLastToEdit(
                                                DB_MASTER, $this->mTitle->getArticleID(),
 -                                              $wgUser->getId(), $this->edittime
 +                                              $user->getId(), $this->edittime
                                        )
                                ) {
                                        # Suppress edit conflict with self, except for section edits where merging is required.
                                return $status;
                        }
  
 -                      if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
 +                      if ( !$this->runPostMergeFilters( $content, $status, $user ) ) {
                                return $status;
                        }
  
                                        return $status;
                                }
                        } elseif ( !$this->allowBlankSummary
 -                              && !$content->equals( $this->getOriginalContent( $wgUser ) )
 +                              && !$content->equals( $this->getOriginalContent( $user ) )
                                && !$content->isRedirect()
                                && md5( $this->summary ) == $this->autoSumm
                        ) {
  
                // 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;
                        $this->summary,
                        $flags,
                        false,
 -                      $wgUser,
 +                      $user,
                        $content->getDefaultFormat(),
                        $this->changeTags,
                        $this->undidRev
                $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
                if ( $result['nullEdit'] ) {
                        // We don't know if it was a null edit until now, so increment here
 -                      $wgUser->pingLimiter( 'linkpurge' );
 +                      $user->pingLimiter( 'linkpurge' );
                }
                $result['redirect'] = $content->isRedirect();
  
                // If the content model changed, add a log entry
                if ( $changingContentModel ) {
                        $this->addContentModelChangeLogEntry(
 -                              $wgUser,
 +                              $user,
                                $new ? false : $oldContentModel,
                                $this->contentModel,
                                $this->summary
         * Register the change of watch status
         */
        protected function updateWatchlist() {
 -              global $wgUser;
 -
 -              if ( !$wgUser->isLoggedIn() ) {
 +              $user = $this->context->getUser();
 +              if ( !$user->isLoggedIn() ) {
                        return;
                }
  
 -              $user = $wgUser;
                $title = $this->mTitle;
                $watch = $this->watchthis;
                // Do this in its own transaction to reduce contention...
        }
  
        public function setHeaders() {
 -              global $wgOut, $wgUser, $wgAjaxEditStash;
 +              $out = $this->context->getOutput();
  
 -              $wgOut->addModules( 'mediawiki.action.edit' );
 -              $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
 +              $out->addModules( 'mediawiki.action.edit' );
 +              $out->addModuleStyles( 'mediawiki.action.edit.styles' );
  
 -              if ( $wgUser->getOption( 'showtoolbar' ) ) {
 +              $user = $this->context->getUser();
 +              if ( $user->getOption( 'showtoolbar' ) ) {
                        // The addition of default buttons is handled by getEditToolbar() which
                        // has its own dependency on this module. The call here ensures the module
                        // is loaded in time (it has position "top") for other modules to register
                        // buttons (e.g. extensions, gadgets, user scripts).
 -                      $wgOut->addModules( 'mediawiki.toolbar' );
 +                      $out->addModules( 'mediawiki.toolbar' );
                }
  
 -              if ( $wgUser->getOption( 'uselivepreview' ) ) {
 -                      $wgOut->addModules( 'mediawiki.action.edit.preview' );
 +              if ( $user->getOption( 'uselivepreview' ) ) {
 +                      $out->addModules( 'mediawiki.action.edit.preview' );
                }
  
 -              if ( $wgUser->getOption( 'useeditwarning' ) ) {
 -                      $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
 +              if ( $user->getOption( 'useeditwarning' ) ) {
 +                      $out->addModules( 'mediawiki.action.edit.editWarning' );
                }
  
                # Enabled article-related sidebar, toplinks, etc.
 -              $wgOut->setArticleRelated( true );
 +              $out->setArticleRelated( true );
  
                $contextTitle = $this->getContextTitle();
                if ( $this->isConflict ) {
                if ( $displayTitle === false ) {
                        $displayTitle = $contextTitle->getPrefixedText();
                }
 -              $wgOut->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
 +              $out->setPageTitle( $this->context->msg( $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( [
 +              $out->addJsConfigVars( [
                        'wgEditMessage' => $msg,
 -                      'wgAjaxEditStash' => $wgAjaxEditStash,
 +                      'wgAjaxEditStash' => $this->context->getConfig()->get( 'AjaxEditStash' ),
                ] );
        }
  
         * Show all applicable editing introductions
         */
        protected function showIntro() {
 -              global $wgOut, $wgUser;
                if ( $this->suppressIntro ) {
                        return;
                }
  
 +              $out = $this->context->getOutput();
                $namespace = $this->mTitle->getNamespace();
  
                if ( $namespace == NS_MEDIAWIKI ) {
                        # Show a warning if editing an interface message
 -                      $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
 +                      $out->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
                        # If this is a default message (but not css or js),
                        # show a hint that it is translatable on translatewiki.net
                        if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
                        ) {
                                $defaultMessageText = $this->mTitle->getDefaultMessageText();
                                if ( $defaultMessageText !== false ) {
 -                                      $wgOut->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
 +                                      $out->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
                                                'translateinterface' );
                                }
                        }
                                # there must be a description url to show a hint to shared repo
                                if ( $descUrl ) {
                                        if ( !$this->mTitle->exists() ) {
 -                                              $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
 +                                              $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
                                                                        'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
                                                ] );
                                        } else {
 -                                              $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
 +                                              $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
                                                                        'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
                                                ] );
                                        }
                        $ip = User::isIP( $username );
                        $block = Block::newFromTarget( $user, $user );
                        if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
 -                              $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
 +                              $out->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
                                        [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
                        } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
                                # Show log extract if the user is currently blocked
                                LogEventsList::showLogExtract(
 -                                      $wgOut,
 +                                      $out,
                                        'block',
                                        MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
                                        '',
                        $helpLink = wfExpandUrl( Skin::makeInternalOrExternalUrl(
                                $this->context->msg( 'helppage' )->inContentLanguage()->text()
                        ) );
 -                      if ( $wgUser->isLoggedIn() ) {
 -                              $wgOut->wrapWikiMsg(
 +                      if ( $this->context->getUser()->isLoggedIn() ) {
 +                              $out->wrapWikiMsg(
                                        // Suppress the external link icon, consider the help url an internal one
                                        "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
                                        [
                                        ]
                                );
                        } else {
 -                              $wgOut->wrapWikiMsg(
 +                              $out->wrapWikiMsg(
                                        // Suppress the external link icon, consider the help url an internal one
                                        "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
                                        [
                if ( !$this->mTitle->exists() ) {
                        $dbr = wfGetDB( DB_REPLICA );
  
 -                      LogEventsList::showLogExtract( $wgOut, [ 'delete', 'move' ], $this->mTitle,
 +                      LogEventsList::showLogExtract( $out, [ 'delete', 'move' ], $this->mTitle,
                                '',
                                [
                                        'lim' => 10,
                if ( $this->editintro ) {
                        $title = Title::newFromText( $this->editintro );
                        if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
 -                              global $wgOut;
                                // Added using template syntax, to take <noinclude>'s into account.
 -                              $wgOut->addWikiTextTitleTidy(
 +                              $this->context->getOutput()->addWikiTextTitleTidy(
                                        '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
                                        $this->mTitle
                                );
        }
  
        /**
 -       * Send the edit form and related headers to $wgOut
 +       * Send the edit form and related headers to OutputPage
         * @param callable|null $formCallback That takes an OutputPage parameter; will be called
         *     during form output near the top, for captchas and the like.
         *
         * use the EditPage::showEditForm:fields hook instead.
         */
        public function showEditForm( $formCallback = null ) {
 -              global $wgOut, $wgUser;
 -
                # need to parse the preview early so that we know which templates are used,
                # otherwise users with "show preview after edit box" will get a blank list
                # we parse this near the beginning so that setHeaders can do the title
                        $previewOutput = $this->getPreviewText();
                }
  
 +              $out = $this->context->getOutput();
 +
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
 -              Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$wgOut ] );
 +              Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$out ] );
  
                $this->setHeaders();
  
                        // 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' );
 +                      $out->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
                        return;
                }
  
                $this->showHeader();
  
 -              $wgOut->addHTML( $this->editFormPageTop );
 +              $out->addHTML( $this->editFormPageTop );
  
 -              if ( $wgUser->getOption( 'previewontop' ) ) {
 +              $user = $this->context->getUser();
 +              if ( $user->getOption( 'previewontop' ) ) {
                        $this->displayPreviewArea( $previewOutput, true );
                }
  
 -              $wgOut->addHTML( $this->editFormTextTop );
 +              $out->addHTML( $this->editFormTextTop );
  
                $showToolbar = true;
                if ( $this->wasDeletedSinceLastEdit() ) {
                                // Add an confirmation checkbox and explanation.
                                $showToolbar = false;
                        } else {
 -                              $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
 +                              $out->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
                                        'deletedwhileediting' );
                        }
                }
  
                // @todo add EditForm plugin interface and use it here!
                //       search for textarea1 and textarea2, and allow EditForm to override all uses.
 -              $wgOut->addHTML( Html::openElement(
 +              $out->addHTML( Html::openElement(
                        'form',
                        [
 -                              // Keep mw-editform-ooui class for backwards-compatibility temporarily
 -                              'class' => 'mw-editform mw-editform-ooui',
 +                              'class' => 'mw-editform',
                                'id' => self::EDITFORM_ID,
                                'name' => self::EDITFORM_ID,
                                'method' => 'post',
  
                if ( is_callable( $formCallback ) ) {
                        wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
 -                      call_user_func_array( $formCallback, [ &$wgOut ] );
 +                      call_user_func_array( $formCallback, [ &$out ] );
                }
  
 +              // Add a check for Unicode support
 +              $out->addHTML( Html::hidden( 'wpUnicodeCheck', self::UNICODE_CHECK ) );
 +
                // Add an empty field to trip up spambots
 -              $wgOut->addHTML(
 +              $out->addHTML(
                        Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
                        . Html::rawElement(
                                'label',
  
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
 -              Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$wgOut ] );
 +              Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$out ] );
  
                // Put these up at the top to ensure they aren't lost on early form submission
                $this->showFormBeforeText();
  
                if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
                        $username = $this->lastDelete->user_name;
 -                      $comment = $this->lastDelete->log_comment;
 +                      $comment = CommentStore::newKey( 'log_comment' )->getComment( $this->lastDelete )->text;
  
                        // It is better to not parse the comment at all than to have templates expanded in the middle
                        // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
                        $key = $comment === ''
                                ? 'confirmrecreate-noreason'
                                : 'confirmrecreate';
 -                      $wgOut->addHTML(
 +                      $out->addHTML(
                                '<div class="mw-confirm-recreate">' .
                                        $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
                                Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
  
                # When the summary is hidden, also hide them on preview/show changes
                if ( $this->nosummary ) {
 -                      $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
 +                      $out->addHTML( Html::hidden( 'nosummary', true ) );
                }
  
                # If a blank edit summary was previously provided, and the appropriate
                # 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' && $this->nosummary ) ) {
 -                      $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
 +                      $out->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
                }
  
                if ( $this->undidRev ) {
 -                      $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
 +                      $out->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
                }
  
                if ( $this->selfRedirect ) {
 -                      $wgOut->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
 +                      $out->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
                }
  
                if ( $this->hasPresetSummary ) {
                }
  
                $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
 -              $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
 +              $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
  
 -              $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
 -              $wgOut->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
 +              $out->addHTML( Html::hidden( 'oldid', $this->oldid ) );
 +              $out->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
  
 -              $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
 -              $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
 +              $out->addHTML( Html::hidden( 'format', $this->contentFormat ) );
 +              $out->addHTML( Html::hidden( 'model', $this->contentModel ) );
  
 -              $wgOut->enableOOUI();
 +              $out->enableOOUI();
  
                if ( $this->section == 'new' ) {
                        $this->showSummaryInput( true, $this->summary );
 -                      $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
 +                      $out->addHTML( $this->getSummaryPreview( true, $this->summary ) );
                }
  
 -              $wgOut->addHTML( $this->editFormTextBeforeContent );
 +              $out->addHTML( $this->editFormTextBeforeContent );
  
 -              if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
 -                      $wgOut->addHTML( self::getEditToolbar( $this->mTitle ) );
 +              if ( !$this->mTitle->isCssJsSubpage() && $showToolbar && $user->getOption( 'showtoolbar' ) ) {
 +                      $out->addHTML( self::getEditToolbar( $this->mTitle ) );
                }
  
                if ( $this->blankArticle ) {
 -                      $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
 +                      $out->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
                }
  
                if ( $this->isConflict ) {
                        $this->showContentForm();
                }
  
 -              $wgOut->addHTML( $this->editFormTextAfterContent );
 +              $out->addHTML( $this->editFormTextAfterContent );
  
                $this->showStandardInputs();
  
  
                $this->showEditTools();
  
 -              $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
 +              $out->addHTML( $this->editFormTextAfterTools . "\n" );
  
 -              $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
 +              $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
  
 -              $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
 +              $out->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
                        Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
  
 -              $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
 +              $out->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
                        self::getPreviewLimitReport( $this->mParserOutput ) ) );
  
 -              $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
 +              $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
  
                if ( $this->isConflict ) {
                        try {
                                        $this->contentFormat,
                                        $ex->getMessage()
                                );
 -                              $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
 +                              $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
                        }
                }
  
                } else {
                        $mode = 'text';
                }
 -              $wgOut->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
 +              $out->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 ) );
 -              $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
 +              $out->addHTML( Html::hidden( 'wpUltimateParam', true ) );
 +              $out->addHTML( $this->editFormTextBottom . "\n</form>\n" );
  
 -              if ( !$wgUser->getOption( 'previewontop' ) ) {
 +              if ( !$user->getOption( 'previewontop' ) ) {
                        $this->displayPreviewArea( $previewOutput, false );
                }
        }
        }
  
        protected function showHeader() {
 -              global $wgOut, $wgUser;
 -              global $wgAllowUserCss, $wgAllowUserJs;
 -
 +              $out = $this->context->getOutput();
 +              $user = $this->context->getUser();
                if ( $this->isConflict ) {
 -                      $this->addExplainConflictHeader( $wgOut );
 +                      $this->addExplainConflictHeader( $out );
                        $this->editRevId = $this->page->getLatest();
                } else {
                        if ( $this->section != '' && $this->section != 'new' ) {
                                }
                        }
  
 -                      $buttonLabel = $this->context->msg( $this->getSaveButtonLabel() )->text();
 +                      $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
  
                        if ( $this->missingComment ) {
 -                              $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
 +                              $out->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
                        }
  
                        if ( $this->missingSummary && $this->section != 'new' ) {
 -                              $wgOut->wrapWikiMsg(
 +                              $out->wrapWikiMsg(
                                        "<div id='mw-missingsummary'>\n$1\n</div>",
                                        [ 'missingsummary', $buttonLabel ]
                                );
                        }
  
                        if ( $this->missingSummary && $this->section == 'new' ) {
 -                              $wgOut->wrapWikiMsg(
 +                              $out->wrapWikiMsg(
                                        "<div id='mw-missingcommentheader'>\n$1\n</div>",
                                        [ 'missingcommentheader', $buttonLabel ]
                                );
                        }
  
                        if ( $this->blankArticle ) {
 -                              $wgOut->wrapWikiMsg(
 +                              $out->wrapWikiMsg(
                                        "<div id='mw-blankarticle'>\n$1\n</div>",
                                        [ 'blankarticle', $buttonLabel ]
                                );
                        }
  
                        if ( $this->selfRedirect ) {
 -                              $wgOut->wrapWikiMsg(
 +                              $out->wrapWikiMsg(
                                        "<div id='mw-selfredirect'>\n$1\n</div>",
                                        [ 'selfredirect', $buttonLabel ]
                                );
                        }
  
                        if ( $this->hookError !== '' ) {
 -                              $wgOut->addWikiText( $this->hookError );
 -                      }
 -
 -                      if ( !$this->checkUnicodeCompliantBrowser() ) {
 -                              $wgOut->addWikiMsg( 'nonunicodebrowser' );
 +                              $out->addWikiText( $this->hookError );
                        }
  
                        if ( $this->section != 'new' ) {
                                if ( $revision ) {
                                        // Let sysop know that this will make private content public if saved
  
 -                                      if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
 -                                              $wgOut->wrapWikiMsg(
 +                                      if ( !$revision->userCan( Revision::DELETED_TEXT, $user ) ) {
 +                                              $out->wrapWikiMsg(
                                                        "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
                                                        'rev-deleted-text-permission'
                                                );
                                        } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
 -                                              $wgOut->wrapWikiMsg(
 +                                              $out->wrapWikiMsg(
                                                        "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
                                                        'rev-deleted-text-view'
                                                );
  
                                        if ( !$revision->isCurrent() ) {
                                                $this->mArticle->setOldSubtitle( $revision->getId() );
 -                                              $wgOut->addWikiMsg( 'editingold' );
 +                                              $out->addWikiMsg( 'editingold' );
                                                $this->isOldRev = true;
                                        }
                                } elseif ( $this->mTitle->exists() ) {
                                        // Something went wrong
  
 -                                      $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
 +                                      $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
                                                [ 'missing-revision', $this->oldid ] );
                                }
                        }
                }
  
                if ( wfReadOnly() ) {
 -                      $wgOut->wrapWikiMsg(
 +                      $out->wrapWikiMsg(
                                "<div id=\"mw-read-only-warning\">\n$1\n</div>",
                                [ 'readonlywarning', wfReadOnlyReason() ]
                        );
 -              } elseif ( $wgUser->isAnon() ) {
 +              } elseif ( $user->isAnon() ) {
                        if ( $this->formtype != 'preview' ) {
 -                              $wgOut->wrapWikiMsg(
 +                              $out->wrapWikiMsg(
                                        "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
                                        [ 'anoneditwarning',
                                                // Log-in link
                                        ]
                                );
                        } else {
 -                              $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
 +                              $out->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
                                        'anonpreviewwarning'
                                );
                        }
                } else {
 -                      if ( $this->isCssJsSubpage ) {
 +                      if ( $this->mTitle->isCssJsSubpage() ) {
                                # Check the skin exists
 -                              if ( $this->isWrongCaseCssJsPage ) {
 -                                      $wgOut->wrapWikiMsg(
 +                              if ( $this->isWrongCaseCssJsPage() ) {
 +                                      $out->wrapWikiMsg(
                                                "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
                                                [ 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
                                        );
                                }
 -                              if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
 -                                      $wgOut->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
 -                                              $this->isCssSubpage ? 'usercssispublic' : 'userjsispublic'
 +                              if ( $this->getTitle()->isSubpageOf( $user->getUserPage() ) ) {
 +                                      $isCssSubpage = $this->mTitle->isCssSubpage();
 +                                      $out->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
 +                                              $isCssSubpage ? 'usercssispublic' : 'userjsispublic'
                                        );
                                        if ( $this->formtype !== 'preview' ) {
 -                                              if ( $this->isCssSubpage && $wgAllowUserCss ) {
 -                                                      $wgOut->wrapWikiMsg(
 +                                              $config = $this->context->getConfig();
 +                                              if ( $isCssSubpage && $config->get( 'AllowUserCss' ) ) {
 +                                                      $out->wrapWikiMsg(
                                                                "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
                                                                [ 'usercssyoucanpreview' ]
                                                        );
                                                }
  
 -                                              if ( $this->isJsSubpage && $wgAllowUserJs ) {
 -                                                      $wgOut->wrapWikiMsg(
 +                                              if ( $this->mTitle->isJsSubpage() && $config->get( 'AllowUserJs' ) ) {
 +                                                      $out->wrapWikiMsg(
                                                                "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
                                                                [ 'userjsyoucanpreview' ]
                                                        );
                ];
        }
  
 +      /**
 +       * 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 [ 'id' => false ] to $userInputAttrs.
 +       *
 +       * @deprecated since 1.30 Use getSummaryInputWidget() instead
 +       * @param string $summary The value of the summary input
 +       * @param string $labelText The html to place inside the label
 +       * @param array $inputAttrs Array of attrs to use on the input
 +       * @param array $spanLabelAttrs Array of attrs to use on the span inside the label
 +       * @return array An array in the format [ $label, $input ]
 +       */
 +      public function getSummaryInput( $summary = "", $labelText = null,
 +              $inputAttrs = null, $spanLabelAttrs = null
 +      ) {
 +              wfDeprecated( __METHOD__, '1.30' );
 +              $inputAttrs = $this->getSummaryInputAttributes( $inputAttrs );
 +              $inputAttrs += Linker::tooltipAndAccesskeyAttribs( 'summary' );
 +
 +              $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
 +                      'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
 +                      'id' => "wpSummaryLabel"
 +              ];
 +
 +              $label = null;
 +              if ( $labelText ) {
 +                      $label = Xml::tags(
 +                              'label',
 +                              $inputAttrs['id'] ? [ 'for' => $inputAttrs['id'] ] : null,
 +                              $labelText
 +                      );
 +                      $label = Xml::tags( 'span', $spanLabelAttrs, $label );
 +              }
 +
 +              $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
 +
 +              return [ $label, $input ];
 +      }
 +
        /**
         * Builds a standard summary input with a label.
         *
 +       * @deprecated since 1.30 Use getSummaryInputWidget() instead
         * @param string $summary The value of the summary input
         * @param string $labelText The html to place inside the label
         * @param array $inputAttrs Array of attrs to use on the input
         * @return OOUI\FieldLayout OOUI FieldLayout with Label and Input
         */
        function getSummaryInputOOUI( $summary = "", $labelText = null, $inputAttrs = null ) {
 +              wfDeprecated( __METHOD__, '1.30' );
 +              $this->getSummaryInputWidget( $summary, $labelText, $inputAttrs );
 +      }
 +
 +      /**
 +       * Builds a standard summary input with a label.
 +       *
 +       * @param string $summary The value of the summary input
 +       * @param string $labelText The html to place inside the label
 +       * @param array $inputAttrs Array of attrs to use on the input
 +       *
 +       * @return OOUI\FieldLayout OOUI FieldLayout with Label and Input
 +       */
 +      function getSummaryInputWidget( $summary = "", $labelText = null, $inputAttrs = null ) {
                $inputAttrs = OOUI\Element::configFromHtmlAttributes(
                        $this->getSummaryInputAttributes( $inputAttrs )
                );
         * @param string $summary The text of the summary to display
         */
        protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
 -              global $wgOut;
 -
                # Add a class if 'missingsummary' is triggered to allow styling of the summary line
                $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
                if ( $isSubjectPreview ) {
                }
  
                $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
 -              $wgOut->addHTML( $this->getSummaryInputOOUI(
 +              $this->context->getOutput()->addHTML( $this->getSummaryInputWidget(
                                $summary,
                                $labelText,
                                [ 'class' => $summaryClass ]
        }
  
        protected function showFormBeforeText() {
 -              global $wgOut;
 -
 -              $wgOut->addHTML( Html::hidden( 'wpSection', htmlspecialchars( $this->section ) ) );
 -              $wgOut->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
 -              $wgOut->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
 -              $wgOut->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
 -              $wgOut->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop ) );
 -
 -              if ( !$this->checkUnicodeCompliantBrowser() ) {
 -                      $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
 -              }
 +              $out = $this->context->getOutput();
 +              $out->addHTML( Html::hidden( 'wpSection', htmlspecialchars( $this->section ) ) );
 +              $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
 +              $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
 +              $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
 +              $out->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
        }
  
        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
                 * include the constant suffix to prevent editing from
                 * broken text-mangling proxies.
                 */
 -              $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
 +              $this->context->getOutput()->addHTML(
 +                      "\n" .
 +                      Html::hidden( "wpEditToken", $this->context->getUser()->getEditToken() ) .
 +                      "\n"
 +              );
        }
  
        /**
        }
  
        protected function showTextbox( $text, $name, $customAttribs = [] ) {
 -              global $wgOut, $wgUser;
 +              $wikitext = $this->addNewLineAtEnd( $text );
  
 -              $wikitext = $this->safeUnicodeOutput( $text );
 -              $wikitext = $this->addNewLineAtEnd( $wikitext );
 +              $attribs = $this->buildTextboxAttribs( $name, $customAttribs, $this->context->getUser() );
  
 -              $attribs = $this->buildTextboxAttribs( $name, $customAttribs, $wgUser );
 -
 -              $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
 +              $this->context->getOutput()->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
        }
  
        protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
 -              global $wgOut;
                $classes = [];
                if ( $isOnTop ) {
                        $classes[] = 'ontop';
                        $attribs['style'] = 'display: none;';
                }
  
 -              $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
 +              $out = $this->context->getOutput();
 +              $out->addHTML( Xml::openElement( 'div', $attribs ) );
  
                if ( $this->formtype == 'preview' ) {
                        $this->showPreview( $previewOutput );
                        $pageViewLang = $this->mTitle->getPageViewLanguage();
                        $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
                                'class' => 'mw-content-' . $pageViewLang->getDir() ];
 -                      $wgOut->addHTML( Html::rawElement( 'div', $attribs ) );
 +                      $out->addHTML( Html::rawElement( 'div', $attribs ) );
                }
  
 -              $wgOut->addHTML( '</div>' );
 +              $out->addHTML( '</div>' );
  
                if ( $this->formtype == 'diff' ) {
                        try {
                                        $this->contentFormat,
                                        $ex->getMessage()
                                );
 -                              $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
 +                              $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
                        }
                }
        }
  
        /**
 -       * Append preview output to $wgOut.
 +       * Append preview output to OutputPage.
         * Includes category rendering if this is a category page.
         *
         * @param string $text The HTML to be output for the preview.
         */
        protected function showPreview( $text ) {
 -              global $wgOut;
                if ( $this->mArticle instanceof CategoryPage ) {
                        $this->mArticle->openShowCategory();
                }
                # This hook seems slightly odd here, but makes things more
                # consistent for extensions.
 -              Hooks::run( 'OutputPageBeforeHTML', [ &$wgOut, &$text ] );
 -              $wgOut->addHTML( $text );
 +              $out = $this->context->getOutput();
 +              Hooks::run( 'OutputPageBeforeHTML', [ &$out, &$text ] );
 +              $out->addHTML( $text );
                if ( $this->mArticle instanceof CategoryPage ) {
                        $this->mArticle->closeShowCategory();
                }
         * save and then make a comparison.
         */
        public function showDiff() {
 -              global $wgUser, $wgContLang, $wgOut;
 +              global $wgContLang;
  
                $oldtitlemsg = 'currentrev';
                # if message does not exist, show diff against the preloaded default
                if ( $newContent ) {
                        Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
  
 -                      $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
 -                      $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
 +                      $user = $this->context->getUser();
 +                      $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
 +                      $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
                }
  
                if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
                                $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 );
                        $difftext = '';
                }
  
 -              $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
 +              $this->context->getOutput()->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
        }
  
        /**
        protected function showHeaderCopyrightWarning() {
                $msg = 'editpage-head-copy-warn';
                if ( !$this->context->msg( $msg )->isDisabled() ) {
 -                      global $wgOut;
 -                      $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
 +                      $this->context->getOutput()->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
                                'editpage-head-copy-warn' );
                }
        }
                $msg = 'editpage-tos-summary';
                Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
                if ( !$this->context->msg( $msg )->isDisabled() ) {
 -                      global $wgOut;
 -                      $wgOut->addHTML( '<div class="mw-tos-summary">' );
 -                      $wgOut->addWikiMsg( $msg );
 -                      $wgOut->addHTML( '</div>' );
 +                      $out = $this->context->getOutput();
 +                      $out->addHTML( '<div class="mw-tos-summary">' );
 +                      $out->addWikiMsg( $msg );
 +                      $out->addHTML( '</div>' );
                }
        }
  
         * characters not present on most keyboards for copying/pasting.
         */
        protected function showEditTools() {
 -              global $wgOut;
 -              $wgOut->addHTML( '<div class="mw-editTools">' .
 +              $this->context->getOutput()->addHTML( '<div class="mw-editTools">' .
                        $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
                        '</div>' );
        }
        }
  
        protected function showStandardInputs( &$tabindex = 2 ) {
 -              global $wgOut;
 -              $wgOut->addHTML( "<div class='editOptions'>\n" );
 +              $out = $this->context->getOutput();
 +              $out->addHTML( "<div class='editOptions'>\n" );
  
                if ( $this->section != 'new' ) {
                        $this->showSummaryInput( false, $this->summary );
 -                      $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
 +                      $out->addHTML( $this->getSummaryPreview( false, $this->summary ) );
                }
  
 -              $checkboxes = $this->getCheckboxesOOUI(
 +              $checkboxes = $this->getCheckboxesWidget(
                        $tabindex,
                        [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
                );
                $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
  
 -              $wgOut->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
 +              $out->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
  
                // Show copyright warning.
 -              $wgOut->addWikiText( $this->getCopywarn() );
 -              $wgOut->addHTML( $this->editFormTextAfterWarn );
 +              $out->addWikiText( $this->getCopywarn() );
 +              $out->addHTML( $this->editFormTextAfterWarn );
  
 -              $wgOut->addHTML( "<div class='editButtons'>\n" );
 -              $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
 +              $out->addHTML( "<div class='editButtons'>\n" );
 +              $out->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
  
                $cancel = $this->getCancelLink();
                if ( $cancel !== '' ) {
                        $this->context->msg( 'word-separator' )->escaped() .
                        $this->context->msg( 'newwindow' )->parse();
  
 -              $wgOut->addHTML( "      <span class='cancelLink'>{$cancel}</span>\n" );
 -              $wgOut->addHTML( "      <span class='editHelp'>{$edithelp}</span>\n" );
 -              $wgOut->addHTML( "</div><!-- editButtons -->\n" );
 +              $out->addHTML( "        <span class='cancelLink'>{$cancel}</span>\n" );
 +              $out->addHTML( "        <span class='editHelp'>{$edithelp}</span>\n" );
 +              $out->addHTML( "</div><!-- editButtons -->\n" );
  
 -              Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $wgOut, &$tabindex ] );
 +              Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $out, &$tabindex ] );
  
 -              $wgOut->addHTML( "</div><!-- editOptions -->\n" );
 +              $out->addHTML( "</div><!-- editOptions -->\n" );
        }
  
        /**
         * If you want to use another entry point to this function, be careful.
         */
        protected function showConflict() {
 -              global $wgOut;
 -
 +              $out = $this->context->getOutput();
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
 -              if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$wgOut ] ) ) {
 +              if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
                        $this->incrementConflictStats();
  
 -                      $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 +                      $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 = $handler->createDifferenceEngine( $this->context );
                        $de->setContent( $content2, $content1 );
                        $de->showDiff(
                                $this->context->msg( 'yourtext' )->parse(),
                                $this->context->msg( 'storedversion' )->text()
                        );
  
 -                      $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
 +                      $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
                        $this->showTextbox2();
                }
        }
         */
        protected function getLastDelete() {
                $dbr = wfGetDB( DB_REPLICA );
 +              $commentQuery = CommentStore::newKey( 'log_comment' )->getJoin();
                $data = $dbr->selectRow(
 -                      [ 'logging', 'user' ],
 +                      [ 'logging', 'user' ] + $commentQuery['tables'],
                        [
                                'log_type',
                                'log_action',
                                'log_user',
                                'log_namespace',
                                'log_title',
 -                              'log_comment',
                                'log_params',
                                'log_deleted',
                                'user_name'
 -                      ], [
 +                      ] + $commentQuery['fields'], [
                                'log_namespace' => $this->mTitle->getNamespace(),
                                'log_title' => $this->mTitle->getDBkey(),
                                'log_type' => 'delete',
                                'user_id=log_user'
                        ],
                        __METHOD__,
 -                      [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ]
 +                      [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
 +                      [
 +                              'user' => [ 'JOIN', 'user_id=log_user' ],
 +                      ] + $commentQuery['joins']
                );
                // Quick paranoid permission checks...
                if ( is_object( $data ) ) {
                        }
  
                        if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
 -                              $data->log_comment = $this->context->msg( 'rev-deleted-comment' )->escaped();
 +                              $data->log_comment_text = $this->context->msg( 'rev-deleted-comment' )->escaped();
 +                              $data->log_comment_data = null;
                        }
                }
  
         * @return string
         */
        public function getPreviewText() {
 -              global $wgOut, $wgRawHtml, $wgLang;
 -              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 = '';
                                // Do not put big scary notice, if previewing the empty
                                // string, which happens when you initially edit
                                // a category page, due to automatic preview-on-open.
 -                              $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
 +                              $parsedNote = $out->parse( "<div class='previewnote'>" .
                                        $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
                                        true, /* interface */true );
                        }
  
                        # provide a anchor link to the editform
                        $continueEditing = '<span class="mw-continue-editing">' .
 -                              '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
 +                              '[[#' . self::EDITFORM_ID . '|' .
 +                              $this->context->getLanguage()->getArrow() . ' ' .
                                $this->context->msg( 'continue-editing' )->text() . ']]</span>';
                        if ( $this->mTriedSave && !$this->mTokenOk ) {
                                if ( $this->mTokenOkExceptSuffix ) {
  
                                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 {
                        $parserOutput = $parserResult['parserOutput'];
                        $previewHTML = $parserResult['html'];
                        $this->mParserOutput = $parserOutput;
 -                      $wgOut->addParserOutputMetadata( $parserOutput );
 +                      $out->addParserOutputMetadata( $parserOutput );
  
                        if ( count( $parserOutput->getWarnings() ) ) {
                                $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
  
                $previewhead = "<div class='previewnote'>\n" .
                        '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
 -                      $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
 +                      $out->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
  
                $pageViewLang = $this->mTitle->getPageViewLanguage();
                $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
         * @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();
         *   - html: The HTML to be displayed
         */
        protected function doPreviewParse( Content $content ) {
 -              global $wgUser;
 +              $user = $this->context->getUser();
                $parserOptions = $this->getPreviewParserOptions();
 -              $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
 +              $pstContent = $content->preSaveTransform( $this->mTitle, $user, $parserOptions );
                $scopedCallback = $parserOptions->setupFakeRevision(
 -                      $this->mTitle, $pstContent, $wgUser );
 +                      $this->mTitle, $pstContent, $user );
                $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
                ScopedCallback::consume( $scopedCallback );
                $parserOutput->setEditSectionTokens( false ); // no section edit links
         *   where bool indicates the checked status of the checkbox
         * @return array
         */
 -      protected function getCheckboxesDefinition( $checked ) {
 -              global $wgUser;
 +      public function getCheckboxesDefinition( $checked ) {
                $checkboxes = [];
  
 +              $user = $this->context->getUser();
                // don't show the minor edit checkbox if it's a new page or section
 -              if ( !$this->isNew && $wgUser->isAllowed( 'minoredit' ) ) {
 +              if ( !$this->isNew && $user->isAllowed( 'minoredit' ) ) {
                        $checkboxes['wpMinoredit'] = [
                                'id' => 'wpMinoredit',
                                'label-message' => 'minoredit',
                        ];
                }
  
 -              if ( $wgUser->isLoggedIn() ) {
 +              if ( $user->isLoggedIn() ) {
                        $checkboxes['wpWatchthis'] = [
                                'id' => 'wpWatchthis',
                                'label-message' => 'watchthis',
         * Returns an array of html code of the following checkboxes old style:
         * minor and watch
         *
 +       * @deprecated since 1.30 Use getCheckboxesWidget() or getCheckboxesDefinition() instead
         * @param int &$tabindex Current tabindex
         * @param array $checked See getCheckboxesDefinition()
         * @return array
         */
        public function getCheckboxes( &$tabindex, $checked ) {
 -              global $wgUseMediaWikiUIEverywhere;
 -
 +              wfDeprecated( __METHOD__, '1.30' );
                $checkboxes = [];
                $checkboxesDef = $this->getCheckboxesDefinition( $checked );
  
                                '&#160;' .
                                Xml::tags( 'label', $labelAttribs, $label );
  
 -                      if ( $wgUseMediaWikiUIEverywhere ) {
 -                              $checkboxHtml = Html::rawElement( 'div', [ 'class' => 'mw-ui-checkbox' ], $checkboxHtml );
 -                      }
 -
                        $checkboxes[ $legacyName ] = $checkboxHtml;
                }
  
        }
  
        /**
 -       * Returns an array of html code of the following checkboxes:
 -       * minor and watch
 +       * Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and
 +       * any other added by extensions.
         *
 +       * @deprecated since 1.30 Use getCheckboxesWidget() or getCheckboxesDefinition() instead
         * @param int &$tabindex Current tabindex
         * @param array $checked Array of checkbox => bool, where bool indicates the checked
         *                 status of the checkbox
         *
 -       * @return array
 +       * @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 );
 +      }
 +
 +      /**
 +       * Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and
 +       * any other added by extensions.
 +       *
 +       * @param int &$tabindex Current tabindex
 +       * @param array $checked Array of checkbox => bool, where bool indicates the checked
 +       *                 status of the checkbox
 +       *
 +       * @return array Associative array of string keys to OOUI\FieldLayout instances
 +       */
 +      public function getCheckboxesWidget( &$tabindex, $checked ) {
                $checkboxes = [];
                $checkboxesDef = $this->getCheckboxesDefinition( $checked );
  
 -              $origTabindex = $tabindex;
 -
                foreach ( $checkboxesDef as $name => $options ) {
                        $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
  
                        if ( isset( $options['title-message'] ) ) {
                                $title = $this->context->msg( $options['title-message'] )->text();
                        }
 -                      if ( isset( $options['label-id'] ) ) {
 -                              $labelAttribs['id'] = $options['label-id'];
 -                      }
  
                        $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
                                new OOUI\CheckboxInputWidget( [
                // Backwards-compatibility hack to run the EditPageBeforeEditChecks hook. It's important,
                // people have used it for the weirdest things completely unrelated to checkboxes...
                // And if we're gonna run it, might as well allow its legacy checkboxes to be shown.
 -              $legacyCheckboxes = $this->getCheckboxes( $origTabindex, $checked );
 +              $legacyCheckboxes = [];
 +              if ( !$this->isNew ) {
 +                      $legacyCheckboxes['minor'] = '';
 +              }
 +              $legacyCheckboxes['watch'] = '';
 +              // Copy new-style checkboxes into an old-style structure
 +              foreach ( $checkboxes as $name => $oouiLayout ) {
 +                      $legacyCheckboxes[$name] = (string)$oouiLayout;
 +              }
 +              // Avoid PHP 7.1 warning of passing $this by reference
 +              $ep = $this;
 +              Hooks::run( 'EditPageBeforeEditChecks', [ &$ep, &$legacyCheckboxes, &$tabindex ], '1.29' );
 +              // Copy back any additional old-style checkboxes into the new-style structure
                foreach ( $legacyCheckboxes as $name => $html ) {
                        if ( $html && !isset( $checkboxes[$name] ) ) {
                                $checkboxes[$name] = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $html ) ] );
        /**
         * Get the message key of the label for the button to save the page
         *
 +       * @since 1.30
         * @return string
         */
 -      private function getSaveButtonLabel() {
 +      protected function getSubmitButtonLabel() {
                $labelAsPublish =
 -                      $this->mArticle->getContext()->getConfig()->get( 'EditSubmitButtonLabelPublish' );
 +                      $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
  
                // Can't use $this->isNew as that's also true if we're adding a new section to an extant page
                $newPage = !$this->mTitle->exists();
        public function getEditButtons( &$tabindex ) {
                $buttons = [];
  
 -              $buttonLabel = $this->context->msg( $this->getSaveButtonLabel() )->text();
 +              $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
  
                $attribs = [
                        'name' => 'wpSave',
         * they have attempted to edit a nonexistent section.
         */
        public function noSuchSectionPage() {
 -              global $wgOut;
 -
 -              $wgOut->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
 +              $out = $this->context->getOutput();
 +              $out->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
  
                $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
  
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
                Hooks::run( 'EditPageNoSuchSection', [ &$editPage, &$res ] );
 -              $wgOut->addHTML( $res );
 +              $out->addHTML( $res );
  
 -              $wgOut->returnToMain( false, $this->mTitle );
 +              $out->returnToMain( false, $this->mTitle );
        }
  
        /**
         * @param string|array|bool $match Text (or array of texts) which triggered one or more filters
         */
        public function spamPageWithContent( $match = false ) {
 -              global $wgOut, $wgLang;
                $this->textbox2 = $this->textbox1;
  
                if ( is_array( $match ) ) {
 -                      $match = $wgLang->listToText( $match );
 +                      $match = $this->context->getLanguage()->listToText( $match );
                }
 -              $wgOut->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
 +              $out = $this->context->getOutput();
 +              $out->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
  
 -              $wgOut->addHTML( '<div id="spamprotected">' );
 -              $wgOut->addWikiMsg( 'spamprotectiontext' );
 +              $out->addHTML( '<div id="spamprotected">' );
 +              $out->addWikiMsg( 'spamprotectiontext' );
                if ( $match ) {
 -                      $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
 +                      $out->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
                }
 -              $wgOut->addHTML( '</div>' );
 +              $out->addHTML( '</div>' );
  
 -              $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 +              $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
                $this->showDiff();
  
 -              $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
 +              $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
                $this->showTextbox2();
  
 -              $wgOut->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
 -      }
 -
 -      /**
 -       * Check if the browser is on a blacklist of user-agents known to
 -       * mangle UTF-8 data on form submission. Returns true if Unicode
 -       * should make it through, false if it's known to be a problem.
 -       * @return bool
 -       */
 -      private function checkUnicodeCompliantBrowser() {
 -              global $wgBrowserBlackList, $wgRequest;
 -
 -              $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
 -              if ( $currentbrowser === false ) {
 -                      // No User-Agent header sent? Trust it by default...
 -                      return true;
 -              }
 -
 -              foreach ( $wgBrowserBlackList as $browser ) {
 -                      if ( preg_match( $browser, $currentbrowser ) ) {
 -                              return false;
 -                      }
 -              }
 -              return true;
 +              $out->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
        }
  
        /**
         * Filter an input field through a Unicode de-armoring process if it
         * came from an old browser with known broken Unicode editing issues.
         *
 +       * @deprecated since 1.30, does nothing
 +       *
         * @param WebRequest $request
         * @param string $field
         * @return string
         */
        protected function safeUnicodeInput( $request, $field ) {
 -              $text = rtrim( $request->getText( $field ) );
 -              return $request->getBool( 'safemode' )
 -                      ? $this->unmakeSafe( $text )
 -                      : $text;
 +              return rtrim( $request->getText( $field ) );
        }
  
        /**
         * Filter an output field through a Unicode armoring process if it is
         * going to an old browser with known broken Unicode editing issues.
         *
 +       * @deprecated since 1.30, does nothing
 +       *
         * @param string $text
         * @return string
         */
        protected function safeUnicodeOutput( $text ) {
 -              return $this->checkUnicodeCompliantBrowser()
 -                      ? $text
 -                      : $this->makeSafe( $text );
 -      }
 -
 -      /**
 -       * A number of web browsers are known to corrupt non-ASCII characters
 -       * in a UTF-8 text editing environment. To protect against this,
 -       * detected browsers will be served an armored version of the text,
 -       * with non-ASCII chars converted to numeric HTML character references.
 -       *
 -       * Preexisting such character references will have a 0 added to them
 -       * to ensure that round-trips do not alter the original data.
 -       *
 -       * @param string $invalue
 -       * @return string
 -       */
 -      private function makeSafe( $invalue ) {
 -              // Armor existing references for reversibility.
 -              $invalue = strtr( $invalue, [ "&#x" => "&#x0" ] );
 -
 -              $bytesleft = 0;
 -              $result = "";
 -              $working = 0;
 -              $valueLength = strlen( $invalue );
 -              for ( $i = 0; $i < $valueLength; $i++ ) {
 -                      $bytevalue = ord( $invalue[$i] );
 -                      if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
 -                              $result .= chr( $bytevalue );
 -                              $bytesleft = 0;
 -                      } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
 -                              $working = $working << 6;
 -                              $working += ( $bytevalue & 0x3F );
 -                              $bytesleft--;
 -                              if ( $bytesleft <= 0 ) {
 -                                      $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
 -                              }
 -                      } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
 -                              $working = $bytevalue & 0x1F;
 -                              $bytesleft = 1;
 -                      } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
 -                              $working = $bytevalue & 0x0F;
 -                              $bytesleft = 2;
 -                      } else { // 1111 0xxx
 -                              $working = $bytevalue & 0x07;
 -                              $bytesleft = 3;
 -                      }
 -              }
 -              return $result;
 -      }
 -
 -      /**
 -       * Reverse the previously applied transliteration of non-ASCII characters
 -       * back to UTF-8. Used to protect data from corruption by broken web browsers
 -       * as listed in $wgBrowserBlackList.
 -       *
 -       * @param string $invalue
 -       * @return string
 -       */
 -      private function unmakeSafe( $invalue ) {
 -              $result = "";
 -              $valueLength = strlen( $invalue );
 -              for ( $i = 0; $i < $valueLength; $i++ ) {
 -                      if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
 -                              $i += 3;
 -                              $hexstring = "";
 -                              do {
 -                                      $hexstring .= $invalue[$i];
 -                                      $i++;
 -                              } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
 -
 -                              // Do some sanity checks. These aren't needed for reversibility,
 -                              // but should help keep the breakage down if the editor
 -                              // breaks one of the entities whilst editing.
 -                              if ( ( substr( $invalue, $i, 1 ) == ";" ) && ( strlen( $hexstring ) <= 6 ) ) {
 -                                      $codepoint = hexdec( $hexstring );
 -                                      $result .= UtfNormal\Utils::codepointToUtf8( $codepoint );
 -                              } else {
 -                                      $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
 -                              }
 -                      } else {
 -                              $result .= substr( $invalue, $i, 1 );
 -                      }
 -              }
 -              // reverse the transform that we made for reversibility reasons.
 -              return strtr( $result, [ "&#x0" => "&#x" ] );
 +              return $text;
        }
  
        /**
         * @since 1.29
         */
        protected function addEditNotices() {
 -              global $wgOut;
 -
 +              $out = $this->context->getOutput();
                $editNotices = $this->mTitle->getEditNotices( $this->oldid );
                if ( count( $editNotices ) ) {
 -                      $wgOut->addHTML( implode( "\n", $editNotices ) );
 +                      $out->addHTML( implode( "\n", $editNotices ) );
                } else {
                        $msg = $this->context->msg( 'editnotice-notext' );
                        if ( !$msg->isDisabled() ) {
 -                              $wgOut->addHTML(
 +                              $out->addHTML(
                                        '<div class="mw-editnotice-notext">'
                                        . $msg->parseAsBlock()
                                        . '</div>'
         * @since 1.29
         */
        protected function addTalkPageText() {
 -              global $wgOut;
 -
                if ( $this->mTitle->isTalkPage() ) {
 -                      $wgOut->addWikiMsg( 'talkpagetext' );
 +                      $this->context->getOutput()->addWikiMsg( 'talkpagetext' );
                }
        }
  
         * @since 1.29
         */
        protected function addLongPageWarningHeader() {
 -              global $wgMaxArticleSize, $wgOut, $wgLang;
 -
                if ( $this->contentLength === false ) {
                        $this->contentLength = strlen( $this->textbox1 );
                }
  
 -              if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) {
 -                      $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
 +              $out = $this->context->getOutput();
 +              $lang = $this->context->getLanguage();
 +              $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',
 -                                      $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ),
 -                                      $wgLang->formatNum( $wgMaxArticleSize )
 +                                      $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
 +                                      $lang->formatNum( $maxArticleSize )
                                ]
                        );
                } else {
                        if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
 -                              $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
 +                              $out->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
                                        [
                                                'longpage-hint',
 -                                              $wgLang->formatSize( strlen( $this->textbox1 ) ),
 +                                              $lang->formatSize( strlen( $this->textbox1 ) ),
                                                strlen( $this->textbox1 )
                                        ]
                                );
         * @since 1.29
         */
        protected function addPageProtectionWarningHeaders() {
 -              global $wgOut;
 -
 +              $out = $this->context->getOutput();
                if ( $this->mTitle->isProtected( 'edit' ) &&
                        MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
                ) {
                                # Then it must be protected based on static groups (regular)
                                $noticeMsg = 'protectedpagewarning';
                        }
 -                      LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
 +                      LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
                                [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
                }
                if ( $this->mTitle->isCascadeProtected() ) {
                                }
                        }
                        $notice .= '</div>';
 -                      $wgOut->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
 +                      $out->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
                }
                if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
 -                      LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
 +                      LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
                                [ 'lim' => 1,
                                        'showIfEmpty' => false,
                                        'msgKey' => [ 'titleprotectedwarning' ],
        protected function addExplainConflictHeader( OutputPage $out ) {
                $out->wrapWikiMsg(
                        "<div class='mw-explainconflict'>\n$1\n</div>",
 -                      [ 'explainconflict', $this->context->msg( $this->getSaveButtonLabel() )->text() ]
 +                      [ 'explainconflict', $this->context->msg( $this->getSubmitButtonLabel() )->text() ]
                );
        }
  
                        ];
  
                // The following classes can be used here:
-               // * mw-editfont-default
                // * mw-editfont-monospace
                // * mw-editfont-sans-serif
                // * mw-editfont-serif
diff --combined includes/Preferences.php
@@@ -1,5 -1,7 +1,5 @@@
  <?php
  /**
 - * Form to edit user preferences.
 - *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
@@@ -554,22 -556,6 +554,22 @@@ class Preferences 
                                        'label-message' => 'tog-ccmeonemails',
                                        'disabled' => $disableEmailPrefs,
                                ];
 +
 +                              if ( $config->get( 'EnableUserEmailBlacklist' )
 +                                       && !$disableEmailPrefs
 +                                       && !(bool)$user->getOption( 'disablemail' )
 +                              ) {
 +                                      $lookup = CentralIdLookup::factory();
 +                                      $ids = $user->getOption( 'email-blacklist', [] );
 +                                      $names = $ids ? $lookup->namesFromCentralIds( $ids, $user ) : [];
 +
 +                                      $defaultPreferences['email-blacklist'] = [
 +                                              'type' => 'usersmultiselect',
 +                                              'label-message' => 'email-blacklist-label',
 +                                              'section' => 'personal/email',
 +                                              'default' => implode( "\n", $names ),
 +                                      ];
 +                              }
                        }
  
                        if ( $config->get( 'EnotifWatchlist' ) ) {
                        $defaultPreferences['skin'] = [
                                'type' => 'radio',
                                'options' => $skinOptions,
 -                              'label' => '&#160;',
                                'section' => 'rendering/skin',
                        ];
                }
                        $defaultPreferences['date'] = [
                                'type' => 'radio',
                                'options' => $dateOptions,
 -                              'label' => '&#160;',
                                'section' => 'rendering/dateformat',
                        ];
                }
                                        $context->msg( 'editfont-monospace' )->text() => 'monospace',
                                        $context->msg( 'editfont-sansserif' )->text() => 'sans-serif',
                                        $context->msg( 'editfont-serif' )->text() => 'serif',
-                                       $context->msg( 'editfont-default' )->text() => 'default',
                                ]
                        ];
                }
                                'label-message' => 'tog-shownumberswatching',
                        ];
                }
 +
 +              if ( $config->get( 'StructuredChangeFiltersShowPreference' ) ) {
 +                      $defaultPreferences['rcenhancedfilters-disable'] = [
 +                              'type' => 'toggle',
 +                              'section' => 'rc/opt-out',
 +                              'label-message' => 'rcfilters-preference-label',
 +                              'help-message' => 'rcfilters-preference-help',
 +                      ];
 +              }
        }
  
        /**
                $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() );
                # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
                $htmlForm->setSubmitTooltip( 'preferences-save' );
 -              $htmlForm->setSubmitID( 'prefsubmit' );
 +              $htmlForm->setSubmitID( 'prefcontrol' );
                $htmlForm->setSubmitCallback( [ 'Preferences', 'tryFormSubmit' ] );
  
                return $htmlForm;
                return $timeZoneList;
        }
  }
 -
 -/** Some tweaks to allow js prefs to work */
 -class PreferencesForm extends HTMLForm {
 -      // Override default value from HTMLForm
 -      protected $mSubSectionBeforeFields = false;
 -
 -      private $modifiedUser;
 -
 -      /**
 -       * @param User $user
 -       */
 -      public function setModifiedUser( $user ) {
 -              $this->modifiedUser = $user;
 -      }
 -
 -      /**
 -       * @return User
 -       */
 -      public function getModifiedUser() {
 -              if ( $this->modifiedUser === null ) {
 -                      return $this->getUser();
 -              } else {
 -                      return $this->modifiedUser;
 -              }
 -      }
 -
 -      /**
 -       * Get extra parameters for the query string when redirecting after
 -       * successful save.
 -       *
 -       * @return array
 -       */
 -      public function getExtraSuccessRedirectParameters() {
 -              return [];
 -      }
 -
 -      /**
 -       * @param string $html
 -       * @return string
 -       */
 -      function wrapForm( $html ) {
 -              $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html );
 -
 -              return parent::wrapForm( $html );
 -      }
 -
 -      /**
 -       * @return string
 -       */
 -      function getButtons() {
 -              $attrs = [ 'id' => 'mw-prefs-restoreprefs' ];
 -
 -              if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
 -                      return '';
 -              }
 -
 -              $html = parent::getButtons();
 -
 -              if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
 -                      $t = $this->getTitle()->getSubpage( 'reset' );
 -
 -                      $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
 -                      $html .= "\n" . $linkRenderer->makeLink( $t, $this->msg( 'restoreprefs' )->text(),
 -                              Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ) );
 -
 -                      $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
 -              }
 -
 -              return $html;
 -      }
 -
 -      /**
 -       * Separate multi-option preferences into multiple preferences, since we
 -       * have to store them separately
 -       * @param array $data
 -       * @return array
 -       */
 -      function filterDataForSubmit( $data ) {
 -              foreach ( $this->mFlatFields as $fieldname => $field ) {
 -                      if ( $field instanceof HTMLNestedFilterable ) {
 -                              $info = $field->mParams;
 -                              $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
 -                              foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
 -                                      $data["$prefix$key"] = $value;
 -                              }
 -                              unset( $data[$fieldname] );
 -                      }
 -              }
 -
 -              return $data;
 -      }
 -
 -      /**
 -       * Get the whole body of the form.
 -       * @return string
 -       */
 -      function getBody() {
 -              return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
 -      }
 -
 -      /**
 -       * Get the "<legend>" for a given section key. Normally this is the
 -       * prefs-$key message but we'll allow extensions to override it.
 -       * @param string $key
 -       * @return string
 -       */
 -      function getLegend( $key ) {
 -              $legend = parent::getLegend( $key );
 -              Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
 -              return $legend;
 -      }
 -
 -      /**
 -       * Get the keys of each top level preference section.
 -       * @return array of section keys
 -       */
 -      function getPreferenceSections() {
 -              return array_keys( array_filter( $this->mFieldTree, 'is_array' ) );
 -      }
 -}
diff --combined languages/i18n/en.json
@@@ -51,7 -51,6 +51,6 @@@
        "underline-never": "Never",
        "underline-default": "Skin or browser default",
        "editfont-style": "Edit area font style:",
-       "editfont-default": "Browser default",
        "editfont-monospace": "Monospaced font",
        "editfont-sansserif": "Sans-serif font",
        "editfont-serif": "Serif font",
        "explainconflict": "Someone else has changed this page since you started editing it.\nThe upper text area contains the page text as it currently exists.\nYour changes are shown in the lower text area.\nYou will have to merge your changes into the existing text.\n<strong>Only</strong> the text in the upper text area will be saved when you press \"$1\".",
        "yourtext": "Your text",
        "storedversion": "Stored revision",
 -      "nonunicodebrowser": "<strong>Warning: Your browser is not Unicode compliant.</strong>\nA workaround is in place to allow you to safely edit pages: Non-ASCII characters will appear in the edit box as hexadecimal codes.",
        "editingold": "<strong>Warning: You are editing an out-of-date revision of this page.</strong>\nIf you save it, any changes made since this revision will be lost.",
 +      "unicode-support-fail": "It appears that your browser does not support Unicode. It is required to edit pages, so your edit was not saved.",
        "yourdiff": "Differences",
        "copyrightwarning": "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).\nIf you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />\nYou are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.\n<strong>Do not submit copyrighted work without permission!</strong>",
        "copyrightwarning2": "Please note that all contributions to {{SITENAME}} may be edited, altered, or removed by other contributors.\nIf you do not want your writing to be edited mercilessly, then do not submit it here.<br />\nYou are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see $1 for details).\n<strong>Do not submit copyrighted work without permission!</strong>",
        "parser-template-loop-warning": "Template loop detected: [[$1]]",
        "template-loop-category": "Pages with template loops",
        "template-loop-category-desc": "The page contains a template loop, ie. a template which calls itself recursively.",
 +      "template-loop-warning": "<strong>Warning:</strong> This page calls [[:$1]] which causes a template loop (an infinite recursive call).",
        "parser-template-recursion-depth-warning": "Template recursion depth limit exceeded ($1)",
        "language-converter-depth-warning": "Language converter depth limit exceeded ($1)",
        "node-count-exceeded-category": "Pages where node count is exceeded",
        "timezoneregion-indian": "Indian Ocean",
        "timezoneregion-pacific": "Pacific Ocean",
        "allowemail": "Enable email from other users",
 +      "email-blacklist-label": "Prohibit these users from sending emails to me:",
        "prefs-searchoptions": "Search",
        "prefs-namespaces": "Namespaces",
        "default": "default",
        "prefs-editor": "Editor",
        "prefs-preview": "Preview",
        "prefs-advancedrc": "Advanced options",
 +      "prefs-opt-out": "Opt out of improvements",
        "prefs-advancedrendering": "Advanced options",
        "prefs-advancedsearchoptions": "Advanced options",
        "prefs-advancedwatchlist": "Advanced options",
        "rcfilters-empty-filter": "No active filters. All contributions are shown.",
        "rcfilters-filterlist-title": "Filters",
        "rcfilters-filterlist-whatsthis": "How do these work?",
 -      "rcfilters-filterlist-feedbacklink": "Provide feedback on the new (beta) filters",
 +      "rcfilters-filterlist-feedbacklink": "Tell us what you think about these (new) filtering tools",
        "rcfilters-highlightbutton-title": "Highlight results",
        "rcfilters-highlightmenu-title": "Select a color",
        "rcfilters-highlightmenu-help": "Select a color to highlight this property",
        "rcfilters-view-namespaces-tooltip": "Filter results by namespace",
        "rcfilters-view-tags-tooltip": "Filter results using edit tags",
        "rcfilters-view-return-to-default-tooltip": "Return to main filter menu",
 +      "rcfilters-view-tags-help-icon-tooltip": "Learn more about Tagged Edits",
        "rcfilters-liveupdates-button": "Live updates",
        "rcfilters-liveupdates-button-title-on": "Turn off live updates",
        "rcfilters-liveupdates-button-title-off": "Display new changes as they happen",
 -      "rcfilters-watchlist-markSeen-button": "Mark all changes as seen",
 +      "rcfilters-watchlist-markseen-button": "Mark all changes as seen",
 +      "rcfilters-watchlist-edit-watchlist-button": "Edit your list of watched pages",
 +      "rcfilters-watchlist-showupdated": "Changes to pages you haven't visited since the changes occurred are in <strong>bold</strong>, with solid markers.",
 +      "rcfilters-preference-label": "Hide the improved version of Recent Changes",
 +      "rcfilters-preference-help": "Rolls back the 2017 interface redesign and all tools added then and since.",
        "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Reset date selection",
        "rclistfrom": "Show new changes starting from $2, $3",
        "uploadbtn": "Upload file",
        "reuploaddesc": "Cancel upload and return to the upload form",
        "upload-tryagain": "Submit modified file description",
 +      "upload-tryagain-nostash": "Submit re-uploaded file and modified description",
        "uploadnologin": "Not logged in",
        "uploadnologintext": "Please $1 to upload files.",
        "upload_directory_missing": "The upload directory ($1) is missing and could not be created by the webserver.",
        "file-deleted-duplicate-notitle": "A file identical to this file has previously been deleted, and the title has been suppressed.\nYou should ask someone with the ability to view suppressed file data to review the situation before proceeding to re-upload it.",
        "uploadwarning": "Upload warning",
        "uploadwarning-text": "Please modify the file description below and try again.",
 +      "uploadwarning-text-nostash": "Please re-upload the file, modify the description below and try again.",
        "savefile": "Save file",
        "uploadedimage": "uploaded \"[[$1]]\"",
        "overwroteimage": "uploaded a new version of \"[[$1]]\"",
        "listfiles_size": "Size",
        "listfiles_description": "Description",
        "listfiles_count": "Versions",
 -      "listfiles-show-all": "Include old versions of images",
 +      "listfiles-show-all": "Include old versions of files",
        "listfiles-latestversion": "Current version",
        "listfiles-latestversion-yes": "Yes",
        "listfiles-latestversion-no": "No",
        "unwatchthispage": "Stop watching",
        "notanarticle": "Not a content page",
        "notvisiblerev": "The last revision by a different user has been deleted",
 -      "watchlist-details": "{{PLURAL:$1|$1 page|$1 pages}} on your watchlist, not separately counting talk pages.",
 +      "watchlist-details": "{{PLURAL:$1|$1 page is|$1 pages are}} on your Watchlist (plus talk pages).",
        "wlheader-enotif": "Email notification is enabled.",
        "wlheader-showupdated": "Pages that have been changed since you last visited them are shown in <strong>bold</strong>.",
        "wlnote": "Below {{PLURAL:$1|is the last change|are the last <strong>$1</strong> changes}} in the last {{PLURAL:$2|hour|<strong>$2</strong> hours}}, as of $3, $4.",
        "sp-contributions-explain": "",
        "sp-contributions-footer": "-",
        "sp-contributions-footer-anon": "-",
 +      "sp-contributions-footer-anon-range": "-",
        "sp-contributions-footer-newbies": "-",
 +      "sp-contributions-outofrange": "Unable to show any results. The requested IP range is larger than the CIDR limit of /$1.",
        "whatlinkshere": "What links here",
        "whatlinkshere-title": "Pages that link to \"$1\"",
        "whatlinkshere-summary": "",
        "unblock": "Unblock user",
        "unblock-summary": "",
        "blockip": "Block {{GENDER:$1|user}}",
 -      "blockip-legend": "Block user",
        "blockiptext": "Use the form below to block write access from a specific IP address or username.\nThis should be done only to prevent vandalism, and in accordance with [[{{MediaWiki:Policy-url}}|policy]].\nFill in a specific reason below (for example, citing particular pages that were vandalized).\nYou can block IP address ranges using the [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] syntax; the largest allowed range is /$1 for IPv4 and /$2 for IPv6.",
        "ipaddressorusername": "IP address or username:",
        "ipbexpiry": "Expiration:",
        "ipb_blocked_as_range": "Error: The IP address $1 is not blocked directly and cannot be unblocked.\nIt is, however, blocked as part of the range $2, which can be unblocked.",
        "ip_range_invalid": "Invalid IP address range.",
        "ip_range_toolarge": "Range blocks larger than /$1 are not allowed.",
 +      "ip_range_exceeded": "The IP range exceeds its maximum range. Allowed range: /$1.",
 +      "ip_range_toolow": "IP ranges are effectively not allowed.",
        "proxyblocker": "Proxy blocker",
        "proxyblockreason": "Your IP address has been blocked because it is an open proxy.\nPlease contact your Internet service provider or technical support of your organization and inform them of this serious security problem.",
        "sorbs": "DNSBL",
        "delete_and_move_text": "The destination page \"[[:$1]]\" already exists.\nDo you want to delete it to make way for the move?",
        "delete_and_move_confirm": "Yes, delete the page",
        "delete_and_move_reason": "Deleted to make way for move from \"[[$1]]\"",
 -      "selfmove": "Source and destination titles are the same;\ncannot move a page over itself.",
 +      "selfmove": " The title is the same;\ncannot move a page over itself.",
        "immobile-source-namespace": "Cannot move pages in namespace \"$1\".",
        "immobile-target-namespace": "Cannot move pages into namespace \"$1\".",
        "immobile-target-namespace-iw": "Interwiki link is not a valid target for page move.",
        "undelete-cantedit": "You cannot undelete this page as you are not allowed to edit this page.",
        "undelete-cantcreate": "You cannot undelete this page as there is no existing page with this name and you are not allowed to create this page.",
        "pagedata-title": "Page data",
 -      "pagedata-text": "This page provides a data interface to pages. Please provide the page title in the URL, using subpage syntax.\n* Content negotiation applies based on you client's Accept header. This means that the page data will be provided in the format preferred by your client.",
 +      "pagedata-text": "This page provides a data interface to pages. Please provide the page title in the URL, using subpage syntax.\n* Content negotiation applies based on your client's Accept header. This means that the page data will be provided in the format preferred by your client.",
        "pagedata-not-acceptable": "No matching format found. Supported MIME types: $1",
        "pagedata-bad-title": "Invalid title: $1."
  }
diff --combined languages/i18n/qqq.json
@@@ -91,7 -91,6 +91,7 @@@
                        "Mormegil",
                        "Mpradeep",
                        "Murma174",
 +                      "MusikAnimal",
                        "Najami",
                        "Naudefj",
                        "Nemo bis",
                        "Jhertel",
                        "Stryn",
                        "Mazab IZW",
 -                      "Mainframe98"
 +                      "Mainframe98",
 +                      "Pginer",
 +                      "Wladek92"
                ]
        },
        "sidebar": "{{notranslate}}",
        "underline-never": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"never underline links\", there are also options {{msg-mw|Underline-always}} and {{msg-mw|Underline-default}}.\n\n{{Gender}}\n{{Identical|Never}}",
        "underline-default": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"underline links as in your user skin or your browser\", there are also options {{msg-mw|Underline-never}} and {{msg-mw|Underline-always}}.\n\n{{Gender}}\n{{Identical|Browser default}}",
        "editfont-style": "Used in [[Special:Preferences]], tab Editing. {{Gender}}",
-       "editfont-default": "Option used in [[Special:Preferences]], tab Editing. {{Gender}}\n{{Identical|Browser default}}",
        "editfont-monospace": "Option used in [[Special:Preferences]], tab Editing. {{Gender}}",
        "editfont-sansserif": "Option used in [[Special:Preferences]], tab Editing. {{Gender}}",
        "editfont-serif": "Option used in [[Special:Preferences]], tab Editing. {{Gender}}",
        "explainconflict": "Appears at the top of a page when there is an edit conflict.\n\nParameters:\n* $1 – The label of the save button – one of {{msg-mw|savearticle}} or {{msg-mw|savechanges}} on save-labelled wiki, or {{msg-mw|publishpage}} or {{msg-mw|publishchanges}} on publish-labelled wikis.\n\nSee also:\n* {{msg-mw|Savearticle}}",
        "yourtext": "Used in Diff Preview page. The diff is between {{msg-mw|currentrev}} and {{msg-mw|yourtext}}.\n\nAlso used in Edit Conflict page; the diff between {{msg-mw|yourtext}} and {{msg-mw|storedversion}}.",
        "storedversion": "This is used in an edit conflict as the label for the top revision that has been stored, as opposed to your version {{msg-mw|yourtext}} that has not been stored which is shown at the bottom of the page.",
 -      "nonunicodebrowser": "Used as warning when editing page.",
        "editingold": "Used as warning when editing an old revision of a page.",
 +      "unicode-support-fail": "Error message shown to users if their browser doesn't support Unicode",
        "yourdiff": "Used as h2 header for the diff of the current version of a page with the user's version in case there is an edit conflict or a spam filter hit.",
        "copyrightwarning": "Copyright warning displayed under the edit box in editor. Parameters:\n* $1 - link\n* $2 - license name",
        "copyrightwarning2": "Copyright warning displayed under the edit box in editor\n*$1 - license name",
        "parser-template-loop-warning": "Parameters:\n* $1 - page title",
        "template-loop-category": "This message is used as a category name for a [[mw:Special:MyLanguage/Help:Tracking categories|tracking category]] where pages with template loops will be listed.",
        "template-loop-category-desc": "Pages with template loops category description. Shown on [[Special:TrackingCategories]].\n\nSee also:\n* {{msg-mw|Template-loop-category}}",
 +      "template-loop-warning": "This message is displayed in edit preview when a template loop is detected on the previewed page.\n\nParameters:\n* $1 - the full title of template which causes the template loop.",
        "parser-template-recursion-depth-warning": "Parameters:\n* $1 - limit value of recursion depth",
        "language-converter-depth-warning": "Error message shown when a page uses too deeply nested language conversion syntax. Parameters:\n* $1 - the value of the depth limit",
        "node-count-exceeded-category": "This message is used as a category name for a [[mw:Help:Tracking categories|tracking category]] where pages are placed automatically if the node-count of the preprocessor exceeds the limit.\n\nSee also:\n* {{msg-mw|Node-count-exceeded-warning}}",
        "pagemerge-logentry": "{{ignored}}This is a ''logentry'' message only used on IRC.\n\nParameters:\n* $1 - the page name of the source of the content to be merged\n* $2 - the page into which the content is merged\n* $3 - a timestamp of limit\n\nThe log and its associated special page 'MergeHistory' is not enabled by default.\n\nPlease note that the parameters in a log entry will appear in the log only in the default language of the wiki. View [[Special:Log]] for examples on translatewiki.net with English default language.",
        "revertmerge": "Used as link text",
        "mergelogpagetext": "Description of the [{{canonicalurl:Special:Log|type=merge&user=&page=&year=&month=-1}} merge log], on the log. The associated [[Special:MergeHistory|Merge]] special page is not enabled by default.",
 -      "history-title": "Displayed as page title when you click on the \"history\" tab. Parameters:\n* $1 - the normal page title",
 +      "history-title": "Displayed as page title when you click on the \"history\" tab. Parameters:\n* $1 - the normal page title\n{{Identical|Revision history of}}",
        "difference-title": "Displayed as page title when viewing the difference between two edits of the same page.\n\nParameters:\n* $1 - the page title of the two revisions",
        "difference-title-multipage": "Displayed as page title when viewing the difference between two edits of different pages.\n\nParameters:\n* $1 - the page title of the old revision\n* $2 - the page title of the new revision",
        "difference-multipage": "Displayed under the title when viewing the difference between two or more pages.\nSee also {{msg-mw|difference}}.",
        "timezoneregion-indian": "Used in \"Time zone\" listbox in [[Special:Preferences#mw-prefsection-datetime|preferences]], \"date and time\" tab.\n{{Related|Timezoneregion}}",
        "timezoneregion-pacific": "Used in \"Time zone\" listbox in [[Special:Preferences#mw-prefsection-datetime|preferences]], \"date and time\" tab.\n{{Related|Timezoneregion}}",
        "allowemail": "Used in [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.",
 +      "email-blacklist-label": "Used in [[Special:Preferences]] > {{int:prefs-prohibit}} > {{int:email}}.",
        "prefs-searchoptions": "{{Identical|Search}}",
        "prefs-namespaces": "Shown as legend of the second fieldset of the tab 'Search' in [[Special:Preferences]]\n{{Identical|Namespace}}",
        "default": "{{Identical|Default}}",
        "prefs-editor": "Used in [[Special:Preferences]], tab \"Editing\" ({{int:prefs-editing}}).\n\n{{Identical|Editor}}",
        "prefs-preview": "Used in [[Special:Preferences]], tab \"Editing\".\n{{Identical|Preview}}",
        "prefs-advancedrc": "Used in [[Special:Preferences]], tab \"Recent changes\".\n{{Identical|Advanced options}}",
 +      "prefs-opt-out": "Used in [[Special:Preferences]], tab \"Recent changes\".",
        "prefs-advancedrendering": "Used in [[Special:Preferences]], tab \"Appearence\".\n{{Identical|Advanced options}}",
        "prefs-advancedsearchoptions": "Used in [[Special:Preferences]], tab \"Search options\".\n{{Identical|Advanced options}}",
        "prefs-advancedwatchlist": "Used in [[Special:Preferences]], tab \"Watchlist\".\n{{Identical|Advanced options}}",
        "rcfilters-limit-shownum": "Title for the button that opens the operation to control how many results are shown. \n\nParameters: $1 - Number of results shown",
        "rcfilters-days-title": "Title for the options to change the number of days for the results shown.",
        "rcfilters-hours-title": "Title for the options to change the number of hours for the results shown.",
 -      "rcfilters-days-show-days": "Title for the button that opens the operation to control the day range for the results. \n\nParameters: $1 - Number of days shown",
 -      "rcfilters-days-show-hours": "Title for the button that opens the operation to control the hour range for the results. \n\nParameters: $1 - Number of hours shown",
 +      "rcfilters-days-show-days": "Title for the button that opens the operation to control the day range for the results. \n\nParameters: $1 - Number of days shown\n{{Identical|Day}}",
 +      "rcfilters-days-show-hours": "Title for the button that opens the operation to control the hour range for the results. \n\nParameters: $1 - Number of hours shown\n{{Identical|Hour}}",
        "rcfilters-highlighted-filters-list": "Text for the tooltip that is displayed over highlighted results, specifying which filters the result matches in [[Special:RecentChanges]] when RCFilters are enabled. \n\nParameters: $1 - A comma separated list of matching filter names.",
        "rcfilters-quickfilters": "Label for the button that opens the saved filter settings menu in [[Special:RecentChanges]]",
        "rcfilters-quickfilters-placeholder-title": "Title for the text shown in the quick filters menu on [[Special:RecentChanges]] if the user has not saved any quick filters.",
        "rcfilters-tag-prefix-namespace-inverted": "Prefix for the namespace inverted tags in [[Special:RecentChanges]]. Namespace tags use a colon (:) as prefix. Please keep this format.\n\nParameters:\n* $1 - Filter name.\n{{Identical|Not}}",
        "rcfilters-tag-prefix-tags": "Prefix for the edit tags in [[Special:RecentChanges]]. Edit tags use a hash (#) as prefix. Please keep this format.\n\nParameters:\n* $1 - Tag display name.",
        "rcfilters-exclude-button-off": "Title for the button that excludes selected namespaces, when it is not yet active.",
 -      "rcfilters-exclude-button-on": "Title for the button that excludes selected namespaces, when it is not yet active.",
 +      "rcfilters-exclude-button-on": "Title for the button that excludes selected namespaces, when it is active.",
        "rcfilters-view-advanced-filters-label": "Label for the view switch that changes between advanced filters in [[Special:RecentChanges]]",
        "rcfilters-view-tags": "Title for the tags view in [[Special:RecentChanges]]\n{{Identical|Tag}}",
        "rcfilters-view-namespaces-tooltip": "Tooltip for the button that loads the namespace view in [[Special:RecentChanges]]",
        "rcfilters-view-tags-tooltip": "Tooltip for the button that loads the tags view in [[Special:RecentChanges]]",
        "rcfilters-view-return-to-default-tooltip": "Tooltip for the button that returns to the default filter view in [[Special:RecentChanges]]",
 +      "rcfilters-view-tags-help-icon-tooltip": "Tooltip for the help button that leads user to [[mw:Special:MyLanguage/Help:New_filters_for_edit_review/Advanced_filters#tags|Help page]] for Tagged Edits",
        "rcfilters-liveupdates-button": "Label for the button to enable or disable live updates on [[Special:RecentChanges]]",
        "rcfilters-liveupdates-button-title-on": "Title for the button to enable or disable live updates on [[Special:RecentChanges]] when the feature is ON.",
        "rcfilters-liveupdates-button-title-off": "Title for the button to enable or disable live updates on [[Special:RecentChanges]] when the feature is OFF.",
 -      "rcfilters-watchlist-markSeen-button": "Label for the button to mark all changes as seen on [[Special:Watchlist]] when using the structured filters interface.",
 +      "rcfilters-watchlist-markseen-button": "Label for the button to mark all changes as seen on [[Special:Watchlist]] when using the structured filters interface.",
 +      "rcfilters-watchlist-edit-watchlist-button": "Label for the button to edit the watched pages on [[Special:Watchlist]] when using the structured filters interface.\n\nCf. {{msg-mw|watchlisttools-edit}}.",
 +      "rcfilters-watchlist-showupdated": "Message at the top of [[Special:Watchlist]] when the Structured filters are enabled that describes what unseen changes look like.\n\nCf. {{msg-mw|wlheader-showupdated}}",
 +      "rcfilters-preference-label": "Option in RecentChanges tab of [[Special:Preferences]].",
 +      "rcfilters-preference-help": "Explanation for the option in the RecentChanges tab of [[Special:Preferences]].",
        "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL",
        "rclistfromreset": "Used on [[Special:RecentChanges]] to reset a selection of a certain date range.",
        "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.",
        "uploadbtn": "Button name in [[Special:Upload]].\n\nSee also:\n* {{msg-mw|Uploadbtn}}\n* {{msg-mw|Accesskey-upload}}\n* {{msg-mw|Tooltip-upload}}\n{{Identical|Upload file}}",
        "reuploaddesc": "Used as button text in the Upload form on [[Special:Upload]].\n\nSee also:\n* {{msg-mw|upload-tryagain|Submit button text}}\n* {{msg-mw|ignorewarning|button text}}",
        "upload-tryagain": "Used as Submit button text in [[Special:Upload]].\n\nSee also:\n* {{msg-mw|Uploaderror|section header}}\n* {{msg-mw|ignorewarning|button text}}\n* {{msg-mw|reuploaddesc|button text}}",
 +      "upload-tryagain-nostash": "Used as Submit button text in [[Special:Upload]] when the upload could not be stashed & the file needs to be reuploaded.\n\nSee also:\n* {{msg-mw|Uploaderror|section header}}\n* {{msg-mw|ignorewarning|button text}}\n* {{msg-mw|reuploaddesc|button text}}",
        "uploadnologin": "Used as title of the error message {{msg-mw|Uploadnologintext}}.\n{{Identical|Not logged in}}",
        "uploadnologintext": "Used as error message.\n\nThe title for this message is {{msg-mw|Uploadnologin}}.\n\nParameters:\n* $1 - link text {{msg-mw|Loginreqlink}}. The link points to [[Special:UserLogin]].\nSee also:\n* {{msg-mw|Whitelistedittext}}\n* {{msg-mw|Nocreatetext}}\n* {{msg-mw|Loginreqpagetext}}",
        "upload_directory_missing": "Parameters:\n* $1 - directory name",
        "file-deleted-duplicate-notitle": "Used in [[Special:Upload]] when the title of the deleted duplicate is not available.\n\nSee also:\n* {{msg-mw|file-deleted-duplicate}}",
        "uploadwarning": "Used as section header in [[Special:Upload]].",
        "uploadwarning-text": "Used in [[Special:Upload]].",
 +      "uploadwarning-text-nostash": "Used in [[Special:Upload]], when the upload could not be stashed & the file needs to be reuploaded.",
        "savefile": "When uploading a file",
        "uploadedimage": "{{ignored}}This is a ''logentry'' message only used on IRC. $1 is the name of the file uploaded.",
        "overwroteimage": "{{ignored}}This is a ''logentry'' message only used on IRC. $1 is the name of the file uploaded.",
        "sp-contributions-explain": "{{optional}}",
        "sp-contributions-footer": "{{ignored}}This is the footer for users that are not anonymous or newbie on [[Special:Contributions]].",
        "sp-contributions-footer-anon": "{{ignored}}This is the footer for anonymous users on [[Special:Contributions]].",
 +      "sp-contributions-footer-anon-range": "{{ignored}}This is the footer for IP ranges on [[Special:Contributions]].",
        "sp-contributions-footer-newbies": "{{ignored}}This is the footer for newbie users on [[Special:Contributions]].",
 +      "sp-contributions-outofrange": "Message shown when a user tries to view contributions of an IP range that's too large. $1 is the numerical limit imposed on the CIDR range.",
        "whatlinkshere": "The text of the link in the toolbox (on the left, below the search menu) going to [[Special:WhatLinksHere]].\n\nSee also:\n* {{msg-mw|Whatlinkshere}}\n* {{msg-mw|Accesskey-t-whatlinkshere}}\n* {{msg-mw|Tooltip-t-whatlinkshere}}",
        "whatlinkshere-title": "Title of the special page [[Special:WhatLinksHere]]. This page appears when you click on the 'What links here' button in the toolbox. $1 is the name of the page concerned.",
        "whatlinkshere-summary": "{{doc-specialpagesummary|whatlinkshere}}",
        "unblock": "{{doc-special|Unblock}}",
        "unblock-summary": "{{doc-specialpagesummary|unblock}}",
        "blockip": "Used as the text of a link in the sidebar toolbox. Clicking this link takes you to [[Special:Block]], with a relevant username or IP address (e.g. \"Username\" on [[User talk:Username]], [[Special:Contributions/Username]], etc.) already filled in.\n\nParameters:\n* $1 - username, for GENDER support\n{{Identical|Block user}}",
 -      "blockip-legend": "Legend/Header for the fieldset around the input form of [[Special:Block]].\n\n{{Identical|Block user}}",
        "blockiptext": "Used in the {{msg-mw|Blockip}} form in [[Special:Block]].\n\nRefers to {{msg-mw|Policy-url}}.\n\nThis message may follow the message {{msg-mw|Ipb-otherblocks-header}} and other block messages.\n\nParameters:\n* $1 - CIDR suffix of the largest allowed IPv4 block (as an integer)\n* $2 - CIDR suffix of the largest allowed IPv6 block (as an integer)\n\nSee also:\n* {{msg-mw|Unblockiptext}}",
        "ipaddressorusername": "{{Identical|IP address or username}}",
        "ipbexpiry": "{{Identical|Expiry}}",
        "ipb_blocked_as_range": "Used when unblock of a single IP fails. Parameters:\n* $1 - IP address\n* $2 - IP address range",
        "ip_range_invalid": "Used as error message in [[Special:Block]].\n\nSee also:\n* {{msg-mw|Range block disabled}}\n* {{msg-mw|Ip range invalid}}\n* {{msg-mw|Ip range toolarge}}",
        "ip_range_toolarge": "Used as error message in [[Special:Block]]. Parameters:\n* $1 - a number from 0 to 32 for IPv4 (from 0 to 128 for IPv6); a part of CIDR (Classless Inter-Domain Routing) notation.\nSee also:\n* {{msg-mw|Range block disabled}}\n* {{msg-mw|Ip range invalid}}\n* {{msg-mw|Ip range toolarge}}",
 +      "ip_range_exceeded": "Used as error message in HTMLUserTextField when an IP range exceeds its maximum amount. See {{mw-msg|ip_range_toolarge}} for parameter.\n/$1 is the width as a number of bits.",
 +      "ip_range_toolow": "Used as error message in HTMLUserTextField, if effectively no IP ranges are interpreted as valid (IPv4 CIDR range /32 or IPv6 /128).",
        "proxyblocker": "Used in [[Special:BlockMe]].\n\nSee also:\n* {{msg-mw|proxyblocker-disabled}}\n* {{msg-mw|proxyblockreason}}\n* {{msg-mw|proxyblocksuccess}}",
        "proxyblockreason": "Used as explanation of the reason in [[Special:BlockMe]].\n\nSee also:\n* {{msg-mw|proxyblocker-disabled}}\n* {{msg-mw|proxyblocker}}\n* {{msg-mw|proxyblocksuccess}}",
        "sorbs": "{{optional}}",
        "table_pager_empty": "Used in a table pager when there are no results (e.g. when there are no images in the table on [[Special:ImageList]]).\n{{Identical|No result}}",
        "autosumm-blank": "The auto summary when blanking the whole page. This is not the same as deleting the page.",
        "autosumm-replace": "The auto summary when a user removes a lot of characters in the page.\n\nParameters:\n* $1 - truncated text",
 -      "autoredircomment": "The auto summary when making a redirect. Parameters:\n* $1 - the page where it redirects to\n* $2 - (Optional) the first X number of characters of the redirect ($2 is usually only used when end users customize the message)",
 +      "autoredircomment": "The auto summary when making a redirect. Parameters:\n* $1 - the page where it redirects to\n* $2 - (Optional) the first X number of characters of the redirect ($2 is usually only used when end users customize the message)\n{{Identical|Redirect}}",
        "autosumm-new": "The auto summary when creating a new page. $1 are the first X number of characters of the new page.",
        "autosumm-newblank": "The automatic edit summary when creating a blank page. This is not the same as blanking a page.",
        "autoblock_whitelist": "{{notranslate}}",
        "diff-form-revid": "Label for the field of the new revision in the comparison for [[Special:Diff]]",
        "diff-form-submit": "Submit button on [[Special:Diff]]",
        "diff-form-summary": "{{doc-specialpagesummary|diff}}",
 -      "permanentlink": "The title of [[Special:PermanentLink]]",
 -      "permanentlink-revid": "Label for the field for the revision ID in [[Special:PermanentLink]]",
 +      "permanentlink": "The title of [[Special:PermanentLink]]\n{{Identical|Permalink}}",
 +      "permanentlink-revid": "Label for the field for the revision ID in [[Special:PermanentLink]]\n{{Identical|Revision ID}}",
        "permanentlink-submit": "Submit button on [[Special:PermanentLink]]",
        "permanentlink-summary": "{{doc-specialpagesummary|permanentlink}}",
        "dberr-problems": "This message does not allow any wiki nor html markup.",
@@@ -111,12 -111,6 +111,6 @@@ span.comment 
  }
  
  /* Edit font preference */
- /* TODO: for 'default' on non-textareas we could compute the default font of textarea in the client */
- .mw-editfont-default:not( textarea ) {
-       font-family: monospace;
- }
- /* Keep this rule separate from the :not rule above so it still works in older browsers */
  .mw-editfont-monospace {
        font-family: monospace;
  }
  li span.deleted,
  span.history-deleted {
        text-decoration: line-through;
 -      color: #888;
 +      color: #72777d;
        font-style: italic;
  }
  
@@@ -278,7 -272,7 +272,7 @@@ p.mw-delete-editreasons 
  
  /* The auto-generated edit comments */
  .autocomment {
 -      color: #808080;
 +      color: #72777d;
  }
  
  /** Generic minor/bot/newpage styling (recent changes) */
@@@ -361,11 -355,11 +355,11 @@@ a.mw-selflink:visited 
   * keep in sync with commonPrint.css
   */
  table.wikitable {
 -      margin: 1em 0;
        background-color: #f8f9fa;
 +      color: #222;
 +      margin: 1em 0;
        border: 1px solid #a2a9b1;
        border-collapse: collapse;
 -      color: #000;
  }
  
  table.wikitable > tr > th,