From dfed9dbd14b13949cfe6f2bdf60fdf49ea3d1638 Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 10 Jun 2025 18:30:05 +0200 Subject: [PATCH 1/2] feat: require double curly braces for placeholders in regex_match rule --- system/Validation/Validation.php | 16 +++++-- tests/system/Validation/FormatRulesTest.php | 42 +++++++++++++++++++ user_guide_src/source/changelogs/v4.7.0.rst | 10 +++++ .../source/libraries/validation.rst | 5 ++- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index f13af5179112..24b692fcc611 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -823,8 +823,12 @@ protected function fillPlaceholders(array $rules, array $data): array continue; } - // Replace the placeholder in the rule - $ruleSet = str_replace('{' . $field . '}', (string) $data[$field], $ruleSet); + // Replace the placeholder in the current rule string + if (str_starts_with($row, 'regex_match[')) { + $row = str_replace('{{' . $field . '}}', (string) $data[$field], $row); + } else { + $row = str_replace('{' . $field . '}', (string) $data[$field], $row); + } } } } @@ -840,7 +844,13 @@ protected function fillPlaceholders(array $rules, array $data): array */ private function retrievePlaceholders(string $rule, array $data): array { - preg_match_all('/{(.+?)}/', $rule, $matches); + if (str_starts_with($rule, 'regex_match[')) { + // For regex_match rules, only look for double-bracket placeholders + preg_match_all('/\{\{((?:(?![{}]).)+?)\}\}/', $rule, $matches); + } else { + // For all other rules, use single-bracket placeholders + preg_match_all('/{(.+?)}/', $rule, $matches); + } return array_intersect($matches[1], array_keys($data)); } diff --git a/tests/system/Validation/FormatRulesTest.php b/tests/system/Validation/FormatRulesTest.php index 68eff136cb93..e711c148fcaf 100644 --- a/tests/system/Validation/FormatRulesTest.php +++ b/tests/system/Validation/FormatRulesTest.php @@ -86,6 +86,48 @@ public function testRegexMatchFalse(): void $this->assertFalse($this->validation->run($data)); } + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/9596 + */ + public function testRegexMatchWithArrayData(): void + { + $data = [ + ['uid' => '2025/06/000001'], + ['uid' => '2025/06/000002'], + ['uid' => '2025/06/000003'], + ['uid' => '2025/06/000004'], + ['uid' => '2025/06/000005'], + ]; + + $this->validation->setRules([ + '*.uid' => 'regex_match[/^(\d{4})\/(0[1-9]|1[0-2])\/\d{6}$/]', + ]); + + $this->assertTrue($this->validation->run($data)); + } + + public function testRegexMatchWithPlaceholder(): void + { + $data = [ + 'code' => 'ABC1234', + 'phone' => '1234567890', + 'prefix' => 'ABC', + 'min_digits' => 10, + 'max_digits' => 15 + ]; + + $this->validation->setRules([ + 'prefix' => 'required|string', + 'min_digits' => 'required|integer', + 'max_digits' => 'required|integer', + 'code' => 'required|regex_match[/^{{prefix}}\d{4}$/]', + 'phone' => 'required|regex_match[/^\d{{{min_digits}},{{max_digits}}}$/]', + ]); + + $result = $this->validation->run($data); + $this->assertTrue($result); + } + #[DataProvider('provideValidUrl')] public function testValidURL(?string $url, bool $isLoose, bool $isStrict): void { diff --git a/user_guide_src/source/changelogs/v4.7.0.rst b/user_guide_src/source/changelogs/v4.7.0.rst index 8ac71bbb08a1..c3d9cd1d5c12 100644 --- a/user_guide_src/source/changelogs/v4.7.0.rst +++ b/user_guide_src/source/changelogs/v4.7.0.rst @@ -23,6 +23,16 @@ BREAKING Behavior Changes ================ +Validation Rules +---------------- + +Placeholders in the ``regex_match`` validation rule must now use double curly braces. +If you previously used single braces like ``regex_match[/^{placeholder}$/]``, you must +update it to use double braces: ``regex_match[/^{{placeholder}}$/]``. + +This change was introduced to avoid ambiguity with regular expression syntax, +where single curly braces (e.g., ``{1,3}``) are used for quantifiers. + Interface Changes ================= diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst index 96416355bb22..a6e9e2947762 100644 --- a/user_guide_src/source/libraries/validation.rst +++ b/user_guide_src/source/libraries/validation.rst @@ -976,7 +976,10 @@ numeric No Fails if field contains anything other than permit_empty No Allows the field to receive an empty array, empty string, null or false. regex_match Yes Fails if field does not match the regular ``regex_match[/regex/]`` - expression. + expression. **Note:** Since v4.7.0, if + you're using a placeholder with this rule, + you must use double braces ``{{...}}`` + instead of single ones ``{...}``. required No Fails if the field is an empty array, empty string, null or false. required_with Yes The field is required when any of the other ``required_with[field1,field2]`` From ac4a58bfe553ce3304032aef40c732cfa1458117 Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 10 Jun 2025 18:34:55 +0200 Subject: [PATCH 2/2] cs fix --- tests/system/Validation/FormatRulesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Validation/FormatRulesTest.php b/tests/system/Validation/FormatRulesTest.php index e711c148fcaf..b6db5b3272a3 100644 --- a/tests/system/Validation/FormatRulesTest.php +++ b/tests/system/Validation/FormatRulesTest.php @@ -113,7 +113,7 @@ public function testRegexMatchWithPlaceholder(): void 'phone' => '1234567890', 'prefix' => 'ABC', 'min_digits' => 10, - 'max_digits' => 15 + 'max_digits' => 15, ]; $this->validation->setRules([