Fix splitting of string rules (#4957)

This commit is contained in:
John Paul E. Balandan, CPA 2021-07-23 21:16:56 +08:00 committed by John Paul E. Balandan, CPA
parent 6e64994e6c
commit eeaae2a218
2 changed files with 96 additions and 11 deletions

View File

@ -672,21 +672,42 @@ class Validation implements ValidationInterface
*/
protected function splitRules(string $rules): array
{
$nonEscapeBracket = '((?<!\\\\)(?:\\\\\\\\)*[\[\]])';
$pipeNotInBracket = sprintf(
'/\|(?=(?:[^\[\]]*%s[^\[\]]*%s)*(?![^\[\]]*%s))/',
$nonEscapeBracket,
$nonEscapeBracket,
$nonEscapeBracket
);
if (strpos($rules, '|') === false) {
return [$rules];
}
$_rules = preg_split($pipeNotInBracket, $rules);
$string = $rules;
$rules = [];
$length = strlen($string);
$cursor = 0;
return array_unique($_rules);
while ($cursor < $length) {
$pos = strpos($string, '|', $cursor);
if ($pos === false) {
// we're in the last rule
$pos = $length;
}
$rule = substr($string, $cursor, $pos - $cursor);
while (
(substr_count($rule, '[') - substr_count($rule, '\['))
!== (substr_count($rule, ']') - substr_count($rule, '\]'))
) {
// the pipe is inside the brackets causing the closing bracket to
// not be included. so, we adjust the rule to include that portion.
$pos = strpos($string, '|', $cursor + strlen($rule) + 1) ?: $length;
$rule = substr($string, $cursor, $pos - $cursor);
}
$rules[] = $rule;
$cursor += strlen($rule) + 1; // +1 to exclude the pipe
}
return array_unique($rules);
}
// Misc
/**
* Resets the class to a blank slate. Should be called whenever
* you need to process more than one array.

View File

@ -984,4 +984,68 @@ final class ValidationTest extends CIUnitTestCase
]],
];
}
/**
* @dataProvider provideStringRulesCases
*
* @param string $input
* @param array $expected
*
* @return void
*
* @see https://github.com/codeigniter4/CodeIgniter4/issues/4929
*/
public function testSplittingOfComplexStringRules(string $input, array $expected): void
{
$splitter = $this->getPrivateMethodInvoker($this->validation, 'splitRules');
$this->assertSame($expected, $splitter($input));
}
public function provideStringRulesCases(): iterable
{
yield [
'required',
['required'],
];
yield [
'required|numeric',
['required', 'numeric'],
];
yield [
'required|max_length[500]|hex',
['required', 'max_length[500]', 'hex'],
];
yield [
'required|numeric|regex_match[/[a-zA-Z]+/]',
['required', 'numeric', 'regex_match[/[a-zA-Z]+/]'],
];
yield [
'required|max_length[500]|regex_match[/^;"\'{}\[\]^<>=/]',
['required', 'max_length[500]', 'regex_match[/^;"\'{}\[\]^<>=/]'],
];
yield [
'regex_match[/^;"\'{}\[\]^<>=/]|regex_match[/[^a-z0-9.\|_]+/]',
['regex_match[/^;"\'{}\[\]^<>=/]', 'regex_match[/[^a-z0-9.\|_]+/]'],
];
yield [
'required|regex_match[/^(01[2689]|09)[0-9]{8}$/]|numeric',
['required', 'regex_match[/^(01[2689]|09)[0-9]{8}$/]', 'numeric'],
];
yield [
'required|regex_match[/^[0-9]{4}[\-\.\[\/][0-9]{2}[\-\.\[\/][0-9]{2}/]|max_length[10]',
['required', 'regex_match[/^[0-9]{4}[\-\.\[\/][0-9]{2}[\-\.\[\/][0-9]{2}/]', 'max_length[10]'],
];
yield [
'required|regex_match[/^(01|2689|09)[0-9]{8}$/]|numeric',
['required', 'regex_match[/^(01|2689|09)[0-9]{8}$/]', 'numeric'],
];
}
}