Completed
Push — master ( 14d2bd...06e609 )
by mw
81:37 queued 59:24
created

includes/datavalues/SMW_DV_Number.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use SMW\Highlighter;
4
use SMW\IntlNumberFormatter;
5
use SMW\Localizer;
6
use SMW\DataValues\ValueFormatters\DataValueFormatter;
7
8
/**
9
 * @ingroup SMWDataValues
10
 */
11
12
/**
13
 * This datavalue implements numerical datavalues, and supports optional
14
 * unit conversions. It parses and manages unit strings, since even plain
15
 * numbers may have (not further specified) units that are stored. However,
16
 * only subclasses implement full unit conversion by extending the methods
17
 * convertToMainUnit() and makeConversionValues().
18
 *
19
 * Units work as follows: a unit is a string, but many such strings might
20
 * refer to the same unit of measurement. There is always one string, that
21
 * canonically represents the unit, and we will call this version of writing
22
 * the unit the /unit id/. IDs for units are needed for tasks like duplicate
23
 * avoidance. If no conversion information is given, any unit is its own ID.
24
 * In any case, units are /normalised/, i.e. given a more standardised meaning
25
 * before being processed. All units, IDs or otherwise, should be suitable for
26
 * printout in wikitext, and main IDs should moreover be suitable for printout
27
 * in HTML.
28
 *
29
 * Subclasses that support unit conversion may interpret the output format set
30
 * via setOutputFormat() to allow a unit to be selected for display. Note that
31
 * this setting does not affect the internal representation of the value
32
 * though. So chosing a specific output format will change the behavior of
33
 * output functions like getLongWikiText(), but not of functions that access
34
 * the value itself, such as getUnit() or getDBKeys().
35
 *
36
 * @author Markus Krötzsch
37
 * @ingroup SMWDataValues
38
 *
39
 * @todo Wiki-HTML-conversion for unit strings must be revisited, as the current
40
 * solution might be unsafe.
41
 */
42
class SMWNumberValue extends SMWDataValue {
43
44
	/**
45
	 * Array with entries unit=>value, mapping a normalized unit to the
46
	 * converted value. Used for conversion tooltips.
47
	 * @var array
48
	 */
49
	protected $m_unitvalues;
50
51
	/**
52
	 * Whether the unit is preferred as prefix or not
53
	 *
54
	 * @var array
55
	 */
56
	protected $prefixalUnitPreference = array();
57
58
	/**
59
	 * Canonical identifier for the unit that the user gave as input. Used
60
	 * to avoid printing this in conversion tooltips again. If the
61
	 * outputformat was set to show another unit, then the values of
62
	 * $m_caption and $m_unitin will be updated as if the formatted string
63
	 * had been the original user input, i.e. the two values reflect what
64
	 * is currently printed.
65
	 * @var string
66
	 */
67
	protected $m_unitin;
68
69
	/**
70
	 * @var integer|null
71
	 */
72
	protected $precision = null;
73
74
	/**
75
	 * @var IntlNumberFormatter
76
	 */
77
	private $intlNumberFormatter = null;
78
79
	/**
80
	 * @since 2.4
81
	 *
82
	 * @param string $typeid
83
	 */
84 46
	public function __construct( $typeid = '' ) {
85 46
		parent::__construct( $typeid );
86 46
		$this->intlNumberFormatter = IntlNumberFormatter::getInstance();
87 46
		$this->intlNumberFormatter->initialize();
88 46
	}
89
90
	/**
91
	 * Parse a string of the form "number unit" where unit is optional. The
92
	 * results are stored in the $number and $unit parameters. Returns an
93
	 * error code.
94
	 * @param $value string to parse
95
	 * @param $number call-by-ref parameter that will be set to the numerical value
96
	 * @param $unit call-by-ref parameter that will be set to the "unit" string (after the number)
97
	 * @return integer 0 (no errors), 1 (no number found at all), 2 (number
98
	 * too large for this platform)
99
	 */
100 44
	public function parseNumberValue( $value, &$number, &$unit, &$asPrefix = false ) {
101
102 44
		$intlNumberFormatter = $this->getNumberFormatter();
103
104
		// Parse to find $number and (possibly) $unit
105 44
		$kiloseparator = $intlNumberFormatter->getSeparator(
106 44
			IntlNumberFormatter::THOUSANDS_SEPARATOR,
107 44
			IntlNumberFormatter::CONTENT_LANGUAGE
108
		);
109
110 44
		$decseparator = $intlNumberFormatter->getSeparator(
111 44
			IntlNumberFormatter::DECIMAL_SEPARATOR,
112 44
			IntlNumberFormatter::CONTENT_LANGUAGE
113
		);
114
115
		// #753
116
		$regex = '/([-+]?\s*(?:' .
117
				// Either numbers like 10,000.99 that start with a digit
118 44
				'\d+(?:\\' . $kiloseparator . '\d\d\d)*(?:\\' . $decseparator . '\d+)?' .
119
				// or numbers like .001 that start with the decimal separator
120 44
				'|\\' . $decseparator . '\d+' .
121 44
				')\s*(?:[eE][-+]?\d+)?)/u';
122
123 44
		$parts = preg_split(
124
			$regex,
125 44
			trim( str_replace( array( '&nbsp;', '&#160;', '&thinsp;', ' ' ), '', $value ) ),
126 44
			2,
127 44
			PREG_SPLIT_DELIM_CAPTURE
128
		);
129
130 44
		if ( count( $parts ) >= 2 ) {
131 43
			$numstring = str_replace( $kiloseparator, '', preg_replace( '/\s*/u', '', $parts[1] ) ); // simplify
132 43
			if ( $decseparator != '.' ) {
133 2
				$numstring = str_replace( $decseparator, '.', $numstring );
134
			}
135 43
			list( $number ) = sscanf( $numstring, "%f" );
136 43
			if ( count( $parts ) >= 3  ) {
137 43
				$asPrefix = $parts[0] !== '';
138 43
				$unit = $this->normalizeUnit( $parts[0] !== '' ? $parts[0] : $parts[2] );
139
			}
140
		}
141
142 44
		if ( ( count( $parts ) == 1 ) || ( $numstring === '' ) ) { // no number found
0 ignored issues
show
The variable $numstring does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
143 2
			return 1;
144 43
		} elseif ( is_infinite( $number ) ) { // number is too large for this platform
145
			return 2;
146
		} else {
147 43
			return 0;
148
		}
149
	}
150
151
	/**
152
	 * @see DataValue::parseUserValue
153
	 */
154 45
	protected function parseUserValue( $value ) {
155
		// Set caption
156 45
		if ( $this->m_caption === false ) {
157 45
			$this->m_caption = $value;
158
		}
159
160 45
		if ( $value !== '' && $value{0} === ':' ) {
161 1
			$this->addErrorMsg( array( 'smw-datavalue-invalid-number', $value ) );
162 1
			return;
163
		}
164
165 44
		$this->m_unitin = false;
166 44
		$this->m_unitvalues = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array of property $m_unitvalues.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
167 44
		$number = $unit = '';
168 44
		$error = $this->parseNumberValue( $value, $number, $unit );
169
170 44
		if ( $error == 1 ) { // no number found
171 2
			$this->addErrorMsg( array( 'smw_nofloat', $value ) );
172 43
		} elseif ( $error == 2 ) { // number is too large for this platform
173
			$this->addErrorMsg( array( 'smw_infinite', $value ) );
174 43
		} elseif ( $this->getTypeID() === '_num' && $unit !== '' ) {
175 1
			$this->addErrorMsg( array( 'smw-datavalue-number-textnotallowed', $unit, $number ) );
176 43
		} elseif ( $this->convertToMainUnit( $number, $unit ) === false ) { // so far so good: now convert unit and check if it is allowed
177 9
			$this->addErrorMsg( array( 'smw_unitnotallowed', $unit ) );
178
		} // note that convertToMainUnit() also sets m_dataitem if valid
179 44
	}
180
181
	/**
182
	 * @see SMWDataValue::loadDataItem()
183
	 * @param $dataitem SMWDataItem
184
	 * @return boolean
185
	 */
186 24
	protected function loadDataItem( SMWDataItem $dataItem ) {
187
188 24
		if ( $dataItem->getDIType() !== SMWDataItem::TYPE_NUMBER ) {
189
			return false;
190
		}
191
192 24
		$this->m_dataitem = $dataItem;
193 24
		$this->m_caption = false;
194 24
		$this->m_unitin = false;
195 24
		$this->makeUserValue();
196 24
		$this->m_unitvalues = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array of property $m_unitvalues.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
197
198 24
		return true;
199
	}
200
201
	/**
202
	 * @see DataValue::setOutputFormat
203
	 *
204
	 * @param $string $formatstring
205
	 */
206 6
	public function setOutputFormat( $formatstring ) {
207
208 6
		if ( $formatstring == $this->m_outformat ) {
209
			return null;
210
		}
211
212
		// #1591
213 6
		$this->findPreferredLanguageFrom( $formatstring );
214
215
		// #1335
216 6
		$this->m_outformat = $this->findPrecisionFrom( $formatstring );
217
218 6
		if ( $this->isValid() ) { // update caption/unitin for this format
219 6
			$this->m_caption = false;
220 6
			$this->m_unitin = false;
221 6
			$this->makeUserValue();
222
		}
223 6
	}
224
225
	/**
226
	 * @since 2.4
227
	 *
228
	 * @return float
229
	 */
230 31
	public function getLocalizedFormattedNumber( $value ) {
231 31
		return $this->getNumberFormatter()->format( $value, $this->getPreferredDisplayPrecision() );
232
	}
233
234
	/**
235
	 * @since 2.4
236
	 *
237
	 * @return float
238
	 */
239 14
	public function getNormalizedFormattedNumber( $value ) {
240 14
		return $this->getNumberFormatter()->format( $value, $this->getPreferredDisplayPrecision(), IntlNumberFormatter::VALUE_FORMAT );
241
	}
242
243
	/**
244
	 * @see DataValue::getShortWikiText
245
	 *
246
	 * @return string
247
	 */
248 30
	public function getShortWikiText( $linker = null ) {
249 30
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_SHORT, $linker );
250
	}
251
252
	/**
253
	 * @see DataValue::getShortHTMLText
254
	 *
255
	 * @return string
256
	 */
257 1
	public function getShortHTMLText( $linker = null ) {
258 1
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_SHORT, $linker );
259
	}
260
261
	/**
262
	 * @see DataValue::getLongWikiText
263
	 *
264
	 * @return string
265
	 */
266
	public function getLongWikiText( $linker = null ) {
267
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_LONG, $linker );
268
	}
269
270
	/**
271
	 * @see DataValue::getLongHTMLText
272
	 *
273
	 * @return string
274
	 */
275 1
	public function getLongHTMLText( $linker = null ) {
276 1
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_LONG, $linker );
277
	}
278
279 18
	public function getNumber() {
280 18
		return $this->isValid() ? $this->m_dataitem->getNumber() : 32202;
281
	}
282
283 11
	public function getWikiValue() {
284 11
		return $this->getDataValueFormatter()->format( DataValueFormatter::VALUE );
285
	}
286
287
	/**
288
	 * @see DataVelue::getInfolinks
289
	 *
290
	 * @return array
291
	 */
292
	public function getInfolinks() {
293
294
		// When generating an infoLink, use the normalized value without any
295
		// precision limitation
296
		$this->setOption( 'no.displayprecision', true );
297
		$infoLinks = parent::getInfolinks();
298
		$this->setOption( 'no.displayprecision', false );
299
300
		return $infoLinks;
301
	}
302
303
	/**
304
	 * @since 2.4
305
	 *
306
	 * @return string
307
	 */
308 27
	public function getCanonicalMainUnit() {
309 27
		return $this->m_unitin;
310
	}
311
312
	/**
313
	 * Returns array of converted unit-value-pairs that can be
314
	 * printed.
315
	 *
316
	 * @since 2.4
317
	 *
318
	 * @return array
319
	 */
320 27
	public function getConvertedUnitValues() {
321 27
		$this->makeConversionValues();
322 27
		return $this->m_unitvalues;
323
	}
324
325
	/**
326
	 * Return the unit in which the returned value is to be interpreted.
327
	 * This string is a plain UTF-8 string without wiki or html markup.
328
	 * The returned value is a canonical ID for the main unit.
329
	 * Returns the empty string if no unit is given for the value.
330
	 * Overwritten by subclasses that support units.
331
	 */
332 8
	public function getUnit() {
333 8
		return '';
334
	}
335
336
	/**
337
	 * @since 2.4
338
	 *
339
	 * @param string $unit
340
	 *
341
	 * @return boolean
342
	 */
343 15
	public function hasPrefixalUnitPreference( $unit ) {
344 15
		return isset( $this->prefixalUnitPreference[$unit] ) && $this->prefixalUnitPreference[$unit];
345
	}
346
347
	/**
348
	 * Create links to mapping services based on a wiki-editable message.
349
	 * The parameters available to the message are:
350
	 * $1: string of numerical value in English punctuation
351
	 * $2: string of integer version of value, in English punctuation
352
	 *
353
	 * @return array
354
	 */
355
	protected function getServiceLinkParams() {
356
		if ( $this->isValid() ) {
357
			return array( strval( $this->m_dataitem->getNumber() ), strval( round( $this->m_dataitem->getNumber() ) ) );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(strval($thi...taitem->getNumber()))); (string[]) is incompatible with the return type of the parent method SMWDataValue::getServiceLinkParams of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
358
		} else {
359
			return array();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type of the parent method SMWDataValue::getServiceLinkParams of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
360
		}
361
	}
362
363
	/**
364
	 * Transform a (typically unit-) string into a normalised form,
365
	 * so that, e.g., "km²" and "km<sup>2</sup>" do not need to be
366
	 * distinguished.
367
	 */
368 43
	public function normalizeUnit( $unit ) {
369 43
		$unit = str_replace( array( '[[', ']]' ), '', trim( $unit ) ); // allow simple links to be used inside annotations
370 43
		$unit = str_replace( array( '²', '<sup>2</sup>' ), '&sup2;', $unit );
371 43
		$unit = str_replace( array( '³', '<sup>3</sup>' ), '&sup3;', $unit );
372 43
		return smwfXMLContentEncode( $unit );
373
	}
374
375
	/**
376
	 * Compute the value based on the given input number and unit string.
377
	 * If the unit is not supported, return false, otherwise return true.
378
	 * This is called when parsing user input, where the given unit value
379
	 * has already been normalized.
380
	 *
381
	 * This class does not support any (non-empty) units, but subclasses
382
	 * may overwrite this behavior.
383
	 * @param $number float value obtained by parsing user input
384
	 * @param $unit string after the numericla user input
385
	 * @return boolean specifying if the unit string is allowed
386
	 */
387 33
	protected function convertToMainUnit( $number, $unit ) {
388 33
		$this->m_dataitem = new SMWDINumber( $number );
389 33
		$this->m_unitin = '';
390 33
		return ( $unit === '' );
391
	}
392
393
	/**
394
	 * This method creates an array of unit-value-pairs that should be
395
	 * printed. Units are the keys and should be canonical unit IDs.
396
	 * The result is stored in $this->m_unitvalues. Again, any class that
397
	 * requires effort for doing this should first check whether the array
398
	 * is already set (i.e. not false) before doing any work.
399
	 * Note that the values should be plain numbers. Output formatting is done
400
	 * later when needed.  Also, it should be checked if the value is valid
401
	 * before trying to calculate with its contents.
402
	 * This method also must call or implement convertToMainUnit().
403
	 *
404
	 * Overwritten by subclasses that support units.
405
	 */
406 20
	protected function makeConversionValues() {
407 20
		$this->m_unitvalues = array( '' => $this->m_dataitem->getNumber() );
408 20
	}
409
410
	/**
411
	 * This method is used when no user input was given to find the best
412
	 * values for m_unitin and m_caption. After conversion,
413
	 * these fields will look as if they were generated from user input,
414
	 * and convertToMainUnit() will have been called (if not, it would be
415
	 * blocked by the presence of m_unitin).
416
	 *
417
	 * Overwritten by subclasses that support units.
418
	 */
419 18
	protected function makeUserValue() {
420 18
		$this->m_caption = '';
421
422 18
		$number = $this->m_dataitem->getNumber();
423
424
		// -u is the format for displaying the unit only
425 18
		if ( $this->m_outformat == '-u' ) {
426
			$this->m_caption = '';
427 18
		} elseif ( ( $this->m_outformat != '-' ) && ( $this->m_outformat != '-n' ) ) {
428 18
			$this->m_caption = $this->getLocalizedFormattedNumber( $number );
429
		} else {
430 2
			$this->m_caption = $this->getNormalizedFormattedNumber( $number );
431
		}
432
433
		// no unit ever, so nothing to do about this
434 18
		$this->m_unitin = '';
435 18
	}
436
437
	/**
438
	 * Return an array of major unit strings (ids only recommended) supported by
439
	 * this datavalue.
440
	 *
441
	 * Overwritten by subclasses that support units.
442
	 */
443
	public function getUnitList() {
444
		return array( '' );
445
	}
446
447 31
	protected function getPreferredDisplayPrecision() {
448
449
		// In case of a value description, don't restrict the value with a display precision
450 31
		if ( $this->getProperty() === null || $this->getOptionValueFor( 'value.description' ) || $this->getOptionValueFor( 'no.displayprecision' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getOptionValueFor('value.description') of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $this->getOptionValueFor('no.displayprecision') of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
451 4
			return false;
452
		}
453
454 31
		if ( $this->precision === null ) {
455 31
			$this->precision = $this->getPropertySpecificationLookup()->getDisplayPrecisionFor(
456 31
				$this->getProperty()
457
			);
458
		}
459
460 31
		return $this->precision;
461
	}
462
463 6
	private function findPrecisionFrom( $formatstring ) {
464
465 6
		if ( strpos( $formatstring, '-' ) === false ) {
466 4
			return $formatstring;
467
		}
468
469 4
		$parts = explode( '-', $formatstring );
470
471
		// Find precision from annotated -p<number of digits> formatstring which
472
		// has priority over a possible _PREC value
473 4
		foreach ( $parts as $key => $value ) {
474 4
			if ( strpos( $value, 'p' ) !== false && is_numeric( substr( $value, 1 ) ) ) {
475 1
				$this->precision = strval( substr( $value, 1 ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like strval(substr($value, 1)) of type string is incompatible with the declared type integer|null of property $precision.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
476 4
				unset( $parts[$key] );
477
			}
478
		}
479
480
		// Rebuild formatstring without a possible p element to ensure other
481
		// options can be used in combination such as -n-p2 etc.
482 4
		return implode( '-', $parts );
483
	}
484
485 45
	private function getNumberFormatter() {
486
487 45
		$this->intlNumberFormatter->setOption(
488 45
			'user.language',
489 45
			$this->getOptionValueFor( 'user.language' )
490
		);
491
492 45
		$this->intlNumberFormatter->setOption(
493 45
			'content.language',
494 45
			$this->getOptionValueFor( 'content.language' )
495
		);
496
497 45
		$this->intlNumberFormatter->setOption(
498 45
			'separator.thousands',
499 45
			$this->getOptionValueFor( 'separator.thousands' )
500
		);
501
502 45
		$this->intlNumberFormatter->setOption(
503 45
			'separator.decimal',
504 45
			$this->getOptionValueFor( 'separator.decimal' )
505
		);
506
507 45
		return $this->intlNumberFormatter;
508
	}
509
510 6
	private function findPreferredLanguageFrom( &$formatstring ) {
511
		// Localized preferred user language
512 6
		if ( strpos( $formatstring, 'LOCL' ) !== false && ( $languageCode = Localizer::getLanguageCodeFrom( $formatstring ) ) !== false ) {
513 1
			$this->intlNumberFormatter->setOption(
514 1
				'preferred.language',
515
				$languageCode
516
			);
517
		}
518
519
		// Remove any remaining
520 6
		$formatstring = str_replace( array( '#LOCL', 'LOCL' ), '', $formatstring );
521 6
	}
522
523
}
524