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 + ), ); } }