*
* @since 1.17
*
- * @param mixed ... Parameters as strings or arrays from
+ * @param mixed $args,... Parameters as strings or arrays from
* Message::numParam() and the like, or a single array of parameters.
*
* @return Message $this
public static function listParam( array $list, $type = 'text' ) {
if ( !isset( self::$listTypeMap[$type] ) ) {
throw new InvalidArgumentException(
- "Invalid type '$type'. Known types are: " . join( ', ', array_keys( self::$listTypeMap ) )
+ "Invalid type '$type'. Known types are: " . implode( ', ', array_keys( self::$listTypeMap ) )
);
}
return [ 'list' => $list, 'type' => $type ];
* @return string
*/
protected function replaceParameters( $message, $type = 'before', $format ) {
+ // A temporary marker for $1 parameters that is only valid
+ // in non-attribute contexts. However if the entire message is escaped
+ // then we don't want to use it because it will be mangled in all contexts
+ // and its unnessary as ->escaped() messages aren't html.
+ $marker = $format === self::FORMAT_ESCAPED ? '$' : '$\'"';
$replacementKeys = [];
foreach ( $this->parameters as $n => $param ) {
list( $paramType, $value ) = $this->extractParam( $param, $format );
- if ( $type === $paramType ) {
- $replacementKeys['$' . ( $n + 1 )] = $value;
+ if ( $type === 'before' ) {
+ if ( $paramType === 'before' ) {
+ $replacementKeys['$' . ( $n + 1 )] = $value;
+ } else /* $paramType === 'after' */ {
+ // To protect against XSS from replacing parameters
+ // inside html attributes, we convert $1 to $'"1.
+ // In the event that one of the parameters ends up
+ // in an attribute, either the ' or the " will be
+ // escaped, breaking the replacement and avoiding XSS.
+ $replacementKeys['$' . ( $n + 1 )] = $marker . ( $n + 1 );
+ }
+ } else {
+ if ( $paramType === 'after' ) {
+ $replacementKeys[$marker . ( $n + 1 )] = $value;
+ }
}
}
$message = strtr( $message, $replacementKeys );
} elseif ( isset( $param['list'] ) ) {
return $this->formatListParam( $param['list'], $param['type'], $format );
} else {
- $warning = 'Invalid parameter for message "' . $this->getKey() . '": ' .
- htmlspecialchars( serialize( $param ) );
- trigger_error( $warning, E_USER_WARNING );
- $e = new Exception;
- wfDebugLog( 'Bug58676', $warning . "\n" . $e->getTraceAsString() );
+ if ( !is_scalar( $param ) ) {
+ $param = serialize( $param );
+ }
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'Bug58676' )->warning(
+ 'Invalid parameter for message "{msgkey}": {param}',
+ [
+ 'exception' => new Exception,
+ 'msgkey' => $this->getKey(),
+ 'param' => htmlspecialchars( $param ),
+ ]
+ );
return [ 'before', '[INVALID]' ];
}
$this->getLanguage()
);
- return $out instanceof ParserOutput ? $out->getText() : $out;
+ return $out instanceof ParserOutput
+ ? $out->getText( [
+ 'enableSectionEditLinks' => false,
+ // Wrapping messages in an extra <div> is probably not expected. If
+ // they're outside the content area they probably shouldn't be
+ // targeted by CSS that's targeting the parser output, and if
+ // they're inside they already are from the outer div.
+ 'unwrap' => true,
+ ] )
+ : $out;
}
/**
*/
protected function formatPlaintext( $plaintext, $format ) {
switch ( $format ) {
- case self::FORMAT_TEXT:
- case self::FORMAT_PLAIN:
- return $plaintext;
-
- case self::FORMAT_PARSE:
- case self::FORMAT_BLOCK_PARSE:
- case self::FORMAT_ESCAPED:
- default:
- return htmlspecialchars( $plaintext, ENT_QUOTES );
-
+ case self::FORMAT_TEXT:
+ case self::FORMAT_PLAIN:
+ return $plaintext;
+
+ case self::FORMAT_PARSE:
+ case self::FORMAT_BLOCK_PARSE:
+ case self::FORMAT_ESCAPED:
+ default:
+ return htmlspecialchars( $plaintext, ENT_QUOTES );
}
}
return $this->extractParam( new RawMessage( $vars, $params ), $format );
}
}
-
-/**
- * Variant of the Message class.
- *
- * Rather than treating the message key as a lookup
- * value (which is passed to the MessageCache and
- * translated as necessary), a RawMessage key is
- * treated as the actual message.
- *
- * All other functionality (parsing, escaping, etc.)
- * is preserved.
- *
- * @since 1.21
- */
-class RawMessage extends Message {
-
- /**
- * Call the parent constructor, then store the key as
- * the message.
- *
- * @see Message::__construct
- *
- * @param string $text Message to use.
- * @param array $params Parameters for the message.
- *
- * @throws InvalidArgumentException
- */
- public function __construct( $text, $params = [] ) {
- if ( !is_string( $text ) ) {
- throw new InvalidArgumentException( '$text must be a string' );
- }
-
- parent::__construct( $text, $params );
-
- // The key is the message.
- $this->message = $text;
- }
-
- /**
- * Fetch the message (in this case, the key).
- *
- * @return string
- */
- public function fetchMessage() {
- // Just in case the message is unset somewhere.
- if ( $this->message === null ) {
- $this->message = $this->key;
- }
-
- return $this->message;
- }
-
-}