diff --git a/adm_config_report.php b/adm_config_report.php
index 5ab6495179..bad0720b9b 100644
--- a/adm_config_report.php
+++ b/adm_config_report.php
@@ -104,9 +104,9 @@ function print_config_value_as_string( $p_type, $p_value, $p_for_display = true
echo (integer)$p_value;
return;
case CONFIG_TYPE_STRING:
- $t_value = string_nl2br( string_html_specialchars( config_eval( $p_value ) ) );
+ $t_value = string_html_specialchars( config_eval( $p_value ) );
if( $p_for_display ) {
- $t_value = '
\'' . $t_value . '\'
';
+ $t_value = '\'' . string_nl2br( $t_value ) . '\'
';
}
echo $t_value;
return;
diff --git a/adm_config_set.php b/adm_config_set.php
index a668a1b8fb..23c894770e 100644
--- a/adm_config_set.php
+++ b/adm_config_set.php
@@ -90,7 +90,8 @@
# For 'default', behavior is based on the global variable's type
-if( $f_type == CONFIG_TYPE_DEFAULT ) {
+# If value is empty, process as per default to ensure proper typecast
+if( $f_type == CONFIG_TYPE_DEFAULT || empty( $f_value ) ) {
$t_config_global_value = config_get_global( $f_config_option );
if( is_string( $t_config_global_value ) ) {
$t_type = CONFIG_TYPE_STRING;
@@ -108,13 +109,15 @@
}
# Parse the value
-if( $t_type == CONFIG_TYPE_STRING ) {
- # Return strings as is
- $t_value = $f_value;
-} else {
+# - Strings are returned as-is
+# - Empty values are typecast as appropriate
+$t_value = $f_value;
+if( $t_type != CONFIG_TYPE_STRING ) {
try {
- $t_parser = new ConfigParser( $f_value );
- $t_value = $t_parser->parse( ConfigParser::EXTRA_TOKENS_IGNORE );
+ if( !empty( $f_value ) ) {
+ $t_parser = new ConfigParser( $f_value );
+ $t_value = $t_parser->parse( ConfigParser::EXTRA_TOKENS_IGNORE );
+ }
switch( $t_type ) {
case CONFIG_TYPE_INT:
diff --git a/core/classes/ConfigParser.class.php b/core/classes/ConfigParser.class.php
index 56df00f58d..ba1e7616a4 100644
--- a/core/classes/ConfigParser.class.php
+++ b/core/classes/ConfigParser.class.php
@@ -27,7 +27,7 @@
* Configuration Parser class
*
* Simple PHP code parser for scalar and array types
- *
+ *
* @package MantisBT
* @subpackage classes
*
@@ -76,6 +76,8 @@ public function parse( $p_extra_tokens = self::EXTRA_TOKENS_ERROR ) {
case T_STRING:
case T_LNUMBER:
case T_DNUMBER:
+ case '-':
+ case '+':
$t_result = $this->process_value();
break;
diff --git a/core/classes/Tokenizer.class.php b/core/classes/Tokenizer.class.php
index 43b30b4fa0..326300ff51 100644
--- a/core/classes/Tokenizer.class.php
+++ b/core/classes/Tokenizer.class.php
@@ -25,10 +25,10 @@
/**
* Tokenizer class
- *
+ *
* Uses PHP's internal token_get_all() function to parse a piece of code
* into tokens
- *
+ *
* @package MantisBT
* @subpackage classes
*/
@@ -44,15 +44,10 @@ class Tokenizer
* Builds the token array from given code, discarding whitespace and
* trailing semicolons
* @param string $p_code PHP code to tokenize
- * @throws Exception if there are no tokens to process
* @throws Exception if given code is not valid
*/
public function __construct( $p_code )
{
- if( empty( $p_code ) ) {
- throw new Exception( 'No more tokens' );
- }
-
# Check syntax to make sure we get valid PHP code
# prepend 'return' statement to ensure the code is not actually executed
$t_code = 'return; ' . $p_code . ';';
@@ -182,7 +177,7 @@ public function ensure_matches( $p_value ) {
$p_value = token_name( $p_value );
}
throw new Exception(
- 'Invalid token: got "' . $this->value() . '", expected "$p_value"'
+ 'Invalid token: got "' . $this->value() . '", expected "' . $p_value . '"'
);
}
$this->pop();
diff --git a/tests/Mantis/ConfigParserTest.php b/tests/Mantis/ConfigParserTest.php
index e257b35698..ff7994c71e 100644
--- a/tests/Mantis/ConfigParserTest.php
+++ b/tests/Mantis/ConfigParserTest.php
@@ -47,63 +47,58 @@
class MantisConfigParserTest extends PHPUnit_Framework_TestCase {
/**
- * @var array List of test cases for scalar types
- */
- private $cases_scalar = array();
-
- /**
- * @var array List of test cases for arrays
- */
- private $cases_array = array();
-
- /**
- * MantisConfigParserTest constructor.
+ * Test with empty string or null
+ *
+ * @throws Exception
*/
- public function __construct() {
- parent::__construct();
-
- $this->initScalarTestCases();
- $this->initArrayTestCases();
+ public function testNoTokens() {
+ $this->setExpectedException( 'Exception', 'No more tokens' );
+ $t_parser = new ConfigParser( '' );
+ $t_parser->parse();
+ $t_parser = new ConfigParser( null );
+ $t_parser->parse();
}
/**
* Test a list of strings representing scalar values, making sure
* the value and the type match
+ * @dataProvider providerScalarTypes
+ *
+ * @param string $p_string The string to parse
+ * @param string $p_type Expected type (PHPUnit_Type::TYPE_xxx constant)
*
* @throws Exception
*/
- public function testScalarTypes() {
- foreach( $this->cases_scalar as $t_string => $t_expected_type ) {
- $t_reference_result = eval( 'return ' . $t_string . ';' );
+ public function testScalarTypes( $p_string, $p_type ) {
+ $t_reference_result = eval( 'return ' . $p_string . ';' );
- $t_parser = new ConfigParser( $t_string );
- $t_parsed_result = $t_parser->parse();
+ $t_parser = new ConfigParser( $p_string );
+ $t_parsed_result = $t_parser->parse();
- $this->assertInternalType( $t_expected_type, $t_parsed_result );
- $this->assertEquals( $t_parsed_result, $t_reference_result, $this->errorMessage( $t_string ) );
- }
+ $this->assertInternalType( $p_type, $t_parsed_result );
+ $this->assertEquals( $t_parsed_result, $t_reference_result, $this->errorMessage( $p_string ) );
}
/**
* Test various types of arrays
+ * @dataProvider providerArrays
+
+ * @param string $p_string Representation of array (e.g. output of var_export)
*
- * @see initArrayTestCases
* @throws Exception
*/
- public function testArrays() {
- foreach( $this->cases_array as $t_string ) {
- $t_reference_result = eval( 'return ' . $t_string . ';' );
-
- # Check that the parsed array matches the model array
- $t_parser = new ConfigParser( $t_string );
- $t_parsed_1 = $t_parser->parse();
- $this->assertEquals( $t_parsed_1, $t_reference_result, $this->errorMessage( $t_string ) );
-
- # Export converted array and parse again: result should match the model
- $t_parser = new ConfigParser( var_export( $t_parsed_1 , true ) );
- $t_parsed_2 = $t_parser->parse();
- $this->assertEquals( $t_parsed_2, $t_reference_result, $this->errorMessage( $t_string ) );
- }
+ public function testArrays( $p_string ) {
+ $t_reference_result = eval( 'return ' . $p_string . ';' );
+
+ # Check that the parsed array matches the model array
+ $t_parser = new ConfigParser( $p_string );
+ $t_parsed_1 = $t_parser->parse();
+ $this->assertEquals( $t_parsed_1, $t_reference_result, $this->errorMessage( $p_string ) );
+
+ # Export converted array and parse again: result should match the model
+ $t_parser = new ConfigParser( var_export( $t_parsed_1 , true ) );
+ $t_parsed_2 = $t_parser->parse();
+ $this->assertEquals( $t_parsed_2, $t_reference_result, $this->errorMessage( $p_string ) );
}
/**
@@ -189,7 +184,7 @@ private function errorMessage( $p_text ) {
. $p_text . "\n"
. "<<<------------------------\n";
}
-
+
/**
* Adds a new test case to the list
*
@@ -200,153 +195,138 @@ private function addArrayCase( $p_string ) {
}
/**
- * Adds a new scalar test case to the list
- *
- * @param string $p_string Value to check
- * @param string $p_type Expected type
+ * Data provider for Scalar types test cases.
+ * Test case structure:
+ * => array( , )
+ * @return array
*/
- private function addScalarCase( $p_string, $p_type ) {
- $this->cases_scalar[$p_string] = $p_type;
+ public function providerScalarTypes() {
+ return array(
+ 'Integer Zero' => array( '0', PHPUnit_Type::TYPE_INT ),
+ 'Integer One' => array( '1', PHPUnit_Type::TYPE_INT ),
+ 'Integer with whitespace' => array( " 1\n", PHPUnit_Type::TYPE_INT ),
+ 'Integer negative' => array( '-1', PHPUnit_Type::TYPE_INT ),
+ 'Integer positive' => array( '+1', PHPUnit_Type::TYPE_INT ),
+
+ 'Float' => array( '1.1', PHPUnit_Type::TYPE_FLOAT ),
+ 'Float negative' => array( '-1.1', PHPUnit_Type::TYPE_FLOAT ),
+ 'Float positive' => array( '+1.1', PHPUnit_Type::TYPE_FLOAT ),
+ 'Float scientific' => array( '1.2e3', PHPUnit_Type::TYPE_FLOAT ),
+
+ 'String empty double-quote' => array( '""', PHPUnit_Type::TYPE_STRING ),
+ 'String empty single-quote' => array( "''", PHPUnit_Type::TYPE_STRING ),
+ 'String whitespace' => array( '" "', PHPUnit_Type::TYPE_STRING ),
+ 'String number double-quote' => array( '"1"', PHPUnit_Type::TYPE_STRING ),
+ 'String number single-quote' => array( "'1'", PHPUnit_Type::TYPE_STRING ),
+
+ 'Built-in string literal null' => array( 'null', PHPUnit_Type::TYPE_NULL ),
+ 'Built-in string literal false' => array( 'false', PHPUnit_Type::TYPE_BOOL ),
+ 'Built-in string literal true' => array( 'true', PHPUnit_Type::TYPE_BOOL ),
+
+ 'Constant = null' => array( 'VERSION_ALL', PHPUnit_Type::TYPE_NULL ),
+ 'Constant = false' => array( 'VERSION_FUTURE', PHPUnit_Type::TYPE_BOOL ),
+ 'Constant = true' => array( 'VERSION_RELEASED', PHPUnit_Type::TYPE_BOOL ),
+ 'Constant = 0' => array( 'OFF', PHPUnit_Type::TYPE_INT ),
+ 'Constant integer' => array( 'DEVELOPER', PHPUnit_Type::TYPE_INT ),
+ 'Constant integer with whitespace' => array( " DEVELOPER\n", PHPUnit_Type::TYPE_INT ),
+ 'Constant string' => array( 'MANTIS_VERSION', PHPUnit_Type::TYPE_STRING ),
+ 'Constant string with whitespace' => array( " MANTIS_VERSION\n", PHPUnit_Type::TYPE_STRING ),
+ );
}
/**
- * Initialize the Scalar type test cases
- */
- private function initScalarTestCases() {
- # Integer
- $this->addScalarCase( '1', PHPUnit_Type::TYPE_INT );
- $this->addScalarCase( " 1\n", PHPUnit_Type::TYPE_INT );
-
- # Float
- $this->addScalarCase( '1.1', PHPUnit_Type::TYPE_FLOAT );
-
- # String
- $this->addScalarCase( '"1"', PHPUnit_Type::TYPE_STRING );
- $this->addScalarCase( "'1'", PHPUnit_Type::TYPE_STRING );
-
- # Built-in string literals
- $this->addScalarCase( 'null', PHPUnit_Type::TYPE_NULL );
- $this->addScalarCase( 'false', PHPUnit_Type::TYPE_BOOL );
- $this->addScalarCase( 'true', PHPUnit_Type::TYPE_BOOL );
-
- # Constants
- $this->addScalarCase( 'VERSION_ALL', PHPUnit_Type::TYPE_NULL ); # null
- $this->addScalarCase( 'VERSION_FUTURE', PHPUnit_Type::TYPE_BOOL ); # false
- $this->addScalarCase( 'VERSION_RELEASED', PHPUnit_Type::TYPE_BOOL ); # true
- $this->addScalarCase( 'OFF', PHPUnit_Type::TYPE_INT ); # 0
- $this->addScalarCase( 'DEVELOPER', PHPUnit_Type::TYPE_INT ); # int
- $this->addScalarCase( " DEVELOPER\n", PHPUnit_Type::TYPE_INT );
- $this->addScalarCase( 'MANTIS_VERSION', PHPUnit_Type::TYPE_STRING ); #string
- $this->addScalarCase( " MANTIS_VERSION\n", PHPUnit_Type::TYPE_STRING );
- }
-
- /**
+ * Data provider for Arrays test cases.
+ * Test case structure:
+ * => array( )
+ * @return array
* Initialize the array test cases list
*/
- private function initArrayTestCases() {
+ public function providerArrays() {
/**
* Template for new test cases
* ---------------------------
- # comment
- $this->addArrayCase(
+ 'case description' => array(
<<<'EOT'
EOT
- );
+ ),
* ---------------------------
*/
- /**
- * Simple arrays
- */
-
- # empty
- $this->addArrayCase( "array( )" );
-
- # one element
- $this->addArrayCase( "array( 1 )" );
-
- # several elements, trailing delimiter
- $this->addArrayCase( "array( 1, 2, )" );
-
- # formatted whitespace
- $this->addArrayCase( "array ( 1, 2, 3 )" );
-
- # no whitespace
- $this->addArrayCase( "array(1,2,3)" );
-
- # arbitrary whitespace
- $this->addArrayCase( " array(\n1,\t2 , 3 )\r " );
-
- # mixed types, quotes
- $this->addArrayCase(
+ return array(
+ /**
+ * Simple arrays
+ */
+ 'SimpleArray empty' => array( "array( )" ),
+ 'SimpleArray one element' => array( "array( 1 )" ),
+ 'SimpleArray several elements, trailing delimiter' => array( "array( 1, 2, )" ),
+ 'SimpleArray formatted whitespace' => array( "array ( 1, 2, 3 )" ),
+ 'SimpleArray no whitespace' => array( "array(1,2,3)" ),
+ 'SimpleArray arbitrary whitespace' => array( " array(\n1,\t2 , 3 )\r " ),
+ 'SimpleArray mixed types, quotes' => array(
<<<'EOT'
array( 1, 'a', "b" )
EOT
- );
-
- # nested quotes
- $this->addArrayCase(
+ ),
+ 'SimpleArray nested quotes' => array(
<<<'EOT'
array( '"a""b"""', "'a''b'''" )
EOT
- );
-
- /**
- * Associative arrays
- */
-
- # associative
- $this->addArrayCase( "array( 0 => 'a', 1 => 'b' )" );
+ ),
- # associative, unordered keys
- $this->addArrayCase( "array( 5 => 'a', 2 => 'b' )" );
+ /**
+ * Associative arrays
+ */
+ 'AssocArray' => array( "array( 0 => 'a', 1 => 'b' )" ),
+ 'AssocArray, unordered keys' => array( "array( 5 => 'a', 2 => 'b' )" ),
+ 'AssocArray, text keys' => array( "array( 'i' => 'a', 'j' => 'b' )" ),
+ 'AssocArray mixed keys' => array( "array( 'i' => 'a', 1 => 'b', 'j' => 'c', 7 => 'd' )" ),
+ 'AssocArray mixed, keys omitted' => array( "array( 'i' => 'a', 1 => 'b', 'c', 'j' => 'd' )" ),
- # associative, text keys
- $this->addArrayCase( "array( 'i' => 'a', 'j' => 'b' )" );
+ # mixed associative, overwriting implicit keys
+ 'AssocArray mixed, overwritten implicit keys' => array( "array( 0 => 'a0', 1 => 'a1', 'axx', 2 => 'a2' )" ),
- # associative, mixed keys
- $this->addArrayCase( "array( 'i' => 'a', 1 => 'b', 'j' => 'c', 7 => 'd' )" );
-
- # mixed associative, omitting some keys
- $this->addArrayCase( "array( 'i' => 'a', 1 => 'b', 'c', 'j' => 'd' )" );
-
- # mixed associative, overwriting implicit keys
- $this->addArrayCase( "array( 0 => 'a0', 1 => 'a1', 'axx', 2 => 'a2' )" );
-
- $this->addArrayCase(
+ 'AssocArray mixed' => array(
<<<'EOT'
array(
array ( 1, 'a', 3 => 1, 4 => 'b', 'x' => 'y' )
)
EOT
- );
+ ),
- /**
- * Use of constants
- */
+ /**
+ * Use of constants
+ */
- # As array values (e.g. handle_bug_threshold)
- $this->addArrayCase( "array( DEVELOPER, MANAGER )" );
+ # e.g. handle_bug_threshold
+ 'Constants as array values' => array( "array( DEVELOPER, MANAGER )" ),
- # As array keys (e.g. status_enum_workflow)
- $this->addArrayCase(
+ # e.g. status_enum_workflow
+ 'Constants as array keys' => array(
<<<'EOT'
array (
NEW_ => '20:feedback,30:acknowledged',
ACKNOWLEDGED => '40:confirmed',
)
EOT
- );
+ ),
- # both (e.g. set_status_threshold)
- $this->addArrayCase( 'array( NEW_ => REPORTER )' );
+ # e.g. set_status_threshold
+ 'Constants as both key and value' => array( 'array( NEW_ => REPORTER )' ),
- /**
- * Multidimensional arrays
- */
+ /**
+ * Multidimensional arrays
+ */
+ 'Multidimentional array' => array(
+ <<<'EOT'
+array(
+ 1 => array( 1, 2 => array() ),
+ array( 'a', 'b', array(3, 4, ) ),
+ 'c' => array( 'd', 5 => 'e' ),
+)
+EOT
+ ),
- # notify_flags sample
- $this->addArrayCase(
+ 'Multidimentional, notify_flags sample' => array(
<<<'EOT'
array(
'updated' => array (
@@ -373,24 +353,12 @@ private function initArrayTestCases() {
),
)
EOT
- );
+ ),
- $this->addArrayCase(
-<<<'EOT'
-array(
- 1 => array( 1, 2 => array() ),
- array( 'a', 'b', array(3, 4, ) ),
- 'c' => array( 'd', 5 => 'e' ),
-)
-EOT
- );
-
- /**
- * Test cases for specific issues reported on the bugtracker
- */
-
- # Test case for issue #0020787
- $this->addArrayCase(
+ /**
+ * Test cases for specific issues reported on the bugtracker
+ */
+ 'Issue #0020787' => array(
<<<'EOT'
array (
'additional_info',
@@ -401,10 +369,9 @@ private function initArrayTestCases() {
'due_date',
)
EOT
- );
+ ),
- # Test case for issue #0020812
- $this->addArrayCase(
+ 'Issue #0020812' => array(
<<<'EOT'
array (
0 =>
@@ -415,33 +382,31 @@ private function initArrayTestCases() {
),
)
EOT
- );
+ ),
- # Test case for issue #0020813
- $this->addArrayCase(
+ 'Issue #0020813' => array(
<<<'EOT'
array(
0 => "aa'aa",
1 => "bb\"bb"
)
EOT
- );
+ ),
- # Test case for issue #0020850
- $this->addArrayCase(
- <<<'EOT'
+ 'Issue #0020850' => array(
+<<<'EOT'
array ( 0 => '""a"' )
EOT
- );
+ ),
- # Test case for issue #0020851
- $this->addArrayCase(
+ 'Issue #0020851' => array(
<<<'EOT'
array (
'a' => 'x1',
'x2',
)
EOT
+ ),
);
}
}