diff --git a/extension/qfq/qfq/helper/KeyValueStringParser.php b/extension/qfq/qfq/helper/KeyValueStringParser.php index 982ded8972cb52382d796294752920226141d7b8..e8527dd82a5972c9336eeb3ba1481ed63c42ec8a 100644 --- a/extension/qfq/qfq/helper/KeyValueStringParser.php +++ b/extension/qfq/qfq/helper/KeyValueStringParser.php @@ -14,6 +14,7 @@ use qfq; require_once(__DIR__ . '/../exceptions/UserFormException.php'); require_once(__DIR__ . '/../../qfq/Constants.php'); +require_once(__DIR__ . '/../../qfq/helper/OnString.php'); /** * Class KeyValueStringParser @@ -102,6 +103,9 @@ class KeyValueStringParser { // Clean any "\r\n" to "\n" $keyValueString = str_replace("\r\n", "\n", $keyValueString); + // Allow {{ }} expressions to span several lines (replace \n inside these expressions) + $keyValueString = OnString::removeNewlinesInNestedExpression($keyValueString); + // Check if there are 'escaped delimiter', If yes, use the more expensive explodeEscape(). if (strpos($keyValueString, '\\' . $listDelimiter) !== false) { $keyValuePairs = self::explodeEscape($listDelimiter, $keyValueString, 2); diff --git a/extension/qfq/qfq/helper/OnString.php b/extension/qfq/qfq/helper/OnString.php index 2fa2461cdeaa9043dd633b6502849654380a40ee..3671b967368dde8e28b1130f2c0911d382f29398 100644 --- a/extension/qfq/qfq/helper/OnString.php +++ b/extension/qfq/qfq/helper/OnString.php @@ -30,7 +30,7 @@ class OnString { return ''; } - return substr($haystack, strrpos($haystack, $needle) + 1); + return substr($haystack, strrpos($haystack, $needle) + strlen($needle)); } /** @@ -145,4 +145,61 @@ class OnString { return $urlParamNew; } + /** + * Removes new lines (\n) from expressions like {{var:SC0:alnumx}} and replaces them with spaces. + * Handles nested expressions like {{SELECT {{var::alnumx}}}} + * This function is useful to allow multiline expressions in form definitions such as: + * sqlInsert = {{INSERT INTO test (grId) + * VALUES(123) }} + * + * @param $str - the string to be parsed + * @return string - the resulting string with new lines in expressions replaced + * @throws UserFormException - alerts the user if the delimiters are not balanced + */ + public static function removeNewlinesInNestedExpression($str) { + $delimStart = '{{'; + $delimEnd = '}}'; + $lastDelimPos = -strlen($delimStart); + $exprDepth = 0; // keeps count of the level of expression depth + $nestingStart = 0; + + // Process the string one start/end delimiter at a time + while(true) { + // find the next start/end delimiter + $nextDelimStartPos = strpos($str, $delimStart, $lastDelimPos + strlen($delimStart)); + $nextDelimEndPos = strpos($str, $delimEnd, $lastDelimPos + strlen($delimEnd)); + if ($nextDelimStartPos === false && $nextDelimEndPos === false) break; + $nextDelimPos = min($nextDelimStartPos, $nextDelimEndPos); + if ($nextDelimStartPos === false) $nextDelimPos = $nextDelimEndPos; + if ($nextDelimEndPos === false) $nextDelimPos = $nextDelimStartPos; + + if ($nextDelimPos == $nextDelimStartPos) { // opening delimiter + if ($exprDepth == 0) $nestingStart = $nextDelimPos; + $exprDepth++; + } else { // closing delimiter + $exprDepth--; + if ($exprDepth < 0) { + throw new UserFormException("Too many closing delimiters '$delimEnd' in '" . $str . "'", ERROR_MISSING_OPEN_DELIMITER); + break; + } elseif ($exprDepth == 0) { + // end of nesting -> replace \n inside nested expression with space + $pre = substr($str, 0, $nestingStart); + $nest = substr($str, $nestingStart, $nextDelimPos - $nestingStart); + $nestNew = str_replace('\n', ' ', $nest); + $post = substr($str, $nextDelimPos); + $str = substr($str, 0, $nestingStart) . + str_replace("\n", " ", substr($str, $nestingStart, $nextDelimPos - $nestingStart)) . + substr($str, $nextDelimPos); + } + } + + $lastDelimPos = $nextDelimPos; + } + if ($exprDepth > 0 ) { + throw new UserFormException("Missing close delimiter '$delimEnd' in '" . $str . "'", ERROR_MISSING_CLOSE_DELIMITER); + } + + return $str; + } + } diff --git a/extension/qfq/tests/phpunit/OnStringTest.php b/extension/qfq/tests/phpunit/OnStringTest.php index e6cf0ac870f3c8defa6b47d407f688fbffe5d588..6868508e0d68fd44ef8720fe72f88a5680ce0a29 100644 --- a/extension/qfq/tests/phpunit/OnStringTest.php +++ b/extension/qfq/tests/phpunit/OnStringTest.php @@ -9,6 +9,7 @@ namespace qfq; require_once(__DIR__ . '/../../qfq/helper/OnString.php'); +require_once(__DIR__ . '/../../qfq/exceptions/UserFormException.php'); use qfq; @@ -69,4 +70,69 @@ class OnStringTest extends TestCase { $this->assertEquals(' te\'st ', OnString::trimQuote("' te'st '")); } + + /** + * + */ + public function testRemoveNewlinesInNestedExpression() { + $this->assertEquals("", OnString::removeNewlinesInNestedExpression("")); + $this->assertEquals("test", OnString::removeNewlinesInNestedExpression("test")); + $this->assertEquals("{{test}}", OnString::removeNewlinesInNestedExpression("{{test}}")); + $this->assertEquals("{{test }}", OnString::removeNewlinesInNestedExpression("{{test\n}}")); + $this->assertEquals("{{test two}}", OnString::removeNewlinesInNestedExpression("{{test\ntwo}}")); + $this->assertEquals("Pre{{test two}}Post", OnString::removeNewlinesInNestedExpression("Pre{{test\ntwo}}Post")); + $this->assertEquals("{{test{{two}}}}", OnString::removeNewlinesInNestedExpression("{{test{{two}}}}")); + $this->assertEquals("{{a {{b }}}}", + OnString::removeNewlinesInNestedExpression("{{a\n{{b\n}}}}")); + $this->assertEquals("{{a b{{c d}}{{e f}} h}}", + OnString::removeNewlinesInNestedExpression("{{a\nb{{c\nd}}{{e\nf}}\nh}}")); + $this->assertEquals("param1=abc\nsqlInsert = {{SELECT * FROM test WHERE '{{var}}'='true'}}", + OnString::removeNewlinesInNestedExpression("param1=abc\nsqlInsert = {{SELECT *\nFROM test\nWHERE '{{var}}'='true'}}")); + $this->assertEquals("sqlInsert = {{SELECT * FROM test WHERE '{{var}}'='true'}}\nparam1=abc", + OnString::removeNewlinesInNestedExpression("sqlInsert = {{SELECT *\nFROM test\nWHERE '{{var}}'='true'}}\nparam1=abc")); + } + + /** + * @expectedException \qfq\UserFormException + * + */ + public function testRemoveNewlinesInNestedExpression_missingClosing_1() { + $str = OnString::removeNewlinesInNestedExpression("Hi! {{Test "); + } + /** + * @expectedException \qfq\UserFormException + * + */ + public function testRemoveNewlinesInNestedExpression_missingClosing_2() { + $str = OnString::removeNewlinesInNestedExpression("Hi! {{Test}"); + } + /** + * @expectedException \qfq\UserFormException + * + */ + public function testRemoveNewlinesInNestedExpression_missingClosing_3() { + $str = OnString::removeNewlinesInNestedExpression("Hi! {{Test {{var }}"); + } + + /** + * @expectedException \qfq\UserFormException + * + */ + public function testRemoveNewlinesInNestedExpression_missingOpening_1() { + $str = OnString::removeNewlinesInNestedExpression("}}"); + } + /** + * @expectedException \qfq\UserFormException + * + */ + public function testRemoveNewlinesInNestedExpression_missingOpening_2() { + $str = OnString::removeNewlinesInNestedExpression("{}}"); + } + /** + * @expectedException \qfq\UserFormException + * + */ + public function testRemoveNewlinesInNestedExpression_missingOpening_3() { + $str = OnString::removeNewlinesInNestedExpression("{{Test}}}}"); + } } \ No newline at end of file