host=gerrit.wikimedia.org
port=29418
project=mediawiki/core.git
-defaultbranch=master
+track=1
defaultrebase=0
Instead of --keep-uploads, use the same option to parserTests.php, but you
must specify a directory with --upload-dir.
* The 'jquery.arrowSteps' ResourceLoader module is now deprecated.
+* (T62604) Core parser functions returning a number now format the number according
+ to the page content language, not wiki content language.
* IP::isConfiguredProxy() and IP::isTrustedProxy() were removed. Callers should
migrate to using the same functions on a ProxyLookup instance, obtainable from
MediaWikiServices.
*/
protected $context;
+ /**
+ * @var bool Whether an old revision is edited
+ */
+ private $isOldRev = false;
+
/**
* @param Article $article
*/
if ( !$revision->isCurrent() ) {
$this->mArticle->setOldSubtitle( $revision->getId() );
$wgOut->addWikiMsg( 'editingold' );
+ $this->isOldRev = true;
}
} elseif ( $this->mTitle->exists() ) {
// Something went wrong
$classes[] = 'mw-textarea-cprotected';
}
}
+ # Is an old revision being edited?
+ if ( $this->isOldRev ) {
+ $classes[] = 'mw-textarea-oldrev';
+ }
$attribs = [ 'tabindex' => 1 ];
if ( !is_array( $types ) ) {
$types = [ $types ];
}
- if ( !$schema ) {
+ if ( $schema === false ) {
$schema = $this->getCoreSchema();
}
$table = $this->realTableName( $table, 'raw' );
function strencode( $s ) {
// Should not be called by us
-
return pg_escape_string( $this->getBindingHandle(), $s );
}
/**
* Create a new load balancer for external storage. The resulting object will be
- * untracked, not chronology-protected, and the caller is responsible for
- * cleaning it up.
+ * untracked, not chronology-protected, and the caller is responsible for cleaning it up.
*
* This method is for only advanced usage and callers should almost always use
* getExternalLB() instead. This method can be useful when a table is used as a
* key/value store. In that cases, one might want to query it in autocommit mode
* (DBO_TRX off) but still use DBO_TRX transaction rounds on other tables.
*
- * @param string $cluster External storage cluster, or false for core
- * @param bool|string $domain Domain ID, or false for the current domain
+ * @param string $cluster External storage cluster name
* @return ILoadBalancer
*/
- public function newExternalLB( $cluster, $domain = false );
+ public function newExternalLB( $cluster );
/**
* Get a cached (tracked) load balancer for external storage
*
- * @param string $cluster External storage cluster, or false for core
- * @param bool|string $domain Domain ID, or false for the current domain
+ * @param string $cluster External storage cluster name
* @return ILoadBalancer
*/
- public function getExternalLB( $cluster, $domain = false );
+ public function getExternalLB( $cluster );
/**
* Execute a function for each tracked load balancer
/**
* @see ILBFactory::newExternalLB()
* @param string $cluster
- * @param bool $domain
* @return LoadBalancer
*/
- abstract public function newExternalLB( $cluster, $domain = false );
+ abstract public function newExternalLB( $cluster );
/**
* @see ILBFactory::getExternalLB()
* @param string $cluster
- * @param bool $domain
* @return LoadBalancer
*/
- abstract public function getExternalLB( $cluster, $domain = false );
+ abstract public function getExternalLB( $cluster );
/**
* Call a method of each tracked load balancer
return $this->mainLBs[$section];
}
- /**
- * @param string $cluster
- * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
- * @throws InvalidArgumentException
- * @return LoadBalancer
- */
- public function newExternalLB( $cluster, $domain = false ) {
+ public function newExternalLB( $cluster ) {
if ( !isset( $this->externalLoads[$cluster] ) ) {
throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
);
}
- /**
- * @param string $cluster External storage cluster, or false for core
- * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
- * @return LoadBalancer
- */
- public function getExternalLB( $cluster, $domain = false ) {
+ public function getExternalLB( $cluster ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
- $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $domain );
+ $this->extLBs[$cluster] = $this->newExternalLB( $cluster );
$this->getChronologyProtector()->initLB( $this->extLBs[$cluster] );
}
return $this->mainLB;
}
- /**
- * @param string $cluster
- * @param bool|string $domain
- * @return LoadBalancer
- * @throws InvalidArgumentException
- */
- public function newExternalLB( $cluster, $domain = false ) {
+ public function newExternalLB( $cluster ) {
if ( !isset( $this->externalClusters[$cluster] ) ) {
throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"." );
}
return $this->newLoadBalancer( $this->externalClusters[$cluster] );
}
- /**
- * @param string $cluster
- * @param bool|string $domain
- * @return LoadBalancer
- */
- public function getExternalLB( $cluster, $domain = false ) {
+ public function getExternalLB( $cluster ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
- $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $domain );
+ $this->extLBs[$cluster] = $this->newExternalLB( $cluster );
$this->getChronologyProtector()->initLB( $this->extLBs[$cluster] );
}
}
/**
- * @param bool|string $wiki
+ * @param bool|string $domain (unused)
* @return LoadBalancerSingle
*/
- public function newMainLB( $wiki = false ) {
+ public function newMainLB( $domain = false ) {
return $this->lb;
}
/**
- * @param bool|string $wiki
+ * @param bool|string $domain (unused)
* @return LoadBalancerSingle
*/
- public function getMainLB( $wiki = false ) {
+ public function getMainLB( $domain = false ) {
return $this->lb;
}
/**
- * @param string $cluster External storage cluster, or false for core
- * @param bool|string $wiki Wiki ID, or false for the current wiki
+ * @param string $cluster External storage cluster name (unused)
* @return LoadBalancerSingle
*/
- public function newExternalLB( $cluster, $wiki = false ) {
+ public function newExternalLB( $cluster ) {
return $this->lb;
}
/**
- * @param string $cluster External storage cluster, or false for core
- * @param bool|string $wiki Wiki ID, or false for the current wiki
+ * @param string $cluster External storage cluster name (unused)
* @return LoadBalancerSingle
*/
- public function getExternalLB( $cluster, $wiki = false ) {
+ public function getExternalLB( $cluster ) {
return $this->lb;
}
return new static( [ 'connection' => $db ] + $params );
}
- /**
- *
- * @param string $server
- * @param bool $dbNameOverride
- *
- * @return IDatabase
- */
protected function reallyOpenConnection( array $server, $dbNameOverride = false ) {
return $this->db;
}
* @throws UnexpectedValueException
*/
public static function newWANCacheFromParams( array $params ) {
+ $erGroup = MediaWikiServices::getInstance()->getEventRelayerGroup();
foreach ( $params['channels'] as $action => $channel ) {
- $params['relayers'][$action] = MediaWikiServices::getInstance()->getEventRelayerGroup()
- ->getRelayer( $channel );
+ $params['relayers'][$action] = $erGroup->getRelayer( $channel );
$params['channels'][$action] = $channel;
}
$params['cache'] = self::newFromParams( $params['store'] );
return $mwObject->matchStartToEnd( $value );
}
- public static function formatRaw( $num, $raw ) {
+ public static function formatRaw( $num, $raw, Language $language ) {
if ( self::matchAgainstMagicword( 'rawsuffix', $raw ) ) {
return $num;
} else {
- global $wgContLang;
- return $wgContLang->formatNum( $num );
+ return $language->formatNum( $num );
}
}
+
public static function numberofpages( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::pages(), $raw );
+ return self::formatRaw( SiteStats::pages(), $raw, $parser->getFunctionLang() );
}
+
public static function numberofusers( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::users(), $raw );
+ return self::formatRaw( SiteStats::users(), $raw, $parser->getFunctionLang() );
}
public static function numberofactiveusers( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::activeUsers(), $raw );
+ return self::formatRaw( SiteStats::activeUsers(), $raw, $parser->getFunctionLang() );
}
+
public static function numberofarticles( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::articles(), $raw );
+ return self::formatRaw( SiteStats::articles(), $raw, $parser->getFunctionLang() );
}
+
public static function numberoffiles( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::images(), $raw );
+ return self::formatRaw( SiteStats::images(), $raw, $parser->getFunctionLang() );
}
+
public static function numberofadmins( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::numberingroup( 'sysop' ), $raw );
+ return self::formatRaw(
+ SiteStats::numberingroup( 'sysop' ),
+ $raw,
+ $parser->getFunctionLang()
+ );
}
+
public static function numberofedits( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::edits(), $raw );
+ return self::formatRaw( SiteStats::edits(), $raw, $parser->getFunctionLang() );
}
+
public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
- return self::formatRaw( SiteStats::pagesInNs( intval( $namespace ) ), $raw );
+ return self::formatRaw(
+ SiteStats::pagesInNs( intval( $namespace ) ),
+ $raw,
+ $parser->getFunctionLang()
+ );
}
public static function numberingroup( $parser, $name = '', $raw = null ) {
- return self::formatRaw( SiteStats::numberingroup( strtolower( $name ) ), $raw );
+ return self::formatRaw(
+ SiteStats::numberingroup( strtolower( $name ) ),
+ $raw,
+ $parser->getFunctionLang()
+ );
}
/**
$title = Title::makeTitleSafe( NS_CATEGORY, $name );
if ( !$title ) { # invalid title
- return self::formatRaw( 0, $raw );
+ return self::formatRaw( 0, $raw, $parser->getFunctionLang() );
}
$wgContLang->findVariantLink( $name, $title, true );
}
$count = $cache[$name][$type];
- return self::formatRaw( $count, $raw );
+ return self::formatRaw( $count, $raw, $parser->getFunctionLang() );
}
/**
$title = Title::newFromText( $page );
if ( !is_object( $title ) ) {
- return self::formatRaw( 0, $raw );
+ return self::formatRaw( 0, $raw, $parser->getFunctionLang() );
}
// fetch revision from cache/database and return the value
// We've had bugs where rev_len was not being recorded for empty pages, see T135414
$length = 0;
}
- return self::formatRaw( $length, $raw );
+ return self::formatRaw( $length, $raw, $parser->getFunctionLang() );
}
/**
/**
* Used by getDB() / setDB()
- * @var IDatabase
+ * @var Database
*/
private $mDb = null;
if ( is_array( $wgProfiler ) && isset( $wgProfiler['class'] ) ) {
$class = $wgProfiler['class'];
+ /** @var Profiler $profiler */
$profiler = new $class(
[ 'sampling' => 1, 'output' => [ $output ] ]
+ $wgProfiler
$this->beginTransaction( $dbw, __METHOD__ );
# Get "active" text records from the revisions table
+ $cur = [];
$this->output( 'Searching for active text records in revisions table...' );
$res = $dbw->select( 'revision', 'rev_text_id', [], __METHOD__, [ 'DISTINCT' ] );
foreach ( $res as $row ) {
"name": "Base",
"classes": [
"mw",
- "mw.Map",
"mw.Message",
"mw.loader",
"mw.loader.store",
$this->output( "Going to run database updates for " . wfWikiID() . "\n" );
if ( $db->getType() === 'sqlite' ) {
+ /** @var Database|DatabaseSqlite $db */
$this->output( "Using SQLite file: '{$db->getDbFilePath()}'\n" );
}
$this->output( "Depending on the size of your database this may take a while!\n" );
}() );
/**
- * Create an object that can be read from or written to from methods that allow
+ * Create an object that can be read from or written to via methods that allow
* interaction both with single and multiple properties at once.
*
- * @example
- *
- * var collection, query, results;
- *
- * // Create your address book
- * collection = new mw.Map();
- *
- * // This data could be coming from an external source (eg. API/AJAX)
- * collection.set( {
- * 'John Doe': 'john@example.org',
- * 'Jane Doe': 'jane@example.org',
- * 'George van Halen': 'gvanhalen@example.org'
- * } );
- *
- * wanted = ['John Doe', 'Jane Doe', 'Daniel Jackson'];
- *
- * // You can detect missing keys first
- * if ( !collection.exists( wanted ) ) {
- * // One or more are missing (in this case: "Daniel Jackson")
- * mw.log( 'One or more names were not found in your address book' );
- * }
- *
- * // Or just let it give you what it can. Optionally fill in from a default.
- * results = collection.get( wanted, 'nobody@example.com' );
- * mw.log( results['Jane Doe'] ); // "jane@example.org"
- * mw.log( results['Daniel Jackson'] ); // "nobody@example.com"
- *
+ * @private
* @class mw.Map
*
* @constructor
- * @param {Object|boolean} [values] The value-baring object to be mapped. Defaults to an
- * empty object.
- * For backwards-compatibility with mw.config, this can also be `true` in which case values
- * are copied to the Window object as global variables (T72470). Values are copied in
- * one direction only. Changes to globals are not reflected in the map.
+ * @param {boolean} [global=false] Whether to synchronise =values to the global
+ * window object (for backwards-compatibility with mw.config; T72470). Values are
+ * copied in one direction only. Changes to globals do not reflect in the map.
*/
- function Map( values ) {
- if ( values === true ) {
- this.values = {};
+ function Map( global ) {
+ this.internalValues = {};
+ if ( global === true ) {
// Override #set to also set the global variable
this.set = function ( selection, value ) {
}
return false;
};
-
- return;
}
- this.values = values || {};
+ // Deprecated since MediaWiki 1.28
+ log.deprecate(
+ this,
+ 'values',
+ this.internalValues,
+ 'mw.Map#values is deprecated. Use mw.Map#get() instead.',
+ 'Map-values'
+ );
}
/**
* @param {Mixed} value
*/
function setGlobalMapValue( map, key, value ) {
- map.values[ key ] = value;
- mw.log.deprecate(
+ map.internalValues[ key ] = value;
+ log.deprecate(
window,
key,
value,
}
Map.prototype = {
+ constructor: Map,
+
/**
* Get the value of one or more keys.
*
* @param {Mixed} [fallback=null] Value for keys that don't exist.
* @return {Mixed|Object| null} If selection was a string, returns the value,
* If selection was an array, returns an object of key/values.
- * If no selection is passed, the 'values' container is returned. (Beware that,
+ * If no selection is passed, the internal container is returned. (Beware that,
* as is the default in JavaScript, the object is returned by reference.)
*/
get: function ( selection, fallback ) {
}
if ( typeof selection === 'string' ) {
- if ( !hasOwn.call( this.values, selection ) ) {
+ if ( !hasOwn.call( this.internalValues, selection ) ) {
return fallback;
}
- return this.values[ selection ];
+ return this.internalValues[ selection ];
}
if ( selection === undefined ) {
- return this.values;
+ return this.internalValues;
}
// Invalid selection key
if ( $.isPlainObject( selection ) ) {
for ( s in selection ) {
- this.values[ s ] = selection[ s ];
+ this.internalValues[ s ] = selection[ s ];
}
return true;
}
if ( typeof selection === 'string' && arguments.length > 1 ) {
- this.values[ selection ] = value;
+ this.internalValues[ selection ] = value;
return true;
}
return false;
if ( $.isArray( selection ) ) {
for ( s = 0; s < selection.length; s++ ) {
- if ( typeof selection[ s ] !== 'string' || !hasOwn.call( this.values, selection[ s ] ) ) {
+ if ( typeof selection[ s ] !== 'string' || !hasOwn.call( this.internalValues, selection[ s ] ) ) {
return false;
}
}
return true;
}
- return typeof selection === 'string' && hasOwn.call( this.values, selection );
+ return typeof selection === 'string' && hasOwn.call( this.internalValues, selection );
}
};
* @param {string} key Name of property to create in `obj`
* @param {Mixed} val The value this property should return when accessed
* @param {string} [msg] Optional text to include in the deprecation message
+ * @param {string} [logName=key] Optional custom name for the feature.
+ * This is used instead of `key` in the message and `mw.deprecate` tracking.
*/
log.deprecate = !Object.defineProperty ? function ( obj, key, val ) {
obj[ key ] = val;
- } : function ( obj, key, val, msg ) {
- msg = 'Use of "' + key + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' );
+ } : function ( obj, key, val, msg, logName ) {
+ logName = logName || key;
+ msg = 'Use of "' + logName + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' );
var logged = new StringSet();
function uniqueTrace() {
var trace = new Error().stack;
enumerable: true,
get: function () {
if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', key );
+ mw.track( 'mw.deprecate', logName );
mw.log.warn( msg );
}
return val;
},
set: function ( newVal ) {
if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', key );
+ mw.track( 'mw.deprecate', logName );
mw.log.warn( msg );
}
val = newVal;
}
return {
name: key.slice( 0, index ),
- version: key.slice( index )
+ version: key.slice( index + 1 )
};
}
*/
mw.storage = {
- localStorage: window.localStorage,
+ localStorage: ( function () {
+ // Catch exceptions to avoid fatal in Chrome's "Block data storage" mode
+ // which throws when accessing the localStorage property itself, as opposed
+ // to the standard behaviour of throwing on getItem/setItem. (T148998)
+ try {
+ return window.localStorage;
+ } catch ( e ) {}
+ }() ),
/**
* Retrieve value from device storage.
* @return {number} Current user's id, or 0 if user is anonymous
*/
getId: function () {
- return mw.config.get( 'wgUserId', 0 );
+ return mw.config.get( 'wgUserId' ) || 0;
},
/**
*/
QUnit.newMwEnvironment = ( function () {
var warn, error, liveConfig, liveMessages,
+ MwMap = mw.config.constructor, // internal use only
ajaxRequests = [];
- liveConfig = mw.config.values;
- liveMessages = mw.messages.values;
+ liveConfig = mw.config;
+ liveMessages = mw.messages;
function suppressWarnings() {
warn = mw.log.warn;
// NOTE: It is important that we suppress warnings because extend() will also access
// deprecated properties and trigger deprecation warnings from mw.log#deprecate.
suppressWarnings();
- copy = $.extend( {}, liveConfig, custom );
+ copy = $.extend( {}, liveConfig.get(), custom );
restoreWarnings();
return copy;
}
function freshMessagesCopy( custom ) {
- return $.extend( /*deep=*/true, {}, liveMessages, custom );
+ return $.extend( /*deep=*/true, {}, liveMessages.get(), custom );
}
/**
setup: function () {
// Greetings, mock environment!
- mw.config.values = freshConfigCopy( localEnv.config );
- mw.messages.values = freshMessagesCopy( localEnv.messages );
+ mw.config = new MwMap();
+ mw.config.set( freshConfigCopy( localEnv.config ) );
+ mw.messages = new MwMap();
+ mw.messages.set( freshMessagesCopy( localEnv.messages ) );
+ // Update reference to mw.messages
+ mw.jqueryMsg.setParserDefaults( {
+ messages: mw.messages
+ } );
+
this.suppressWarnings = suppressWarnings;
this.restoreWarnings = restoreWarnings;
$( document ).off( 'ajaxSend', trackAjax );
// Farewell, mock environment!
- mw.config.values = liveConfig;
- mw.messages.values = liveMessages;
+ mw.config = liveConfig;
+ mw.messages = liveMessages;
+ // Restore reference to mw.messages
+ mw.jqueryMsg.setParserDefaults( {
+ messages: liveMessages
+ } );
// As a convenience feature, automatically restore warnings if they're
// still suppressed by the end of the test.
return mw.loader.using( 'test.stale' )
.then( function () {
assert.strictEqual( count, 1 );
+ // After implementing, registry contains version as implemented by the response.
+ assert.strictEqual( mw.loader.getVersion( 'test.stale' ), 'v1', 'Override version' );
assert.strictEqual( mw.loader.getState( 'test.stale' ), 'ready' );
assert.ok( mw.loader.store.get( 'test.stale' ), 'In store' );
} )
);
} );
- QUnit.test( 'mw.Map', 35, function ( assert ) {
+ QUnit.test( 'mw.Map', function ( assert ) {
var arry, conf, funky, globalConf, nummy, someValues;
conf = new mw.Map();
+
// Dummy variables
funky = function () {};
arry = [];
lorem: 'ipsum'
}, 'Map.get returns multiple values correctly as an object' );
- assert.deepEqual( conf, new mw.Map( conf.values ), 'new mw.Map maps over existing values-bearing object' );
-
assert.deepEqual( conf.get( [ 'foo', 'notExist' ] ), {
foo: 'bar',
notExist: null
}, 'Map.get return includes keys that were not found as null values' );
// Interacting with globals and accessing the values object
+ this.suppressWarnings();
assert.strictEqual( conf.get(), conf.values, 'Map.get returns the entire values object by reference (if called without arguments)' );
+ this.restoreWarnings();
conf.set( 'globalMapChecker', 'Hi' );
}
} );
- QUnit.test( 'mw.config', 1, function ( assert ) {
- assert.ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' );
- } );
-
- QUnit.test( 'mw.message & mw.messages', 100, function ( assert ) {
+ QUnit.test( 'mw.message & mw.messages', function ( assert ) {
var goodbye, hello;
// Convenience method for asserting the same result for multiple formats
}
assert.ok( mw.messages, 'messages defined' );
- assert.ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' );
assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
hello = mw.message( 'hello' );
QUnit.test( 'getters (anonymous)', function ( assert ) {
// Forge an anonymous user
mw.config.set( 'wgUserName', null );
- delete mw.config.values.wgUserId;
+ mw.config.set( 'wgUserId', null );
assert.strictEqual( mw.user.getName(), null, 'getName()' );
assert.strictEqual( mw.user.isAnon(), true, 'isAnon()' );