Commit 8ce27f22 authored by Dries's avatar Dries

Issue #1273968 by Sutharsan, penyaskito, chx: Fixed locale.module uses eval.

parent eed488bb
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\ConfigException; use Drupal\Core\Config\ConfigException;
use Drupal\Core\DrupalKernel; use Drupal\Core\DrupalKernel;
use Drupal\Component\Gettext\PoHeader;
use Drupal\Component\Uuid\Uuid; use Drupal\Component\Uuid\Uuid;
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
...@@ -475,9 +476,14 @@ function update_prepare_d8_language() { ...@@ -475,9 +476,14 @@ function update_prepare_d8_language() {
$prefixes = array(); $prefixes = array();
$domains = array(); $domains = array();
foreach ($languages as $language) { foreach ($languages as $language) {
$header = new PoHeader($language->language);
$language->formula = str_replace('$n', 'n', $language->formula);
$plural_forms = 'nplurals=' . $language->plurals . '; plural=' . $language->formula . ';';
$parsed = $header->parsePluralForms($plural_forms);
list($nplurals, $formula) = $parsed;
$plurals[$language->language] = array( $plurals[$language->language] = array(
'plurals' => $language->plurals, 'plurals' => $nplurals,
'formula' => $language->formula, 'formula' => $formula,
); );
$javascript[$language->language] = $language->javascript; $javascript[$language->language] = $language->javascript;
$prefixes[$language->language] = $language->prefix; $prefixes[$language->language] = $language->prefix;
......
...@@ -189,10 +189,14 @@ public function __toString() { ...@@ -189,10 +189,14 @@ public function __toString() {
* The Plural-Forms entry value. * The Plural-Forms entry value.
* *
* @return * @return
* An array containing the number of plural forms and the converted version * An indexed array of parsed plural formula data. Containing:
* of the formula that can be evaluated with PHP later. * - 'nplurals': The number of plural forms defined by the plural formula.
* - 'plurals': Array of plural positions keyed by plural value.
*
* @throws Exception
*/ */
function parsePluralForms($pluralforms) { function parsePluralForms($pluralforms) {
$plurals = array();
// First, delete all whitespace. // First, delete all whitespace.
$pluralforms = strtr($pluralforms, array(" " => "", "\t" => "")); $pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
...@@ -214,14 +218,31 @@ function parsePluralForms($pluralforms) { ...@@ -214,14 +218,31 @@ function parsePluralForms($pluralforms) {
return FALSE; return FALSE;
} }
// Get PHP version of the plural formula. // If the number of plurals is zero, we return a default result.
$plural = $this->parseArithmetic($plural); if ($nplurals == 0) {
return array($nplurals, array('default' => 0));
}
// Calculate possible plural positions of different plural values. All known
// plural formula's are repetitive above 100.
// For data compression we store the last position the array value
// changes and store it as default.
$element_stack = $this->parseArithmetic($plural);
$default = 0;
if ($element_stack !== FALSE) {
for ($i = 0; $i <= 199; $i++) {
$plurals[$i] = $this->evaluatePlural($element_stack, $i);
}
$default = $plurals[$i - 1];
$plurals = array_filter($plurals, function ($value) use ($default) {
return ($value != $default);
});
$plurals['default'] = $default;
if ($plural !== FALSE) { return array($nplurals, $plurals);
return array($nplurals, $plural);
} }
else { else {
throw new Exception('The plural formula could not be parsed.'); throw new \Exception('The plural formula could not be parsed.');
} }
} }
...@@ -247,7 +268,7 @@ private function parseHeader($header) { ...@@ -247,7 +268,7 @@ private function parseHeader($header) {
} }
/** /**
* Parses and sanitizes an arithmetic formula into a PHP expression. * Parses and sanitizes an arithmetic formula into a plural element stack.
* *
* While parsing, we ensure, that the operators have the right * While parsing, we ensure, that the operators have the right
* precedence and associativity. * precedence and associativity.
...@@ -256,7 +277,7 @@ private function parseHeader($header) { ...@@ -256,7 +277,7 @@ private function parseHeader($header) {
* A string containing the arithmetic formula. * A string containing the arithmetic formula.
* *
* @return * @return
* A version of the formula to evaluate with PHP later. * A stack of values and operations to be evaluated.
*/ */
private function parseArithmetic($string) { private function parseArithmetic($string) {
// Operator precedence table. // Operator precedence table.
...@@ -319,8 +340,9 @@ private function parseArithmetic($string) { ...@@ -319,8 +340,9 @@ private function parseArithmetic($string) {
$element_stack[] = $topop; $element_stack[] = $topop;
$topop = array_pop($operator_stack); $topop = array_pop($operator_stack);
} }
$return = $element_stack;
// Now extract formula from stack. // Now validate stack.
$previous_size = count($element_stack) + 1; $previous_size = count($element_stack) + 1;
while (count($element_stack) < $previous_size) { while (count($element_stack) < $previous_size) {
$previous_size = count($element_stack); $previous_size = count($element_stack);
...@@ -344,12 +366,7 @@ private function parseArithmetic($string) { ...@@ -344,12 +366,7 @@ private function parseArithmetic($string) {
} }
// If only one element is left, the number of operators is appropriate. // If only one element is left, the number of operators is appropriate.
if (count($element_stack) == 1) { return count($element_stack) == 1 ? $return : FALSE;
return $element_stack[0];
}
else {
return FALSE;
}
} }
/** /**
...@@ -417,4 +434,136 @@ private function tokenizeFormula($formula) { ...@@ -417,4 +434,136 @@ private function tokenizeFormula($formula) {
return $tokens; return $tokens;
} }
/**
* Evaluate the plural element stack using a plural value.
*
* Using an element stack, which represents a plural formula, we calculate
* which plural string should be used for a given plural value.
*
* An example of plural formula parting and evaluation:
* Plural formula: 'n!=1'
* This formula is parsed by parseArithmetic() to a stack (array) of elements:
* array(
* 0 => '$n',
* 1 => '1',
* 2 => '!=',
* );
* The evaluatePlural() method evaluates the $element_stack using the plural
* value $n. Before the actual evaluation, the '$n' in the array is replaced
* by the value of $n.
* For example: $n = 2 results in:
* array(
* 0 => '2',
* 1 => '1',
* 2 => '!=',
* );
* The stack is processed until only one element is (the result) is left. In
* every iteration the top elements of the stack, up until the first operator,
* are evaluated. After evaluation the arguments and the operator itself are
* removed and replaced by the evaluation result. This is typically 2
* arguments and 1 element for the operator.
* Because the operator is '!=' the example stack is evaluated as:
* $f = (int) 2 != 1;
* The resulting stack is:
* array(
* 0 => 1,
* );
* With only one element left in the stack (the final result) the loop is
* terminated and the result is returned.
*
* @param array $element_stack
* Array of plural formula values and operators create by parseArithmetic().
* @param integer $n
* The @count number for which we are determining the right plural position.
*
* @return integer
* Number of the plural string to be used for the given plural value.
*
* @see parseArithmetic()
* @throws Exception
*/
protected function evaluatePlural($element_stack, $n) {
$count = count($element_stack);
$limit = $count;
// Replace the '$n' value in the formula by the plural value.
for ($i = 0; $i < $count; $i++) {
if ($element_stack[$i] === '$n') {
$element_stack[$i] = $n;
}
}
// We process the stack until only one element is (the result) is left.
// We limit the number of evaluation cycles to prevent an endless loop in
// case the stack contains an error.
while (isset($element_stack[1])) {
for ($i = 2; $i < $count; $i++) {
// There's no point in checking non-symbols. Also, switch(TRUE) would
// match any case and so it would break.
if (is_bool($element_stack[$i]) || is_numeric($element_stack[$i])) {
continue;
}
$f = NULL;
$length = 3;
$delta = 2;
switch ($element_stack[$i]) {
case '==':
$f = $element_stack[$i - 2] == $element_stack[$i - 1];
break;
case '!=':
$f = $element_stack[$i - 2] != $element_stack[$i - 1];
break;
case '<=':
$f = $element_stack[$i - 2] <= $element_stack[$i - 1];
break;
case '>=':
$f = $element_stack[$i - 2] >= $element_stack[$i - 1];
break;
case '<':
$f = $element_stack[$i - 2] < $element_stack[$i - 1];
break;
case '>':
$f = $element_stack[$i - 2] > $element_stack[$i - 1];
break;
case '+':
$f = $element_stack[$i - 2] + $element_stack[$i - 1];
break;
case '-':
$f = $element_stack[$i - 2] - $element_stack[$i - 1];
break;
case '*':
$f = $element_stack[$i - 2] * $element_stack[$i - 1];
break;
case '/':
$f = $element_stack[$i - 2] / $element_stack[$i - 1];
break;
case '%':
$f = $element_stack[$i - 2] % $element_stack[$i - 1];
break;
case '&&':
$f = $element_stack[$i - 2] && $element_stack[$i - 1];
break;
case '||':
$f = $element_stack[$i - 2] || $element_stack[$i - 1];
break;
case ':':
$f = $element_stack[$i - 3] ? $element_stack[$i - 2] : $element_stack[$i - 1];
// This operator has 3 preceding elements, instead of the default 2.
$length = 5;
$delta = 3;
break;
}
// If the element is an operator we remove the processed elements and
// store the result.
if (isset($f)) {
array_splice($element_stack, $i - $delta, $length, $f);
break;
}
}
}
if (!$limit) {
throw new \Exception('The plural formula could not be evaluated.');
}
return (int) $element_stack[0];
}
} }
...@@ -98,11 +98,15 @@ function testGetPluralFormat() { ...@@ -98,11 +98,15 @@ function testGetPluralFormat() {
1 => 0, 1 => 0,
0 => 1, 0 => 1,
5 => 1, 5 => 1,
123 => 1,
235 => 1,
), ),
'fr' => array( 'fr' => array(
1 => 0, 1 => 0,
0 => 0, 0 => 0,
5 => 1, 5 => 1,
123 => 1,
235 => 1,
), ),
'hr' => array( 'hr' => array(
1 => 0, 1 => 0,
...@@ -110,6 +114,8 @@ function testGetPluralFormat() { ...@@ -110,6 +114,8 @@ function testGetPluralFormat() {
0 => 2, 0 => 2,
2 => 1, 2 => 1,
8 => 2, 8 => 2,
123 => 1,
235 => 2,
), ),
'hu' => array( 'hu' => array(
1 => -1, 1 => -1,
......
...@@ -370,9 +370,12 @@ function locale_get_plural($count, $langcode = NULL) { ...@@ -370,9 +370,12 @@ function locale_get_plural($count, $langcode = NULL) {
// $count and statically cache the result for the combination of language // $count and statically cache the result for the combination of language
// and count, since the result will always be identical. // and count, since the result will always be identical.
if (!empty($plural_formulas[$langcode])) { if (!empty($plural_formulas[$langcode])) {
// $n is used inside the expression in the eval(). // Plural formulas are stored as an array for 0-199. 100 is the highest
$n = $count; // modulo used but storing 0-99 is not enough because below 100 we often
$plural_indexes[$langcode][$count] = @eval('return intval(' . $plural_formulas[$langcode]['formula'] . ');'); // find exceptions (1, 2, etc).
$index = $count > 199 ? 100 + ($count % 100) : $count;
$plural_indexes[$langcode][$count] = isset($plural_formulas[$langcode]['formula'][$index]) ? $plural_formulas[$langcode]['formula'][$index] : $plural_formulas[$langcode]['formula']['default'];
} }
// In case there is no plural formula for English (no imported translation // In case there is no plural formula for English (no imported translation
// for English), use a default formula. // for English), use a default formula.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment