diff --git a/system/Language/en/Validation.php b/system/Language/en/Validation.php index facf512d559a..9d0197c2b3b0 100644 --- a/system/Language/en/Validation.php +++ b/system/Language/en/Validation.php @@ -53,6 +53,7 @@ 'required' => 'The {field} field is required.', 'required_with' => 'The {field} field is required when {param} is present.', 'required_without' => 'The {field} field is required when {param} is not present.', + 'required_if' => 'The {field} field is required when the {param} field has the given value.', 'string' => 'The {field} field must be a valid string.', 'timezone' => 'The {field} field must be a valid timezone.', 'valid_base64' => 'The {field} field must be a valid base64 string.', diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php index 06586a64190d..b02920e3a997 100644 --- a/system/Validation/Rules.php +++ b/system/Validation/Rules.php @@ -436,6 +436,47 @@ public function required_without( return true; } + /** + * This field is required when any of the other required fields have their expected values present + * in the data. + * + * Example (The special_option field is required when the normal_option,1,2 field has the given value.): + * + * required_if[normal_option,1,2] + * + * @param string|null $str + * @param string|null $fieldWithValue that we should check if present + * @param array $data Complete list of field from the form + */ + public function required_if($str = null, ?string $fieldWithValue = null, array $data = []): bool + { + if ($fieldWithValue === null || $data === []) { + throw new InvalidArgumentException('You must supply the parameters: field,expected_values, data.'); + } + + // Separate fields and expected values + $parts = explode(',', $fieldWithValue); + $field = array_shift($parts); // Get field + $expectedValues = $parts; // The remainder is the expected value + + if (trim($field) === '' || $expectedValues === []) { + throw new InvalidArgumentException('You must supply the expected values of field: E.g. field,value1,value2,...'); + } + + // If the field does not exist in the data, immediately return true + if (! array_key_exists($field, $data)) { + return true; + } + + // If the value of a field matches one of the expected values, then this field is required + if (in_array($data[$field], $expectedValues, true)) { + // The field to be checked must exist and cannot be empty + return trim($str ?? '') !== ''; + } + + return true; + } + /** * The field exists in $data. * diff --git a/system/Validation/StrictRules/Rules.php b/system/Validation/StrictRules/Rules.php index e4fc4fc91142..c4916bf95b78 100644 --- a/system/Validation/StrictRules/Rules.php +++ b/system/Validation/StrictRules/Rules.php @@ -407,6 +407,23 @@ public function required_without( return $this->nonStrictRules->required_without($str, $otherFields, $data, $error, $field); } + /** + * This field is required when any of the other required fields have their expected values present + * in the data. + * + * Example (The special_option field is required when the normal_option,1,2 field has the given value.): + * + * required_if[normal_option,1,2] + * + * @param string|null $str + * @param string|null $fieldWithValue that we should check if present + * @param array $data Complete list of field from the form + */ + public function required_if($str = null, ?string $fieldWithValue = null, array $data = []): bool + { + return $this->nonStrictRules->required_if($str, $fieldWithValue, $data); + } + /** * The field exists in $data. * diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index 30245811e583..fd9487a1c144 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -436,7 +436,7 @@ private function processPermitEmpty($value, array $rules, array $data) $rule = $match[1]; $param = $match[2]; - if (! in_array($rule, ['required_with', 'required_without'], true)) { + if (! in_array($rule, ['required_with', 'required_without', 'required_if'], true)) { continue; } diff --git a/tests/system/Validation/RulesTest.php b/tests/system/Validation/RulesTest.php index 7c69fa24838e..4f5a26245301 100644 --- a/tests/system/Validation/RulesTest.php +++ b/tests/system/Validation/RulesTest.php @@ -16,6 +16,7 @@ use CodeIgniter\Test\CIUnitTestCase; use Config\Services; use ErrorException; +use Generator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use stdClass; @@ -923,4 +924,80 @@ public function testFieldExistsErrorMessage(): void $this->validation->getErrors() ); } + + /** + * @param array $data + */ + #[DataProvider('provideRequiredIf')] + public function testRequiredIf(bool $expected, array $data): void + { + $this->validation->setRules([ + 'is_internal' => 'in_list[0,1,2,3]|permit_empty', + 'identity_number' => 'required_if[is_internal,1,2]', + ]); + + $result = $this->validation->run($data); + + $this->assertSame($expected, $result); + } + + public static function provideRequiredIf(): Generator + { + yield from [ + // `is_internal` and `identity_number` do not exist + [true, []], + // `identity_number` is not required because field `is_internal` + // value does not match with any value in the rule params + [true, ['is_internal' => '', 'identity_number' => '']], + [true, ['is_internal' => '0', 'identity_number' => '']], + [true, ['is_internal' => '3', 'identity_number' => '']], + // `identity_number` is required and exist + [false, ['is_internal' => '1', 'identity_number' => '']], + [false, ['is_internal' => '2', 'identity_number' => '']], + [true, ['is_internal' => '1', 'identity_number' => '123']], + [true, ['is_internal' => '2', 'identity_number' => '123']], + // `identity_number` is required but do not exist + [false, ['is_internal' => '1']], + [false, ['is_internal' => '2']], + // `identity_number` with integer value + [true, ['is_internal' => '1', 'identity_number' => '3207783']], + [true, ['is_internal' => '2', 'identity_number' => '3207783']], + [true, ['identity_number' => '3207783']], + [true, ['identity_number' => '3207783']], + // `identity_number` with string value + [true, ['is_internal' => '1', 'identity_number' => 'a']], + [true, ['is_internal' => '2', 'identity_number' => 'b']], + [true, ['identity_number' => 'a']], + [true, ['identity_number' => 'b']], + ]; + } + + /** + * @param array $data + */ + #[DataProvider('provideRequiredIfWorkWithOtherRule')] + public function testRequiredIfWorkWithOtherRule(bool $expected, array $data): void + { + $this->validation->setRules([ + 'is_internal' => 'in_list[0,1,2,3]|permit_empty', + 'identity_number' => 'required_if[is_internal,1,2]|permit_empty|integer', + ]); + + $result = $this->validation->run($data); + + $this->assertSame($expected, $result); + } + + public static function provideRequiredIfWorkWithOtherRule(): Generator + { + yield from [ + // `identity_number` with integer value + [true, ['is_internal' => '1', 'identity_number' => '3207783']], + [true, ['is_internal' => '2', 'identity_number' => '3207783']], + // `identity_number` is not empty, but it is not an integer + // value, which triggers the Validation.integer error message. + [false, ['is_internal' => '1', 'identity_number' => 'a']], + [false, ['is_internal' => '2', 'identity_number' => 'b']], + ]; + } } diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index 57a36d19cf4e..0872ba776bd8 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -146,8 +146,9 @@ Libraries - **FileCollection:** Added ``retainMultiplePatterns()`` to ``FileCollection`` class. See :ref:`FileCollection::retainMultiplePatterns() `. -- **Validation:** Added ``min_dims`` validation rule to ``FileRules`` class. See - :ref:`Validation `. +- **Validation:** + - Added ``min_dims`` validation rule to ``FileRules`` class. See :ref:`Validation `. + - Added ``required_if`` validation rule to ``Rules`` class. See :ref:`Validation `. Helpers and Functions ===================== @@ -167,6 +168,7 @@ Message Changes - Added ``Validation.min_dims`` message - Added ``Errors.badRequest`` and ``Errors.sorryBadRequest`` +- Added ``Validation.required_if`` message ******* Changes diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst index 940207bed4a6..61228cb649f0 100644 --- a/user_guide_src/source/libraries/validation.rst +++ b/user_guide_src/source/libraries/validation.rst @@ -972,6 +972,9 @@ regex_match Yes Fails if field does not match the regular expression. required No Fails if the field is an empty array, empty string, null or false. +required_if Yes The field is required when the other field ``required_if[field,value1,value2,...]`` + has the given values. (This rule was + added in v4.6.0.) required_with Yes The field is required when any of the other ``required_with[field1,field2]`` fields is not `empty()`_ in the data. required_without Yes The field is required when any of the other ``required_without[field1,field2]``