Merge "MessageCache: do not store the EXCESSIVE array as it is only needed for HASH"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 4 Oct 2018 02:43:09 +0000 (02:43 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 4 Oct 2018 02:43:09 +0000 (02:43 +0000)
25 files changed:
RELEASE-NOTES-1.32
docs/hooks.txt
includes/DefaultSettings.php
includes/Title.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/IDatabase.php
includes/preferences/DefaultPreferencesFactory.php
includes/specials/SpecialPreferences.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialWatchlist.php
includes/specials/forms/PreferencesFormLegacy.php
includes/utils/UIDGenerator.php
maintenance/importDump.php
resources/Resources.php
resources/src/mediawiki.special.preferences.ooui/confirmClose.js [new file with mode: 0644]
resources/src/mediawiki.special.preferences.ooui/convertmessagebox.js [new file with mode: 0644]
resources/src/mediawiki.special.preferences.ooui/personalEmail.js [new file with mode: 0644]
resources/src/mediawiki.special.preferences.ooui/timezone.js [new file with mode: 0644]
resources/src/mediawiki.special.preferences/confirmClose.js [deleted file]
resources/src/mediawiki.special.preferences/convertmessagebox.js [deleted file]
resources/src/mediawiki.special.preferences/personalEmail.js [deleted file]
resources/src/mediawiki.special.preferences/timezone.js [deleted file]
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
tests/phpunit/includes/specials/SpecialWatchlistTest.php

index a934950..63d0894 100644 (file)
@@ -329,6 +329,9 @@ because of Phabricator reports.
   phase.
 * The global function wfErrorLog, deprecated since 1.25, has now been removed.
   Use MWLoggerLegacyLogger::emit or UDPTransport.
+ The hooks 'SpecialRecentChangesQuery' & 'SpecialWatchlistQuery', deprecated in
+  1.23, were removed. Instead, use ChangesListSpecialPageStructuredFilters or
+  ChangesListSpecialPageQuery.
 
 === Deprecations in 1.32 ===
 * HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
@@ -497,6 +500,7 @@ because of Phabricator reports.
 * The image_comment_temp database table is merged into the image table and
   deprecated. Since access should be mediated by the CommentStore class, this
   change shouldn't affect external code.
+* (T206147) Database::close() will no longer commit any open transactions.
 
 == Compatibility ==
 MediaWiki 1.32 requires PHP 7.0.0 or later. Although HHVM 3.18.5 or later is
index d82f56e..78ed1b4 100644 (file)
@@ -3338,17 +3338,6 @@ SpecialRecentChanges.
 &$extraOpts: array of added items, to which can be added
 $opts: FormOptions for this request
 
-'SpecialRecentChangesQuery': DEPRECATED since 1.23! Use
-ChangesListSpecialPageStructuredFilters or ChangesListSpecialPageQuery instead.
-Called when building SQL query for SpecialRecentChanges and
-SpecialRecentChangesLinked.
-&$conds: array of WHERE conditionals for query
-&$tables: array of tables to be queried
-&$join_conds: join conditions for the tables
-$opts: FormOptions for this request
-&$query_options: array of options for the database request
-&$select: Array of columns to select
-
 'SpecialResetTokensTokens': Called when building token list for
 SpecialResetTokens.
 &$tokens: array of token information arrays in the format of
@@ -3452,15 +3441,6 @@ SpecialWatchlist. Allows extensions to register custom values they have
 inserted to rc_type so they can be returned as part of the watchlist.
 &$nonRevisionTypes: array of values in the rc_type field of recentchanges table
 
-'SpecialWatchlistQuery': DEPRECATED since 1.23! Use
-ChangesListSpecialPageStructuredFilters or ChangesListSpecialPageQuery instead.
-Called when building sql query for SpecialWatchlist.
-&$conds: array of WHERE conditionals for query
-&$tables: array of tables to be queried
-&$join_conds: join conditions for the tables
-&$fields: array of query fields
-$opts: A FormOptions object with watchlist options for the current request
-
 'TestCanonicalRedirect': Called when about to force a redirect to a canonical
 URL for a title when we have no other parameters on the URL. Gives a chance for
 extensions that alter page view behavior radically to abort that redirect or
index 9ea1a25..2668cd7 100644 (file)
@@ -3282,14 +3282,6 @@ $wgHTMLFormAllowTableFormat = true;
  */
 $wgUseMediaWikiUIEverywhere = false;
 
-/**
- * Temporary variable that determines whether Special:Preferences should use OOUI or not.
- * This will be removed later and OOUI will become the only option.
- *
- * @since 1.32
- */
-$wgOOUIPreferences = true;
-
 /**
  * Whether to label the store-to-database-and-show-to-others button in the editor
  * as "Save page"/"Save changes" if false (the default) or, if true, instead as
index 5b0c3bc..de551b4 100644 (file)
@@ -2447,7 +2447,7 @@ class Title implements LinkTarget {
                # XXX: this might be better using restrictions
 
                if ( $action === 'patrol' ) {
-                       return [];
+                       return $errors;
                }
 
                if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
index 5c0a8c7..1b3e6cc 100644 (file)
@@ -911,7 +911,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $exception = null; // error to throw after disconnecting
 
                if ( $this->conn ) {
-                       // Resolve any dangling transaction first
+                       // Roll back any dangling transaction first
                        if ( $this->trxLevel ) {
                                if ( $this->trxAtomicLevels ) {
                                        // Cannot let incomplete atomic sections be committed
@@ -922,6 +922,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                        );
                                } elseif ( $this->trxAutomatic ) {
                                        // Only the connection manager can commit non-empty DBO_TRX transactions
+                                       // (empty ones we can silently roll back)
                                        if ( $this->writesOrCallbacksPending() ) {
                                                $exception = new DBUnexpectedError(
                                                        $this,
@@ -929,11 +930,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                                        ": mass commit/rollback of peer transaction required (DBO_TRX set)."
                                                );
                                        }
-                               } elseif ( $this->trxLevel ) {
-                                       // Commit explicit transactions as if this was commit()
-                                       $this->queryLogger->warning(
-                                               __METHOD__ . ": writes or callbacks still pending.",
-                                               [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+                               } else {
+                                       // Manual transactions should have been committed or rolled
+                                       // back, even if empty.
+                                       $exception = new DBUnexpectedError(
+                                               $this,
+                                               __METHOD__ . ": transaction is still open (from {$this->trxFname})."
                                        );
                                }
 
@@ -944,15 +946,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                        );
                                }
 
-                               // Commit or rollback the changes and run any callbacks as needed
-                               if ( $this->trxStatus === self::STATUS_TRX_OK && !$exception ) {
-                                       $this->commit(
-                                               __METHOD__,
-                                               $this->trxAutomatic ? self::FLUSHING_INTERNAL : self::FLUSHING_ONE
-                                       );
-                               } else {
-                                       $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
-                               }
+                               // Rollback the changes and run any callbacks as needed
+                               $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
                        }
 
                        // Close the actual connection in the binding handle
index b1582a1..83216d4 100644 (file)
@@ -486,8 +486,8 @@ interface IDatabase {
         * Close the database connection
         *
         * This should only be called after any transactions have been resolved,
-        * aside from read-only transactions (assuming no callbacks are registered).
-        * If a transaction is still open anyway, it will be committed if possible.
+        * aside from read-only automatic transactions (assuming no callbacks are registered).
+        * If a transaction is still open anyway, it will be rolled back.
         *
         * @throws DBError
         * @return bool Operation success. true if already closed.
index 880da60..f32b1b7 100644 (file)
@@ -26,7 +26,6 @@ use DateTimeZone;
 use Exception;
 use Hooks;
 use Html;
-use HtmlArmor;
 use HTMLForm;
 use HTMLFormField;
 use IContextSource;
@@ -49,7 +48,6 @@ use Psr\Log\LoggerAwareTrait;
 use Psr\Log\NullLogger;
 use Skin;
 use SpecialPage;
-use SpecialPreferences;
 use Status;
 use Title;
 use UnexpectedValueException;
@@ -113,12 +111,10 @@ class DefaultPreferencesFactory implements PreferencesFactory {
        public function getFormDescriptor( User $user, IContextSource $context ) {
                $preferences = [];
 
-               if ( SpecialPreferences::isOouiEnabled( $context ) ) {
-                       OutputPage::setupOOUI(
-                               strtolower( $context->getSkin()->getSkinName() ),
-                               $context->getLanguage()->getDir()
-                       );
-               }
+               OutputPage::setupOOUI(
+                       strtolower( $context->getSkin()->getSkinName() ),
+                       $context->getLanguage()->getDir()
+               );
 
                $canIPUseHTTPS = wfCanIPUseHTTPS( $context->getRequest()->getIP() );
                $this->profilePreferences( $user, $context, $preferences, $canIPUseHTTPS );
@@ -247,8 +243,6 @@ class DefaultPreferencesFactory implements PreferencesFactory {
        protected function profilePreferences(
                User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS
        ) {
-               $oouiEnabled = SpecialPreferences::isOouiEnabled( $context );
-
                // retrieving user name for GENDER and misc.
                $userName = $user->getName();
 
@@ -360,23 +354,15 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
                        new PasswordAuthenticationRequest(), false )->isGood()
                ) {
-                       if ( $oouiEnabled ) {
-                               $link = new \OOUI\ButtonWidget( [
+                       $defaultPreferences['password'] = [
+                               'type' => 'info',
+                               'raw' => true,
+                               'default' => (string)new \OOUI\ButtonWidget( [
                                        'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
                                                'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
                                        ] ),
                                        'label' => $context->msg( 'prefs-resetpass' )->text(),
-                               ] );
-                       } else {
-                               $link = $this->linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ),
-                                       $context->msg( 'prefs-resetpass' )->text(), [],
-                                       [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
-                       }
-
-                       $defaultPreferences['password'] = [
-                               'type' => 'info',
-                               'raw' => true,
-                               'default' => (string)$link,
+                               ] ),
                                'label-message' => 'yourpassword',
                                'section' => 'personal/info',
                        ];
@@ -524,28 +510,15 @@ class DefaultPreferencesFactory implements PreferencesFactory {
 
                                $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
                                if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
-                                       if ( $oouiEnabled ) {
-                                               $link = new \OOUI\ButtonWidget( [
-                                                       'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
-                                                               'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
-                                                       ] ),
-                                                       'label' =>
-                                                               $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
-                                               ] );
-
-                                               $emailAddress .= $emailAddress == '' ? $link : ( '<br />' . $link );
-                                       } else {
-                                               $link = $this->linkRenderer->makeLink(
-                                                       SpecialPage::getTitleFor( 'ChangeEmail' ),
+                                       $button = new \OOUI\ButtonWidget( [
+                                               'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
+                                                       'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+                                               ] ),
+                                               'label' =>
                                                        $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
-                                                       [],
-                                                       [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+                                       ] );
 
-                                               $emailAddress .= $emailAddress == '' ? $link : (
-                                                       $context->msg( 'word-separator' )->escaped()
-                                                       . $context->msg( 'parentheses' )->rawParams( $link )->escaped()
-                                               );
-                                       }
+                                       $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
                                }
 
                                $defaultPreferences['emailaddress'] = [
@@ -579,19 +552,11 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                                                $emailauthenticationclass = 'mw-email-authenticated';
                                        } else {
                                                $disableEmailPrefs = true;
-                                               if ( $oouiEnabled ) {
-                                                       $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
-                                                               new \OOUI\ButtonWidget( [
-                                                                       'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
-                                                                       'label' => $context->msg( 'emailconfirmlink' )->text(),
-                                                               ] );
-                                               } else {
-                                                       $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
-                                                               $this->linkRenderer->makeKnownLink(
-                                                                       SpecialPage::getTitleFor( 'Confirmemail' ),
-                                                                       $context->msg( 'emailconfirmlink' )->text()
-                                                               ) . '<br />';
-                                               }
+                                               $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
+                                                       new \OOUI\ButtonWidget( [
+                                                               'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
+                                                               'label' => $context->msg( 'emailconfirmlink' )->text(),
+                                                       ] );
                                                $emailauthenticationclass = "mw-email-not-authenticated";
                                        }
                                } else {
@@ -1076,8 +1041,6 @@ class DefaultPreferencesFactory implements PreferencesFactory {
        protected function watchlistPreferences(
                User $user, IContextSource $context, &$defaultPreferences
        ) {
-               $oouiEnabled = SpecialPreferences::isOouiEnabled( $context );
-
                $watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
 
                # # Watchlist #####################################
@@ -1091,29 +1054,20 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        ];
                        foreach ( $editWatchlistModes as $mode => $options ) {
                                // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
-                               if ( $oouiEnabled ) {
-                                       $editWatchlistLinks .=
-                                               new \OOUI\ButtonWidget( [
-                                                       'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
-                                                       'flags' => $options[ 'flags' ],
-                                                       'label' => new \OOUI\HtmlSnippet(
-                                                               $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
-                                                       ),
-                                               ] );
-                               } else {
-                                       $editWatchlistLinksOld[] = $this->linkRenderer->makeKnownLink(
-                                               SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] ),
-                                               new HtmlArmor( $context->msg( "prefs-editwatchlist-{$mode}" )->parse() )
-                                       );
-                               }
+                               $editWatchlistLinks .=
+                                       new \OOUI\ButtonWidget( [
+                                               'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
+                                               'flags' => $options[ 'flags' ],
+                                               'label' => new \OOUI\HtmlSnippet(
+                                                       $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
+                                               ),
+                                       ] );
                        }
 
                        $defaultPreferences['editwatchlist'] = [
                                'type' => 'info',
                                'raw' => true,
-                               'default' => $oouiEnabled ?
-                                       $editWatchlistLinks :
-                                       $context->getLanguage()->pipeList( $editWatchlistLinksOld ),
+                               'default' => $editWatchlistLinks,
                                'label-message' => 'prefs-editwatchlist-label',
                                'section' => 'watchlist/editwatchlist',
                        ];
@@ -1237,30 +1191,20 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        'type' => 'api',
                ];
 
-               if ( $oouiEnabled ) {
-                       $tokenButton = new \OOUI\ButtonWidget( [
-                               'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
-                                       'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
-                               ] ),
-                               'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
-                       ] );
-                       $defaultPreferences['watchlisttoken-info'] = [
-                               'type' => 'info',
-                               'section' => 'watchlist/tokenwatchlist',
-                               'label-message' => 'prefs-watchlist-token',
-                               'help-message' => 'prefs-help-tokenmanagement',
-                               'raw' => true,
-                               'default' => (string)$tokenButton,
-                       ];
-               } else {
-                       $defaultPreferences['watchlisttoken-info'] = [
-                               'type' => 'info',
-                               'section' => 'watchlist/tokenwatchlist',
-                               'label-message' => 'prefs-watchlist-token',
-                               'default' => $user->getTokenFromOption( 'watchlisttoken' ),
-                               'help-message' => 'prefs-help-watchlist-token2',
-                       ];
-               }
+               $tokenButton = new \OOUI\ButtonWidget( [
+                       'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
+                               'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+                       ] ),
+                       'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
+               ] );
+               $defaultPreferences['watchlisttoken-info'] = [
+                       'type' => 'info',
+                       'section' => 'watchlist/tokenwatchlist',
+                       'label-message' => 'prefs-watchlist-token',
+                       'help-message' => 'prefs-help-tokenmanagement',
+                       'raw' => true,
+                       'default' => (string)$tokenButton,
+               ];
 
                $defaultPreferences['wlenhancedfilters-disable'] = [
                        'type' => 'toggle',
@@ -1484,10 +1428,8 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                $formClass = PreferencesFormLegacy::class,
                array $remove = []
        ) {
-               if ( SpecialPreferences::isOouiEnabled( $context ) ) {
-                       // We use ButtonWidgets in some of the getPreferences() functions
-                       $context->getOutput()->enableOOUI();
-               }
+               // We use ButtonWidgets in some of the getPreferences() functions
+               $context->getOutput()->enableOOUI();
 
                $formDescriptor = $this->getFormDescriptor( $user, $context );
                if ( count( $remove ) ) {
@@ -1690,13 +1632,6 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                                $urlOptions['eauth'] = 1;
                        }
 
-                       if (
-                               $context->getRequest()->getFuzzyBool( 'ooui' ) !==
-                               $context->getConfig()->get( 'OOUIPreferences' )
-                       ) {
-                               $urlOptions[ 'ooui' ] = $context->getRequest()->getFuzzyBool( 'ooui' ) ? 1 : 0;
-                       }
-
                        $urlOptions += $form->getExtraSuccessRedirectParameters();
 
                        $url = $form->getTitle()->getFullURL( $urlOptions );
index 08b33c1..04be22b 100644 (file)
@@ -29,35 +29,15 @@ use MediaWiki\MediaWikiServices;
  * @ingroup SpecialPage
  */
 class SpecialPreferences extends SpecialPage {
-       /**
-        * @var bool Whether OOUI should be enabled here
-        */
-       private $oouiEnabled = false;
-
        function __construct() {
                parent::__construct( 'Preferences' );
        }
 
-       /**
-        * Check if OOUI mode is enabled, by config or query string
-        *
-        * @since 1.32
-        * @param IContextSource $context The context.
-        * @return bool
-        */
-       public static function isOouiEnabled( IContextSource $context ) {
-               return $context->getRequest()->getFuzzyBool( 'ooui',
-                       $context->getConfig()->get( 'OOUIPreferences' )
-               );
-       }
-
        public function doesWrites() {
                return true;
        }
 
        public function execute( $par ) {
-               $this->oouiEnabled = static::isOouiEnabled( $this->getContext() );
-
                $this->setHeaders();
                $this->outputHeader();
                $out = $this->getOutput();
@@ -72,14 +52,9 @@ class SpecialPreferences extends SpecialPage {
                        return;
                }
 
-               if ( $this->oouiEnabled ) {
-                       $out->addModules( 'mediawiki.special.preferences.ooui' );
-                       $out->addModuleStyles( 'mediawiki.special.preferences.styles.ooui' );
-                       $out->addModuleStyles( 'oojs-ui-widgets.styles' );
-               } else {
-                       $out->addModules( 'mediawiki.special.preferences' );
-                       $out->addModuleStyles( 'mediawiki.special.preferences.styles' );
-               }
+               $out->addModules( 'mediawiki.special.preferences.ooui' );
+               $out->addModuleStyles( 'mediawiki.special.preferences.styles.ooui' );
+               $out->addModuleStyles( 'oojs-ui-widgets.styles' );
 
                $session = $this->getRequest()->getSession();
                if ( $session->get( 'specialPreferencesSaveSuccess' ) ) {
@@ -112,49 +87,14 @@ class SpecialPreferences extends SpecialPage {
                $htmlForm = $this->getFormObject( $user, $this->getContext() );
                $sectionTitles = $htmlForm->getPreferenceSections();
 
-               if ( $this->oouiEnabled ) {
-                       $prefTabs = [];
-                       foreach ( $sectionTitles as $key ) {
-                               $prefTabs[] = [
-                                       'name' => $key,
-                                       'label' => $htmlForm->getLegend( $key ),
-                               ];
-                       }
-                       $out->addJsConfigVars( 'wgPreferencesTabs', $prefTabs );
-               } else {
-
-                       $prefTabs = '';
-                       foreach ( $sectionTitles as $key ) {
-                               $prefTabs .= Html::rawElement( 'li',
-                                       [
-                                               'role' => 'presentation',
-                                               'class' => ( $key === 'personal' ) ? 'selected' : null
-                                       ],
-                                       Html::element( 'a',
-                                               [
-                                                       'id' => 'preftab-' . $key,
-                                                       'role' => 'tab',
-                                                       'href' => '#mw-prefsection-' . $key,
-                                                       'aria-controls' => 'mw-prefsection-' . $key,
-                                                       'aria-selected' => ( $key === 'personal' ) ? 'true' : 'false',
-                                                       'tabIndex' => ( $key === 'personal' ) ? 0 : -1,
-                                               ],
-                                               $htmlForm->getLegend( $key )
-                                       )
-                               );
-                       }
-
-                       $out->addHTML(
-                               Html::rawElement( 'ul',
-                                       [
-                                               'id' => 'preftoc',
-                                               'role' => 'tablist'
-                                       ],
-                                       $prefTabs )
-                       );
+               $prefTabs = [];
+               foreach ( $sectionTitles as $key ) {
+                       $prefTabs[] = [
+                               'name' => $key,
+                               'label' => $htmlForm->getLegend( $key ),
+                       ];
                }
-
-               $htmlForm->addHiddenField( 'ooui', $this->oouiEnabled ? '1' : '0' );
+               $out->addJsConfigVars( 'wgPreferencesTabs', $prefTabs );
 
                $htmlForm->show();
        }
@@ -167,11 +107,7 @@ class SpecialPreferences extends SpecialPage {
         */
        protected function getFormObject( $user, IContextSource $context ) {
                $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
-               if ( $this->oouiEnabled ) {
-                       $form = $preferencesFactory->getForm( $user, $context, PreferencesFormOOUI::class );
-               } else {
-                       $form = $preferencesFactory->getForm( $user, $context, PreferencesFormLegacy::class );
-               }
+               $form = $preferencesFactory->getForm( $user, $context, PreferencesFormOOUI::class );
                return $form;
        }
 
index 3e8bf12..170f792 100644 (file)
@@ -339,17 +339,6 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                return $rows;
        }
 
-       protected function runMainQueryHook( &$tables, &$fields, &$conds,
-               &$query_options, &$join_conds, $opts
-       ) {
-               return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
-                       && Hooks::run(
-                               'SpecialRecentChangesQuery',
-                               [ &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ],
-                               '1.23'
-                       );
-       }
-
        protected function getDB() {
                return wfGetDB( DB_REPLICA, 'recentchanges' );
        }
index 2445c10..feb449c 100644 (file)
@@ -425,17 +425,6 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                );
        }
 
-       protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options,
-               &$join_conds, $opts
-       ) {
-               return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
-                       && Hooks::run(
-                               'SpecialWatchlistQuery',
-                               [ &$conds, &$tables, &$join_conds, &$fields, $opts ],
-                               '1.23'
-                       );
-       }
-
        /**
         * Return a IDatabase object for reading
         *
index c1f1026..951e5ce 100644 (file)
  * @file
  */
 
-use MediaWiki\MediaWikiServices;
-
 /**
  * Form to edit user preferences.
  *
  * @since 1.32
  */
-class PreferencesFormLegacy 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 = $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 ) {
-               $aliasKey = ( $key === 'optoutwatchlist' || $key === 'optoutrc' ) ? 'opt-out' : $key;
-               $legend = parent::getLegend( $aliasKey );
-               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' ) );
-       }
+class PreferencesFormLegacy extends PreferencesFormOOUI {
+       // No-op
 }
 
 /**
  * Retain the old class name for backwards compatibility.
- * In the future, this alias will be changed to point to PreferencesFormOOUI.
  *
  * @deprecated since 1.32
  */
index 52740c9..4b5410e 100644 (file)
@@ -475,11 +475,12 @@ class UIDGenerator {
                // microtime() and gettimeofday() can drift from time() at least on Windows.
                // The drift is immediate for processes running while the system clock changes.
                // time() does not have this problem. See https://bugs.php.net/bug.php?id=42659.
-               if ( abs( time() - $time[0] ) >= 2 ) {
+               $drift = time() - $time[0];
+               if ( abs( $drift ) >= 2 ) {
                        // We don't want processes using too high or low timestamps to avoid duplicate
                        // UIDs and clock sequence number churn. This process should just be restarted.
                        flock( $handle, LOCK_UN ); // abort
-                       throw new RuntimeException( "Process clock is outdated or drifted." );
+                       throw new RuntimeException( "Process clock is outdated or drifted ({$drift}s)." );
                }
                // If microtime() is synced and a clock change was detected, then the clock went back
                if ( $clockChanged ) {
index 9834473..7c20748 100644 (file)
@@ -84,7 +84,7 @@ TEXT
                );
                $this->addOption( 'image-base-path', 'Import files from a specified path', false, true );
                $this->addOption( 'skip-to', 'Start from nth page by skipping first n-1 pages', false, true );
-               $this->addOption( 'username-interwiki', 'Use interwiki usernames with this prefix', false, true );
+               $this->addOption( 'username-prefix', 'Prefix for interwiki usernames', false, true );
                $this->addOption( 'no-local-users',
                        'Treat all usernames as interwiki. ' .
                        'The default is to assign edits to local users where they exist.',
index 9c832dc..9603830 100644 (file)
@@ -2139,42 +2139,15 @@ return [
                        'oojs-ui-core',
                ],
        ],
-       'mediawiki.special.preferences' => [
-               'targets' => [ 'desktop', 'mobile' ],
-               'scripts' => [
-                       'resources/src/mediawiki.special.preferences/confirmClose.js',
-                       'resources/src/mediawiki.special.preferences/convertmessagebox.js',
-                       'resources/src/mediawiki.special.preferences/tabs.legacy.js',
-                       'resources/src/mediawiki.special.preferences/timezone.js',
-                       'resources/src/mediawiki.special.preferences/personalEmail.js',
-               ],
-               'messages' => [
-                       'prefs-tabs-navigation-hint',
-                       'prefswarning-warning',
-                       'saveprefs',
-                       'savedprefs',
-               ],
-               'dependencies' => [
-                       'mediawiki.language',
-                       'mediawiki.confirmCloseWindow',
-                       'mediawiki.notification.convertmessagebox',
-               ],
-       ],
-       'mediawiki.special.preferences.styles' => [
-               'targets' => [ 'desktop', 'mobile' ],
-               // legacy
-               'styles' => 'resources/src/mediawiki.special.preferences.styles.css',
-       ],
        'mediawiki.special.preferences.ooui' => [
                'targets' => [ 'desktop', 'mobile' ],
                'scripts' => [
-                       // FIXME: This uses files already belonging to another module
-                       'resources/src/mediawiki.special.preferences/confirmClose.js',
-                       'resources/src/mediawiki.special.preferences/convertmessagebox.js',
+                       'resources/src/mediawiki.special.preferences.ooui/confirmClose.js',
+                       'resources/src/mediawiki.special.preferences.ooui/convertmessagebox.js',
                        'resources/src/mediawiki.special.preferences.ooui/editfont.js',
                        'resources/src/mediawiki.special.preferences.ooui/tabs.js',
-                       'resources/src/mediawiki.special.preferences/timezone.js',
-                       'resources/src/mediawiki.special.preferences/personalEmail.js',
+                       'resources/src/mediawiki.special.preferences.ooui/timezone.js',
+                       'resources/src/mediawiki.special.preferences.ooui/personalEmail.js',
                ],
                'messages' => [
                        'prefs-tabs-navigation-hint',
diff --git a/resources/src/mediawiki.special.preferences.ooui/confirmClose.js b/resources/src/mediawiki.special.preferences.ooui/confirmClose.js
new file mode 100644 (file)
index 0000000..55d7ce9
--- /dev/null
@@ -0,0 +1,82 @@
+/*!
+ * JavaScript for Special:Preferences: Enable save button and prevent the window being accidentally
+ * closed when any form field is changed.
+ */
+( function () {
+       $( function () {
+               var allowCloseWindow, saveButton, restoreButton;
+
+               // Check if all of the form values are unchanged.
+               // (This function could be changed to infuse and check OOUI widgets, but that would only make it
+               // slower and more complicated. It works fine to treat them as HTML elements.)
+               function isPrefsChanged() {
+                       var inputs = $( '#mw-prefs-form :input[name]' ),
+                               input, $input, inputType,
+                               index, optIndex,
+                               opt;
+
+                       for ( index = 0; index < inputs.length; index++ ) {
+                               input = inputs[ index ];
+                               $input = $( input );
+
+                               // Different types of inputs have different methods for accessing defaults
+                               if ( $input.is( 'select' ) ) {
+                                       // <select> has the property defaultSelected for each option
+                                       for ( optIndex = 0; optIndex < input.options.length; optIndex++ ) {
+                                               opt = input.options[ optIndex ];
+                                               if ( opt.selected !== opt.defaultSelected ) {
+                                                       return true;
+                                               }
+                                       }
+                               } else if ( $input.is( 'input' ) || $input.is( 'textarea' ) ) {
+                                       // <input> has defaultValue or defaultChecked
+                                       inputType = input.type;
+                                       if ( inputType === 'radio' || inputType === 'checkbox' ) {
+                                               if ( input.checked !== input.defaultChecked ) {
+                                                       return true;
+                                               }
+                                       } else if ( input.value !== input.defaultValue ) {
+                                               return true;
+                                       }
+                               }
+                       }
+
+                       return false;
+               }
+
+               saveButton = OO.ui.infuse( $( '#prefcontrol' ) );
+               restoreButton = OO.ui.infuse( $( '#mw-prefs-restoreprefs' ) );
+
+               // Disable the button to save preferences unless preferences have changed
+               // Check if preferences have been changed before JS has finished loading
+               saveButton.setDisabled( !isPrefsChanged() );
+               // Attach capturing event handlers to the document, to catch events inside OOUI dropdowns:
+               // * Use capture because OO.ui.SelectWidget also does, and it stops event propagation,
+               //   so the event is not fired on descendant elements
+               // * Attach to the document because the dropdowns are in the .oo-ui-defaultOverlay element
+               //   (and it doesn't exist yet at this point, so we can't attach them to it)
+               [ 'change', 'keyup', 'mouseup' ].forEach( function ( eventType ) {
+                       document.addEventListener( eventType, function () {
+                               // Make sure SelectWidget's event handlers run first
+                               setTimeout( function () {
+                                       saveButton.setDisabled( !isPrefsChanged() );
+                               } );
+                       }, true );
+               } );
+
+               // Set up a message to notify users if they try to leave the page without
+               // saving.
+               allowCloseWindow = mw.confirmCloseWindow( {
+                       test: isPrefsChanged,
+                       message: mw.msg( 'prefswarning-warning', mw.msg( 'saveprefs' ) ),
+                       namespace: 'prefswarning'
+               } );
+               $( '#mw-prefs-form' ).on( 'submit', allowCloseWindow.release );
+               restoreButton.on( 'click', function () {
+                       allowCloseWindow.release();
+                       // The default behavior of events in OOUI is always prevented. Follow the link manually.
+                       // Note that middle-click etc. still works, as it doesn't emit a OOUI 'click' event.
+                       location.href = restoreButton.getHref();
+               } );
+       } );
+}() );
diff --git a/resources/src/mediawiki.special.preferences.ooui/convertmessagebox.js b/resources/src/mediawiki.special.preferences.ooui/convertmessagebox.js
new file mode 100644 (file)
index 0000000..45bdda2
--- /dev/null
@@ -0,0 +1,9 @@
+/*!
+ * JavaScript for Special:Preferences: Check for successbox to replace with notifications.
+ */
+( function () {
+       $( function () {
+               var convertmessagebox = require( 'mediawiki.notification.convertmessagebox' );
+               convertmessagebox();
+       } );
+}() );
diff --git a/resources/src/mediawiki.special.preferences.ooui/personalEmail.js b/resources/src/mediawiki.special.preferences.ooui/personalEmail.js
new file mode 100644 (file)
index 0000000..10023ef
--- /dev/null
@@ -0,0 +1,24 @@
+/*!
+ * JavaScript for Special:Preferences: Email preferences better UX
+ */
+( function () {
+       $( function () {
+               var allowEmail, allowEmailFromNewUsers;
+
+               allowEmail = $( '#wpAllowEmail' );
+               allowEmailFromNewUsers = $( '#wpAllowEmailFromNewUsers' );
+
+               function toggleDisabled() {
+                       if ( allowEmail.is( ':checked' ) && allowEmail.is( ':enabled' ) ) {
+                               allowEmailFromNewUsers.prop( 'disabled', false );
+                       } else {
+                               allowEmailFromNewUsers.prop( 'disabled', true );
+                       }
+               }
+
+               if ( allowEmail ) {
+                       allowEmail.on( 'change', toggleDisabled );
+                       toggleDisabled();
+               }
+       } );
+}() );
diff --git a/resources/src/mediawiki.special.preferences.ooui/timezone.js b/resources/src/mediawiki.special.preferences.ooui/timezone.js
new file mode 100644 (file)
index 0000000..7ab3be0
--- /dev/null
@@ -0,0 +1,99 @@
+/*!
+ * JavaScript for Special:Preferences: Timezone field enhancements.
+ */
+( function () {
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               var timezoneWidget, $localtimeHolder, servertime,
+                       $target = $root.find( '#wpTimeCorrection' );
+
+               if (
+                       !$target.length ||
+                       $target.closest( '.mw-htmlform-autoinfuse-lazy' ).length
+               ) {
+                       return;
+               }
+
+               // Timezone functions.
+               // Guesses Timezone from browser and updates fields onchange.
+
+               // This is identical to OO.ui.infuse( ... ), but it makes the class name of the result known.
+               try {
+                       timezoneWidget = mw.widgets.SelectWithInputWidget.static.infuse( $target );
+               } catch ( err ) {
+                       // This preference could theoretically be disabled ($wgHiddenPrefs)
+                       timezoneWidget = null;
+               }
+
+               $localtimeHolder = $( '#wpLocalTime' );
+               servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 10 );
+
+               function minutesToHours( min ) {
+                       var tzHour = Math.floor( Math.abs( min ) / 60 ),
+                               tzMin = Math.abs( min ) % 60,
+                               tzString = ( ( min >= 0 ) ? '' : '-' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour +
+                                       ':' + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin;
+                       return tzString;
+               }
+
+               function hoursToMinutes( hour ) {
+                       var minutes,
+                               arr = hour.split( ':' );
+
+                       arr[ 0 ] = parseInt( arr[ 0 ], 10 );
+
+                       if ( arr.length === 1 ) {
+                               // Specification is of the form [-]XX
+                               minutes = arr[ 0 ] * 60;
+                       } else {
+                               // Specification is of the form [-]XX:XX
+                               minutes = Math.abs( arr[ 0 ] ) * 60 + parseInt( arr[ 1 ], 10 );
+                               if ( arr[ 0 ] < 0 ) {
+                                       minutes *= -1;
+                               }
+                       }
+                       // Gracefully handle non-numbers.
+                       if ( isNaN( minutes ) ) {
+                               return 0;
+                       } else {
+                               return minutes;
+                       }
+               }
+
+               function updateTimezoneSelection() {
+                       var minuteDiff, localTime,
+                               type = timezoneWidget.dropdowninput.getValue();
+
+                       if ( type === 'other' ) {
+                               // User specified time zone manually in <input>
+                               // Grab data from the textbox, parse it.
+                               minuteDiff = hoursToMinutes( timezoneWidget.textinput.getValue() );
+                       } else {
+                               // Time zone not manually specified by user
+                               if ( type === 'guess' ) {
+                                       // Get browser timezone & fill it in
+                                       minuteDiff = -( new Date().getTimezoneOffset() );
+                                       timezoneWidget.textinput.setValue( minutesToHours( minuteDiff ) );
+                                       timezoneWidget.dropdowninput.setValue( 'other' );
+                               } else {
+                                       // Grab data from the dropdown value
+                                       minuteDiff = parseInt( type.split( '|' )[ 1 ], 10 ) || 0;
+                               }
+                       }
+
+                       // Determine local time from server time and minutes difference, for display.
+                       localTime = servertime + minuteDiff;
+
+                       // Bring time within the [0,1440) range.
+                       localTime = ( ( localTime % 1440 ) + 1440 ) % 1440;
+
+                       $localtimeHolder.text( mw.language.convertNumber( minutesToHours( localTime ) ) );
+               }
+
+               if ( timezoneWidget ) {
+                       timezoneWidget.dropdowninput.on( 'change', updateTimezoneSelection );
+                       timezoneWidget.textinput.on( 'change', updateTimezoneSelection );
+                       updateTimezoneSelection();
+               }
+
+       } );
+}() );
diff --git a/resources/src/mediawiki.special.preferences/confirmClose.js b/resources/src/mediawiki.special.preferences/confirmClose.js
deleted file mode 100644 (file)
index 03309de..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*!
- * JavaScript for Special:Preferences: Enable save button and prevent the window being accidentally
- * closed when any form field is changed.
- */
-( function () {
-       $( function () {
-               var allowCloseWindow, saveButton, restoreButton,
-                       oouiEnabled = $( '#mw-prefs-form' ).hasClass( 'mw-htmlform-ooui' );
-
-               // Check if all of the form values are unchanged.
-               // (This function could be changed to infuse and check OOUI widgets, but that would only make it
-               // slower and more complicated. It works fine to treat them as HTML elements.)
-               function isPrefsChanged() {
-                       var inputs = $( '#mw-prefs-form :input[name]' ),
-                               input, $input, inputType,
-                               index, optIndex,
-                               opt;
-
-                       for ( index = 0; index < inputs.length; index++ ) {
-                               input = inputs[ index ];
-                               $input = $( input );
-
-                               // Different types of inputs have different methods for accessing defaults
-                               if ( $input.is( 'select' ) ) {
-                                       // <select> has the property defaultSelected for each option
-                                       for ( optIndex = 0; optIndex < input.options.length; optIndex++ ) {
-                                               opt = input.options[ optIndex ];
-                                               if ( opt.selected !== opt.defaultSelected ) {
-                                                       return true;
-                                               }
-                                       }
-                               } else if ( $input.is( 'input' ) || $input.is( 'textarea' ) ) {
-                                       // <input> has defaultValue or defaultChecked
-                                       inputType = input.type;
-                                       if ( inputType === 'radio' || inputType === 'checkbox' ) {
-                                               if ( input.checked !== input.defaultChecked ) {
-                                                       return true;
-                                               }
-                                       } else if ( input.value !== input.defaultValue ) {
-                                               return true;
-                                       }
-                               }
-                       }
-
-                       return false;
-               }
-
-               if ( oouiEnabled ) {
-                       saveButton = OO.ui.infuse( $( '#prefcontrol' ) );
-                       restoreButton = OO.ui.infuse( $( '#mw-prefs-restoreprefs' ) );
-
-                       // Disable the button to save preferences unless preferences have changed
-                       // Check if preferences have been changed before JS has finished loading
-                       saveButton.setDisabled( !isPrefsChanged() );
-                       // Attach capturing event handlers to the document, to catch events inside OOUI dropdowns:
-                       // * Use capture because OO.ui.SelectWidget also does, and it stops event propagation,
-                       //   so the event is not fired on descendant elements
-                       // * Attach to the document because the dropdowns are in the .oo-ui-defaultOverlay element
-                       //   (and it doesn't exist yet at this point, so we can't attach them to it)
-                       [ 'change', 'keyup', 'mouseup' ].forEach( function ( eventType ) {
-                               document.addEventListener( eventType, function () {
-                                       // Make sure SelectWidget's event handlers run first
-                                       setTimeout( function () {
-                                               saveButton.setDisabled( !isPrefsChanged() );
-                                       } );
-                               }, true );
-                       } );
-               } else {
-                       // Disable the button to save preferences unless preferences have changed
-                       // Check if preferences have been changed before JS has finished loading
-                       $( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
-                       $( '#preferences > fieldset' ).on( 'change keyup mouseup', function () {
-                               $( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
-                       } );
-               }
-
-               // Set up a message to notify users if they try to leave the page without
-               // saving.
-               allowCloseWindow = mw.confirmCloseWindow( {
-                       test: isPrefsChanged,
-                       message: mw.msg( 'prefswarning-warning', mw.msg( 'saveprefs' ) ),
-                       namespace: 'prefswarning'
-               } );
-               $( '#mw-prefs-form' ).on( 'submit', allowCloseWindow.release );
-               if ( oouiEnabled ) {
-                       restoreButton.on( 'click', function () {
-                               allowCloseWindow.release();
-                               // The default behavior of events in OOUI is always prevented. Follow the link manually.
-                               // Note that middle-click etc. still works, as it doesn't emit a OOUI 'click' event.
-                               location.href = restoreButton.getHref();
-                       } );
-               } else {
-                       $( '#mw-prefs-restoreprefs' ).on( 'click', allowCloseWindow.release );
-               }
-       } );
-}() );
diff --git a/resources/src/mediawiki.special.preferences/convertmessagebox.js b/resources/src/mediawiki.special.preferences/convertmessagebox.js
deleted file mode 100644 (file)
index 45bdda2..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * JavaScript for Special:Preferences: Check for successbox to replace with notifications.
- */
-( function () {
-       $( function () {
-               var convertmessagebox = require( 'mediawiki.notification.convertmessagebox' );
-               convertmessagebox();
-       } );
-}() );
diff --git a/resources/src/mediawiki.special.preferences/personalEmail.js b/resources/src/mediawiki.special.preferences/personalEmail.js
deleted file mode 100644 (file)
index 10023ef..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*!
- * JavaScript for Special:Preferences: Email preferences better UX
- */
-( function () {
-       $( function () {
-               var allowEmail, allowEmailFromNewUsers;
-
-               allowEmail = $( '#wpAllowEmail' );
-               allowEmailFromNewUsers = $( '#wpAllowEmailFromNewUsers' );
-
-               function toggleDisabled() {
-                       if ( allowEmail.is( ':checked' ) && allowEmail.is( ':enabled' ) ) {
-                               allowEmailFromNewUsers.prop( 'disabled', false );
-                       } else {
-                               allowEmailFromNewUsers.prop( 'disabled', true );
-                       }
-               }
-
-               if ( allowEmail ) {
-                       allowEmail.on( 'change', toggleDisabled );
-                       toggleDisabled();
-               }
-       } );
-}() );
diff --git a/resources/src/mediawiki.special.preferences/timezone.js b/resources/src/mediawiki.special.preferences/timezone.js
deleted file mode 100644 (file)
index bdfe9a1..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-/*!
- * JavaScript for Special:Preferences: Timezone field enhancements.
- */
-( function () {
-       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
-               var $tzSelect, $tzTextbox, timezoneWidget, $localtimeHolder, servertime,
-                       $target = $root.find( '#wpTimeCorrection' ),
-                       oouiEnabled = $( '#mw-prefs-form' ).hasClass( 'mw-htmlform-ooui' );
-
-               if (
-                       !$target.length ||
-                       $target.closest( '.mw-htmlform-autoinfuse-lazy' ).length
-               ) {
-                       return;
-               }
-
-               // Timezone functions.
-               // Guesses Timezone from browser and updates fields onchange.
-
-               if ( oouiEnabled ) {
-                       // This is identical to OO.ui.infuse( ... ), but it makes the class name of the result known.
-                       try {
-                               timezoneWidget = mw.widgets.SelectWithInputWidget.static.infuse( $target );
-                       } catch ( err ) {
-                               // This preference could theoretically be disabled ($wgHiddenPrefs)
-                               timezoneWidget = null;
-                       }
-               } else {
-                       $tzSelect = $target;
-                       $tzTextbox = $( '#wpTimeCorrection-other' );
-               }
-
-               $localtimeHolder = $( '#wpLocalTime' );
-               servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 10 );
-
-               function minutesToHours( min ) {
-                       var tzHour = Math.floor( Math.abs( min ) / 60 ),
-                               tzMin = Math.abs( min ) % 60,
-                               tzString = ( ( min >= 0 ) ? '' : '-' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour +
-                                       ':' + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin;
-                       return tzString;
-               }
-
-               function hoursToMinutes( hour ) {
-                       var minutes,
-                               arr = hour.split( ':' );
-
-                       arr[ 0 ] = parseInt( arr[ 0 ], 10 );
-
-                       if ( arr.length === 1 ) {
-                               // Specification is of the form [-]XX
-                               minutes = arr[ 0 ] * 60;
-                       } else {
-                               // Specification is of the form [-]XX:XX
-                               minutes = Math.abs( arr[ 0 ] ) * 60 + parseInt( arr[ 1 ], 10 );
-                               if ( arr[ 0 ] < 0 ) {
-                                       minutes *= -1;
-                               }
-                       }
-                       // Gracefully handle non-numbers.
-                       if ( isNaN( minutes ) ) {
-                               return 0;
-                       } else {
-                               return minutes;
-                       }
-               }
-
-               function updateTimezoneSelection() {
-                       var minuteDiff, localTime,
-                               type = oouiEnabled ? timezoneWidget.dropdowninput.getValue() : $tzSelect.val(),
-                               val = oouiEnabled ? timezoneWidget.textinput.getValue() : $tzTextbox.val();
-
-                       if ( type === 'other' ) {
-                               // User specified time zone manually in <input>
-                               // Grab data from the textbox, parse it.
-                               minuteDiff = hoursToMinutes( val );
-                       } else {
-                               // Time zone not manually specified by user
-                               if ( type === 'guess' ) {
-                                       // Get browser timezone & fill it in
-                                       minuteDiff = -( new Date().getTimezoneOffset() );
-                                       if ( oouiEnabled ) {
-                                               timezoneWidget.textinput.setValue( minutesToHours( minuteDiff ) );
-                                               timezoneWidget.dropdowninput.setValue( 'other' );
-                                       } else {
-                                               $tzTextbox.val( minutesToHours( minuteDiff ) );
-                                               $tzSelect.val( 'other' );
-                                       }
-                               } else {
-                                       // Grab data from the dropdown value
-                                       minuteDiff = parseInt( type.split( '|' )[ 1 ], 10 ) || 0;
-                               }
-                       }
-
-                       // Determine local time from server time and minutes difference, for display.
-                       localTime = servertime + minuteDiff;
-
-                       // Bring time within the [0,1440) range.
-                       localTime = ( ( localTime % 1440 ) + 1440 ) % 1440;
-
-                       $localtimeHolder.text( mw.language.convertNumber( minutesToHours( localTime ) ) );
-               }
-
-               if ( oouiEnabled ) {
-                       if ( timezoneWidget ) {
-                               timezoneWidget.dropdowninput.on( 'change', updateTimezoneSelection );
-                               timezoneWidget.textinput.on( 'change', updateTimezoneSelection );
-                               updateTimezoneSelection();
-                       }
-               } else {
-                       if ( $tzSelect.length && $tzTextbox.length ) {
-                               $tzSelect.change( updateTimezoneSelection );
-                               $tzTextbox.blur( updateTimezoneSelection );
-                               updateTimezoneSelection();
-                       }
-               }
-
-       } );
-}() );
index dd84b7e..baf8243 100644 (file)
@@ -454,6 +454,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers Title::checkUserConfigPermissions
         */
        public function testJsConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
                $this->setUser( $this->userName );
 
                $this->setTitle( NS_USER, $this->userName . '/test.js' );
@@ -466,7 +468,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        [ [ 'badaccess-group0' ], [ 'mycustomjsprotected', 'bogus' ] ],
                        [ [ 'badaccess-group0' ], [ 'mycustomjsprotected', 'bogus' ] ],
-                       [ [ 'badaccess-group0' ] ]
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
                );
        }
 
@@ -476,6 +479,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers Title::checkUserConfigPermissions
         */
        public function testJsonConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
                $this->setUser( $this->userName );
 
                $this->setTitle( NS_USER, $this->userName . '/test.json' );
@@ -488,7 +493,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ],
                        [ [ 'badaccess-group0' ] ],
-                       [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ]
+                       [ [ 'badaccess-group0' ], [ 'mycustomjsonprotected', 'bogus' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
                );
        }
 
@@ -498,6 +504,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers Title::checkUserConfigPermissions
         */
        public function testCssConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
                $this->setUser( $this->userName );
 
                $this->setTitle( NS_USER, $this->userName . '/test.css' );
@@ -510,7 +518,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        [ [ 'badaccess-group0' ] ],
                        [ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ],
-                       [ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ]
+                       [ [ 'badaccess-group0' ], [ 'mycustomcssprotected', 'bogus' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
                );
        }
 
@@ -520,6 +529,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers Title::checkUserConfigPermissions
         */
        public function testOtherJsConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
                $this->setUser( $this->userName );
 
                $this->setTitle( NS_USER, $this->altUserName . '/test.js' );
@@ -532,7 +543,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        [ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
                        [ [ 'badaccess-group0' ], [ 'customjsprotected', 'bogus' ] ],
-                       [ [ 'badaccess-group0' ] ]
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
                );
        }
 
@@ -542,6 +554,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers Title::checkUserConfigPermissions
         */
        public function testOtherJsonConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
                $this->setUser( $this->userName );
 
                $this->setTitle( NS_USER, $this->altUserName . '/test.json' );
@@ -554,7 +568,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ],
                        [ [ 'badaccess-group0' ] ],
-                       [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ]
+                       [ [ 'badaccess-group0' ], [ 'customjsonprotected', 'bogus' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
                );
        }
 
@@ -564,6 +579,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers Title::checkUserConfigPermissions
         */
        public function testOtherCssConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
                $this->setUser( $this->userName );
 
                $this->setTitle( NS_USER, $this->altUserName . '/test.css' );
@@ -576,7 +593,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        [ [ 'badaccess-group0' ] ],
                        [ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ],
-                       [ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ]
+                       [ [ 'badaccess-group0' ], [ 'customcssprotected', 'bogus' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
                );
        }
 
@@ -586,6 +604,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers Title::checkUserConfigPermissions
         */
        public function testOtherNonConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
                $this->setUser( $this->userName );
 
                $this->setTitle( NS_USER, $this->altUserName . '/tempo' );
@@ -598,7 +618,31 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        [ [ 'badaccess-group0' ] ],
                        [ [ 'badaccess-group0' ] ],
-                       [ [ 'badaccess-group0' ] ]
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
+               );
+       }
+
+       /**
+        * @todo This should use data providers like the other methods here.
+        * @covers Title::checkUserConfigPermissions
+        */
+       public function testPatrolActionConfigEditPermissions() {
+               $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
+                       getFormattedNsText( NS_PROJECT );
+               $this->setUser( 'anon' );
+               $this->setTitle( NS_USER, 'ToPatrolOrNotToPatrol' );
+               $this->runConfigEditPermissions(
+                       [ [ 'badaccess-group0' ] ],
+
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-group0' ] ],
+
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-group0' ] ],
+                       [ [ 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ] ]
                );
        }
 
@@ -609,7 +653,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                $resultMyJs,
                $resultUserCss,
                $resultUserJson,
-               $resultUserJs
+               $resultUserJs,
+               $resultPatrol
        ) {
                $this->setUserPerm( '' );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
@@ -639,6 +684,10 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultUserJs, $result );
 
+               $this->setUserPerm( '' );
+               $result = $this->title->getUserPermissionsErrors( 'patrol', $this->user );
+               $this->assertEquals( $resultPatrol, $result );
+
                $this->setUserPerm( [ 'edituserjs', 'edituserjson', 'editusercss' ] );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( [ [ 'badaccess-group0' ] ], $result );
index 600e0d3..4488d9e 100644 (file)
@@ -2057,11 +2057,22 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->database->onTransactionCommitOrIdle( function () use ( $fname ) {
                        $this->database->query( 'SELECT 1', $fname );
                } );
+               $this->database->onTransactionResolution( function () use ( $fname ) {
+                       $this->database->query( 'SELECT 2', $fname );
+               } );
                $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
-               $this->database->close();
+               try {
+                       $this->database->close();
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( DBUnexpectedError $ex ) {
+                       $this->assertSame(
+                               "Wikimedia\Rdbms\Database::close: transaction is still open (from $fname).",
+                               $ex->getMessage()
+                       );
+               }
 
                $this->assertFalse( $this->database->isOpen() );
-               $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; COMMIT; SELECT 1' );
+               $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK; SELECT 2' );
                $this->assertEquals( 0, $this->database->trxLevel() );
        }
 
@@ -2125,7 +2136,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->database->clearFlag( IDatabase::DBO_TRX );
 
                $this->assertFalse( $this->database->isOpen() );
-               $this->assertLastSql( 'BEGIN; SELECT 1; COMMIT' );
+               $this->assertLastSql( 'BEGIN; SELECT 1; ROLLBACK' );
                $this->assertEquals( 0, $this->database->trxLevel() );
        }
 }
index 5adbed8..2ebefac 100644 (file)
@@ -18,11 +18,6 @@ class SpecialWatchlistTest extends SpecialPageTestBase {
                        null
                );
 
-               $this->setTemporaryHook(
-                       'SpecialWatchlistQuery',
-                       null
-               );
-
                $this->setTemporaryHook(
                        'ChangesListSpecialPageQuery',
                        null