Merge "Replace WikiExporter streaming (unbuffered) mode with batched queries"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 2 Oct 2018 05:16:07 +0000 (05:16 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 2 Oct 2018 05:16:07 +0000 (05:16 +0000)
83 files changed:
CODE_OF_CONDUCT.md
RELEASE-NOTES-1.32
docs/hooks.txt
includes/DefaultSettings.php
includes/EditPage.php
includes/GlobalFunctions.php
includes/OrderedStreamingForkController.php
includes/ServiceWiring.php
includes/SiteStats.php
includes/Storage/NameTableStore.php
includes/Title.php
includes/actions/RawAction.php
includes/api/ApiOptions.php
includes/api/ApiStashEdit.php
includes/auth/LocalPasswordPrimaryAuthenticationProvider.php
includes/cache/MessageCache.php
includes/changes/OldChangesList.php
includes/content/WikiTextStructure.php
includes/debug/logger/LegacyLogger.php
includes/debug/logger/monolog/LegacyFormatter.php
includes/debug/logger/monolog/LegacyHandler.php
includes/exception/MWException.php
includes/exception/MWExceptionRenderer.php
includes/filerepo/FileRepo.php
includes/filerepo/file/File.php
includes/filerepo/file/ForeignDBFile.php
includes/filerepo/file/LocalFile.php
includes/htmlform/fields/HTMLInfoField.php
includes/installer/i18n/en.json
includes/installer/i18n/qqq.json
includes/interwiki/ClassicInterwikiLookup.php
includes/jobqueue/JobRunner.php
includes/jobqueue/utils/PurgeJobUtils.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/parser/RemexStripTagHandler.php
includes/parser/Sanitizer.php
includes/preferences/DefaultPreferencesFactory.php
includes/skins/Skin.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialExpandTemplates.php
includes/specials/SpecialWatchlist.php
includes/user/User.php
includes/watcheditem/WatchedItemStore.php
languages/classes/LanguageKk.php
languages/classes/LanguageKu.php
languages/messages/MessagesAr.php
languages/messages/MessagesAs.php
languages/messages/MessagesBho.php
languages/messages/MessagesBo.php
languages/messages/MessagesCkb.php
languages/messages/MessagesDz.php
languages/messages/MessagesFa.php
languages/messages/MessagesGu.php
languages/messages/MessagesHi.php
languages/messages/MessagesKk_arab.php
languages/messages/MessagesKm.php
languages/messages/MessagesKn.php
languages/messages/MessagesKs_arab.php
languages/messages/MessagesKs_deva.php
languages/messages/MessagesKu_arab.php
languages/messages/MessagesLo.php
languages/messages/MessagesMr.php
languages/messages/MessagesNe.php
languages/messages/MessagesNew.php
languages/messages/MessagesOr.php
languages/messages/MessagesPi.php
languages/messages/MessagesPs.php
languages/messages/MessagesSa.php
languages/messages/MessagesSkr_arab.php
maintenance/dictionary/mediawiki.dic
maintenance/includes/DeleteLocalPasswords.php
maintenance/populateArchiveRevId.php
resources/src/mediawiki.widgets/mw.widgets.CategoryTagItemWidget.js
tests/phpunit/includes/api/ApiOptionsTest.php
tests/phpunit/includes/content/WikitextStructureTest.php
tests/phpunit/includes/db/DatabasePostgresTest.php
tests/phpunit/includes/parser/SanitizerTest.php
tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php

index d8e5d08..498acf7 100644 (file)
@@ -1 +1 @@
-The development of this software is covered by a [Code of Conduct](https://www.mediawiki.org/wiki/Code_of_Conduct).
+The development of this software is covered by a [Code of Conduct](https://www.mediawiki.org/wiki/Special:MyLanguage/Code_of_Conduct).
index 60f5e6a..6c200d9 100644 (file)
@@ -93,6 +93,9 @@ production.
   This action should be considered deprecated and should not be used directly.
 * Extensions overriding ContentHandler::getUndoContent() will need to be
   updated for the changed method signature.
+* Added a new hook, 'UserGetRightsRemove', which can be used to remove rights
+  from user. Unlike the 'UserGetRights' it will ensure that removed rights
+  will not be reinserted.
 
 === External library changes in 1.32 ===
 
@@ -119,6 +122,7 @@ production.
 * SpecialPage::execute() will now only call checkLoginSecurityLevel() if
   getLoginSecurityLevel() returns non-false.
 * (T43720, T46197) Improved page display title handling for category pages
+* (T65080) Fixed resetting options of some types via API action=options.
 
 === Action API changes in 1.32 ===
 * Added templated parameters.
@@ -323,6 +327,8 @@ because of Phabricator reports.
 * The '--tidy' option to maintenance/parse.php has been removed.  Tidying
   the output is now the default.  Use '--no-tidy' to bypass the tidy
   phase.
+* The global function wfErrorLog, deprecated since 1.25, has now been removed.
+  Use MWLoggerLegacyLogger::emit or UDPTransport.
 
 === Deprecations in 1.32 ===
 * HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
@@ -466,6 +472,9 @@ because of Phabricator reports.
 * QuickTemplate::msgHtml() and BaseTemplate::msgHtml() have been deprecated
   as they promote bad practises. I18n messages should always be properly
   escaped.
+* Skin::getDynamicStylesheetQuery() has been deprecated. It always
+  returns action=raw&ctype=text/css which callers should use directly.
+* Class LegacyFormatter is deprecated.
 
 === Other changes in 1.32 ===
 * (T198811) The following tables have had their UNIQUE indexes turned into
index 063bbe5..d82f56e 100644 (file)
@@ -3783,6 +3783,13 @@ $context: IContextSource object
 $user: User to get rights for
 &$rights: Current rights
 
+'UserGetRightsRemove': Called in User::getRights(). This hook override
+the UserGetRights hook. It can be used to remove rights from user
+and ensure that will not be reinserted by the other hook callbacks
+therefore this hook should not be used to add any rights, use UserGetRights instead.
+$user: User to get rights for
+&$rights: Current rights
+
 'UserGroupsChanged': Called after user groups are changed.
 $user: User whose groups changed
 $added: Groups added
index 96d2e22..9ea1a25 100644 (file)
@@ -6929,34 +6929,6 @@ $wgRCWatchCategoryMembership = false;
  */
 $wgUseRCPatrol = true;
 
-/**
- * Whether a preference is displayed for structured change filters.
- * If false, no preference is displayed and structured change filters are disabled.
- * If true, structured change filters are *enabled* by default, and a preference is displayed
- * that lets users disable them.
- *
- * Temporary variable during development and will be removed.
- *
- * @since 1.30
- */
-$wgStructuredChangeFiltersShowPreference = false;
-
-/**
- * Whether a preference is displayed for structured change filters on watchlist.
- * Works just like $wgStructuredChangeFiltersShowPreference.
- *
- * Temporary variable during development and will be removed
- * @since 1.32
- */
-$wgStructuredChangeFiltersShowWatchlistPreference = false;
-
-/**
- * Whether to enable RCFilters app on Special:Watchlist
- *
- * Temporary variable during development and will be removed.
- */
-$wgStructuredChangeFiltersOnWatchlist = false;
-
 /**
  * Polling rate, in seconds, used by the 'live update' and 'view newest' features
  * of the RCFilters app on SpecialRecentChanges and Special:Watchlist.
index 7384ca2..8c4b3c8 100644 (file)
@@ -2716,7 +2716,7 @@ ERROR;
         *
         * @param string|null|bool $text Text to unserialize
         * @return Content|bool|null The content object created from $text. If $text was false
-        *   or null, false resp. null will be  returned instead.
+        *   or null, then false or null will be returned instead.
         *
         * @throws MWException If unserializing the text results in a Content
         *   object that is not an instance of TextContent and
index 336cb89..868fda3 100644 (file)
@@ -197,11 +197,10 @@ function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
  *       [ 'y' ]
  *     ]
  *
- * @param array $array1,...
+ * @param array ...$args
  * @return array
  */
-function wfMergeErrorArrays( /*...*/ ) {
-       $args = func_get_args();
+function wfMergeErrorArrays( ...$args ) {
        $out = [];
        foreach ( $args as $errors ) {
                foreach ( $errors as $params ) {
@@ -1147,26 +1146,6 @@ function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
        MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
 }
 
-/**
- * Log to a file without getting "file size exceeded" signals.
- *
- * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
- * send lines to the specified port, prefixed by the specified prefix and a space.
- * @since 1.25 support for additional context data
- *
- * @param string $text
- * @param string $file Filename
- * @param array $context Additional logging context data
- * @throws MWException
- * @deprecated since 1.25 Use \MediaWiki\Logger\LegacyLogger::emit or UDPTransport
- */
-function wfErrorLog( $text, $file, array $context = [] ) {
-       wfDeprecated( __METHOD__, '1.25' );
-       $logger = LoggerFactory::getInstance( 'wfErrorLog' );
-       $context['destination'] = $file;
-       $logger->info( trim( $text ), $context );
-}
-
 /**
  * @todo document
  * @todo Move logic to MediaWiki.php
@@ -2196,13 +2175,13 @@ function wfStringToBool( $val ) {
  * (https://bugs.php.net/bug.php?id=26285) and the locale problems on Linux in
  * PHP 5.2.6+ (bug backported to earlier distro releases of PHP).
  *
- * @param string $args,... strings to escape and glue together,
+ * @param string|string[] ...$args strings to escape and glue together,
  *  or a single array of strings parameter
  * @return string
  * @deprecated since 1.30 use MediaWiki\Shell::escape()
  */
-function wfEscapeShellArg( /*...*/ ) {
-       return Shell::escape( ...func_get_args() );
+function wfEscapeShellArg( ...$args ) {
+       return Shell::escape( ...$args );
 }
 
 /**
@@ -2645,11 +2624,11 @@ function wfGetPrecompiledData( $name ) {
  * Make a cache key for the local wiki.
  *
  * @deprecated since 1.30 Call makeKey on a BagOStuff instance
- * @param string $args,...
+ * @param string ...$args
  * @return string
  */
-function wfMemcKey( /*...*/ ) {
-       return ObjectCache::getLocalClusterInstance()->makeKey( ...func_get_args() );
+function wfMemcKey( ...$args ) {
+       return ObjectCache::getLocalClusterInstance()->makeKey( ...$args );
 }
 
 /**
@@ -2659,11 +2638,10 @@ function wfMemcKey( /*...*/ ) {
  *
  * @param string $db
  * @param string $prefix
- * @param string $args,...
+ * @param string ...$args
  * @return string
  */
-function wfForeignMemcKey( $db, $prefix /*...*/ ) {
-       $args = array_slice( func_get_args(), 2 );
+function wfForeignMemcKey( $db, $prefix, ...$args ) {
        $keyspace = $prefix ? "$db-$prefix" : $db;
        return ObjectCache::getLocalClusterInstance()->makeKeyInternal( $keyspace, $args );
 }
@@ -2677,11 +2655,11 @@ function wfForeignMemcKey( $db, $prefix /*...*/ ) {
  *
  * @deprecated since 1.30 Call makeGlobalKey on a BagOStuff instance
  * @since 1.26
- * @param string $args,...
+ * @param string ...$args
  * @return string
  */
-function wfGlobalCacheKey( /*...*/ ) {
-       return ObjectCache::getLocalClusterInstance()->makeGlobalKey( ...func_get_args() );
+function wfGlobalCacheKey( ...$args ) {
+       return ObjectCache::getLocalClusterInstance()->makeGlobalKey( ...$args );
 }
 
 /**
index ff29cb5..11abc81 100644 (file)
@@ -134,9 +134,12 @@ class OrderedStreamingForkController extends ForkController {
         */
        protected function consumeNoFork() {
                while ( !feof( $this->input ) ) {
-                       $line = trim( fgets( $this->input ) );
-                       if ( $line ) {
-                               $result = call_user_func( $this->workCallback, $line );
+                       $data = fgets( $this->input );
+                       if ( $data[ strlen( $data ) - 1 ] == "\n" ) {
+                               $data = substr( $data, 0, -1 );
+                       }
+                       if ( strlen( $data ) !== 0 ) {
+                               $result = call_user_func( $this->workCallback, $data );
                                fwrite( $this->output, "$result\n" );
                        }
                }
@@ -160,8 +163,12 @@ class OrderedStreamingForkController extends ForkController {
                                        $this->updateAvailableSockets( $sockets, $used, $sockets ? 0 : 5 );
                                } while ( !$sockets );
                        }
-                       $data = trim( $data );
-                       if ( !$data ) {
+                       // Strip the trailing \n. The last line of a file might not have a trailing
+                       // \n though
+                       if ( $data[ strlen( $data ) - 1 ] == "\n" ) {
+                               $data = substr( $data, 0, -1 );
+                       }
+                       if ( strlen( $data ) === 0 ) {
                                continue;
                        }
                        $socket = array_pop( $sockets );
index cf2def2..dac3de6 100644 (file)
@@ -278,7 +278,7 @@ return [
                                        // Also test DjVu
                                        $deja = new DjVuImage( $file );
                                        if ( $deja->isValid() ) {
-                                               $logger->info( __METHOD__ . ": detected $file as image/vnd.djvu\n" );
+                                               $logger->info( "Detected $file as image/vnd.djvu\n" );
                                                $mime = 'image/vnd.djvu';
 
                                                return;
index 745c891..e3cb617 100644 (file)
@@ -149,11 +149,12 @@ class SiteStats {
         */
        public static function numberingroup( $group ) {
                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+               $fname = __METHOD__;
 
                return $cache->getWithSetCallback(
                        $cache->makeKey( 'SiteStats', 'groupcounts', $group ),
                        $cache::TTL_HOUR,
-                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $group ) {
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $group, $fname ) {
                                $dbr = self::getLB()->getConnection( DB_REPLICA );
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
@@ -164,7 +165,7 @@ class SiteStats {
                                                'ug_group' => $group,
                                                'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() )
                                        ],
-                                       __METHOD__
+                                       $fname
                                );
                        },
                        [ 'pcTTL' => $cache::TTL_PROC_LONG ]
@@ -199,11 +200,12 @@ class SiteStats {
         */
        public static function pagesInNs( $ns ) {
                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+               $fname = __METHOD__;
 
                return $cache->getWithSetCallback(
                        $cache->makeKey( 'SiteStats', 'page-in-namespace', $ns ),
                        $cache::TTL_HOUR,
-                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $ns ) {
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $ns, $fname ) {
                                $dbr = self::getLB()->getConnection( DB_REPLICA );
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
@@ -211,7 +213,7 @@ class SiteStats {
                                        'page',
                                        'COUNT(*)',
                                        [ 'page_namespace' => $ns ],
-                                       __METHOD__
+                                       $fname
                                );
                        },
                        [ 'pcTTL' => $cache::TTL_PROC_LONG ]
index 6c7919d..c1dd09d 100644 (file)
@@ -262,11 +262,12 @@ class NameTableStore {
                if ( array_key_exists( $id, $table ) ) {
                        return $table[$id];
                }
+               $fname = __METHOD__;
 
                $table = $this->cache->getWithSetCallback(
                        $this->getCacheKey(),
                        $this->cacheTTL,
-                       function ( $oldValue, &$ttl, &$setOpts ) use ( $id ) {
+                       function ( $oldValue, &$ttl, &$setOpts ) use ( $id, $fname ) {
                                // Check if cached value is up-to-date enough to have $id
                                if ( is_array( $oldValue ) && array_key_exists( $id, $oldValue ) ) {
                                        // Completely leave the cache key alone
@@ -279,7 +280,7 @@ class NameTableStore {
                                        // Log a fallback to master
                                        if ( $source === DB_MASTER ) {
                                                $this->logger->info(
-                                                       __METHOD__ . 'falling back to master select from ' .
+                                                       $fname . ' falling back to master select from ' .
                                                        $this->table . ' with id ' . $id
                                                );
                                        }
index 59164e0..5b0c3bc 100644 (file)
@@ -4778,7 +4778,39 @@ class Title implements LinkTarget {
        }
 
        /**
-        * Get the default message text or false if the message doesn't exist
+        * Get the default (plain) message contents for an page that overrides an
+        * interface message key.
+        *
+        * Primary use cases:
+        *
+        * - Article:
+        *    - Show default when viewing the page. The Article::getSubstituteContent
+        *      method displays the default message content, instead of the
+        *      'noarticletext' placeholder message normally used.
+        *
+        * - EditPage:
+        *    - Title of edit page. When creating an interface message override,
+        *      the editor is told they are "Editing the page", instead of
+        *      "Creating the page". (EditPage::setHeaders)
+        *    - Edit notice. The 'translateinterface' edit notice is shown when creating
+        *      or editing a an interface message override. (EditPage::showIntro)
+        *    - Opening the editor. The contents of the localisation message are used
+        *      as contents of the editor when creating a new page in the MediaWiki
+        *      namespace. This simplifies the process for editors when "changing"
+        *      an interface message by creating an override. (EditPage::getContentObject)
+        *    - Showing a diff. The left-hand side of a diff when an editor is
+        *      previewing their changes before saving the creation of a page in the
+        *      MediaWiki namespace. (EditPage::showDiff)
+        *    - Disallowing a save. When attempting to create a a MediaWiki-namespace
+        *      page with the proposed content matching the interface message default,
+        *      the save is rejected, the same way we disallow blank pages from being
+        *      created. (EditPage::internalAttemptSave)
+        *
+        * - ApiEditPage:
+        *    - Default content, when using the 'prepend' or 'append' feature.
+        *
+        * - SkinTemplate:
+        *    - Label the create action as "Edit", if the page can be an override.
         *
         * @return string|bool
         */
index 817c9fd..b5a6d3a 100644 (file)
@@ -163,47 +163,35 @@ class RawAction extends FormlessAction {
                $title = $this->getTitle();
                $request = $this->getRequest();
 
-               // If it's a MediaWiki message we can just hit the message cache
-               if ( $request->getBool( 'usemsgcache' ) && $title->getNamespace() == NS_MEDIAWIKI ) {
-                       // The first "true" is to use the database, the second is to use
-                       // the content langue and the last one is to specify the message
-                       // key already contains the language in it ("/de", etc.).
-                       $text = MessageCache::singleton()->get( $title->getDBkey(), true, true, true );
-                       // If the message doesn't exist, return a blank
-                       if ( $text === false ) {
-                               $text = '';
-                       }
-               } else {
-                       // Get it from the DB
-                       $rev = Revision::newFromTitle( $title, $this->getOldId() );
-                       if ( $rev ) {
-                               $lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() );
-                               $request->response()->header( "Last-modified: $lastmod" );
+               // Get it from the DB
+               $rev = Revision::newFromTitle( $title, $this->getOldId() );
+               if ( $rev ) {
+                       $lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() );
+                       $request->response()->header( "Last-modified: $lastmod" );
 
-                               // Public-only due to cache headers
-                               $content = $rev->getContent();
+                       // Public-only due to cache headers
+                       $content = $rev->getContent();
 
-                               if ( $content === null ) {
-                                       // revision not found (or suppressed)
+                       if ( $content === null ) {
+                               // revision not found (or suppressed)
+                               $text = false;
+                       } elseif ( !$content instanceof TextContent ) {
+                               // non-text content
+                               wfHttpError( 415, "Unsupported Media Type", "The requested page uses the content model `"
+                                       . $content->getModel() . "` which is not supported via this interface." );
+                               die();
+                       } else {
+                               // want a section?
+                               $section = $request->getIntOrNull( 'section' );
+                               if ( $section !== null ) {
+                                       $content = $content->getSection( $section );
+                               }
+
+                               if ( $content === null || $content === false ) {
+                                       // section not found (or section not supported, e.g. for JS, JSON, and CSS)
                                        $text = false;
-                               } elseif ( !$content instanceof TextContent ) {
-                                       // non-text content
-                                       wfHttpError( 415, "Unsupported Media Type", "The requested page uses the content model `"
-                                               . $content->getModel() . "` which is not supported via this interface." );
-                                       die();
                                } else {
-                                       // want a section?
-                                       $section = $request->getIntOrNull( 'section' );
-                                       if ( $section !== null ) {
-                                               $content = $content->getSection( $section );
-                                       }
-
-                                       if ( $content === null || $content === false ) {
-                                               // section not found (or section not supported, e.g. for JS, JSON, and CSS)
-                                               $text = false;
-                                       } else {
-                                               $text = $content->getNativeData();
-                                       }
+                                       $text = $content->getNativeData();
                                }
                        }
                }
index fe7d10d..3ea827c 100644 (file)
@@ -80,12 +80,18 @@ class ApiOptions extends ApiBase {
                        switch ( $prefsKinds[$key] ) {
                                case 'registered':
                                        // Regular option.
-                                       if ( $htmlForm === null ) {
-                                               // We need a dummy HTMLForm for the validate callback...
-                                               $htmlForm = new HTMLForm( [], $this );
+                                       if ( $value === null ) {
+                                               // Reset it
+                                               $validation = true;
+                                       } else {
+                                               // Validate
+                                               if ( $htmlForm === null ) {
+                                                       // We need a dummy HTMLForm for the validate callback...
+                                                       $htmlForm = new HTMLForm( [], $this );
+                                               }
+                                               $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
+                                               $validation = $field->validate( $value, $user->getOptions() );
                                        }
-                                       $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
-                                       $validation = $field->validate( $value, $user->getOptions() );
                                        break;
                                case 'registered-multiselect':
                                case 'registered-checkmatrix':
index ab9ae8e..17c8040 100644 (file)
@@ -174,6 +174,7 @@ class ApiStashEdit extends ApiBase {
 
                $title = $page->getTitle();
                $key = self::getStashKey( $title, self::getContentHash( $content ), $user );
+               $fname = __METHOD__;
 
                // Use the master DB to allow for fast blocking locks on the "save path" where this
                // value might actually be used to complete a page edit. If the edit submission request
@@ -182,13 +183,13 @@ class ApiStashEdit extends ApiBase {
                // need to duplicate parsing of the same content/user/summary bundle, so try to avoid
                // blocking at all here.
                $dbw = wfGetDB( DB_MASTER );
-               if ( !$dbw->lock( $key, __METHOD__, 0 ) ) {
+               if ( !$dbw->lock( $key, $fname, 0 ) ) {
                        // De-duplicate requests on the same key
                        return self::ERROR_BUSY;
                }
                /** @noinspection PhpUnusedLocalVariableInspection */
-               $unlocker = new ScopedCallback( function () use ( $dbw, $key ) {
-                       $dbw->unlock( $key, __METHOD__ );
+               $unlocker = new ScopedCallback( function () use ( $dbw, $key, $fname ) {
+                       $dbw->unlock( $key, $fname );
                } );
 
                $cutoffTime = time() - self::PRESUME_FRESH_TTL_SEC;
index 86a6aae..c538ee7 100644 (file)
@@ -136,7 +136,8 @@ class LocalPasswordPrimaryAuthenticationProvider
                // @codeCoverageIgnoreStart
                if ( $this->getPasswordFactory()->needsUpdate( $pwhash ) ) {
                        $newHash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
-                       \DeferredUpdates::addCallableUpdate( function () use ( $newHash, $oldRow ) {
+                       $fname = __METHOD__;
+                       \DeferredUpdates::addCallableUpdate( function () use ( $newHash, $oldRow, $fname ) {
                                $dbw = wfGetDB( DB_MASTER );
                                $dbw->update(
                                        'user',
@@ -145,7 +146,7 @@ class LocalPasswordPrimaryAuthenticationProvider
                                                'user_id' => $oldRow->user_id,
                                                'user_password' => $oldRow->user_password
                                        ],
-                                       __METHOD__
+                                       $fname
                                );
                        } );
                }
index 7a1b988..aa929bc 100644 (file)
@@ -582,10 +582,11 @@ class MessageCache {
                        // Ignore $wgMaxMsgCacheEntrySize so the process cache is up to date
                        $this->cache->setField( $code, $title, ' ' . $text );
                }
+               $fname = __METHOD__;
 
                // (b) Update the shared caches in a deferred update with a fresh DB snapshot
                DeferredUpdates::addCallableUpdate(
-                       function () use ( $title, $msg, $code ) {
+                       function () use ( $title, $msg, $code, $fname ) {
                                global $wgMaxMsgCacheEntrySize;
                                // Allow one caller at a time to avoid race conditions
                                $scopedLock = $this->getReentrantScopedLock(
@@ -593,7 +594,7 @@ class MessageCache {
                                );
                                if ( !$scopedLock ) {
                                        LoggerFactory::getInstance( 'MessageCache' )->error(
-                                               __METHOD__ . ': could not acquire lock to update {title} ({code})',
+                                               $fname . ': could not acquire lock to update {title} ({code})',
                                                [ 'title' => $title, 'code' => $code ] );
                                        return;
                                }
@@ -1029,14 +1030,15 @@ class MessageCache {
         * @return string Either " <MESSAGE>" or "!NONEXISTANT"
         */
        private function loadCachedMessagePageEntry( $dbKey, $code, $hash ) {
+               $fname = __METHOD__;
                return $this->srvCache->getWithSetCallback(
                        $this->srvCache->makeKey( 'messages-big', $hash, $dbKey ),
                        IExpiringStore::TTL_MINUTE,
-                       function () use ( $code, $dbKey, $hash ) {
+                       function () use ( $code, $dbKey, $hash, $fname ) {
                                return $this->wanCache->getWithSetCallback(
                                        $this->bigMessageCacheKey( $hash, $dbKey ),
                                        $this->mExpiry,
-                                       function ( $oldValue, &$ttl, &$setOpts ) use ( $dbKey, $code ) {
+                                       function ( $oldValue, &$ttl, &$setOpts ) use ( $dbKey, $code, $fname ) {
                                                // Try loading the message from the database
                                                $dbr = wfGetDB( DB_REPLICA );
                                                $setOpts += Database::getCacheSetOptions( $dbr );
@@ -1053,7 +1055,7 @@ class MessageCache {
                                                        $message = $this->getMessageTextFromContent( $content );
                                                } else {
                                                        LoggerFactory::getInstance( 'MessageCache' )->warning(
-                                                               __METHOD__ . ': failed to load page text for \'{titleKey}\'',
+                                                               $fname . ': failed to load page text for \'{titleKey}\'',
                                                                [ 'titleKey' => $dbKey, 'code' => $code ]
                                                        );
                                                        $message = null;
index a2af01c..a26f5b6 100644 (file)
@@ -60,7 +60,10 @@ class OldChangesList extends ChangesList {
                ) {
                        return false;
                }
-               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 'isReservedDataAttribute' ] );
+               $attribs = array_filter( $attribs,
+                       [ Sanitizer::class, 'isReservedDataAttribute' ],
+                       ARRAY_FILTER_USE_KEY
+               );
 
                $dateheader = ''; // $html now contains only <li>...</li>, for hooks' convenience.
                $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
index 1128d7b..a82ffa1 100644 (file)
@@ -161,10 +161,6 @@ class WikiTextStructure {
 
                $this->openingText = $this->extractHeadingBeforeFirstHeading( $text );
 
-               // Add extra spacing around break tags so text crammed together like<br>this
-               // doesn't make one word.
-               $text = str_replace( '<br', "\n<br", $text );
-
                $formatter = new HtmlFormatter( $text );
 
                // Strip elements from the page that we never want in the search text.
index d4a79d9..9d0789c 100644 (file)
@@ -29,7 +29,7 @@ use Psr\Log\LogLevel;
 use UDPTransport;
 
 /**
- * PSR-3 logger that mimics the historic implementation of MediaWiki's
+ * PSR-3 logger that mimics the historic implementation of MediaWiki's former
  * wfErrorLog logging implementation.
  *
  * This logger is configured by the following global configuration variables:
@@ -145,10 +145,6 @@ class LegacyLogger extends AbstractLogger {
                        // specfied.
                        $shouldEmit = (bool)$wgDBerrorLog;
 
-               } elseif ( $channel === 'wfErrorLog' ) {
-                       // All messages on the wfErrorLog channel should be emitted.
-                       $shouldEmit = true;
-
                } elseif ( $channel === 'wfDebug' ) {
                        // wfDebug messages are emitted if a catch all logging file has
                        // been specified. Checked explicitly so that 'private' flagged
@@ -192,10 +188,9 @@ class LegacyLogger extends AbstractLogger {
        /**
         * Format a message.
         *
-        * Messages to the 'wfDebug', 'wfLogDBError' and 'wfErrorLog' channels
-        * receive special formatting to mimic the historic output of the functions
-        * of the same name. All other channel values are formatted based on the
-        * historic output of the `wfDebugLog()` global function.
+        * Messages to the 'wfDebug' and 'wfLogDBError' channels receive special formatting to mimic the
+        * historic output of the functions of the same name. All other channel values are formatted
+        * based on the historic output of the `wfDebugLog()` global function.
         *
         * @param string $channel
         * @param string $message
@@ -211,9 +206,6 @@ class LegacyLogger extends AbstractLogger {
                } elseif ( $channel === 'wfLogDBError' ) {
                        $text = self::formatAsWfLogDBError( $channel, $message, $context );
 
-               } elseif ( $channel === 'wfErrorLog' ) {
-                       $text = "{$message}\n";
-
                } elseif ( $channel === 'profileoutput' ) {
                        // Legacy wfLogProfilingData formatitng
                        $forward = '';
index 92624a0..e16dd4c 100644 (file)
@@ -24,10 +24,11 @@ use MediaWiki\Logger\LegacyLogger;
 use Monolog\Formatter\NormalizerFormatter;
 
 /**
- * Log message formatter that mimics the legacy log message formatting of
- * `wfDebug`, `wfDebugLog`, `wfLogDBError` and `wfErrorLog` global functions by
- * delegating the formatting to \MediaWiki\Logger\LegacyLogger.
+ * Log message formatter that mimics the legacy log message formatting of `wfDebug`, `wfDebugLog`,
+ * `wfLogDBError` and the former `wfErrorLog` global functions by delegating the formatting to
+ * \MediaWiki\Logger\LegacyLogger.
  *
+ * @deprecated since 1.32
  * @since 1.25
  * @copyright © 2013 Wikimedia Foundation and contributors
  * @see \MediaWiki\Logger\LegacyLogger
index dbeb136..258a9a8 100644 (file)
@@ -27,7 +27,7 @@ use Monolog\Logger;
 use UnexpectedValueException;
 
 /**
- * Log handler that replicates the behavior of MediaWiki's wfErrorLog()
+ * Log handler that replicates the behavior of MediaWiki's former wfErrorLog()
  * logging service. Log output can be directed to a local file, a PHP stream,
  * or a udp2log server.
  *
index ffeafff..af835e4 100644 (file)
@@ -76,6 +76,7 @@ class MWException extends Exception {
                global $wgSitename;
                $args = array_slice( func_get_args(), 2 );
 
+               // FIXME: Keep logic in sync with MWExceptionRenderer::msg.
                $res = false;
                if ( $this->useMessageCache() ) {
                        try {
index 5036383..de2af6b 100644 (file)
@@ -199,6 +199,8 @@ class MWExceptionRenderer {
        private static function msg( $key, $fallback /*[, params...] */ ) {
                global $wgSitename;
                $args = array_slice( func_get_args(), 2 );
+
+               // FIXME: Keep logic in sync with MWException::msg.
                try {
                        $res = wfMessage( $key, $args )->text();
                } catch ( Exception $e ) {
index 858e124..455d38f 100644 (file)
@@ -810,8 +810,9 @@ class FileRepo {
         */
        public function getDescriptionStylesheetUrl() {
                if ( isset( $this->scriptDirUrl ) ) {
-                       return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
-                               wfArrayToCgi( Skin::getDynamicStylesheetQuery() ) );
+                       // Must match canonical query parameter order for optimum caching
+                       // See Title::getCdnUrls
+                       return $this->makeUrl( 'title=MediaWiki:Filepage.css&action=raw&ctype=text/css' );
                }
 
                return false;
index 5a0cf6a..a30d213 100644 (file)
@@ -2071,13 +2071,14 @@ abstract class File implements IDBAccessObject {
                                $lang->getCode(),
                                md5( $this->getName() )
                        );
+                       $fname = __METHOD__;
 
                        return $cache->getWithSetCallback(
                                $key,
                                $this->repo->descriptionCacheExpiry ?: $cache::TTL_UNCACHEABLE,
-                               function ( $oldValue, &$ttl, array &$setOpts ) use ( $renderUrl ) {
+                               function ( $oldValue, &$ttl, array &$setOpts ) use ( $renderUrl, $fname ) {
                                        wfDebug( "Fetching shared description from $renderUrl\n" );
-                                       $res = Http::get( $renderUrl, [], __METHOD__ );
+                                       $res = Http::get( $renderUrl, [], $fname );
                                        if ( !$res ) {
                                                $ttl = WANObjectCache::TTL_UNCACHEABLE;
                                        }
index ee4df1d..1869967 100644 (file)
@@ -152,6 +152,7 @@ class ForeignDBFile extends LocalFile {
                }
 
                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+               $fname = __METHOD__;
 
                return $cache->getWithSetCallback(
                        $this->repo->getLocalCacheKey(
@@ -161,9 +162,9 @@ class ForeignDBFile extends LocalFile {
                                $touched
                        ),
                        $this->repo->descriptionCacheExpiry ?: $cache::TTL_UNCACHEABLE,
-                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $renderUrl ) {
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $renderUrl, $fname ) {
                                wfDebug( "Fetching shared description from $renderUrl\n" );
-                               $res = Http::get( $renderUrl, [], __METHOD__ );
+                               $res = Http::get( $renderUrl, [], $fname );
                                if ( !$res ) {
                                        $ttl = WANObjectCache::TTL_UNCACHEABLE;
                                }
index b6c249b..36a44dc 100644 (file)
@@ -1696,6 +1696,7 @@ class LocalFile extends File {
                # Defer purges, page creation, and link updates in case they error out.
                # The most important thing is that files and the DB registry stay synced.
                $dbw->endAtomic( __METHOD__ );
+               $fname = __METHOD__;
 
                # Do some cache purges after final commit so that:
                # a) Changes are more likely to be seen post-purge
@@ -1706,7 +1707,7 @@ class LocalFile extends File {
                                __METHOD__,
                                function () use (
                                        $reupload, $wikiPage, $newPageContent, $comment, $user,
-                                       $logEntry, $logId, $descId, $tags
+                                       $logEntry, $logId, $descId, $tags, $fname
                                ) {
                                        # Update memcache after the commit
                                        $this->invalidateCache();
@@ -1758,7 +1759,7 @@ class LocalFile extends File {
                                                'logging',
                                                $update,
                                                [ 'log_id' => $logId ],
-                                               __METHOD__
+                                               $fname
                                        );
                                        $this->getRepo()->getMasterDB()->insert(
                                                'log_search',
@@ -1767,7 +1768,7 @@ class LocalFile extends File {
                                                        'ls_value' => $logEntry->getAssociatedRevId(),
                                                        'ls_log_id' => $logId,
                                                ],
-                                               __METHOD__
+                                               $fname
                                        );
 
                                        # Add change tags, if any
index 1376d0c..a98f112 100644 (file)
@@ -75,6 +75,22 @@ class HTMLInfoField extends HTMLFormField {
                return parent::getRaw( $value );
        }
 
+       /**
+        * @param mixed $value
+        * @return OOUI\FieldLayout
+        * @since 1.32
+        */
+       public function getOOUI( $value ) {
+               if ( !empty( $this->mParams['rawrow'] ) ) {
+                       if ( !( $value instanceof OOUI\FieldLayout ) ) {
+                               throw new Exception( "'default' must be a FieldLayout or subclass when using 'rawrow'" );
+                       }
+                       return $value;
+               }
+
+               return parent::getOOUI( $value );
+       }
+
        protected function needsLabel() {
                return false;
        }
index 87bf792..c89be17 100644 (file)
        "config-install-mainpage-failed": "Could not insert main page: $1",
        "config-install-done": "<strong>Congratulations!</strong>\nYou have installed MediaWiki.\n\nThe installer has generated a <code>LocalSettings.php</code> file.\nIt contains all your configuration.\n\nYou will need to download it and put it in the base of your wiki installation (the same directory as index.php). The download should have started automatically.\n\nIf the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:\n\n$3\n\n<strong>Note:</strong> If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.\n\nWhen that has been done, you can <strong>[$2 enter your wiki]</strong>.",
        "config-install-done-path": "<strong>Congratulations!</strong>\nYou have installed MediaWiki.\n\nThe installer has generated a <code>LocalSettings.php</code> file.\nIt contains all your configuration.\n\nYou will need to download it and put it at <code>$4</code>. The download should have started automatically.\n\nIf the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:\n\n$3\n\n<strong>Note:</strong> If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.\n\nWhen that has been done, you can <strong>[$2 enter your wiki]</strong>.",
-       "config-install-success": "MediaWiki has been successfully installed. You can now\nvisit <$1$2> to view your wiki.\nIf you have questions, check out our frequently asked questions list:\n<https://www.mediawiki.org/wiki/Manual:FAQ> or use one of the\nsupport forums linked on that page.",
+       "config-install-success": "MediaWiki has been successfully installed. You can now visit <$1$2> to view your wiki.\nIf you have questions, check out our frequently asked questions list:\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> or use one of the\nsupport forums linked on that page.",
        "config-download-localsettings": "Download <code>LocalSettings.php</code>",
        "config-help": "help",
        "config-help-tooltip": "click to expand",
index c868169..e423bcd 100644 (file)
@@ -20,7 +20,8 @@
                        "Jdforrester",
                        "Liuxinyu970226",
                        "Metalhead64",
-                       "Tacsipacsi"
+                       "Tacsipacsi",
+                       "Zoranzoki21"
                ]
        },
        "config-desc": "Short description of the installer.",
        "config-unknown-collation": "Warning messages in the MediaWiki installer for the database type MySQL when an unrecognised collation is used.",
        "config-db-web-account": "Fieldset legend in MediaWiki installer",
        "config-db-web-help": "Help text in MediaWiki installer.",
-       "config-db-web-account-same": "checkbox label",
-       "config-db-web-create": "checkbox label",
+       "config-db-web-account-same": "Checkbox label about setting of database account for web access.",
+       "config-db-web-create": "Checkbox label about creating of new database account.",
        "config-db-web-no-create-privs": "Error message in the MediaWiki installer.",
        "config-mysql-engine": "Field label for MySQL storage engine in the MediaWiki installer.",
        "config-mysql-innodb": "Option for the MySQL storage engine in the MediaWiki installer.",
index 05cf933..cdf4cde 100644 (file)
@@ -279,10 +279,11 @@ class ClassicInterwikiLookup implements InterwikiLookup {
                        }
                }
 
+               $fname = __METHOD__;
                $iwData = $this->objectCache->getWithSetCallback(
                        $this->objectCache->makeKey( 'interwiki', $prefix ),
                        $this->objectCacheExpiry,
-                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) {
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix, $fname ) {
                                $dbr = wfGetDB( DB_REPLICA ); // TODO: inject LoadBalancer
 
                                $setOpts += Database::getCacheSetOptions( $dbr );
@@ -291,7 +292,7 @@ class ClassicInterwikiLookup implements InterwikiLookup {
                                        'interwiki',
                                        self::selectFields(),
                                        [ 'iw_prefix' => $prefix ],
-                                       __METHOD__
+                                       $fname
                                );
 
                                return $row ? (array)$row : '!NONEXISTENT';
index dab9b14..1e83167 100644 (file)
@@ -576,12 +576,12 @@ class JobRunner implements LoggerAwareInterface {
                $this->debugCallback( $msg );
 
                // Wait for an exclusive lock to commit
-               if ( !$dbwSerial->lock( 'jobrunner-serial-commit', __METHOD__, 30 ) ) {
+               if ( !$dbwSerial->lock( 'jobrunner-serial-commit', $fnameTrxOwner, 30 ) ) {
                        // This will trigger a rollback in the main loop
                        throw new DBError( $dbwSerial, "Timed out waiting on commit queue." );
                }
-               $unlocker = new ScopedCallback( function () use ( $dbwSerial ) {
-                       $dbwSerial->unlock( 'jobrunner-serial-commit', __METHOD__ );
+               $unlocker = new ScopedCallback( function () use ( $dbwSerial, $fnameTrxOwner ) {
+                       $dbwSerial->unlock( 'jobrunner-serial-commit', $fnameTrxOwner );
                } );
 
                // Wait for the replica DBs to catch up
index 5d8a6cf..ef364b5 100644 (file)
@@ -36,11 +36,12 @@ class PurgeJobUtils {
                if ( $dbkeys === [] ) {
                        return;
                }
+               $fname = __METHOD__;
 
                DeferredUpdates::addUpdate( new AutoCommitUpdate(
                        $dbw,
                        __METHOD__,
-                       function () use ( $dbw, $namespace, $dbkeys ) {
+                       function () use ( $dbw, $namespace, $dbkeys, $fname ) {
                                $services = MediaWikiServices::getInstance();
                                $lbFactory = $services->getDBLoadBalancerFactory();
                                // Determine which pages need to be updated.
@@ -55,7 +56,7 @@ class PurgeJobUtils {
                                                'page_title' => $dbkeys,
                                                'page_touched < ' . $dbw->addQuotes( $now )
                                        ],
-                                       __METHOD__
+                                       $fname
                                );
 
                                if ( !$ids ) {
@@ -63,7 +64,7 @@ class PurgeJobUtils {
                                }
 
                                $batchSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
-                               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+                               $ticket = $lbFactory->getEmptyTransactionTicket( $fname );
                                foreach ( array_chunk( $ids, $batchSize ) as $idBatch ) {
                                        $dbw->update(
                                                'page',
@@ -72,9 +73,9 @@ class PurgeJobUtils {
                                                        'page_id' => $idBatch,
                                                        'page_touched < ' . $dbw->addQuotes( $now ) // handle races
                                                ],
-                                               __METHOD__
+                                               $fname
                                        );
-                                       $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+                                       $lbFactory->commitAndWaitForReplication( $fname, $ticket );
                                }
                        }
                ) );
index e276d09..f37364f 100644 (file)
@@ -1020,13 +1020,35 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        abstract protected function doQuery( $sql );
 
        /**
-        * Determine whether a query writes to the DB.
-        * Should return true if unsure.
+        * Determine whether a query writes to the DB. When in doubt, this returns true.
+        *
+        * Main use cases:
+        *
+        * - Subsequent web requests should not need to wait for replication from
+        *   the master position seen by this web request, unless this request made
+        *   changes to the master. This is handled by ChronologyProtector by checking
+        *   doneWrites() at the end of the request. doneWrites() returns true if any
+        *   query set lastWriteTime; which query() does based on isWriteQuery().
+        *
+        * - Reject write queries to replica DBs, in query().
         *
         * @param string $sql
         * @return bool
         */
        protected function isWriteQuery( $sql ) {
+               // BEGIN and COMMIT queries are considered read queries here.
+               // Database backends and drivers (MySQL, MariaDB, php-mysqli) generally
+               // treat these as write queries, in that their results have "affected rows"
+               // as meta data as from writes, instead of "num rows" as from reads.
+               // But, we treat them as read queries because when reading data (from
+               // either replica or master) we use transactions to enable repeatable-read
+               // snapshots, which ensures we get consistent results from the same snapshot
+               // for all queries within a request. Use cases:
+               // - Treating these as writes would trigger ChronologyProtector (see method doc).
+               // - We use this method to reject writes to replicas, but we need to allow
+               //   use of transactions on replicas for read snapshots. This fine given
+               //   that transactions by themselves don't make changes, only actual writes
+               //   within the transaction matter, which we still detect.
                return !preg_match(
                        '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
        }
@@ -1041,17 +1063,21 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
        /**
         * Determine whether a SQL statement is sensitive to isolation level.
+        *
         * A SQL statement is considered transactable if its result could vary
         * depending on the transaction isolation level. Operational commands
         * such as 'SET' and 'SHOW' are not considered to be transactable.
         *
+        * Main purpose: Used by query() to decide whether to begin a transaction
+        * before the current query (in DBO_TRX mode, on by default).
+        *
         * @param string $sql
         * @return bool
         */
        protected function isTransactableQuery( $sql ) {
                return !in_array(
                        $this->getQueryVerb( $sql ),
-                       [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET', 'CREATE', 'ALTER' ],
+                       [ 'BEGIN', 'ROLLBACK', 'COMMIT', 'SET', 'SHOW', 'CREATE', 'ALTER' ],
                        true
                );
        }
index 0f57551..8d82965 100644 (file)
@@ -824,11 +824,12 @@ abstract class DatabaseMysqlBase extends Database {
                        // Using one key for all cluster replica DBs is preferable
                        $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
                );
+               $fname = __METHOD__;
 
                return $cache->getWithSetCallback(
                        $key,
                        $cache::TTL_INDEFINITE,
-                       function () use ( $cache, $key ) {
+                       function () use ( $cache, $key, $fname ) {
                                // Get and leave a lock key in place for a short period
                                if ( !$cache->lock( $key, 0, 10 ) ) {
                                        return false; // avoid master connection spike slams
@@ -841,7 +842,7 @@ abstract class DatabaseMysqlBase extends Database {
 
                                // Connect to and query the master; catch errors to avoid outages
                                try {
-                                       $res = $conn->query( 'SELECT @@server_id AS id', __METHOD__ );
+                                       $res = $conn->query( 'SELECT @@server_id AS id', $fname );
                                        $row = $res ? $res->fetchObject() : false;
                                        $id = $row ? (int)$row->id : 0;
                                } catch ( DBError $e ) {
@@ -1043,11 +1044,12 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBQueryError If the variable doesn't exist for some reason
         */
        protected function getServerId() {
+               $fname = __METHOD__;
                return $this->srvCache->getWithSetCallback(
                        $this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServer() ),
                        self::SERVER_ID_CACHE_TTL,
-                       function () {
-                               $res = $this->query( "SELECT @@server_id AS id", __METHOD__ );
+                       function () use ( $fname ) {
+                               $res = $this->query( "SELECT @@server_id AS id", $fname );
                                return intval( $this->fetchObject( $res )->id );
                        }
                );
index c8edc39..9d5eca6 100644 (file)
@@ -297,7 +297,7 @@ class DatabaseSqlite extends Database {
                return $this->query( "ATTACH DATABASE $file AS $name", $fname );
        }
 
-       function isWriteQuery( $sql ) {
+       protected function isWriteQuery( $sql ) {
                return parent::isWriteQuery( $sql ) && !preg_match( '/^(ATTACH|PRAGMA)\b/i', $sql );
        }
 
index 00b4130..f36d98e 100644 (file)
@@ -1256,10 +1256,11 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function closeAll() {
-               $this->forEachOpenConnection( function ( IDatabase $conn ) {
+               $fname = __METHOD__;
+               $this->forEachOpenConnection( function ( IDatabase $conn ) use ( $fname ) {
                        $host = $conn->getServer();
                        $this->connLogger->debug(
-                               __METHOD__ . ": closing connection to database '$host'." );
+                               $fname . ": closing connection to database '$host'." );
                        $conn->close();
                } );
 
@@ -1447,6 +1448,7 @@ class LoadBalancer implements ILoadBalancer {
                } );
 
                $e = null; // first exception
+               $fname = __METHOD__;
                // Loop until callbacks stop adding callbacks on other connections
                do {
                        // Run any pending callbacks for each connection...
@@ -1464,13 +1466,13 @@ class LoadBalancer implements ILoadBalancer {
                                }
                        );
                        // Clear out any active transactions left over from callbacks...
-                       $this->forEachOpenMasterConnection( function ( Database $conn ) use ( &$e ) {
+                       $this->forEachOpenMasterConnection( function ( Database $conn ) use ( &$e, $fname ) {
                                if ( $conn->writesPending() ) {
                                        // A callback from another handle wrote to this one and DBO_TRX is set
-                                       $this->queryLogger->warning( __METHOD__ . ": found writes pending." );
+                                       $this->queryLogger->warning( $fname . ": found writes pending." );
                                        $fnames = implode( ', ', $conn->pendingWriteAndCallbackCallers() );
                                        $this->queryLogger->warning(
-                                               __METHOD__ . ": found writes pending ($fnames).",
+                                               $fname . ": found writes pending ($fnames).",
                                                [
                                                        'db_server' => $conn->getServer(),
                                                        'db_name' => $conn->getDBname()
@@ -1479,10 +1481,10 @@ class LoadBalancer implements ILoadBalancer {
                                } elseif ( $conn->trxLevel() ) {
                                        // A callback from another handle read from this one and DBO_TRX is set,
                                        // which can easily happen if there is only one DB (no replicas)
-                                       $this->queryLogger->debug( __METHOD__ . ": found empty transaction." );
+                                       $this->queryLogger->debug( $fname . ": found empty transaction." );
                                }
                                try {
-                                       $conn->commit( __METHOD__, $conn::FLUSHING_ALL_PEERS );
+                                       $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
                                } catch ( Exception $ex ) {
                                        $e = $e ?: $ex;
                                }
index 2839147..41c6bf4 100644 (file)
@@ -26,10 +26,18 @@ class RemexStripTagHandler implements TokenHandler {
                $this->text .= substr( $text, $start, $length );
        }
        function startTag( $name, Attributes $attrs, $selfClose, $sourceStart, $sourceLength ) {
-               // Do nothing.
+               // Inject whitespace for typical block-level tags to
+               // prevent merging unrelated<br>words.
+               if ( $this->isBlockLevelTag( $name ) ) {
+                       $this->text .= ' ';
+               }
        }
        function endTag( $name, $sourceStart, $sourceLength ) {
-               // Do nothing.
+               // Inject whitespace for typical block-level tags to
+               // prevent merging unrelated<br>words.
+               if ( $this->isBlockLevelTag( $name ) ) {
+                       $this->text .= ' ';
+               }
        }
        function doctype( $name, $public, $system, $quirks, $sourceStart, $sourceLength ) {
                // Do nothing.
@@ -37,4 +45,63 @@ class RemexStripTagHandler implements TokenHandler {
        function comment( $text, $sourceStart, $sourceLength ) {
                // Do nothing.
        }
+
+       // Per https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
+       // retrieved on sept 12, 2018. <br> is not block level but was added anyways.
+       // The following is a complete list of all HTML block level elements
+       // (although "block-level" is not technically defined for elements that are
+       // new in HTML5).
+       // Structured as tag => true to allow O(1) membership test.
+       static private $BLOCK_LEVEL_TAGS = [
+               'address' => true,
+               'article' => true,
+               'aside' => true,
+               'blockquote' => true,
+               'br' => true,
+               'canvas' => true,
+               'dd' => true,
+               'div' => true,
+               'dl' => true,
+               'dt' => true,
+               'fieldset' => true,
+               'figcaption' => true,
+               'figure' => true,
+               'figcaption' => true,
+               'footer' => true,
+               'form' => true,
+               'h1' => true,
+               'h2' => true,
+               'h3' => true,
+               'h4' => true,
+               'h5' => true,
+               'h6' => true,
+               'header' => true,
+               'hgroup' => true,
+               'hr' => true,
+               'li' => true,
+               'main' => true,
+               'nav' => true,
+               'noscript' => true,
+               'ol' => true,
+               'output' => true,
+               'p' => true,
+               'pre' => true,
+               'section' => true,
+               'table' => true,
+               'tfoot' => true,
+               'ul' => true,
+               'video' => true,
+       ];
+
+       /**
+        * Detect block level tags. Of course css can make anything a block
+        * level tag, but this is still better than nothing.
+        *
+        * @param string $tagName HTML tag name
+        * @return bool True when tag is an html block level element
+        */
+       private function isBlockLevelTag( $tagName ) {
+               $key = strtolower( trim( $tagName ) );
+               return isset( self::$BLOCK_LEVEL_TAGS[$key] );
+       }
 }
index d885e24..85c71ee 100644 (file)
@@ -1508,10 +1508,10 @@ class Sanitizer {
         * @return string
         */
        private static function normalizeWhitespace( $text ) {
-               return preg_replace(
-                       '/\r\n|[\x20\x0d\x0a\x09]/',
+               return trim( preg_replace(
+                       '/(?:\r\n|[\x20\x0d\x0a\x09])+/',
                        ' ',
-                       $text );
+                       $text ) );
        }
 
        /**
index 555493a..880da60 100644 (file)
@@ -1060,14 +1060,12 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        ];
                }
 
-               if ( $this->config->get( 'StructuredChangeFiltersShowPreference' ) ) {
-                       $defaultPreferences['rcenhancedfilters-disable'] = [
-                               'type' => 'toggle',
-                               'section' => 'rc/optoutrc',
-                               'label-message' => 'rcfilters-preference-label',
-                               'help-message' => 'rcfilters-preference-help',
-                       ];
-               }
+               $defaultPreferences['rcenhancedfilters-disable'] = [
+                       'type' => 'toggle',
+                       'section' => 'rc/optoutrc',
+                       'label-message' => 'rcfilters-preference-label',
+                       'help-message' => 'rcfilters-preference-help',
+               ];
        }
 
        /**
@@ -1264,14 +1262,12 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        ];
                }
 
-               if ( $this->config->get( 'StructuredChangeFiltersShowWatchlistPreference' ) ) {
-                       $defaultPreferences['wlenhancedfilters-disable'] = [
-                               'type' => 'toggle',
-                               'section' => 'watchlist/optoutwatchlist',
-                               'label-message' => 'rcfilters-watchlist-preference-label',
-                               'help-message' => 'rcfilters-watchlist-preference-help',
-                       ];
-               }
+               $defaultPreferences['wlenhancedfilters-disable'] = [
+                       'type' => 'toggle',
+                       'section' => 'watchlist/optoutwatchlist',
+                       'label-message' => 'rcfilters-watchlist-preference-label',
+                       'help-message' => 'rcfilters-watchlist-preference-help',
+               ];
        }
 
        /**
index e426f7f..ed4045d 100644 (file)
@@ -414,17 +414,13 @@ abstract class Skin extends ContextSource {
        /**
         * Get the query to generate a dynamic stylesheet
         *
+        * @deprecated since 1.32 Use action=raw&ctype=text/css directly.
         * @return array
         */
        public static function getDynamicStylesheetQuery() {
-               global $wgSquidMaxage;
-
                return [
                                'action' => 'raw',
-                               'maxage' => $wgSquidMaxage,
-                               'usemsgcache' => 'yes',
                                'ctype' => 'text/css',
-                               'smaxage' => $wgSquidMaxage,
                        ];
        }
 
index 43b4e98..36d3eef 100644 (file)
@@ -1846,11 +1846,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * @return bool
         */
        public function isStructuredFilterUiEnabledByDefault() {
-               if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
-                       return !$this->getUser()->getDefaultOption( 'rcenhancedfilters-disable' );
-               } else {
-                       return $this->getUser()->getDefaultOption( 'rcenhancedfilters' );
-               }
+               return true;
        }
 
        /**
@@ -1862,11 +1858,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * @return bool
         */
        public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
-               if ( $config->get( 'StructuredChangeFiltersShowPreference' ) ) {
-                       return !$user->getOption( 'rcenhancedfilters-disable' );
-               } else {
-                       return $user->getOption( 'rcenhancedfilters' );
-               }
+               return !$user->getOption( 'rcenhancedfilters-disable' );
        }
 
        /**
index 73ca76b..4587d40 100644 (file)
@@ -115,7 +115,7 @@ class SpecialExpandTemplates extends SpecialPage {
                        }
 
                        $config = $this->getConfig();
-                       if ( $config->get( 'UseTidy' ) && $options->getTidy() ) {
+                       if ( MWTidy::isEnabled() && $options->getTidy() ) {
                                $tmp = MWTidy::tidy( $tmp );
                        }
 
index 432cfcc..2445c10 100644 (file)
@@ -111,15 +111,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
        }
 
        public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
-               if ( !$config->get( 'StructuredChangeFiltersOnWatchlist' ) ) {
-                       return false;
-               }
-
-               if ( $config->get( 'StructuredChangeFiltersShowWatchlistPreference' ) ) {
-                       return !$user->getOption( 'wlenhancedfilters-disable' );
-               } else {
-                       return $user->getOption( 'rcenhancedfilters' );
-               }
+               return !$user->getOption( 'wlenhancedfilters-disable' );
        }
 
        /**
index aaa7663..12623e8 100644 (file)
@@ -1051,7 +1051,7 @@ class User implements IDBAccessObject, UserIdentity {
                // Certain names may be reserved for batch processes.
                foreach ( $reservedUsernames as $reserved ) {
                        if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
-                               $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
+                               $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
                        }
                        if ( $reserved == $name ) {
                                return false;
@@ -2876,6 +2876,7 @@ class User implements IDBAccessObject, UserIdentity {
         * @return bool
         */
        public function setPassword( $str ) {
+               wfDeprecated( __METHOD__, '1.27' );
                return $this->setPasswordInternal( $str );
        }
 
@@ -2888,6 +2889,7 @@ class User implements IDBAccessObject, UserIdentity {
         *  through the web interface.
         */
        public function setInternalPassword( $str ) {
+               wfDeprecated( __METHOD__, '1.27' );
                $this->setPasswordInternal( $str );
        }
 
@@ -3540,6 +3542,7 @@ class User implements IDBAccessObject, UserIdentity {
                                }
                        }
 
+                       Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
                        // Force reindexation of rights when a hook has unset one of them
                        $this->mRights = array_values( array_unique( $this->mRights ) );
 
@@ -4389,7 +4392,7 @@ class User implements IDBAccessObject, UserIdentity {
                                        'user',
                                        'user_id',
                                        [ 'user_name' => $this->mName ],
-                                       __METHOD__,
+                                       $fname,
                                        [ 'LOCK IN SHARE MODE' ]
                                );
                                $loaded = false;
@@ -4399,7 +4402,7 @@ class User implements IDBAccessObject, UserIdentity {
                                        }
                                }
                                if ( !$loaded ) {
-                                       throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
+                                       throw new MWException( $fname . ": hit a key conflict attempting " .
                                                "to insert user '{$this->mName}' row, but it was not present in select!" );
                                }
                                return Status::newFatal( 'userexists' );
@@ -4545,6 +4548,8 @@ class User implements IDBAccessObject, UserIdentity {
         * @return bool True if the given password is correct, otherwise False
         */
        public function checkPassword( $password ) {
+               wfDeprecated( __METHOD__, '1.27' );
+
                $manager = AuthManager::singleton();
                $reqs = AuthenticationRequest::loadRequestsFromSubmission(
                        $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
@@ -4578,6 +4583,7 @@ class User implements IDBAccessObject, UserIdentity {
         * @return bool True if matches, false otherwise
         */
        public function checkTemporaryPassword( $plaintext ) {
+               wfDeprecated( __METHOD__, '1.27' );
                // Can't check the temporary password individually.
                return $this->checkPassword( $plaintext );
        }
index 6b0c2aa..1b92f51 100644 (file)
@@ -823,7 +823,7 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
 
                                        $dbw = $this->getConnectionRef( DB_MASTER );
                                        $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-                                       $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
+                                       $ticket = $factory->getEmptyTransactionTicket( $fname );
 
                                        $watchersChunks = array_chunk( $watchers, $wgUpdateRowsPerQuery );
                                        foreach ( $watchersChunks as $watchersChunk ) {
@@ -838,7 +838,7 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
                                                );
                                                if ( count( $watchersChunks ) > 1 ) {
                                                        $factory->commitAndWaitForReplication(
-                                                               __METHOD__, $ticket, [ 'domain' => $dbw->getDomainID() ]
+                                                               $fname, $ticket, [ 'domain' => $dbw->getDomainID() ]
                                                        );
                                                }
                                        }
index 4184508..ea35ac8 100644 (file)
@@ -166,23 +166,23 @@ class KkConverter extends LanguageConverter {
 
                $this->mCyLa2Arab = [
                        # # Punctuation -> Arabic
-                       '/#|№|No\./u' => '؀', # &#x0600;
-                       '/\,/' => '،', # &#x060C;
-                       '/;/' => '؛', # &#x061B;
-                       '/\?/' => '؟', # &#x061F;
-                       '/%/' => '٪', # &#x066A;
-                       '/\*/' => '٭', # &#x066D;
+                       '/#|№|No\./u' => '؀', # U+0600
+                       '/\,/' => '،', # U+060C
+                       '/;/' => '؛', # U+061B
+                       '/\?/' => '؟', # U+061F
+                       '/%/' => '٪', # U+066A
+                       '/\*/' => '٭', # U+066D
                        # # Digits -> Arabic
-                       '/0/' => '۰', # &#x06F0;
-                       '/1/' => '۱', # &#x06F1;
-                       '/2/' => '۲', # &#x06F2;
-                       '/3/' => '۳', # &#x06F3;
-                       '/4/' => '۴', # &#x06F4;
-                       '/5/' => '۵', # &#x06F5;
-                       '/6/' => '۶', # &#x06F6;
-                       '/7/' => '۷', # &#x06F7;
-                       '/8/' => '۸', # &#x06F8;
-                       '/9/' => '۹', # &#x06F9;
+                       '/0/' => '۰', # U+06F0
+                       '/1/' => '۱', # U+06F1
+                       '/2/' => '۲', # U+06F2
+                       '/3/' => '۳', # U+06F3
+                       '/4/' => '۴', # U+06F4
+                       '/5/' => '۵', # U+06F5
+                       '/6/' => '۶', # U+06F6
+                       '/7/' => '۷', # U+06F7
+                       '/8/' => '۸', # U+06F8
+                       '/9/' => '۹', # U+06F9
                        # # Cyrillic -> Arabic
                        '/Аллаһ/ui' => 'ﷲ',
                        '/([АӘЕЁИОӨҰҮЭЮЯЪЬ])е/ui' => '$1يە',
index b90ca41..580f64a 100644 (file)
@@ -57,16 +57,16 @@ class KuConverter extends LanguageConverter {
                '؟' => '?',
 
                # digits
-               '٠' => '0', # &#x0660;
-               '١' => '1', # &#x0661;
-               '٢' => '2', # &#x0662;
-               '٣' => '3', # &#x0663;
-               '٤' => '4', # &#x0664;
-               '٥' => '5', # &#x0665;
-               '٦' => '6', # &#x0666;
-               '٧' => '7', # &#x0667;
-               '٨' => '8', # &#x0668;
-               '٩' => '9', # &#x0669;
+               '٠' => '0', # U+0660
+               '١' => '1', # U+0661
+               '٢' => '2', # U+0662
+               '٣' => '3', # U+0663
+               '٤' => '4', # U+0664
+               '٥' => '5', # U+0665
+               '٦' => '6', # U+0666
+               '٧' => '7', # U+0667
+               '٨' => '8', # U+0668
+               '٩' => '9', # U+0669
        ];
 
        public $mLatinToArabic = [
@@ -130,16 +130,16 @@ class KuConverter extends LanguageConverter {
 
 /*             # deactivated for now, breaks links i.e. in header of Special:Recentchanges :-(
                # digits
-               '0' => '٠', # &#x0660;
-               '1' => '١', # &#x0661;
-               '2' => '٢', # &#x0662;
-               '3' => '٣', # &#x0663;
-               '4' => '٤', # &#x0664;
-               '5' => '٥', # &#x0665;
-               '6' => '٦', # &#x0666;
-               '7' => '٧', # &#x0667;
-               '8' => '٨', # &#x0668;
-               '9' => '٩', # &#x0669;
+               '0' => '٠', # U+0660
+               '1' => '١', # U+0661
+               '2' => '٢', # U+0662
+               '3' => '٣', # U+0663
+               '4' => '٤', # U+0664
+               '5' => '٥', # U+0665
+               '6' => '٦', # U+0666
+               '7' => '٧', # U+0667
+               '8' => '٨', # U+0668
+               '9' => '٩', # U+0669
 */
                ];
 
index 3bc2245..60be21c 100644 (file)
@@ -86,21 +86,21 @@ $dateFormats = [
 ];
 
 $digitTransformTable = [
-       '0' => '٠', # &#x0660;
-       '1' => '١', # &#x0661;
-       '2' => '٢', # &#x0662;
-       '3' => '٣', # &#x0663;
-       '4' => '٤', # &#x0664;
-       '5' => '٥', # &#x0665;
-       '6' => '٦', # &#x0666;
-       '7' => '٧', # &#x0667;
-       '8' => '٨', # &#x0668;
-       '9' => '٩', # &#x0669;
+       '0' => '٠', # U+0660
+       '1' => '١', # U+0661
+       '2' => '٢', # U+0662
+       '3' => '٣', # U+0663
+       '4' => '٤', # U+0664
+       '5' => '٥', # U+0665
+       '6' => '٦', # U+0666
+       '7' => '٧', # U+0667
+       '8' => '٨', # U+0668
+       '9' => '٩', # U+0669
 ];
 
 $separatorTransformTable = [
-       '.' => '٫', # &#x066b;
-       ',' => '٬', # &#x066c;
+       '.' => '٫', # U+066B
+       ',' => '٬', # U+066C
 ];
 
 $namespaceNames = [
index 2516fc7..55a2323 100644 (file)
@@ -152,16 +152,16 @@ $magicWords = [
 ];
 
 $digitTransformTable = [
-       '0' => '০', # &#x09e6;
-       '1' => '১', # &#x09e7;
-       '2' => '২', # &#x09e8;
-       '3' => '৩', # &#x09e9;
-       '4' => '৪', # &#x09ea;
-       '5' => '৫', # &#x09eb;
-       '6' => '৬', # &#x09ec;
-       '7' => '৭', # &#x09ed;
-       '8' => '৮', # &#x09ee;
-       '9' => '৯', # &#x09ef;
+       '0' => '০', # U+09E6
+       '1' => '১', # U+09E7
+       '2' => '২', # U+09E8
+       '3' => '৩', # U+09E9
+       '4' => '৪', # U+09EA
+       '5' => '৫', # U+09EB
+       '6' => '৬', # U+09EC
+       '7' => '৭', # U+09ED
+       '8' => '৮', # U+09EE
+       '9' => '৯', # U+09EF
 ];
 
 $digitGroupingPattern = "##,##,###";
index 548a8d6..8ad1b1e 100644 (file)
@@ -52,14 +52,14 @@ $specialPageAliases = [
 ];
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
index 043f4d1..89cd03c 100644 (file)
@@ -9,14 +9,14 @@
  */
 
 $digitTransformTable = [
-       '0' => '༠', # &#x0f20;
-       '1' => '༡', # &#x0f21;
-       '2' => '༢', # &#x0f22;
-       '3' => '༣', # &#x0f23;
-       '4' => '༤', # &#x0f24;
-       '5' => '༥', # &#x0f25;
-       '6' => '༦', # &#x0f26;
-       '7' => '༧', # &#x0f27;
-       '8' => '༨', # &#x0f28;
-       '9' => '༩', # &#x0f29;
+       '0' => '༠', # U+0F20
+       '1' => '༡', # U+0F21
+       '2' => '༢', # U+0F22
+       '3' => '༣', # U+0F23
+       '4' => '༤', # U+0F24
+       '5' => '༥', # U+0F25
+       '6' => '༦', # U+0F26
+       '7' => '༧', # U+0F27
+       '8' => '༨', # U+0F28
+       '9' => '༩', # U+0F29
 ];
index e615462..a9131fd 100644 (file)
@@ -120,21 +120,21 @@ $magicWords = [
 ];
 
 $digitTransformTable = [
-       '0' => '٠', # &#x0660;
-       '1' => '١', # &#x0661;
-       '2' => '٢', # &#x0662;
-       '3' => '٣', # &#x0663;
-       '4' => '٤', # &#x0664;
-       '5' => '٥', # &#x0665;
-       '6' => '٦', # &#x0666;
-       '7' => '٧', # &#x0667;
-       '8' => '٨', # &#x0668;
-       '9' => '٩', # &#x0669;
+       '0' => '٠', # U+0660
+       '1' => '١', # U+0661
+       '2' => '٢', # U+0662
+       '3' => '٣', # U+0663
+       '4' => '٤', # U+0664
+       '5' => '٥', # U+0665
+       '6' => '٦', # U+0666
+       '7' => '٧', # U+0667
+       '8' => '٨', # U+0668
+       '9' => '٩', # U+0669
 ];
 
 $separatorTransformTable = [
-       '.' => '٫', # &#x066b;
-       ',' => '٬', # &#x066c;
+       '.' => '٫', # U+066B
+       ',' => '٬', # U+066C
 ];
 
 $datePreferences = [
index 50ae191..46015a8 100644 (file)
@@ -9,14 +9,14 @@
  */
 
 $digitTransformTable = [
-       '0' => '༠', # &#x0f20;
-       '1' => '༡', # &#x0f21;
-       '2' => '༢', # &#x0f22;
-       '3' => '༣', # &#x0f23;
-       '4' => '༤', # &#x0f24;
-       '5' => '༥', # &#x0f25;
-       '6' => '༦', # &#x0f26;
-       '7' => '༧', # &#x0f27;
-       '8' => '༨', # &#x0f28;
-       '9' => '༩', # &#x0f29;
+       '0' => '༠', # U+0F20
+       '1' => '༡', # U+0F21
+       '2' => '༢', # U+0F22
+       '3' => '༣', # U+0F23
+       '4' => '༤', # U+0F24
+       '5' => '༥', # U+0F25
+       '6' => '༦', # U+0F26
+       '7' => '༧', # U+0F27
+       '8' => '༨', # U+0F28
+       '9' => '༩', # U+0F29
 ];
index 4e10908..bda468c 100644 (file)
@@ -318,22 +318,22 @@ $magicWords = [
 ];
 
 $digitTransformTable = [
-       '0' => '۰', # &#x06f0;
-       '1' => '۱', # &#x06f1;
-       '2' => '۲', # &#x06f2;
-       '3' => '۳', # &#x06f3;
-       '4' => '۴', # &#x06f4;
-       '5' => '۵', # &#x06f5;
-       '6' => '۶', # &#x06f6;
-       '7' => '۷', # &#x06f7;
-       '8' => '۸', # &#x06f8;
-       '9' => '۹', # &#x06f9;
-       '%' => '٪', # &#x066a;
+       '0' => '۰', # U+06F0
+       '1' => '۱', # U+06F1
+       '2' => '۲', # U+06F2
+       '3' => '۳', # U+06F3
+       '4' => '۴', # U+06F4
+       '5' => '۵', # U+06F5
+       '6' => '۶', # U+06F6
+       '7' => '۷', # U+06F7
+       '8' => '۸', # U+06F8
+       '9' => '۹', # U+06F9
+       '%' => '٪', # U+066A
 ];
 
 $separatorTransformTable = [
-       '.' => '٫', # &#x066b;
-       ',' => '٬', # &#x066c;
+       '.' => '٫', # U+066B
+       ',' => '٬', # U+066C
 ];
 
 /**
index aec3a76..a011808 100644 (file)
@@ -112,16 +112,16 @@ $specialPageAliases = [
 ];
 
 $digitTransformTable = [
-       '0' => '૦', # &#x0ae6;
-       '1' => '૧', # &#x0ae7;
-       '2' => '૨', # &#x0ae8;
-       '3' => '૩', # &#x0ae9;
-       '4' => '૪', # &#x0aea;
-       '5' => '૫', # &#x0aeb;
-       '6' => '૬', # &#x0aec;
-       '7' => '૭', # &#x0aed;
-       '8' => '૮', # &#x0aee;
-       '9' => '૯', # &#x0aef;
+       '0' => '૦', # U+0AE6
+       '1' => '૧', # U+0AE7
+       '2' => '૨', # U+0AE8
+       '3' => '૩', # U+0AE9
+       '4' => '૪', # U+0AEA
+       '5' => '૫', # U+0AEB
+       '6' => '૬', # U+0AEC
+       '7' => '૭', # U+0AED
+       '8' => '૮', # U+0AEE
+       '9' => '૯', # U+0AEF
 ];
 
 $digitGroupingPattern = "##,##,###";
index 5a70a83..5bcad86 100644 (file)
@@ -285,16 +285,16 @@ $magicWords = [
 ];
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
 $linkTrail = "/^([a-z\x{0900}-\x{0963}\x{0966}-\x{A8E0}-\x{A8FF}]+)(.*)$/sDu";
 
index 6032773..2ff8c61 100644 (file)
@@ -17,21 +17,21 @@ $fallback = 'kk-cyrl';
 $rtl = true;
 
 $digitTransformTable = [
-       '0' => '۰', # &#x06f0;
-       '1' => '۱', # &#x06f1;
-       '2' => '۲', # &#x06f2;
-       '3' => '۳', # &#x06f3;
-       '4' => '۴', # &#x06f4;
-       '5' => '۵', # &#x06f5;
-       '6' => '۶', # &#x06f6;
-       '7' => '۷', # &#x06f7;
-       '8' => '۸', # &#x06f8;
-       '9' => '۹', # &#x06f9;
+       '0' => '۰', # U+06F0
+       '1' => '۱', # U+06F1
+       '2' => '۲', # U+06F2
+       '3' => '۳', # U+06F3
+       '4' => '۴', # U+06F4
+       '5' => '۵', # U+06F5
+       '6' => '۶', # U+06F6
+       '7' => '۷', # U+06F7
+       '8' => '۸', # U+06F8
+       '9' => '۹', # U+06F9
 ];
 
 $separatorTransformTable = [
-       '.' => '٫', # &#x066b;
-       ',' => '٬', # &#x066c;
+       '.' => '٫', # U+066B
+       ',' => '٬', # U+066C
 ];
 
 $fallback8bitEncoding = 'windows-1256';
index 299beb8..65577a9 100644 (file)
@@ -61,16 +61,16 @@ $namespaceAliases = [
 ];
 
 $digitTransformTable = [
-       '0' => '០', # &#x17e0;
-       '1' => '១', # &#x17e1;
-       '2' => '២', # &#x17e2;
-       '3' => '៣', # &#x17e3;
-       '4' => '៤', # &#x17e4;
-       '5' => '៥', # &#x17e5;
-       '6' => '៦', # &#x17e6;
-       '7' => '៧', # &#x17e7;
-       '8' => '៨', # &#x17e8;
-       '9' => '៩', # &#x17e9;
+       '0' => '០', # U+17E0
+       '1' => '១', # U+17E1
+       '2' => '២', # U+17E2
+       '3' => '៣', # U+17E3
+       '4' => '៤', # U+17E4
+       '5' => '៥', # U+17E5
+       '6' => '៦', # U+17E6
+       '7' => '៧', # U+17E7
+       '8' => '៨', # U+17E8
+       '9' => '៩', # U+17E9
 ];
 
 $separatorTransformTable = [
index 5fa77e9..0078261 100644 (file)
@@ -49,16 +49,16 @@ $namespaceNames = [
 ];
 
 $digitTransformTable = [
-       '0' => '೦', # &#x0ce6;
-       '1' => '೧', # &#x0ce7;
-       '2' => '೨', # &#x0ce8;
-       '3' => '೩', # &#x0ce9;
-       '4' => '೪', # &#x0cea;
-       '5' => '೫', # &#x0ceb;
-       '6' => '೬', # &#x0cec;
-       '7' => '೭', # &#x0ced;
-       '8' => '೮', # &#x0cee;
-       '9' => '೯', # &#x0cef;
+       '0' => '೦', # U+0CE6
+       '1' => '೧', # U+0CE7
+       '2' => '೨', # U+0CE8
+       '3' => '೩', # U+0CE9
+       '4' => '೪', # U+0CEA
+       '5' => '೫', # U+0CEB
+       '6' => '೬', # U+0CEC
+       '7' => '೭', # U+0CED
+       '8' => '೮', # U+0CEE
+       '9' => '೯', # U+0CEF
 ];
 
 $digitGroupingPattern = "##,##,###";
index 27ac9f1..f4ee3a0 100644 (file)
@@ -33,19 +33,19 @@ $namespaceNames = [
 ];
 
 $digitTransformTable = [
-       '0' => '٠', # &#x0660;
-       '1' => '١', # &#x0661;
-       '2' => '٢', # &#x0662;
-       '3' => '٣', # &#x0663;
-       '4' => '٤', # &#x0664;
-       '5' => '٥', # &#x0665;
-       '6' => '٦', # &#x0666;
-       '7' => '٧', # &#x0667;
-       '8' => '٨', # &#x0668;
-       '9' => '٩', # &#x0669;
+       '0' => '٠', # U+0660
+       '1' => '١', # U+0661
+       '2' => '٢', # U+0662
+       '3' => '٣', # U+0663
+       '4' => '٤', # U+0664
+       '5' => '٥', # U+0665
+       '6' => '٦', # U+0666
+       '7' => '٧', # U+0667
+       '8' => '٨', # U+0668
+       '9' => '٩', # U+0669
 ];
 
 $separatorTransformTable = [
-       '.' => '٫', # &#x066b;
-       ',' => '٬', # &#x066c;
+       '.' => '٫', # U+066B
+       ',' => '٬', # U+066C
 ];
index 55dc004..ad84b77 100644 (file)
@@ -30,14 +30,14 @@ $namespaceNames = [
 ];
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
index 0a80a62..4d53e36 100644 (file)
@@ -19,19 +19,19 @@ $fallback = 'ckb';
 $rtl = true;
 
 $digitTransformTable = [
-       '0' => '٠', # &#x0660;
-       '1' => '١', # &#x0661;
-       '2' => '٢', # &#x0662;
-       '3' => '٣', # &#x0663;
-       '4' => '٤', # &#x0664;
-       '5' => '٥', # &#x0665;
-       '6' => '٦', # &#x0666;
-       '7' => '٧', # &#x0667;
-       '8' => '٨', # &#x0668;
-       '9' => '٩', # &#x0669;
+       '0' => '٠', # U+0660
+       '1' => '١', # U+0661
+       '2' => '٢', # U+0662
+       '3' => '٣', # U+0663
+       '4' => '٤', # U+0664
+       '5' => '٥', # U+0665
+       '6' => '٦', # U+0666
+       '7' => '٧', # U+0667
+       '8' => '٨', # U+0668
+       '9' => '٩', # U+0669
 ];
 
 $separatorTransformTable = [
-       '.' => '٫', # &#x066b;
-       ',' => '٬', # &#x066c;
+       '.' => '٫', # U+066B
+       ',' => '٬', # U+066C
 ];
index 665b222..a3f29da 100644 (file)
@@ -80,14 +80,14 @@ $specialPageAliases = [
 ];
 
 $digitTransformTable = [
-       '0' => '໐', # &#x0ed0;
-       '1' => '໑', # &#x0ed1;
-       '2' => '໒', # &#x0ed2;
-       '3' => '໓', # &#x0ed3;
-       '4' => '໔', # &#x0ed4;
-       '5' => '໕', # &#x0ed5;
-       '6' => '໖', # &#x0ed6;
-       '7' => '໗', # &#x0ed7;
-       '8' => '໘', # &#x0ed8;
-       '9' => '໙', # &#x0ed9;
+       '0' => '໐', # U+0ED0
+       '1' => '໑', # U+0ED1
+       '2' => '໒', # U+0ED2
+       '3' => '໓', # U+0ED3
+       '4' => '໔', # U+0ED4
+       '5' => '໕', # U+0ED5
+       '6' => '໖', # U+0ED6
+       '7' => '໗', # U+0ED7
+       '8' => '໘', # U+0ED8
+       '9' => '໙', # U+0ED9
 ];
index c172d08..03e2f87 100644 (file)
@@ -311,16 +311,16 @@ $magicWords = [
 ];
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
 
 $linkTrail = "/^([\u{0900}-\u{0963}\u{0971}-\u{097F}\u{FEFF}\u{200D}]+)(.*)$/sDu";
index c6ab806..32cf115 100644 (file)
@@ -46,14 +46,14 @@ $specialPageAliases = [
 ];
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
index 65a7595..86ef77b 100644 (file)
@@ -28,14 +28,14 @@ $namespaceNames = [
 ];
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
index d17105a..022da81 100644 (file)
  */
 
 $digitTransformTable = [
-       '0' => '୦', # &#x0b66;
-       '1' => '୧', # &#x0b67;
-       '2' => '୨', # &#x0b68;
-       '3' => '୩', # &#x0b69;
-       '4' => '୪', # &#x0b6a;
-       '5' => '୫', # &#x0b6b;
-       '6' => '୬', # &#x0b6c;
-       '7' => '୭', # &#x0b6d;
-       '8' => '୮', # &#x0b6e;
-       '9' => '୯', # &#x0b6f;
+       '0' => '୦', # U+0B66
+       '1' => '୧', # U+0B67
+       '2' => '୨', # U+0B68
+       '3' => '୩', # U+0B69
+       '4' => '୪', # U+0B6A
+       '5' => '୫', # U+0B6B
+       '6' => '୬', # U+0B6C
+       '7' => '୭', # U+0B6D
+       '8' => '୮', # U+0B6E
+       '9' => '୯', # U+0B6F
 ];
 
 $linkTrail = "/^([a-z\x{0B00}-\x{0B7F}]+)(.*)$/sDu";
index 7062e07..8a3ecd0 100644 (file)
@@ -31,14 +31,14 @@ $namespaceNames = [
 ];
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
index 0b203b0..3f01876 100644 (file)
 $rtl = true;
 
 $digitTransformTable = [
-       '0' => '۰', # &#x1776;
-       '1' => '۱', # &#x1777;
-       '2' => '۲', # &#x1778;
-       '3' => '۳', # &#x1779;
-       '4' => '۴', # &#x1780;
-       '5' => '۵', # &#x1781;
-       '6' => '۶', # &#x1782;
-       '7' => '۷', # &#x1783;
-       '8' => '۸', # &#x1784;
-       '9' => '۹', # &#x1785;
+       '0' => '۰', # U+06F0
+       '1' => '۱', # U+06F1
+       '2' => '۲', # U+06F2
+       '3' => '۳', # U+06F3
+       '4' => '۴', # U+06F4
+       '5' => '۵', # U+06F5
+       '6' => '۶', # U+06F6
+       '7' => '۷', # U+06F7
+       '8' => '۸', # U+06F8
+       '9' => '۹', # U+06F9
 ];
 
 $separatorTransformTable = [
-       '.' => '٫', # &#x066b;
-       ',' => '٬', # &#x066c;
+       '.' => '٫', # U+066B
+       ',' => '٬', # U+066C
 ];
 
 $namespaceNames = [
index 81a8a14..72dec65 100644 (file)
 $fallback = 'hi';
 
 $digitTransformTable = [
-       '0' => '०', # &#x0966;
-       '1' => '१', # &#x0967;
-       '2' => '२', # &#x0968;
-       '3' => '३', # &#x0969;
-       '4' => '४', # &#x096a;
-       '5' => '५', # &#x096b;
-       '6' => '६', # &#x096c;
-       '7' => '७', # &#x096d;
-       '8' => '८', # &#x096e;
-       '9' => '९', # &#x096f;
+       '0' => '०', # U+0966
+       '1' => '१', # U+0967
+       '2' => '२', # U+0968
+       '3' => '३', # U+0969
+       '4' => '४', # U+096A
+       '5' => '५', # U+096B
+       '6' => '६', # U+096C
+       '7' => '७', # U+096D
+       '8' => '८', # U+096E
+       '9' => '९', # U+096F
 ];
 
 $linkPrefixExtension = false;
index 27276aa..f5a7c01 100644 (file)
@@ -13,15 +13,15 @@ $fallback = 'ur, pnb';
 $rtl = true;
 
 $digitTransformTable = [
-       '0' => '۰', # &#x06f0;
-       '1' => '۱', # &#x06f1;
-       '2' => '۲', # &#x06f2;
-       '3' => '۳', # &#x06f3;
-       '4' => '۴', # &#x06f4;
-       '5' => '۵', # &#x06f5;
-       '6' => '۶', # &#x06f6;
-       '7' => '۷', # &#x06f7;
-       '8' => '۸', # &#x06f8;
-       '9' => '۹', # &#x06f9;
-       '%' => '٪', # &#x066a;
+       '0' => '۰', # U+06F0
+       '1' => '۱', # U+06F1
+       '2' => '۲', # U+06F2
+       '3' => '۳', # U+06F3
+       '4' => '۴', # U+06F4
+       '5' => '۵', # U+06F5
+       '6' => '۶', # U+06F6
+       '7' => '۷', # U+06F7
+       '8' => '۸', # U+06F8
+       '9' => '۹', # U+06F9
+       '%' => '٪', # U+066A
 ];
index ff06e49..a0177b1 100644 (file)
@@ -4322,7 +4322,6 @@ useemail
 uselang
 uselivepreview
 usemod
-usemsgcache
 usenewrc
 user
 useragent
index 02c8bed..747319d 100644 (file)
@@ -143,6 +143,7 @@ ERROR
                        );
                }
                $this->total += $dbw->affectedRows();
+               MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->waitForReplication();
        }
 
        /**
index 60f5e8a..6eb2d6d 100644 (file)
@@ -116,8 +116,8 @@ class PopulateArchiveRevId extends LoggedUpdateMaintenance {
                                        $toDelete[] = $id;
 
                                        $maxId = max(
-                                               (int)$dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], __METHOD__ ),
-                                               (int)$dbw->selectField( 'slots', 'MAX(slot_revision_id)', [], __METHOD__ )
+                                               (int)$dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], $fname ),
+                                               (int)$dbw->selectField( 'slots', 'MAX(slot_revision_id)', [], $fname )
                                        );
                                        if ( $id <= $maxId ) {
                                                $dbw->insert( 'revision', [ 'rev_id' => $maxId + 1 ] + self::$dummyRev, $fname );
@@ -220,7 +220,43 @@ class PopulateArchiveRevId extends LoggedUpdateMaintenance {
                        );
                }
                if ( !$rev ) {
-                       throw new UnexpectedValueException( 'No revisions are available to copy' );
+                       // Since no revisions are available to copy, generate a dummy
+                       // revision to a dummy page, then rollback the commit
+                       wfDebug( __METHOD__ . ": No revisions are available to copy\n" );
+
+                       $dbw->begin();
+
+                       // Make a title and revision and insert them
+                       $title = Title::newFromText( "PopulateArchiveRevId_4b05b46a81e29" );
+                       $page = WikiPage::factory( $title );
+                       $updater = $page->newPageUpdater(
+                               User::newSystemUser( 'Maintenance script', [ 'steal' => true ] )
+                       );
+                       $updater->setContent(
+                               'main',
+                               ContentHandler::makeContent( "Content for dummy rev", $title )
+                       );
+                       $updater->saveRevision(
+                               CommentStoreComment::newUnsavedComment( 'dummy rev summary' ),
+                               EDIT_NEW | EDIT_SUPPRESS_RC
+                       );
+
+                       // get the revision row just inserted
+                       $rev = $dbw->selectRow(
+                               'revision',
+                               '*',
+                               [],
+                               __METHOD__,
+                               [ 'ORDER BY' => 'rev_timestamp ASC' ]
+                       );
+
+                       $dbw->rollback();
+               }
+               if ( !$rev ) {
+                       // This should never happen.
+                       throw new UnexpectedValueException(
+                               'No revisions are available to copy, and one couldn\'t be created'
+                       );
                }
 
                unset( $rev->rev_id );
index 819bc23..a15337c 100644 (file)
        };
 
        /**
-        * Category selector capsule item widget. Extends OO.ui.CapsuleItemWidget with the ability to link
+        * Category selector tag item widget. Extends OO.ui.TagItemWidget with the ability to link
         * to the given page, and to show its existence status (i.e., whether it is a redlink).
         *
         * @class mw.widgets.CategoryTagItemWidget
                                .addClass( 'new' );
                }
        };
-
-       // For backwards compatibility. See T183299.
-       mw.widgets.CategoryCapsuleItemWidget = mw.widgets.CategoryTagItemWidget;
 }() );
index 29c7dae..30ba1c1 100644 (file)
@@ -61,6 +61,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                                [ $this, 'hookGetPreferences' ]
                        ]
                ] );
+               $this->mergeMwGlobalArrayValue( 'wgDefaultUserOptions', [
+                       'testradio' => 'option1',
+               ] );
+               // Workaround for static caching in User::getDefaultOptions()
+               $this->setContentLang( Language::factory( 'qqq' ) );
        }
 
        public function hookGetPreferences( $user, &$preferences ) {
@@ -90,7 +95,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'default' => [],
                ];
 
-               return true;
+               $preferences['testradio'] = [
+                       'type' => 'radio',
+                       'options' => [ 'Option 1' => 'option1', 'Option 2' => 'option2' ],
+                       'section' => 'test',
+               ];
        }
 
        /**
@@ -106,6 +115,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'willBeNull' => 'registered',
                        'willBeEmpty' => 'registered',
                        'willBeHappy' => 'registered',
+                       'testradio' => 'registered',
                        'testmultiselect-opt1' => 'registered-multiselect',
                        'testmultiselect-opt2' => 'registered-multiselect',
                        'testmultiselect-opt3' => 'registered-multiselect',
@@ -243,65 +253,6 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $this->assertEquals( self::$Success, $response );
        }
 
-       public function testOptionWithValue() {
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'resetOptions' );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'setOption' )
-                       ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'saveSettings' );
-
-               $request = $this->getSampleRequest( [ 'optionname' => 'name', 'optionvalue' => 'value' ] );
-
-               $response = $this->executeQuery( $request );
-
-               $this->assertEquals( self::$Success, $response );
-       }
-
-       public function testOptionResetValue() {
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'resetOptions' );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'setOption' )
-                       ->with( $this->equalTo( 'name' ), $this->identicalTo( null ) );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'saveSettings' );
-
-               $request = $this->getSampleRequest( [ 'optionname' => 'name' ] );
-               $response = $this->executeQuery( $request );
-
-               $this->assertEquals( self::$Success, $response );
-       }
-
-       public function testChange() {
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'resetOptions' );
-
-               $this->mUserMock->expects( $this->exactly( 3 ) )
-                       ->method( 'setOption' )
-                       ->withConsecutive(
-                               [ $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) ],
-                               [ $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ],
-                               [ $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ]
-                       );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'saveSettings' );
-
-               $request = $this->getSampleRequest( [
-                       'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy'
-               ] );
-
-               $response = $this->executeQuery( $request );
-
-               $this->assertEquals( self::$Success, $response );
-       }
-
        public function testResetChangeOption() {
                $this->mUserMock->expects( $this->once() )
                        ->method( 'resetOptions' );
@@ -328,95 +279,121 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $this->assertEquals( self::$Success, $response );
        }
 
-       public function testMultiSelect() {
+       /**
+        * @dataProvider provideOptionManupulation
+        * @param array $params
+        * @param array $setOptions
+        * @param array|null $result
+        */
+       public function testOptionManupulation( array $params, array $setOptions, array $result = null,
+               $message = ''
+       ) {
                $this->mUserMock->expects( $this->never() )
                        ->method( 'resetOptions' );
 
-               $this->mUserMock->expects( $this->exactly( 4 ) )
+               $this->mUserMock->expects( $this->exactly( count( $setOptions ) ) )
                        ->method( 'setOption' )
-                       ->withConsecutive(
-                               [ $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) ],
-                               [ $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) ],
-                               [ $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) ],
-                               [ $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) ]
-                       );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'saveSettings' );
-
-               $request = $this->getSampleRequest( [
-                       'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|'
-                               . 'testmultiselect-opt3=|testmultiselect-opt4=0'
-               ] );
-
-               $response = $this->executeQuery( $request );
-
-               $this->assertEquals( self::$Success, $response );
-       }
-
-       public function testSpecialOption() {
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'resetOptions' );
-
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'saveSettings' );
-
-               $request = $this->getSampleRequest( [
-                       'change' => 'special=1'
-               ] );
-
-               $response = $this->executeQuery( $request );
-
-               $this->assertEquals( [
-                       'options' => 'success',
-                       'warnings' => [
-                               'options' => [
-                                       'warnings' => "Validation error for \"special\": cannot be set by this module."
-                               ]
-                       ]
-               ], $response );
-       }
-
-       public function testUnknownOption() {
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'resetOptions' );
-
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'saveSettings' );
-
-               $request = $this->getSampleRequest( [
-                       'change' => 'unknownOption=1'
-               ] );
+                       ->withConsecutive( ...$setOptions );
+
+               if ( $setOptions ) {
+                       $this->mUserMock->expects( $this->once() )
+                               ->method( 'saveSettings' );
+               } else {
+                       $this->mUserMock->expects( $this->never() )
+                               ->method( 'saveSettings' );
+               }
 
+               $request = $this->getSampleRequest( $params );
                $response = $this->executeQuery( $request );
 
-               $this->assertEquals( [
-                       'options' => 'success',
-                       'warnings' => [
-                               'options' => [
-                                       'warnings' => "Validation error for \"unknownOption\": not a valid preference."
-                               ]
-                       ]
-               ], $response );
+               if ( !$result ) {
+                       $result = self::$Success;
+               }
+               $this->assertEquals( $result, $response, $message );
        }
 
-       public function testUserjsOption() {
-               $this->mUserMock->expects( $this->never() )
-                       ->method( 'resetOptions' );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'setOption' )
-                       ->with( $this->equalTo( 'userjs-option' ), $this->equalTo( '1' ) );
-
-               $this->mUserMock->expects( $this->once() )
-                       ->method( 'saveSettings' );
-
-               $request = $this->getSampleRequest( [
-                       'change' => 'userjs-option=1'
-               ] );
-
-               $response = $this->executeQuery( $request );
-
-               $this->assertEquals( self::$Success, $response );
+       public function provideOptionManupulation() {
+               return [
+                       [
+                               [ 'change' => 'userjs-option=1' ],
+                               [ [ 'userjs-option', '1' ] ],
+                               null,
+                               'Setting userjs options',
+                       ],
+                       [
+                               [ 'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy' ],
+                               [
+                                       [ 'willBeNull', null ],
+                                       [ 'willBeEmpty', '' ],
+                                       [ 'willBeHappy', 'Happy' ],
+                               ],
+                               null,
+                               'Basic option setting',
+                       ],
+                       [
+                               [ 'change' => 'testradio=option2' ],
+                               [ [ 'testradio', 'option2' ] ],
+                               null,
+                               'Changing radio options',
+                       ],
+                       [
+                               [ 'change' => 'testradio' ],
+                               [ [ 'testradio', null ] ],
+                               null,
+                               'Resetting radio options',
+                       ],
+                       [
+                               [ 'change' => 'unknownOption=1' ],
+                               [],
+                               [
+                                       'options' => 'success',
+                                       'warnings' => [
+                                               'options' => [
+                                                       'warnings' => "Validation error for \"unknownOption\": not a valid preference."
+                                               ],
+                                       ],
+                               ],
+                               'Unrecognized options should be rejected',
+                       ],
+                       [
+                               [ 'change' => 'special=1' ],
+                               [],
+                               [
+                                       'options' => 'success',
+                                       'warnings' => [
+                                               'options' => [
+                                                       'warnings' => "Validation error for \"special\": cannot be set by this module."
+                                               ]
+                                       ]
+                               ],
+                               'Refuse setting special options',
+                       ],
+                       [
+                               [
+                                       'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|'
+                                               . 'testmultiselect-opt3=|testmultiselect-opt4=0'
+                               ],
+                               [
+                                       [ 'testmultiselect-opt1', true ],
+                                       [ 'testmultiselect-opt2', null ],
+                                       [ 'testmultiselect-opt3', false ],
+                                       [ 'testmultiselect-opt4', false ],
+                               ],
+                               null,
+                               'Setting multiselect options',
+                       ],
+                       [
+                               [ 'optionname' => 'name', 'optionvalue' => 'value' ],
+                               [ [ 'name', 'value' ] ],
+                               null,
+                               'Setting options via optionname/optionvalue'
+                       ],
+                       [
+                               [ 'optionname' => 'name' ],
+                               [ [ 'name', null ] ],
+                               null,
+                               'Resetting options via optionname without optionvalue',
+                       ],
+               ];
        }
 }
index 88f4d8f..607549f 100644 (file)
@@ -102,9 +102,15 @@ Then we got more<br>text
 END;
                $struct = $this->getStructure( $text );
                $this->assertEquals( "Opening text is opening.", $struct->getOpeningText() );
-               $this->assertEquals( "Opening text is opening.   Then we got more text",
+               $this->assertEquals( "Opening text is opening. Then we got more text",
                        $struct->getMainText() );
                $this->assertEquals( [ "Header table row in table another row in table" ],
                        $struct->getAuxiliaryText() );
        }
+
+       public function testPreservesWordSpacing() {
+               $text = "<dd><dl>foo</dl><dl>bar</dl></dd><p>baz</p>";
+               $struct = $this->getStructure( $text );
+               $this->assertEquals( "foo bar baz", $struct->getMainText() );
+       }
 }
index 5c2aa2b..6b1ed7f 100644 (file)
@@ -11,15 +11,17 @@ use Wikimedia\TestingAccessWrapper;
 class DatabasePostgresTest extends MediaWikiTestCase {
 
        private function doTestInsertIgnore() {
-               $reset = new ScopedCallback( function () {
+               $fname = __METHOD__;
+               $reset = new ScopedCallback( function () use ( $fname ) {
                        if ( $this->db->explicitTrxActive() ) {
-                               $this->db->rollback( __METHOD__ );
+                               $this->db->rollback( $fname );
                        }
-                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ) );
+                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ), $fname );
                } );
 
                $this->db->query(
-                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER NOT NULL PRIMARY KEY)"
+                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER NOT NULL PRIMARY KEY)",
+                       __METHOD__
                );
                $this->db->insert( 'foo', [ [ 'i' => 1 ], [ 'i' => 2 ] ], __METHOD__ );
 
@@ -92,19 +94,22 @@ class DatabasePostgresTest extends MediaWikiTestCase {
        }
 
        private function doTestInsertSelectIgnore() {
-               $reset = new ScopedCallback( function () {
+               $fname = __METHOD__;
+               $reset = new ScopedCallback( function () use ( $fname ) {
                        if ( $this->db->explicitTrxActive() ) {
-                               $this->db->rollback( __METHOD__ );
+                               $this->db->rollback( $fname );
                        }
-                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ) );
-                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'bar' ) );
+                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ), $fname );
+                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'bar' ), $fname );
                } );
 
                $this->db->query(
-                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER)"
+                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER)",
+                       __METHOD__
                );
                $this->db->query(
-                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'bar' )} (i INTEGER NOT NULL PRIMARY KEY)"
+                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'bar' )} (i INTEGER NOT NULL PRIMARY KEY)",
+                       __METHOD__
                );
                $this->db->insert( 'bar', [ [ 'i' => 1 ], [ 'i' => 2 ] ], __METHOD__ );
 
index b5965c4..a8b0f90 100644 (file)
@@ -514,7 +514,7 @@ class SanitizerTest extends MediaWikiTestCase {
        public function provideStripAllTags() {
                return [
                        [ '<p>Foo</p>', 'Foo' ],
-                       [ '<p id="one">Foo</p><p id="two">Bar</p>', 'FooBar' ],
+                       [ '<p id="one">Foo</p><p id="two">Bar</p>', 'Foo Bar' ],
                        [ "<p>Foo</p>\n<p>Bar</p>", 'Foo Bar' ],
                        [ '<p>Hello &lt;strong&gt; wor&#x6c;&#100; caf&eacute;</p>', 'Hello <strong> world café' ],
                        [
index d84fcd7..d57d489 100644 (file)
@@ -91,7 +91,12 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
        /**
         * @dataProvider validateOptionsProvider
         */
-       public function testValidateOptions( $optionsToSet, $expectedRedirect, $expectedRedirectOptions ) {
+       public function testValidateOptions(
+               $optionsToSet,
+               $expectedRedirect,
+               $expectedRedirectOptions,
+               $rcfilters
+       ) {
                $redirectQuery = [];
                $redirected = false;
                $output = $this->getMockBuilder( OutputPage::class )
@@ -110,6 +115,7 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
 
                // Give users patrol permissions so we can test that.
                $user = $this->getTestSysop()->getUser();
+               $user->setOption( 'rcenhancedfilters-disable', $rcfilters ? 0 : 1 );
                $ctx->setUser( $user );
 
                // Disable this hook or it could break changeType
index 19a1875..b874215 100644 (file)
@@ -15,13 +15,6 @@ use Wikimedia\TestingAccessWrapper;
  * @covers ChangesListSpecialPage
  */
 class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase {
-       public function setUp() {
-               parent::setUp();
-               $this->setMwGlobals( [
-                       'wgStructuredChangeFiltersShowPreference' => true,
-               ] );
-       }
-
        protected function getPage() {
                $mock = $this->getMockBuilder( ChangesListSpecialPage::class )
                        ->setConstructorArgs(
@@ -1004,57 +997,68 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                                [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
                                true,
                                [ 'userExpLevel' => 'unregistered', 'hidebots' => 1, ],
+                               true,
                        ],
                        [
                                [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
                                true,
                                [ 'hidebots' => 0, 'hidehumans' => 1 ],
+                               true,
                        ],
                        [
                                [ 'hideanons' => 1 ],
                                true,
-                               [ 'userExpLevel' => 'registered' ]
+                               [ 'userExpLevel' => 'registered' ],
+                               true,
                        ],
                        [
                                [ 'hideliu' => 1 ],
                                true,
-                               [ 'userExpLevel' => 'unregistered' ]
+                               [ 'userExpLevel' => 'unregistered' ],
+                               true,
                        ],
                        [
                                [ 'hideanons' => 1, 'hidebots' => 1 ],
                                true,
-                               [ 'userExpLevel' => 'registered', 'hidebots' => 1 ]
+                               [ 'userExpLevel' => 'registered', 'hidebots' => 1 ],
+                               true,
                        ],
                        [
                                [ 'hideliu' => 1, 'hidebots' => 0 ],
                                true,
-                               [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ]
+                               [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ],
+                               true,
                        ],
                        [
                                [ 'hidemyself' => 1, 'hidebyothers' => 1 ],
                                true,
                                [],
+                               true,
                        ],
                        [
                                [ 'hidebots' => 1, 'hidehumans' => 1 ],
                                true,
                                [],
+                               true,
                        ],
                        [
                                [ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
                                true,
                                [],
+                               true,
                        ],
                        [
                                [ 'hideminor' => 1, 'hidemajor' => 1 ],
                                true,
                                [],
+                               true,
                        ],
                        [
                                // changeType
                                [ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],
                                true,
                                [],
+                               true,
                        ],
                ];
        }
index 0b6962d..11988da 100644 (file)
@@ -46,6 +46,7 @@ class SpecialRecentchangesTest extends AbstractChangesListSpecialPageTestCase {
                                [ 'hideanons' => 1, 'hideliu' => 1 ],
                                true,
                                [ 'hideliu' => 1 ],
+                               false,
                        ],
                ];
        }