3 * This class provides methods for fetching interface messages and
4 * processing them into variety of formats that are needed in MediaWiki.
6 * It is intented to replace the old wfMsg* functions that over time grew
8 * @see https://www.mediawiki.org/wiki/New_messages_API for
9 * equivalence between old and new functions.
11 * Below, you will find several examples of wfMessage() usage.
14 * Fetching a message text for interface message
16 * $button = Xml::button( wfMessage( 'submit' )->text() );
19 * Messages can have parameters:
22 * wfMessage( 'welcome-to' )->params( $wgSitename )->text();
25 * {{GRAMMAR}} and friends work correctly
27 * wfMessage( 'are-friends', $user, $friend );
28 * wfMessage( 'bad-message' )->rawParams( '<script>...</script>' )->escaped();
31 * Sometimes the message text ends up in the database, so content language is needed.
33 * wfMessage( 'file-log', $user, $filename )->inContentLanguage()->text()
37 * Checking whether a message exists:
39 * wfMessage( 'mysterious-message' )->exists()
42 * If you want to use a different language:
44 * wfMessage( 'email-header' )->inLanguage( $user->getOption( 'language' ) )->plain()
47 * @note You cannot parse the text except in the content or interface
50 * Comparison with old wfMsg* functions:
55 * wfMsgExt( 'key', array( 'parseinline' ), 'apple' );
56 * === wfMessage( 'key', 'apple' )->parse();
59 * Parseinline is used because it is more useful when pre-building html.
60 * In normal use it is better to use OutputPage::(add|wrap)WikiMsg.
62 * Places where html cannot be used. {{-transformation is done.
64 * wfMsgExt( 'key', array( 'parsemag' ), 'apple', 'pear' );
65 * === wfMessage( 'key', 'apple', 'pear' )->text();
68 * Shortcut for escaping the message too, similar to wfMsgHTML, but
69 * parameters are not replaced after escaping by default.
71 * $escaped = wfMessage( 'key' )->rawParams( 'apple' )->escaped();
75 * - test, can we have tests?
77 * @see https://www.mediawiki.org/wiki/WfMessage()
78 * @see https://www.mediawiki.org/wiki/New_messages_API
79 * @see https://www.mediawiki.org/wiki/Localisation
82 * @author Niklas Laxström
86 * In which language to get this message. True, which is the default,
87 * means the current interface language, false content language.
89 protected $interface = true;
92 * In which language to get this message. Overrides the $interface
97 protected $language = null;
105 * List of parameters which will be substituted into the message.
107 protected $parameters = array();
110 * Format for the message.
111 * Supported formats are:
113 * * escaped (transform+htmlspecialchars)
118 protected $format = 'parse';
121 * Whether database can be used.
123 protected $useDatabase = true;
126 * Title object to use as context
128 protected $title = null;
137 * @param $key: message key, or array of message keys to try and use the first non-empty message for
138 * @param $params Array message parameters
139 * @return Message: $this
141 public function __construct( $key, $params = array() ) {
144 $this->parameters
= array_values( $params );
145 $this->language
= $wgLang;
149 * Factory function that is just wrapper for the real constructor. It is
150 * intented to be used instead of the real constructor, because it allows
151 * chaining method calls, while new objects don't.
152 * @param $key String: message key
153 * @param Varargs: parameters as Strings
154 * @return Message: $this
156 public static function newFromKey( $key /*...*/ ) {
157 $params = func_get_args();
158 array_shift( $params );
159 return new self( $key, $params );
163 * Factory function accepting multiple message keys and returning a message instance
164 * for the first message which is non-empty. If all messages are empty then an
165 * instance of the first message key is returned.
166 * @param Varargs: message keys (or first arg as an array of all the message keys)
167 * @return Message: $this
169 public static function newFallbackSequence( /*...*/ ) {
170 $keys = func_get_args();
171 if ( func_num_args() == 1 ) {
172 if ( is_array($keys[0]) ) {
173 // Allow an array to be passed as the first argument instead
174 $keys = array_values($keys[0]);
176 // Optimize a single string to not need special fallback handling
180 return new self( $keys );
184 * Adds parameters to the parameter list of this message.
185 * @param Varargs: parameters as Strings, or a single argument that is an array of Strings
186 * @return Message: $this
188 public function params( /*...*/ ) {
189 $args = func_get_args();
190 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
193 $args_values = array_values( $args );
194 $this->parameters
= array_merge( $this->parameters
, $args_values );
199 * Add parameters that are substituted after parsing or escaping.
200 * In other words the parsing process cannot access the contents
201 * of this type of parameter, and you need to make sure it is
202 * sanitized beforehand. The parser will see "$n", instead.
203 * @param Varargs: raw parameters as Strings (or single argument that is an array of raw parameters)
204 * @return Message: $this
206 public function rawParams( /*...*/ ) {
207 $params = func_get_args();
208 if ( isset( $params[0] ) && is_array( $params[0] ) ) {
209 $params = $params[0];
211 foreach( $params as $param ) {
212 $this->parameters
[] = self
::rawParam( $param );
218 * Add parameters that are numeric and will be passed through
219 * Language::formatNum before substitution
220 * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
221 * @return Message: $this
223 public function numParams( /*...*/ ) {
224 $params = func_get_args();
225 if ( isset( $params[0] ) && is_array( $params[0] ) ) {
226 $params = $params[0];
228 foreach( $params as $param ) {
229 $this->parameters
[] = self
::numParam( $param );
235 * Set the language and the title from a context object
237 * @param $context IContextSource
238 * @return Message: $this
240 public function setContext( IContextSource
$context ) {
241 $this->inLanguage( $context->getLanguage() );
242 $this->title( $context->getTitle() );
248 * Request the message in any language that is supported.
249 * As a side effect interface message status is unconditionally
251 * @param $lang Mixed: language code or Language object.
252 * @return Message: $this
254 public function inLanguage( $lang ) {
255 if ( $lang instanceof Language ||
$lang instanceof StubUserLang
) {
256 $this->language
= $lang;
257 } elseif ( is_string( $lang ) ) {
258 if( $this->language
->getCode() != $lang ) {
259 $this->language
= Language
::factory( $lang );
262 $type = gettype( $lang );
263 throw new MWException( __METHOD__
. " must be "
264 . "passed a String or Language object; $type given"
267 $this->interface = false;
272 * Request the message in the wiki's content language,
273 * unless it is disabled for this message.
274 * @see $wgForceUIMsgAsContentMsg
275 * @return Message: $this
277 public function inContentLanguage() {
278 global $wgForceUIMsgAsContentMsg;
279 if ( in_array( $this->key
, (array)$wgForceUIMsgAsContentMsg ) ) {
284 $this->interface = false;
285 $this->language
= $wgContLang;
290 * Enable or disable database use.
291 * @param $value Boolean
292 * @return Message: $this
294 public function useDatabase( $value ) {
295 $this->useDatabase
= (bool) $value;
300 * Set the Title object to use as context when transforming the message
302 * @param $title Title object
303 * @return Message: $this
305 public function title( $title ) {
306 $this->title
= $title;
311 * Returns the message parsed from wikitext to HTML.
312 * @return String: HTML
314 public function toString() {
315 $string = $this->getMessageText();
317 # Replace parameters before text parsing
318 $string = $this->replaceParameters( $string, 'before' );
320 # Maybe transform using the full parser
321 if( $this->format
=== 'parse' ) {
322 $string = $this->parseText( $string );
324 if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
327 } elseif( $this->format
=== 'block-parse' ){
328 $string = $this->parseText( $string );
329 } elseif( $this->format
=== 'text' ){
330 $string = $this->transformText( $string );
331 } elseif( $this->format
=== 'escaped' ){
332 $string = $this->transformText( $string );
333 $string = htmlspecialchars( $string, ENT_QUOTES
, 'UTF-8', false );
336 # Raw parameter replacement
337 $string = $this->replaceParameters( $string, 'after' );
343 * Magic method implementation of the above (for PHP >= 5.2.0), so we can do, eg:
344 * $foo = Message::get($key);
345 * $string = "<abbr>$foo</abbr>";
348 public function __toString() {
349 return $this->toString();
353 * Fully parse the text from wikitext to HTML
354 * @return String parsed HTML
356 public function parse() {
357 $this->format
= 'parse';
358 return $this->toString();
362 * Returns the message text. {{-transformation is done.
363 * @return String: Unescaped message text.
365 public function text() {
366 $this->format
= 'text';
367 return $this->toString();
371 * Returns the message text as-is, only parameters are subsituted.
372 * @return String: Unescaped untransformed message text.
374 public function plain() {
375 $this->format
= 'plain';
376 return $this->toString();
380 * Returns the parsed message text which is always surrounded by a block element.
381 * @return String: HTML
383 public function parseAsBlock() {
384 $this->format
= 'block-parse';
385 return $this->toString();
389 * Returns the message text. {{-transformation is done and the result
390 * is escaped excluding any raw parameters.
391 * @return String: Escaped message text.
393 public function escaped() {
394 $this->format
= 'escaped';
395 return $this->toString();
399 * Check whether a message key has been defined currently.
400 * @return Bool: true if it is and false if not.
402 public function exists() {
403 return $this->fetchMessage() !== false;
407 * Check whether a message does not exist, or is an empty string
408 * @return Bool: true if is is and false if not
409 * @todo FIXME: Merge with isDisabled()?
411 public function isBlank() {
412 $message = $this->fetchMessage();
413 return $message === false ||
$message === '';
417 * Check whether a message does not exist, is an empty string, or is "-"
418 * @return Bool: true if is is and false if not
420 public function isDisabled() {
421 $message = $this->fetchMessage();
422 return $message === false ||
$message === '' ||
$message === '-';
429 public static function rawParam( $value ) {
430 return array( 'raw' => $value );
437 public static function numParam( $value ) {
438 return array( 'num' => $value );
442 * Substitutes any paramaters into the message text.
443 * @param $message String: the message text
444 * @param $type String: either before or after
447 protected function replaceParameters( $message, $type = 'before' ) {
448 $replacementKeys = array();
449 foreach( $this->parameters
as $n => $param ) {
450 list( $paramType, $value ) = $this->extractParam( $param );
451 if ( $type === $paramType ) {
452 $replacementKeys['$' . ($n +
1)] = $value;
455 $message = strtr( $message, $replacementKeys );
460 * Extracts the parameter type and preprocessed the value if needed.
461 * @param $param String|Array: Parameter as defined in this class.
462 * @return Tuple(type, value)
463 * @throws MWException
465 protected function extractParam( $param ) {
466 if ( is_array( $param ) && isset( $param['raw'] ) ) {
467 return array( 'after', $param['raw'] );
468 } elseif ( is_array( $param ) && isset( $param['num'] ) ) {
469 // Replace number params always in before step for now.
470 // No support for combined raw and num params
471 return array( 'before', $this->language
->formatNum( $param['num'] ) );
472 } elseif ( !is_array( $param ) ) {
473 return array( 'before', $param );
475 throw new MWException( "Invalid message parameter" );
480 * Wrapper for what ever method we use to parse wikitext.
481 * @param $string String: Wikitext message contents
482 * @return string Wikitext parsed into HTML
484 protected function parseText( $string ) {
485 return MessageCache
::singleton()->parse( $string, $this->title
, /*linestart*/true, $this->interface, $this->language
)->getText();
489 * Wrapper for what ever method we use to {{-transform wikitext.
490 * @param $string String: Wikitext message contents
491 * @return string Wikitext with {{-constructs replaced with their values.
493 protected function transformText( $string ) {
494 return MessageCache
::singleton()->transform( $string, $this->interface, $this->language
, $this->title
);
498 * Returns the textual value for the message.
499 * @return Message contents or placeholder
501 protected function getMessageText() {
502 $message = $this->fetchMessage();
503 if ( $message === false ) {
504 return '<' . htmlspecialchars( is_array($this->key
) ?
$this->key
[0] : $this->key
) . '>';
511 * Wrapper for what ever method we use to get message contents
515 protected function fetchMessage() {
516 if ( !isset( $this->message
) ) {
517 $cache = MessageCache
::singleton();
518 if ( is_array($this->key
) ) {
519 foreach ( $this->key
as $key ) {
520 $message = $cache->get( $key, $this->useDatabase
, $this->language
);
521 if ( $message !== false && $message !== '' ) {
525 $this->message
= $message;
527 $this->message
= $cache->get( $this->key
, $this->useDatabase
, $this->language
);
530 return $this->message
;