aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>2024-10-21 15:38:56 +0000
committerGerrit Code Review <gerrit@wikimedia.org>2024-10-21 15:38:56 +0000
commitcd7a5998e92ba7cd0f1abd26f4e2abba9815ac96 (patch)
tree798ef404247e0cb5bcef637af9a0d91832a4be63
parentdd56496b83450ec46e8203aac5e280d92f379ab4 (diff)
parent9d56257d8c154cf3020ae7d0a1dd2f45d5211453 (diff)
downloadmediawikicore-cd7a5998e92ba7cd0f1abd26f4e2abba9815ac96.tar.gz
mediawikicore-cd7a5998e92ba7cd0f1abd26f4e2abba9815ac96.zip
Merge "Make Message and MessageValue compatible"
-rw-r--r--RELEASE-NOTES-1.4311
-rw-r--r--includes/Message/Converter.php86
-rw-r--r--includes/Message/Message.php268
-rw-r--r--includes/Message/MessageFormatterFactory.php2
-rw-r--r--includes/Message/TextFormatter.php24
-rw-r--r--includes/Status/StatusFormatter.php6
-rw-r--r--includes/api/ApiResult.php4
-rw-r--r--includes/libs/Message/ITextFormatter.php8
-rw-r--r--includes/libs/Message/ListParam.php2
-rw-r--r--includes/libs/Message/MessageSpecifier.php2
-rw-r--r--includes/libs/Message/MessageValue.php36
-rw-r--r--includes/libs/Message/README.md13
-rw-r--r--includes/libs/Message/ScalarParam.php14
-rw-r--r--includes/libs/StatusValue.php80
-rw-r--r--includes/specials/SpecialBlock.php4
-rw-r--r--tests/phpunit/includes/Message/TextFormatterTest.php16
-rw-r--r--tests/phpunit/includes/Status/StatusTest.php8
-rw-r--r--tests/phpunit/includes/api/ApiBaseTest.php2
-rw-r--r--tests/phpunit/includes/auth/AuthManagerTest.php7
-rw-r--r--tests/phpunit/includes/language/MessageTest.php10
-rw-r--r--tests/phpunit/integration/includes/Rest/Handler/DiscoveryHandlerTest.php4
-rw-r--r--tests/phpunit/integration/includes/Rest/Handler/Helper/HtmlInputTransformHelperTest.php3
-rw-r--r--tests/phpunit/integration/includes/Rest/Handler/ModuleSpecHandlerTest.php4
-rw-r--r--tests/phpunit/mocks/DummyServicesTrait.php5
-rw-r--r--tests/phpunit/mocks/FakeQqxMessageLocalizer.php8
-rw-r--r--tests/phpunit/unit/includes/Message/ConverterTest.php16
-rw-r--r--tests/phpunit/unit/includes/libs/Message/ScalarParamTest.php4
27 files changed, 303 insertions, 344 deletions
diff --git a/RELEASE-NOTES-1.43 b/RELEASE-NOTES-1.43
index efc86ffbedfb..9d43d782e886 100644
--- a/RELEASE-NOTES-1.43
+++ b/RELEASE-NOTES-1.43
@@ -112,6 +112,11 @@ For notes on 1.42.x and older releases, see HISTORY.
$this->getOutput()->addWikiMsg( $msg );
}
}
+* (T358779) The MessageValue class can now be used instead of Message in most
+ places (in methods that accept the MessageSpecifier interface). This allows
+ using localisation messages in code that doesn't know the user's language,
+ such as many hooks, without relying on global state. To convert between them,
+ use MessageValue::newFromSpecifier() and Message::newFromSpecifier().
* The REST API framework now supports defining redirects in route definition
files. See MediaWiki\Rest\Handler\RedirectHandler for details.
* (T13555) Skins can enable the 'supportsMwHeading' option for new, more
@@ -260,6 +265,12 @@ because of Phabricator reports.
=== Breaking changes in 1.43 ===
+* (T358779) The format of parameters used by the Message class has changed.
+ Instead of arrays in a special format, they are now MessageParam objects.
+ Code that simply called methods like Message::numParam() and didn't look
+ inside the values they return should be unaffected. Code that depended on
+ the formatted params being arrays or accessed their keys will need updates.
+ Example patches: https://gerrit.wikimedia.org/r/q/topic:message-param
* ErrorPageError public properties 'msg' and 'title' may now contain
any MessageSpecifier object, not just Message.
* Reset button functionality suppressReset() and $mShowReset from HTMLForm
diff --git a/includes/Message/Converter.php b/includes/Message/Converter.php
index 7b9e07e05f05..861c3bf11ab8 100644
--- a/includes/Message/Converter.php
+++ b/includes/Message/Converter.php
@@ -2,110 +2,34 @@
namespace MediaWiki\Message;
-use InvalidArgumentException;
-use Wikimedia\Message\ListParam;
-use Wikimedia\Message\MessageParam;
use Wikimedia\Message\MessageSpecifier;
use Wikimedia\Message\MessageValue;
-use Wikimedia\Message\ParamType;
-use Wikimedia\Message\ScalarParam;
/**
* Converter between Message and MessageValue
* @since 1.35
+ * @deprecated since 1.43
*/
class Converter {
/**
- * Allow the Message class to be mocked in tests by constructing objects in
- * a protected method.
- *
- * @internal
- * @param string $key
- * @return Message
- */
- public function createMessage( $key ) {
- return new Message( $key );
- }
-
- /**
* Convert a Message to a MessageValue
+ * @deprecated since 1.43 Use MessageValue::newFromSpecifier() instead
* @param MessageSpecifier $m
* @return MessageValue
*/
public function convertMessage( MessageSpecifier $m ) {
- $mv = new MessageValue( $m->getKey() );
- foreach ( $m->getParams() as $param ) {
- $mv->params( $this->convertParam( $param ) );
- }
- return $mv;
- }
-
- /**
- * Convert a Message parameter to a MessageParam
- * @param array|string|int $param
- * @return MessageParam
- */
- private function convertParam( $param ) {
- if ( $param instanceof MessageSpecifier ) {
- return new ScalarParam( ParamType::TEXT, $this->convertMessage( $param ) );
- }
- if ( !is_array( $param ) ) {
- return new ScalarParam( ParamType::TEXT, $param );
- }
-
- if ( isset( $param['list'] ) && isset( $param['type'] ) ) {
- $convertedElements = [];
- foreach ( $param['list'] as $element ) {
- $convertedElements[] = $this->convertParam( $element );
- }
- return new ListParam( $param['type'], $convertedElements );
- }
-
- foreach ( ParamType::cases() as $type ) {
- if ( $type !== ParamType::LIST && isset( $param[$type] ) ) {
- return new ScalarParam( $type, $param[$type] );
- }
- }
-
- throw new InvalidArgumentException( "Unrecognized Message param: " . json_encode( $param ) );
+ return MessageValue::newFromSpecifier( $m );
}
/**
* Convert a MessageValue to a Message
+ * @deprecated since 1.43 Use Message::newFromSpecifier() instead
* @param MessageValue $mv
* @return Message
*/
public function convertMessageValue( MessageValue $mv ) {
- $m = $this->createMessage( $mv->getKey() );
- foreach ( $mv->getParams() as $param ) {
- $m->params( $this->convertMessageParam( $param ) );
- }
- return $m;
- }
-
- /**
- * Convert a MessageParam to a Message parameter
- * @param MessageParam $param
- * @return array|string|int
- */
- private function convertMessageParam( MessageParam $param ) {
- if ( $param instanceof ListParam ) {
- $convertedElements = [];
- foreach ( $param->getValue() as $element ) {
- $convertedElements[] = $this->convertMessageParam( $element );
- }
- return Message::listParam( $convertedElements, $param->getListType() );
- }
- $value = $param->getValue();
- if ( $value instanceof MessageValue ) {
- $value = $this->convertMessageValue( $value );
- }
-
- if ( $param->getType() === ParamType::TEXT ) {
- return $value;
- }
- return [ $param->getType() => $value ];
+ return Message::newFromSpecifier( $mv );
}
}
diff --git a/includes/Message/Message.php b/includes/Message/Message.php
index 20b22fc0f553..d714946ad554 100644
--- a/includes/Message/Message.php
+++ b/includes/Message/Message.php
@@ -41,7 +41,12 @@ use Serializable;
use Stringable;
use Wikimedia\Assert\Assert;
use Wikimedia\Bcp47Code\Bcp47Code;
+use Wikimedia\Message\ListParam;
+use Wikimedia\Message\ListType;
+use Wikimedia\Message\MessageParam;
use Wikimedia\Message\MessageSpecifier;
+use Wikimedia\Message\ParamType;
+use Wikimedia\Message\ScalarParam;
/**
* The Message class deals with fetching and processing of interface message
@@ -164,10 +169,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
* @var array
*/
protected static $listTypeMap = [
- 'comma' => 'commaList',
- 'semicolon' => 'semicolonList',
- 'pipe' => 'pipeList',
- 'text' => 'listToText',
+ ListType::COMMA => 'commaList',
+ ListType::SEMICOLON => 'semicolonList',
+ ListType::PIPE => 'pipeList',
+ ListType::AND => 'listToText',
];
/**
@@ -210,7 +215,8 @@ class Message implements Stringable, MessageSpecifier, Serializable {
protected $overriddenKey = null;
/**
- * @var array List of parameters which will be substituted into the message.
+ * @var (MessageParam|Message|string|int|float)[] List of parameters which will be substituted
+ * into the message.
*/
protected $parameters = [];
@@ -270,7 +276,7 @@ class Message implements Stringable, MessageSpecifier, Serializable {
throw new InvalidArgumentException( '$key must be a string or non-empty array' );
}
- $this->parameters = array_values( $params );
+ $this->params( ...$params );
// User language is only resolved in getLanguage(). This helps preserve the
// semantic intent of "user language" across serialize() and unserialize().
$this->language = $language;
@@ -329,7 +335,19 @@ class Message implements Stringable, MessageSpecifier, Serializable {
$this->isInterface = $data['interface'];
$this->key = $data['key'];
$this->keysToTry = $data['keysToTry'];
- $this->parameters = $data['parameters'];
+ // Accept old serialization format for compatibility with pre-MessageParam stored values
+ $this->parameters = array_map( static function ( $param ) {
+ if ( is_array( $param ) ) {
+ $codec = MediaWikiServices::getInstance()->getJsonCodec();
+ if ( isset( $param['type'] ) ) {
+ return ListParam::newFromJsonArray( $codec, $param );
+ } else {
+ return ScalarParam::newFromJsonArray( $codec, $param );
+ }
+ } else {
+ return $param;
+ }
+ }, $data['parameters'] );
$this->useDatabase = $data['useDatabase'];
$this->language = $data['language']
? MediaWikiServices::getInstance()->getLanguageFactory()
@@ -391,7 +409,7 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @since 1.21
*
- * @return array
+ * @return (MessageParam|Message|string|int|float)[]
*/
public function getParams() {
return $this->parameters;
@@ -539,30 +557,25 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @since 1.17
*
- * @param mixed ...$args Parameters as strings or arrays from
- * Message::numParam() and the like, or a single array of parameters.
+ * @param MessageParam|MessageSpecifier|string|int|float|array ...$params Parameters as strings or
+ * MessageParam values (from Message::numParam() and the like), or a single array of parameters.
*
* @return self $this
*/
- public function params( ...$args ) {
- // If $args has only one entry and it's an array, then it's either a
- // non-varargs call or it happens to be a call with just a single
- // "special" parameter. Since the "special" parameters don't have any
- // numeric keys, we'll test that to differentiate the cases.
- if ( count( $args ) === 1 && isset( $args[0] ) && is_array( $args[0] ) ) {
- if ( $args[0] === [] ) {
- $args = [];
- } else {
- foreach ( $args[0] as $key => $value ) {
- if ( is_int( $key ) ) {
- $args = $args[0];
- break;
- }
- }
+ public function params( ...$params ) {
+ if ( count( $params ) === 1 && isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ if ( $param instanceof ScalarParam && $param->getType() === ParamType::TEXT ) {
+ // Unwrap for compatibility with legacy code that inspects the parameters
+ $param = $param->getValue();
+ }
+ if ( $param instanceof MessageSpecifier ) {
+ $param = static::newFromSpecifier( $param );
}
+ $this->parameters[] = $param;
}
-
- $this->parameters = array_merge( $this->parameters, array_values( $args ) );
return $this;
}
@@ -1191,10 +1204,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
* @param mixed $raw
* @param-taint $raw html,exec_html
*
- * @return array Array with a single "raw" key.
+ * @return ScalarParam
*/
- public static function rawParam( $raw ) {
- return [ 'raw' => $raw ];
+ public static function rawParam( $raw ): ScalarParam {
+ return new ScalarParam( ParamType::RAW, $raw );
}
/**
@@ -1202,10 +1215,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param mixed $num
*
- * @return array Array with a single "num" key.
+ * @return ScalarParam
*/
- public static function numParam( $num ) {
- return [ 'num' => $num ];
+ public static function numParam( $num ): ScalarParam {
+ return new ScalarParam( ParamType::NUM, $num );
}
/**
@@ -1213,10 +1226,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param int $duration
*
- * @return int[] Array with a single "duration" key.
+ * @return ScalarParam
*/
- public static function durationParam( $duration ) {
- return [ 'duration' => $duration ];
+ public static function durationParam( $duration ): ScalarParam {
+ return new ScalarParam( ParamType::DURATION_LONG, $duration );
}
/**
@@ -1224,10 +1237,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param string $expiry
*
- * @return string[] Array with a single "expiry" key.
+ * @return ScalarParam
*/
- public static function expiryParam( $expiry ) {
- return [ 'expiry' => $expiry ];
+ public static function expiryParam( $expiry ): ScalarParam {
+ return new ScalarParam( ParamType::EXPIRY, $expiry );
}
/**
@@ -1235,10 +1248,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param string $dateTime
*
- * @return string[] Array with a single "datetime" key.
+ * @return ScalarParam
*/
- public static function dateTimeParam( string $dateTime ) {
- return [ 'datetime' => $dateTime ];
+ public static function dateTimeParam( string $dateTime ): ScalarParam {
+ return new ScalarParam( ParamType::DATETIME, $dateTime );
}
/**
@@ -1246,10 +1259,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param string $date
*
- * @return string[] Array with a single "date" key.
+ * @return ScalarParam
*/
- public static function dateParam( string $date ) {
- return [ 'date' => $date ];
+ public static function dateParam( string $date ): ScalarParam {
+ return new ScalarParam( ParamType::DATE, $date );
}
/**
@@ -1257,10 +1270,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param string $time
*
- * @return string[] Array with a single "time" key.
+ * @return ScalarParam
*/
- public static function timeParam( string $time ) {
- return [ 'time' => $time ];
+ public static function timeParam( string $time ): ScalarParam {
+ return new ScalarParam( ParamType::TIME, $time );
}
/**
@@ -1268,10 +1281,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param string $userGroup
*
- * @return string[] Array with a single "group" key.
+ * @return ScalarParam
*/
- public static function userGroupParam( string $userGroup ) {
- return [ 'group' => $userGroup ];
+ public static function userGroupParam( string $userGroup ): ScalarParam {
+ return new ScalarParam( ParamType::GROUP, $userGroup );
}
/**
@@ -1280,11 +1293,11 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param Stringable $object
*
- * @return Stringable[] Array with a single "object" key.
+ * @return ScalarParam
*/
- public static function objectParam( Stringable $object ) {
+ public static function objectParam( Stringable $object ): ScalarParam {
wfDeprecated( __METHOD__, '1.43' );
- return [ 'object' => $object ];
+ return new ScalarParam( ParamType::OBJECT, $object );
}
/**
@@ -1292,10 +1305,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param int|float $period
*
- * @return int[]|float[] Array with a single "period" key.
+ * @return ScalarParam
*/
- public static function timeperiodParam( $period ) {
- return [ 'period' => $period ];
+ public static function timeperiodParam( $period ): ScalarParam {
+ return new ScalarParam( ParamType::DURATION_SHORT, $period );
}
/**
@@ -1303,10 +1316,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param int $size
*
- * @return int[] Array with a single "size" key.
+ * @return ScalarParam
*/
- public static function sizeParam( $size ) {
- return [ 'size' => $size ];
+ public static function sizeParam( $size ): ScalarParam {
+ return new ScalarParam( ParamType::SIZE, $size );
}
/**
@@ -1314,10 +1327,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param int $bitrate
*
- * @return int[] Array with a single "bitrate" key.
+ * @return ScalarParam
*/
- public static function bitrateParam( $bitrate ) {
- return [ 'bitrate' => $bitrate ];
+ public static function bitrateParam( $bitrate ): ScalarParam {
+ return new ScalarParam( ParamType::BITRATE, $bitrate );
}
/**
@@ -1325,26 +1338,21 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @param string $plaintext
*
- * @return string[] Array with a single "plaintext" key.
+ * @return ScalarParam
*/
- public static function plaintextParam( $plaintext ) {
- return [ 'plaintext' => $plaintext ];
+ public static function plaintextParam( $plaintext ): ScalarParam {
+ return new ScalarParam( ParamType::PLAINTEXT, $plaintext );
}
/**
* @since 1.29
*
* @param array $list
- * @param string $type 'comma', 'semicolon', 'pipe', 'text'
- * @return array Array with "list" and "type" keys.
+ * @param string $type One of the ListType constants
+ * @return ListParam
*/
- public static function listParam( array $list, $type = 'text' ) {
- if ( !isset( self::$listTypeMap[$type] ) ) {
- throw new InvalidArgumentException(
- "Invalid type '$type'. Known types are: " . implode( ', ', array_keys( self::$listTypeMap ) )
- );
- }
- return [ 'list' => $list, 'type' => $type ];
+ public static function listParam( array $list, $type = ListType::AND ): ListParam {
+ return new ListParam( $type, $list );
}
/**
@@ -1390,66 +1398,69 @@ class Message implements Stringable, MessageSpecifier, Serializable {
*
* @since 1.18
*
- * @param mixed $param Parameter as defined in this class.
+ * @param ScalarParam|ListParam|MessageSpecifier|string $param Parameter as defined in this class.
* @param string $format One of the FORMAT_* constants.
*
* @return array Array with the parameter type (either "before" or "after") and the value.
*/
protected function extractParam( $param, $format ) {
- if ( is_array( $param ) ) {
- if ( isset( $param['raw'] ) ) {
- return [ 'after', $param['raw'] ];
- } elseif ( isset( $param['num'] ) ) {
- // Replace number params always in before step for now.
- // No support for combined raw and num params
- return [ 'before', $this->getLanguage()->formatNum( $param['num'] ) ];
- } elseif ( isset( $param['duration'] ) ) {
- return [ 'before', $this->getLanguage()->formatDuration( $param['duration'] ) ];
- } elseif ( isset( $param['expiry'] ) ) {
- return [ 'before', $this->getLanguage()->formatExpiry( $param['expiry'] ) ];
- } elseif ( isset( $param['datetime'] ) ) {
- return [ 'before', $this->getLanguage()->timeanddate( $param['datetime'] ) ];
- } elseif ( isset( $param['date'] ) ) {
- return [ 'before', $this->getLanguage()->date( $param['date'] ) ];
- } elseif ( isset( $param['time'] ) ) {
- return [ 'before', $this->getLanguage()->time( $param['time'] ) ];
- } elseif ( isset( $param['group'] ) ) {
- return [ 'before', $this->getLanguage()->getGroupName( $param['group'] ) ];
- } elseif ( isset( $param['period'] ) ) {
- return [ 'before', $this->getLanguage()->formatTimePeriod( $param['period'] ) ];
- } elseif ( isset( $param['size'] ) ) {
- return [ 'before', $this->getLanguage()->formatSize( $param['size'] ) ];
- } elseif ( isset( $param['bitrate'] ) ) {
- return [ 'before', $this->getLanguage()->formatBitrate( $param['bitrate'] ) ];
- } elseif ( isset( $param['plaintext'] ) ) {
- return [ 'after', $this->formatPlaintext( $param['plaintext'], $format ) ];
- } elseif ( isset( $param['list'] ) ) {
- return $this->formatListParam( $param['list'], $param['type'], $format );
- } elseif ( isset( $param['object'] ) ) {
- $obj = $param['object'];
- if ( $obj instanceof UserGroupMembershipParam ) {
- return [
+ if ( $param instanceof ScalarParam ) {
+ switch ( $param->getType() ) {
+ case ParamType::RAW:
+ return [ 'after', $this->extractParam( $param->getValue(), self::FORMAT_PARSE )[1] ];
+ case ParamType::NUM:
+ // Replace number params always in before step for now.
+ // No support for combined raw and num params
+ return [ 'before', $this->getLanguage()->formatNum( $param->getValue() ) ];
+ case ParamType::DURATION_LONG:
+ return [ 'before', $this->getLanguage()->formatDuration( $param->getValue() ) ];
+ case ParamType::EXPIRY:
+ return [ 'before', $this->getLanguage()->formatExpiry( $param->getValue() ) ];
+ case ParamType::DATETIME:
+ return [ 'before', $this->getLanguage()->timeanddate( $param->getValue() ) ];
+ case ParamType::DATE:
+ return [ 'before', $this->getLanguage()->date( $param->getValue() ) ];
+ case ParamType::TIME:
+ return [ 'before', $this->getLanguage()->time( $param->getValue() ) ];
+ case ParamType::GROUP:
+ return [ 'before', $this->getLanguage()->getGroupName( $param->getValue() ) ];
+ case ParamType::DURATION_SHORT:
+ return [ 'before', $this->getLanguage()->formatTimePeriod( $param->getValue() ) ];
+ case ParamType::SIZE:
+ return [ 'before', $this->getLanguage()->formatSize( $param->getValue() ) ];
+ case ParamType::BITRATE:
+ return [ 'before', $this->getLanguage()->formatBitrate( $param->getValue() ) ];
+ case ParamType::PLAINTEXT:
+ return [ 'after', $this->formatPlaintext( $param->getValue(), $format ) ];
+ case ParamType::OBJECT:
+ $obj = $param->getValue();
+ if ( $obj instanceof UserGroupMembershipParam ) {
+ return [
'before',
$this->getLanguage()->getGroupMemberName( $obj->getGroup(), $obj->getMember() )
- ];
- } else {
- return [ 'before', $obj->__toString() ];
- }
- } else {
- LoggerFactory::getInstance( 'Bug58676' )->warning(
- 'Invalid parameter for message "{msgkey}": {param}',
- [
- 'exception' => new RuntimeException,
- 'msgkey' => $this->key,
- 'param' => htmlspecialchars( serialize( $param ) ),
- ]
- );
-
- return [ 'before', '[INVALID]' ];
+ ];
+ } else {
+ return [ 'before', $obj->__toString() ];
+ }
+ case ParamType::TEXT: // impossible because we unwrapped it in params()
+ default:
+ throw new \LogicException( "Invalid ScalarParam type: {$param->getType()}" );
}
- } elseif ( $param instanceof Message ) {
+ } elseif ( $param instanceof ListParam ) {
+ return $this->formatListParam( $param->getValue(), $param->getListType(), $format );
+ } elseif ( is_array( $param ) ) {
+ LoggerFactory::getInstance( 'Bug58676' )->warning(
+ 'Invalid parameter for message "{msgkey}": {param}',
+ [
+ 'exception' => new RuntimeException,
+ 'msgkey' => $this->key,
+ 'param' => htmlspecialchars( serialize( $param ) ),
+ ]
+ );
+ return [ 'before', '[INVALID]' ];
+ } elseif ( $param instanceof MessageSpecifier ) {
// Match language, flags, etc. to the current message.
- $msg = clone $param;
+ $msg = static::newFromSpecifier( $param );
if ( $msg->language !== $this->language || $msg->useDatabase !== $this->useDatabase ) {
// Cache depends on these parameters
$msg->message = null;
@@ -1614,6 +1625,10 @@ class Message implements Stringable, MessageSpecifier, Serializable {
$vars = [];
$list = [];
foreach ( $params as $n => $p ) {
+ if ( $p instanceof ScalarParam && $p->getType() === ParamType::TEXT ) {
+ // Unwrap like in params()
+ $p = $p->getValue();
+ }
[ $type, $value ] = $this->extractParam( $p, $format );
$types[$type] = true;
$list[] = $value;
@@ -1630,7 +1645,6 @@ class Message implements Stringable, MessageSpecifier, Serializable {
// return the concatenated values as 'after'. We handle this by turning
// the list into a RawMessage and processing that as a parameter.
$vars = $this->getLanguage()->$func( $vars );
- // @phan-suppress-next-line SecurityCheck-DoubleEscaped RawMessage is safe here
return $this->extractParam( new RawMessage( $vars, $params ), $format );
}
}
diff --git a/includes/Message/MessageFormatterFactory.php b/includes/Message/MessageFormatterFactory.php
index a10926bb3c89..a00d457b00bb 100644
--- a/includes/Message/MessageFormatterFactory.php
+++ b/includes/Message/MessageFormatterFactory.php
@@ -33,7 +33,7 @@ class MessageFormatterFactory implements IMessageFormatterFactory {
public function getTextFormatter( $langCode ): ITextFormatter {
if ( !isset( $this->textFormatters[$langCode] ) ) {
$this->textFormatters[$langCode] = new TextFormatter(
- $langCode, new Converter(), $this->format );
+ $langCode, $this->format );
}
return $this->textFormatters[$langCode];
}
diff --git a/includes/Message/TextFormatter.php b/includes/Message/TextFormatter.php
index cec56724a8ce..417875a31a4d 100644
--- a/includes/Message/TextFormatter.php
+++ b/includes/Message/TextFormatter.php
@@ -3,15 +3,12 @@
namespace MediaWiki\Message;
use Wikimedia\Message\ITextFormatter;
-use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\MessageSpecifier;
/**
* The MediaWiki-specific implementation of ITextFormatter
*/
class TextFormatter implements ITextFormatter {
- /** @var Converter */
- private $converter;
-
/** @var string */
private $langCode;
@@ -27,16 +24,13 @@ class TextFormatter implements ITextFormatter {
*
* @internal
* @param string $langCode
- * @param Converter $converter
* @param string $format
*/
public function __construct(
string $langCode,
- Converter $converter,
string $format = Message::FORMAT_TEXT
) {
$this->langCode = $langCode;
- $this->converter = $converter;
$this->format = $format;
}
@@ -44,8 +38,20 @@ class TextFormatter implements ITextFormatter {
return $this->langCode;
}
- public function format( MessageValue $mv ) {
- $message = $this->converter->convertMessageValue( $mv );
+ /**
+ * Allow the Message class to be mocked in tests by constructing objects in
+ * a protected method.
+ *
+ * @internal
+ * @param MessageSpecifier $spec
+ * @return Message
+ */
+ protected function createMessage( MessageSpecifier $spec ) {
+ return Message::newFromSpecifier( $spec );
+ }
+
+ public function format( MessageSpecifier $mv ): string {
+ $message = $this->createMessage( $mv );
$message->inLanguage( $this->langCode );
return $message->toString( $this->format );
}
diff --git a/includes/Status/StatusFormatter.php b/includes/Status/StatusFormatter.php
index fc522cf834e0..615b76fd316c 100644
--- a/includes/Status/StatusFormatter.php
+++ b/includes/Status/StatusFormatter.php
@@ -32,6 +32,7 @@ use Psr\Log\LoggerInterface;
use RuntimeException;
use StatusValue;
use UnexpectedValueException;
+use Wikimedia\Message\MessageParam;
use Wikimedia\Message\MessageSpecifier;
/**
@@ -259,9 +260,8 @@ class StatusFormatter {
$context = [];
$i = 1;
foreach ( $params as $param ) {
- if ( is_array( $param ) && count( $param ) === 1 ) {
- // probably Message::numParam() or similar
- $param = reset( $param );
+ if ( $param instanceof MessageParam ) {
+ $param = $param->getValue();
}
if ( is_int( $param ) || is_float( $param ) || is_string( $param ) ) {
$context["parameter$i"] = $param;
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index a2d12094e08c..ff1c4b4cb621 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -355,6 +355,10 @@ class ApiResult implements ApiSerializable {
$ex
);
}
+ } elseif ( $value instanceof \Wikimedia\Message\MessageParam ) {
+ // HACK Support code that puts $msg->getParams() directly into API responses
+ // (e.g. ApiErrorFormatter::formatRawMessage()).
+ $value = $value->getType() === 'text' ? $value->getValue() : $value->jsonSerialize();
} elseif ( is_callable( [ $value, '__toString' ] ) ) {
$value = (string)$value;
} else {
diff --git a/includes/libs/Message/ITextFormatter.php b/includes/libs/Message/ITextFormatter.php
index 79ee097cf6e9..fa71a26a95f6 100644
--- a/includes/libs/Message/ITextFormatter.php
+++ b/includes/libs/Message/ITextFormatter.php
@@ -3,7 +3,7 @@
namespace Wikimedia\Message;
/**
- * Converts MessageValue message specifiers to localized plain text in a certain language.
+ * Converts MessageSpecifier objects to localized plain text in a certain language.
*
* The caller cannot modify the details of message translation, such as which
* of multiple sources the message is taken from. Any such flags may be injected
@@ -23,13 +23,13 @@ interface ITextFormatter {
public function getLangCode();
/**
- * Convert a MessageValue to text.
+ * Convert a MessageSpecifier to text.
*
* The result is not safe for use as raw HTML.
*
- * @param MessageValue $message
+ * @param MessageSpecifier $message
* @return string
* @return-taint tainted
*/
- public function format( MessageValue $message );
+ public function format( MessageSpecifier $message ): string;
}
diff --git a/includes/libs/Message/ListParam.php b/includes/libs/Message/ListParam.php
index 48823aa3808f..3b308915d828 100644
--- a/includes/libs/Message/ListParam.php
+++ b/includes/libs/Message/ListParam.php
@@ -20,7 +20,7 @@ class ListParam extends MessageParam {
* @stable to call.
*
* @param string $listType One of the ListType constants.
- * @param (MessageParam|MessageValue|string|int|float)[] $elements Values in the list.
+ * @param (MessageParam|MessageSpecifier|string|int|float)[] $elements Values in the list.
* Values that are not instances of MessageParam are wrapped using ParamType::TEXT.
*/
public function __construct( $listType, array $elements ) {
diff --git a/includes/libs/Message/MessageSpecifier.php b/includes/libs/Message/MessageSpecifier.php
index d54673b39c36..843e431a8b18 100644
--- a/includes/libs/Message/MessageSpecifier.php
+++ b/includes/libs/Message/MessageSpecifier.php
@@ -38,7 +38,7 @@ interface MessageSpecifier {
/**
* Returns the message parameters
*
- * @return array
+ * @return (MessageParam|MessageSpecifier|string|int|float)[]
*/
public function getParams();
}
diff --git a/includes/libs/Message/MessageValue.php b/includes/libs/Message/MessageValue.php
index fae7cd45f8cd..59ee2b56e446 100644
--- a/includes/libs/Message/MessageValue.php
+++ b/includes/libs/Message/MessageValue.php
@@ -18,7 +18,7 @@ use Stringable;
*
* @newable
*/
-class MessageValue implements JsonDeserializable {
+class MessageValue implements JsonDeserializable, MessageSpecifier {
use JsonDeserializableTrait;
/** @var string */
@@ -31,7 +31,7 @@ class MessageValue implements JsonDeserializable {
* @stable to call
*
* @param string $key
- * @param (MessageParam|MessageValue|string|int|float)[] $params Values that are not instances
+ * @param (MessageParam|MessageSpecifier|string|int|float)[] $params Values that are not instances
* of MessageParam are wrapped using ParamType::TEXT.
*/
public function __construct( $key, $params = [] ) {
@@ -43,7 +43,7 @@ class MessageValue implements JsonDeserializable {
/**
* Static constructor for easier chaining of `->params()` methods
* @param string $key
- * @param (MessageParam|MessageValue|string|int|float)[] $params
+ * @param (MessageParam|MessageSpecifier|string|int|float)[] $params
* @return MessageValue
*/
public static function new( $key, $params = [] ) {
@@ -51,6 +51,22 @@ class MessageValue implements JsonDeserializable {
}
/**
+ * Convert from any MessageSpecifier to a MessageValue.
+ *
+ * When the given object is an instance of MessageValue, the same object is returned.
+ *
+ * @since 1.43
+ * @param MessageSpecifier $spec
+ * @return MessageValue
+ */
+ public static function newFromSpecifier( MessageSpecifier $spec ) {
+ if ( $spec instanceof MessageValue ) {
+ return $spec;
+ }
+ return new MessageValue( $spec->getKey(), $spec->getParams() );
+ }
+
+ /**
* Get the message key
*
* @return string
@@ -71,7 +87,7 @@ class MessageValue implements JsonDeserializable {
/**
* Chainable mutator which adds text parameters and MessageParam parameters
*
- * @param MessageParam|MessageValue|string|int|float ...$values
+ * @param MessageParam|MessageSpecifier|string|int|float ...$values
* @return $this
*/
public function params( ...$values ) {
@@ -89,7 +105,7 @@ class MessageValue implements JsonDeserializable {
* Chainable mutator which adds text parameters with a common type
*
* @param string $type One of the ParamType constants
- * @param MessageValue|string|int|float ...$values Scalar values
+ * @param MessageSpecifier|string|int|float ...$values Scalar values
* @return $this
*/
public function textParamsOfType( $type, ...$values ) {
@@ -118,7 +134,7 @@ class MessageValue implements JsonDeserializable {
* Chainable mutator which adds list parameters with a common type
*
* @param string $listType One of the ListType constants
- * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+ * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value
* is an array of items suitable to pass as $params to ListParam::__construct()
* @return $this
*/
@@ -132,7 +148,7 @@ class MessageValue implements JsonDeserializable {
/**
* Chainable mutator which adds parameters of type text (ParamType::TEXT).
*
- * @param MessageValue|string|int|float ...$values
+ * @param MessageSpecifier|string|int|float ...$values
* @return $this
*/
public function textParams( ...$values ) {
@@ -287,7 +303,7 @@ class MessageValue implements JsonDeserializable {
* The list parameters thus created are formatted as a comma-separated list,
* or some local equivalent.
*
- * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+ * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value
* is an array of items suitable to pass as $params to ListParam::__construct()
* @return $this
*/
@@ -301,7 +317,7 @@ class MessageValue implements JsonDeserializable {
* The list parameters thus created are formatted as a semicolon-separated
* list, or some local equivalent.
*
- * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+ * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value
* is an array of items suitable to pass as $params to ListParam::__construct()
* @return $this
*/
@@ -315,7 +331,7 @@ class MessageValue implements JsonDeserializable {
* The list parameters thus created are formatted as a pipe ("|") -separated
* list, or some local equivalent.
*
- * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+ * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value
* is an array of items suitable to pass as $params to ListParam::__construct()
* @return $this
*/
diff --git a/includes/libs/Message/README.md b/includes/libs/Message/README.md
index 7bace8a48162..15231e149d40 100644
--- a/includes/libs/Message/README.md
+++ b/includes/libs/Message/README.md
@@ -21,7 +21,8 @@ contain **placeholders**, which represents a place in the message where a
text other than these placeholders and formatting commands, or it might be in a
**markup language** such as wikitext or Markdown.
-A **formatter** is used to convert the message key and parameters into a text
+A **formatter** is used to convert the message key and parameters
+(that is, a **message specifier**) into a text
representation in a particular language and **output format**.
The library itself imposes few restrictions on all of these concepts; this
@@ -44,12 +45,12 @@ $message = new MessageValue( 'message-key', [
] );
// Fluent interface
-$message = ( new MessageValue( 'message-key' ) )
+$message = MessageValue::new( 'message-key' )
->params( 'parameter', new MessageValue( 'another-message' ) )
->numParams( 12345 );
// Formatting
-$messageFormatter = $serviceContainter->get( 'MessageFormatterFactory' )->getTextFormatter( 'de' );
+$messageFormatter = $serviceContainer->get( 'MessageFormatterFactory' )->getTextFormatter( 'de' );
$output = $messageFormatter->format( $message );
</pre>
@@ -64,6 +65,12 @@ Messages and their parameters are represented by newable value objects.
parameters. It is mutable in that parameters can be added to the object after
creation.
+**MessageSpecifier** is an interface implemented by MessageValue (and, outside
+of the Wikimedia\Message namespace, also MediaWiki\Message\Message), which only
+provides getter methods for the key and parameters, and no way to mutate
+the object. It should be used in methods that output or inspect messages,
+but aren't supposed to modify them.
+
**MessageParam** is an abstract value class representing a parameter to a message.
It has a type (using constants defined in the **ParamType** class) and a value. It
has two implementations:
diff --git a/includes/libs/Message/ScalarParam.php b/includes/libs/Message/ScalarParam.php
index bb6245cc668d..729746f500ac 100644
--- a/includes/libs/Message/ScalarParam.php
+++ b/includes/libs/Message/ScalarParam.php
@@ -25,7 +25,7 @@ class ScalarParam extends MessageParam {
*
* @param string $type One of the ParamType constants.
* Using ParamType::OBJECT is deprecated since 1.43.
- * @param string|int|float|MessageValue|Stringable $value
+ * @param string|int|float|MessageSpecifier|Stringable $value
*/
public function __construct( $type, $value ) {
if ( !in_array( $type, ParamType::cases() ) ) {
@@ -38,15 +38,17 @@ class ScalarParam extends MessageParam {
}
if ( $type === ParamType::OBJECT ) {
wfDeprecatedMsg( 'Using ParamType::OBJECT was deprecated in MediaWiki 1.43', '1.43' );
- } elseif ( $value instanceof Stringable ) {
- // Stringify the stringable to ensure that $this->value is JSON-serializable
+ } elseif ( $value instanceof MessageSpecifier ) {
+ // Ensure that $this->value is JSON-serializable, even if $value is not
// (but don't do it when using ParamType::OBJECT, since those objects may not expect it)
+ $value = MessageValue::newFromSpecifier( $value );
+ } elseif ( $value instanceof Stringable || is_callable( [ $value, '__toString' ] ) ) {
+ // TODO: Remove separate '__toString' check above once we drop PHP 7.4
$value = (string)$value;
- } elseif ( !is_string( $value ) && !is_numeric( $value ) &&
- !$value instanceof MessageValue ) {
+ } elseif ( !is_string( $value ) && !is_numeric( $value ) ) {
$type = get_debug_type( $value );
throw new InvalidArgumentException(
- "Scalar parameter must be a string, number, or MessageValue; got $type"
+ "Scalar parameter must be a string, number, Stringable, or MessageSpecifier; got $type"
);
}
diff --git a/includes/libs/StatusValue.php b/includes/libs/StatusValue.php
index 5c2262a3ccf0..41365c838e35 100644
--- a/includes/libs/StatusValue.php
+++ b/includes/libs/StatusValue.php
@@ -18,9 +18,8 @@
* @file
*/
-use MediaWiki\Message\Converter;
-use MediaWiki\Message\Message;
use Wikimedia\Assert\Assert;
+use Wikimedia\Message\MessageParam;
use Wikimedia\Message\MessageSpecifier;
use Wikimedia\Message\MessageValue;
@@ -36,12 +35,15 @@ use Wikimedia\Message\MessageValue;
* informed as to what went wrong. Calling the fatal() function sets an error
* message and simultaneously switches off the OK flag.
*
- * The recommended pattern for Status objects is to return a StatusValue
- * unconditionally, i.e. both on success and on failure -- so that the
- * developer of the calling code is reminded that the function can fail, and
- * so that a lack of error-handling will be explicit.
+ * The recommended pattern for functions returning StatusValue objects is
+ * to return a StatusValue unconditionally, both on success and on failure
+ * (similarly to Option, Maybe, Promise etc. objects in other languages) --
+ * so that the developer of the calling code is reminded that the function
+ * can fail, and so that a lack of error-handling will be explicit.
*
- * The use of Message objects should be avoided when serializability is needed.
+ * This class accepts any MessageSpecifier objects. The use of Message objects
+ * should be avoided when serializability is needed. Use MessageValue in that
+ * case instead.
*
* @newable
* @stable to extend
@@ -229,7 +231,10 @@ class StatusValue implements Stringable {
$params = $key->getParams();
$key = $key->getKey();
}
- if ( $newKey === $key && $newParams === $params ) {
+
+ // This uses loose equality as we must support equality between MessageParam objects
+ // (e.g. ScalarParam), including when they are created separate and not by-ref equal.
+ if ( $newKey === $key && $newParams == $params ) {
if ( $type === 'warning' && $newType === 'error' ) {
$type = 'error';
}
@@ -245,13 +250,11 @@ class StatusValue implements Stringable {
/**
* Add a new warning
*
- * @param string|MessageSpecifier|MessageValue $message Message key or object
+ * @param string|MessageSpecifier $message Message key or object
* @param mixed ...$parameters
* @return $this
*/
public function warning( $message, ...$parameters ) {
- $message = $this->normalizeMessage( $message );
-
return $this->addError( [
'type' => 'warning',
'message' => $message,
@@ -263,13 +266,11 @@ class StatusValue implements Stringable {
* Add an error, do not set fatal flag
* This can be used for non-fatal errors
*
- * @param string|MessageSpecifier|MessageValue $message Message key or object
+ * @param string|MessageSpecifier $message Message key or object
* @param mixed ...$parameters
* @return $this
*/
public function error( $message, ...$parameters ) {
- $message = $this->normalizeMessage( $message );
-
return $this->addError( [
'type' => 'error',
'message' => $message,
@@ -281,7 +282,7 @@ class StatusValue implements Stringable {
* Add an error and set OK to false, indicating that the operation
* as a whole was fatal
*
- * @param string|MessageSpecifier|MessageValue $message Message key or object
+ * @param string|MessageSpecifier $message Message key or object
* @param mixed ...$parameters
* @return $this
*/
@@ -343,6 +344,9 @@ class StatusValue implements Stringable {
/**
* Returns a list of error messages, optionally only those of the given type
*
+ * If the `warning()` or `error()` method was called with a MessageSpecifier object,
+ * this method is guaranteed to return the same object.
+ *
* @since 1.43
* @param ?string $type If provided, only return messages of the type 'warning' or 'error'
* @phan-param null|'warning'|'error' $type
@@ -358,8 +362,7 @@ class StatusValue implements Stringable {
if ( $key instanceof MessageSpecifier ) {
$result[] = $key;
} else {
- // TODO: Make MessageValue implement MessageSpecifier, and use a MessageValue here
- $result[] = new Message( $key, $params );
+ $result[] = new MessageValue( $key, $params );
}
}
}
@@ -372,7 +375,7 @@ class StatusValue implements Stringable {
* Any message using the same key will be found (ignoring the message parameters).
*
* @param string $message Message key to search for
- * (this parameter used to allow MessageSpecifier|MessageValue too, deprecated since 1.43)
+ * (this parameter used to allow MessageSpecifier too, deprecated since 1.43)
* @return bool
*/
public function hasMessage( $message ) {
@@ -380,10 +383,6 @@ class StatusValue implements Stringable {
wfDeprecatedMsg( 'Passing MessageSpecifier to hasMessage()' .
' was deprecated in MediaWiki 1.43', '1.43' );
$message = $message->getKey();
- } elseif ( $message instanceof MessageValue ) {
- wfDeprecatedMsg( 'Passing MessageValue to hasMessage()' .
- ' was deprecated in MediaWiki 1.43', '1.43' );
- $message = $message->getKey();
}
foreach ( $this->errors as [ 'message' => $key ] ) {
@@ -402,7 +401,7 @@ class StatusValue implements Stringable {
* Any messages using the same keys will be found (ignoring the message parameters).
*
* @param string ...$messages Message keys to search for
- * (this parameter used to allow MessageSpecifier|MessageValue too, deprecated since 1.43)
+ * (this parameter used to allow MessageSpecifier too, deprecated since 1.43)
* @return bool
*/
public function hasMessagesExcept( ...$messages ) {
@@ -412,10 +411,6 @@ class StatusValue implements Stringable {
wfDeprecatedMsg( 'Passing MessageSpecifier to hasMessagesExcept()' .
' was deprecated in MediaWiki 1.43', '1.43' );
$message = $message->getKey();
- } elseif ( $message instanceof MessageValue ) {
- wfDeprecatedMsg( 'Passing MessageValue to hasMessagesExcept()' .
- ' was deprecated in MediaWiki 1.43', '1.43' );
- $message = $message->getKey();
}
$exceptedKeys[] = $message;
}
@@ -440,17 +435,14 @@ class StatusValue implements Stringable {
* (regardless of whether it was stored as string or as MessageSpecifier, and ignoring the
* message parameters).
*
- * When using a MessageValue as the `$source` parameter, this function does not work. This is a
- * bug, but it's impractical to fix. Therefore, passing a MessageValue is deprecated (since 1.43).
- *
* When using a MessageSpecifier as the `$source` parameter, the message will only be replaced
* when the same MessageSpecifier object was stored in the StatusValue (compared with `===`).
* Since the only reliable way to obtain one is to use getErrors(), which is deprecated,
- * passing a MessageSpecifier is deprecated as well (since 1.43).
+ * passing a MessageSpecifier is deprecated (since 1.43).
*
* @param string $source Message key to search for
- * (this parameter used to allow MessageSpecifier|MessageValue too, deprecated since 1.43)
- * @param MessageSpecifier|MessageValue|string $dest Replacement message key or object
+ * (this parameter used to allow MessageSpecifier too, deprecated since 1.43)
+ * @param MessageSpecifier|string $dest Replacement message key or object
* @return bool Return true if the replacement was done, false otherwise.
*/
public function replaceMessage( $source, $dest ) {
@@ -459,14 +451,8 @@ class StatusValue implements Stringable {
if ( $source instanceof MessageSpecifier ) {
wfDeprecatedMsg( 'Passing MessageSpecifier as $source to replaceMessage()' .
' was deprecated in MediaWiki 1.43', '1.43' );
- } elseif ( $source instanceof MessageValue ) {
- wfDeprecatedMsg( 'Passing MessageValue as $source to replaceMessage()' .
- ' was deprecated in MediaWiki 1.43', '1.43' );
- $source = $this->normalizeMessage( $source );
}
- $dest = $this->normalizeMessage( $dest );
-
foreach ( $this->errors as [ 'message' => &$message, 'params' => &$params ] ) {
if ( $message === $source ||
( $message instanceof MessageSpecifier && $message->getKey() === $source )
@@ -548,6 +534,8 @@ class StatusValue implements Stringable {
$r = '[ ' . self::flattenParams( $p ) . ' ]';
} elseif ( $p instanceof MessageSpecifier ) {
$r = '{ ' . $p->getKey() . ': ' . self::flattenParams( $p->getParams() ) . ' }';
+ } elseif ( $p instanceof MessageParam ) {
+ $r = $p->dump();
} else {
$r = (string)$p;
}
@@ -580,18 +568,4 @@ class StatusValue implements Stringable {
return $result;
}
-
- /**
- * @param MessageSpecifier|MessageValue|string $message
- *
- * @return MessageSpecifier|string
- */
- private function normalizeMessage( $message ) {
- if ( $message instanceof MessageValue ) {
- $converter = new Converter();
- return $converter->convertMessageValue( $message );
- }
-
- return $message;
- }
}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index 7cb0895ed4c6..1de0354cf2d7 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -296,7 +296,9 @@ class SpecialBlock extends FormSpecialPage {
) );
if ( $this->useCodex ) {
- $this->codexFormData[ 'blockPreErrors' ] = array_map( 'strval', $this->preErrors );
+ $this->codexFormData[ 'blockPreErrors' ] = array_map( function ( $errMsg ) {
+ return $this->msg( $errMsg )->parse();
+ }, $this->preErrors );
}
}
}
diff --git a/tests/phpunit/includes/Message/TextFormatterTest.php b/tests/phpunit/includes/Message/TextFormatterTest.php
index 0ffaf7565490..5ec2b7f128dd 100644
--- a/tests/phpunit/includes/Message/TextFormatterTest.php
+++ b/tests/phpunit/includes/Message/TextFormatterTest.php
@@ -2,7 +2,6 @@
namespace MediaWiki\Tests\Message;
-use MediaWiki\Message\Converter;
use MediaWiki\Message\Message;
use MediaWiki\Message\TextFormatter;
use MediaWiki\Message\UserGroupMembershipParam;
@@ -24,13 +23,14 @@ class TextFormatterTest extends MediaWikiIntegrationTestCase {
$includeWikitext = false,
$format = Message::FORMAT_TEXT
) {
- $converter = $this->getMockBuilder( Converter::class )
+ $formatter = $this->getMockBuilder( TextFormatter::class )
->onlyMethods( [ 'createMessage' ] )
+ ->setConstructorArgs( [ $langCode, $format ] )
->getMock();
- $converter->method( 'createMessage' )
- ->willReturnCallback( function ( $key ) use ( $includeWikitext ) {
+ $formatter->method( 'createMessage' )
+ ->willReturnCallback( function ( $spec ) use ( $includeWikitext ) {
$message = $this->getMockBuilder( Message::class )
- ->setConstructorArgs( [ $key ] )
+ ->setConstructorArgs( [ $spec ] )
->onlyMethods( [ 'fetchMessage' ] )
->getMock();
@@ -47,11 +47,11 @@ class TextFormatterTest extends MediaWikiIntegrationTestCase {
return $message;
} );
- return new TextFormatter( $langCode, $converter, $format );
+ return $formatter;
}
public function testGetLangCode() {
- $formatter = new TextFormatter( 'fr', new Converter );
+ $formatter = new TextFormatter( 'fr' );
$this->assertSame( 'fr', $formatter->getLangCode() );
}
@@ -87,7 +87,7 @@ class TextFormatterTest extends MediaWikiIntegrationTestCase {
new ScalarParam( ParamType::BITRATE, 100 ),
new MessageValue( 'test3', [ 'c', new MessageValue( 'test4', [ 'd', 'e' ] ) ] )
] ),
- 'test test2 a b x(comma-separator)(bitrate-bits)(comma-separator)test3 c test4 d e'
+ 'test (test2: a, b) x(comma-separator)(bitrate-bits)(comma-separator)(test3: c, (test4: d, e))'
];
yield [ ( new MessageValue( 'test' ) )
diff --git a/tests/phpunit/includes/Status/StatusTest.php b/tests/phpunit/includes/Status/StatusTest.php
index 5796b471490b..483ff35ce875 100644
--- a/tests/phpunit/includes/Status/StatusTest.php
+++ b/tests/phpunit/includes/Status/StatusTest.php
@@ -260,7 +260,6 @@ class StatusTest extends MediaWikiLangTestCase {
$this->expectDeprecationAndContinue( '/Passing MessageSpecifier/' );
$this->assertTrue( $status->hasMessage( wfMessage( 'bad-msg' ) ) );
$this->assertTrue( $status->hasMessage( wfMessage( 'bad-msg-value' ) ) );
- $this->expectDeprecationAndContinue( '/Passing MessageValue/' );
$this->assertTrue( $status->hasMessage( new MessageValue( 'bad-msg' ) ) );
$this->assertTrue( $status->hasMessage( new MessageValue( 'bad-msg-value' ) ) );
$this->assertFalse( $status->hasMessage( 'good' ) );
@@ -278,7 +277,6 @@ class StatusTest extends MediaWikiLangTestCase {
$this->assertFalse( $status->hasMessagesExcept(
'good', 'bad', 'bad-msg', 'bad-msg-value' ) );
$this->expectDeprecationAndContinue( '/Passing MessageSpecifier/' );
- $this->expectDeprecationAndContinue( '/Passing MessageValue/' );
$this->assertFalse( $status->hasMessagesExcept(
wfMessage( 'bad' ), new MessageValue( 'bad-msg' ), 'bad-msg-value' ) );
}
@@ -630,7 +628,7 @@ class StatusTest extends MediaWikiLangTestCase {
}
public function testReplaceMessageValue() {
- $this->expectDeprecationAndContinue( '/Passing MessageValue/' );
+ $this->expectDeprecationAndContinue( '/Passing MessageSpecifier/' );
$status = new Status();
$messageVal = new MessageValue( 'key1', [ 'foo1', 'bar1' ] );
@@ -639,10 +637,8 @@ class StatusTest extends MediaWikiLangTestCase {
$status->replaceMessage( $messageVal, $newMessageVal );
- // Replacing by searching for a MessageValue DOES NOT WORK at all
- // (that's why this is deprecated)
$conv = new \MediaWiki\Message\Converter;
- $this->assertEquals( $messageVal, $conv->convertMessage( $status->errors[0]['message'] ) );
+ $this->assertEquals( $newMessageVal, $conv->convertMessage( $status->errors[0]['message'] ) );
}
public function testReplaceMessageByKey() {
diff --git a/tests/phpunit/includes/api/ApiBaseTest.php b/tests/phpunit/includes/api/ApiBaseTest.php
index bfc310a1a73c..0c407651558a 100644
--- a/tests/phpunit/includes/api/ApiBaseTest.php
+++ b/tests/phpunit/includes/api/ApiBaseTest.php
@@ -357,7 +357,7 @@ class ApiBaseTest extends ApiTestCase {
? [ $warn->getKey(), ...$warn->getParams() ]
: $warn;
}, $mock->warnings );
- $this->assertSame( $warnings, $actualWarnings );
+ $this->assertEquals( $warnings, $actualWarnings );
}
if ( !empty( $paramSettings[ParamValidator::PARAM_SENSITIVE] ) ||
diff --git a/tests/phpunit/includes/auth/AuthManagerTest.php b/tests/phpunit/includes/auth/AuthManagerTest.php
index f68d10835485..30bba01f33f0 100644
--- a/tests/phpunit/includes/auth/AuthManagerTest.php
+++ b/tests/phpunit/includes/auth/AuthManagerTest.php
@@ -3427,7 +3427,12 @@ class AuthManagerTest extends MediaWikiIntegrationTestCase {
'sysop',
true
],
- 'globally blocked' => [ 'global-ip', [], 'anon', false ],
+ 'globally blocked' => [
+ 'global-ip',
+ [ 'systemBlock' => 'test-systemBlock' ],
+ 'anon',
+ false
+ ],
];
}
diff --git a/tests/phpunit/includes/language/MessageTest.php b/tests/phpunit/includes/language/MessageTest.php
index c27213c7e424..1e1ad60387cc 100644
--- a/tests/phpunit/includes/language/MessageTest.php
+++ b/tests/phpunit/includes/language/MessageTest.php
@@ -129,7 +129,7 @@ class MessageTest extends MediaWikiLangTestCase {
$returned = $msg->params( ...$args );
$this->assertSame( $msg, $returned );
- $this->assertSame( $expected, $msg->getParams() );
+ $this->assertEquals( $expected, $msg->getParams() );
}
public static function provideConstructorLanguage() {
@@ -863,7 +863,7 @@ class MessageTest extends MediaWikiLangTestCase {
yield "Serializing raw parameters" => [
fn () => ( new Message( 'parentheses' ) )->rawParams( '<a>foo</a>' ),
- 'O:25:"MediaWiki\Message\Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:11:"parentheses";s:9:"keysToTry";a:1:{i:0;s:11:"parentheses";}s:10:"parameters";a:1:{i:0;a:1:{s:3:"raw";s:10:"<a>foo</a>";}}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
+ 'O:25:"MediaWiki\Message\Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:11:"parentheses";s:9:"keysToTry";a:1:{i:0;s:11:"parentheses";}s:10:"parameters";a:1:{i:0;O:29:"Wikimedia\Message\ScalarParam":2:{s:7:"' . chr( 0 ) . '*' . chr( 0 ) . 'type";s:3:"raw";s:8:"' . chr( 0 ) . '*' . chr( 0 ) . 'value";s:10:"<a>foo</a>";}}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
'(<a>foo</a>)',
];
@@ -883,6 +883,12 @@ class MessageTest extends MediaWikiLangTestCase {
public function provideSerializationLegacy() {
// Test cases where we can test only unserialization, because the serialization format changed.
+ yield "MW 1.42: Magic arrays instead of MessageParam objects" => [
+ fn () => ( new Message( 'parentheses' ) )->rawParams( '<a>foo</a>' ),
+ 'O:25:"MediaWiki\Message\Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:11:"parentheses";s:9:"keysToTry";a:1:{i:0;s:11:"parentheses";}s:10:"parameters";a:1:{i:0;a:1:{s:3:"raw";s:10:"<a>foo</a>";}}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
+ '(<a>foo</a>)',
+ ];
+
yield "MW 1.41: Un-namespaced class" => [
fn () => new Message( 'mainpage' ),
'O:7:"Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:8:"mainpage";s:9:"keysToTry";a:1:{i:0;s:8:"mainpage";}s:10:"parameters";a:0:{}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
diff --git a/tests/phpunit/integration/includes/Rest/Handler/DiscoveryHandlerTest.php b/tests/phpunit/integration/includes/Rest/Handler/DiscoveryHandlerTest.php
index 38c3d486ac4a..e99e3da3abc7 100644
--- a/tests/phpunit/integration/includes/Rest/Handler/DiscoveryHandlerTest.php
+++ b/tests/phpunit/integration/includes/Rest/Handler/DiscoveryHandlerTest.php
@@ -17,7 +17,7 @@ use MediaWikiIntegrationTestCase;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Constraint\Constraint;
use Wikimedia\Message\ITextFormatter;
-use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\MessageSpecifier;
/**
* @covers \MediaWiki\Rest\Handler\DiscoveryHandler
@@ -50,7 +50,7 @@ class DiscoveryHandlerTest extends MediaWikiIntegrationTestCase {
return 'qqx';
}
- public function format( MessageValue $message ) {
+ public function format( MessageSpecifier $message ): string {
return $message->dump();
}
};
diff --git a/tests/phpunit/integration/includes/Rest/Handler/Helper/HtmlInputTransformHelperTest.php b/tests/phpunit/integration/includes/Rest/Handler/Helper/HtmlInputTransformHelperTest.php
index ababdd187a54..556657cb11d3 100644
--- a/tests/phpunit/integration/includes/Rest/Handler/Helper/HtmlInputTransformHelperTest.php
+++ b/tests/phpunit/integration/includes/Rest/Handler/Helper/HtmlInputTransformHelperTest.php
@@ -10,7 +10,6 @@ use MediaWiki\Edit\ParsoidRenderID;
use MediaWiki\Edit\SelserContext;
use MediaWiki\MainConfigNames;
use MediaWiki\MainConfigSchema;
-use MediaWiki\Message\Converter;
use MediaWiki\Message\TextFormatter;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
@@ -683,7 +682,7 @@ class HtmlInputTransformHelperTest extends MediaWikiIntegrationTestCase {
}
private function createResponse() {
- $responseFactory = new ResponseFactory( [ new TextFormatter( 'qqx', new Converter() ) ] );
+ $responseFactory = new ResponseFactory( [ new TextFormatter( 'qqx' ) ] );
$response = $responseFactory->create();
return $response;
}
diff --git a/tests/phpunit/integration/includes/Rest/Handler/ModuleSpecHandlerTest.php b/tests/phpunit/integration/includes/Rest/Handler/ModuleSpecHandlerTest.php
index 83135ee1d44b..e58b6d3fbff5 100644
--- a/tests/phpunit/integration/includes/Rest/Handler/ModuleSpecHandlerTest.php
+++ b/tests/phpunit/integration/includes/Rest/Handler/ModuleSpecHandlerTest.php
@@ -18,7 +18,7 @@ use MediaWikiIntegrationTestCase;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Constraint\Constraint;
use Wikimedia\Message\ITextFormatter;
-use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\MessageSpecifier;
use Wikimedia\ParamValidator\ParamValidator;
/**
@@ -52,7 +52,7 @@ class ModuleSpecHandlerTest extends MediaWikiIntegrationTestCase {
return 'qqx';
}
- public function format( MessageValue $message ) {
+ public function format( MessageSpecifier $message ): string {
return $message->dump();
}
};
diff --git a/tests/phpunit/mocks/DummyServicesTrait.php b/tests/phpunit/mocks/DummyServicesTrait.php
index 3ced732e216b..99272229537a 100644
--- a/tests/phpunit/mocks/DummyServicesTrait.php
+++ b/tests/phpunit/mocks/DummyServicesTrait.php
@@ -51,6 +51,7 @@ use PHPUnit\Framework\MockObject\MockObject;
use Psr\Container\ContainerInterface;
use Psr\Log\NullLogger;
use Wikimedia\Message\ITextFormatter;
+use Wikimedia\Message\MessageSpecifier;
use Wikimedia\Message\MessageValue;
use Wikimedia\ObjectFactory\ObjectFactory;
use Wikimedia\Rdbms\ConfiguredReadOnlyMode;
@@ -474,8 +475,8 @@ trait DummyServicesTrait {
return 'qqx';
}
- public function format( MessageValue $message ) {
- if ( $this->dumpMessages ) {
+ public function format( MessageSpecifier $message ): string {
+ if ( $this->dumpMessages && $message instanceof MessageValue ) {
return $message->dump();
}
return $message->getKey();
diff --git a/tests/phpunit/mocks/FakeQqxMessageLocalizer.php b/tests/phpunit/mocks/FakeQqxMessageLocalizer.php
index 7c2c8d2e93a9..c08a58024085 100644
--- a/tests/phpunit/mocks/FakeQqxMessageLocalizer.php
+++ b/tests/phpunit/mocks/FakeQqxMessageLocalizer.php
@@ -8,6 +8,7 @@ use LanguageQqx;
use MediaWiki\Language\Language;
use MediaWiki\Message\Message;
use MessageLocalizer;
+use Wikimedia\Message\MessageSpecifier;
/**
* A MessageLocalizer that does not make database/service calls, for use in unit tests
@@ -32,6 +33,13 @@ class FakeQqxMessageLocalizer implements MessageLocalizer {
return "($this->key$*)";
}
+ public static function newFromSpecifier( $value ) {
+ if ( $value instanceof MessageSpecifier ) {
+ return new self( $value );
+ }
+ return parent::newFromSpecifier( $value );
+ }
+
public function getLanguage(): Language {
return new class() extends LanguageQqx {
diff --git a/tests/phpunit/unit/includes/Message/ConverterTest.php b/tests/phpunit/unit/includes/Message/ConverterTest.php
index 1c3fe00bbce0..b7cfe6b5dbd5 100644
--- a/tests/phpunit/unit/includes/Message/ConverterTest.php
+++ b/tests/phpunit/unit/includes/Message/ConverterTest.php
@@ -2,7 +2,6 @@
namespace MediaWiki\Tests\Unit\Message;
-use InvalidArgumentException;
use MediaWiki\Language\Language;
use MediaWiki\Language\RawMessage;
use MediaWiki\Message\Converter;
@@ -16,14 +15,6 @@ use Wikimedia\Message\MessageValue;
*/
class ConverterTest extends MediaWikiUnitTestCase {
- public function testCreateMessage() {
- $converter = new Converter();
- $m = $converter->createMessage( 'foobar' );
- $this->assertInstanceOf( Message::class, $m );
- $this->assertSame( 'foobar', $m->getKey() );
- $this->assertSame( [], $m->getParams() );
- }
-
/** @dataProvider provideConversions */
public function testConvertMessage( Message $m, MessageValue $mv ) {
$converter = new Converter();
@@ -113,13 +104,6 @@ class ConverterTest extends MediaWikiUnitTestCase {
];
}
- public function testConvertMessage_invalidParam() {
- $m = Message::newFromKey( 'foobar', [ 'foo' => 'bar' ] );
- $converter = new Converter();
- $this->expectException( InvalidArgumentException::class );
- $converter->convertMessage( $m );
- }
-
public static function provideConversions_RawMessage() {
yield 'No params' => [
new RawMessage( 'Foo Bar' ),
diff --git a/tests/phpunit/unit/includes/libs/Message/ScalarParamTest.php b/tests/phpunit/unit/includes/libs/Message/ScalarParamTest.php
index 10b1eb3abb28..1d58ee978a8f 100644
--- a/tests/phpunit/unit/includes/libs/Message/ScalarParamTest.php
+++ b/tests/phpunit/unit/includes/libs/Message/ScalarParamTest.php
@@ -80,7 +80,7 @@ class ScalarParamTest extends MediaWikiUnitTestCase {
public function testConstruct_badValueNULL() {
$this->expectException( InvalidArgumentException::class );
$this->expectExceptionMessage(
- 'Scalar parameter must be a string, number, or MessageValue; got null'
+ 'Scalar parameter must be a string, number, Stringable, or MessageSpecifier; got null'
);
new ScalarParam( ParamType::TEXT, null );
}
@@ -88,7 +88,7 @@ class ScalarParamTest extends MediaWikiUnitTestCase {
public function testConstruct_badValueClass() {
$this->expectException( InvalidArgumentException::class );
$this->expectExceptionMessage(
- 'Scalar parameter must be a string, number, or MessageValue; got stdClass'
+ 'Scalar parameter must be a string, number, Stringable, or MessageSpecifier; got stdClass'
);
new ScalarParam( ParamType::TEXT, new stdClass );
}