Skip to content

Commit f60f50e

Browse files
committed
Fix dot notation for if_exist
1 parent 4841326 commit f60f50e

2 files changed

Lines changed: 109 additions & 21 deletions

File tree

system/Validation/Validation.php

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,30 @@ protected function processRules(string $field, string $label = null, $value, $ru
215215

216216
if (in_array('if_exist', $rules, true))
217217
{
218-
// If the if_exist rule is defined
219-
// and the current field does not exist in the input data
220-
// we can return true, ignoring all other rules to this field.
221-
if (! array_key_exists($field, array_flatten_with_dots($data)))
218+
$flattenedData = array_flatten_with_dots($data);
219+
$ifExistField = $field;
220+
221+
if (strpos($field, '.*') !== false)
222+
{
223+
// We'll change the dot notation into a PCRE pattern
224+
// that can be used later
225+
$ifExistField = str_replace('\.\*', '\.(?:[^\.]+)', preg_quote($field, '/'));
226+
227+
$dataIsExisting = array_reduce(array_keys($flattenedData), static function ($carry, $item) use ($ifExistField) {
228+
$pattern = sprintf('/%s/u', $ifExistField);
229+
return $carry || preg_match($pattern, $item) === 1;
230+
}, false);
231+
}
232+
else
233+
{
234+
$dataIsExisting = array_key_exists($ifExistField, $flattenedData);
235+
}
236+
237+
unset($ifExistField, $flattenedData);
238+
239+
if (! $dataIsExisting)
222240
{
241+
// we return early if `if_exist` is not satisfied. we have nothing to do here.
223242
return true;
224243
}
225244

@@ -345,6 +364,7 @@ protected function processRules(string $field, string $label = null, $value, $ru
345364
*/
346365
public function withRequest(RequestInterface $request): ValidationInterface
347366
{
367+
/** @var IncomingRequest $request */
348368
if (strpos($request->getHeaderLine('Content-Type'), 'application/json') !== false)
349369
{
350370
$this->data = $request->getJSON(true);
@@ -363,7 +383,6 @@ public function withRequest(RequestInterface $request): ValidationInterface
363383
return $this;
364384
}
365385

366-
//--------------------------------------------------------------------
367386
//--------------------------------------------------------------------
368387
// Rules
369388
//--------------------------------------------------------------------
@@ -812,24 +831,21 @@ protected function splitRules(string $rules): array
812831
{
813832
$nonEscapeBracket = '((?<!\\\\)(?:\\\\\\\\)*[\[\]])';
814833
$pipeNotInBracket = sprintf(
815-
'/\|(?=(?:[^\[\]]*%s[^\[\]]*%s)*(?![^\[\]]*%s))/',
816-
$nonEscapeBracket,
817-
$nonEscapeBracket,
818-
$nonEscapeBracket
834+
'/\|(?=(?:[^\[\]]*%s[^\[\]]*%s)*(?![^\[\]]*%s))/',
835+
$nonEscapeBracket,
836+
$nonEscapeBracket,
837+
$nonEscapeBracket
819838
);
820839

821-
$_rules = preg_split(
822-
$pipeNotInBracket,
823-
$rules
824-
);
840+
$rules = preg_split($pipeNotInBracket, $rules);
825841

826-
return array_unique($_rules);
842+
return array_unique($rules);
827843
}
828844

829-
//--------------------------------------------------------------------
830845
//--------------------------------------------------------------------
831846
// Misc
832847
//--------------------------------------------------------------------
848+
833849
/**
834850
* Resets the class to a blank slate. Should be called whenever
835851
* you need to process more than one array.
@@ -845,6 +861,4 @@ public function reset(): ValidationInterface
845861

846862
return $this;
847863
}
848-
849-
//--------------------------------------------------------------------
850864
}

tests/system/Validation/ValidationTest.php

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
<?php namespace CodeIgniter\Validation;
1+
<?php
2+
3+
namespace CodeIgniter\Validation;
24

35
use CodeIgniter\HTTP\IncomingRequest;
46
use CodeIgniter\HTTP\URI;
@@ -9,9 +11,11 @@
911
use Config\Services;
1012
use Tests\Support\Validation\TestRules;
1113

12-
class ValidationTest extends CIUnitTestCase
14+
/**
15+
* @internal
16+
*/
17+
final class ValidationTest extends CIUnitTestCase
1318
{
14-
1519
/**
1620
* @var Validation
1721
*/
@@ -945,5 +949,75 @@ public function testTranslatedLabelTagReplacement()
945949
$this->assertEquals($expected, $errors['Username']);
946950
}
947951

948-
//--------------------------------------------------------------------
952+
/**
953+
* @dataProvider dotNotationForIfExistProvider
954+
*
955+
* @see https://github.com/codeigniter4/CodeIgniter4/issues/4521
956+
*
957+
* @param boolean $expected
958+
* @param array $rules
959+
* @param array $data
960+
*
961+
* @return void
962+
*/
963+
public function testDotNotationOnIfExistRule(bool $expected, array $rules, array $data): void
964+
{
965+
$actual = $this->validation->setRules($rules)->run($data);
966+
$this->assertSame($expected, $actual);
967+
}
968+
969+
public function dotNotationForIfExistProvider()
970+
{
971+
yield 'dot-on-end-fail' => [
972+
false,
973+
['status.*' => 'if_exist|in_list[status_1,status_2]'],
974+
['status' => ['bad-status']],
975+
];
976+
977+
yield 'dot-on-end-pass' => [
978+
true,
979+
['status.*' => 'if_exist|in_list[status_1,status_2]'],
980+
['status' => ['status_1']],
981+
];
982+
983+
yield 'dot-on-middle-fail' => [
984+
false,
985+
['fizz.*.baz' => 'if_exist|numeric'],
986+
['fizz' => [
987+
'bar' => ['baz' => 'yes'],
988+
]],
989+
];
990+
991+
yield 'dot-on-middle-pass' => [
992+
true,
993+
['fizz.*.baz' => 'if_exist|numeric'],
994+
['fizz' => [
995+
'bar' => ['baz' => 30],
996+
]],
997+
];
998+
999+
yield 'dot-multiple-fail' => [
1000+
false,
1001+
['fizz.*.bar.*' => 'if_exist|numeric'],
1002+
['fizz' => [
1003+
'bos' => [
1004+
'bar' => [
1005+
'bub' => 'noo',
1006+
],
1007+
],
1008+
]],
1009+
];
1010+
1011+
yield 'dot-multiple-pass' => [
1012+
true,
1013+
['fizz.*.bar.*' => 'if_exist|numeric'],
1014+
['fizz' => [
1015+
'bos' => [
1016+
'bar' => [
1017+
'bub' => 5,
1018+
],
1019+
],
1020+
]],
1021+
];
1022+
}
9491023
}

0 commit comments

Comments
 (0)