diff options
author | jenkins-bot <jenkins-bot@gerrit.wikimedia.org> | 2024-10-21 15:38:56 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@wikimedia.org> | 2024-10-21 15:38:56 +0000 |
commit | cd7a5998e92ba7cd0f1abd26f4e2abba9815ac96 (patch) | |
tree | 798ef404247e0cb5bcef637af9a0d91832a4be63 | |
parent | dd56496b83450ec46e8203aac5e280d92f379ab4 (diff) | |
parent | 9d56257d8c154cf3020ae7d0a1dd2f45d5211453 (diff) | |
download | mediawikicore-cd7a5998e92ba7cd0f1abd26f4e2abba9815ac96.tar.gz mediawikicore-cd7a5998e92ba7cd0f1abd26f4e2abba9815ac96.zip |
Merge "Make Message and MessageValue compatible"
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 ); } |