From 8480fed9338af422cfeb693e560b5796140f8d19 Mon Sep 17 00:00:00 2001 From: Joe Ferguson Date: Tue, 14 Jul 2026 11:25:29 -0500 Subject: [PATCH] Move string field defaults into EventInput Review feedback: with EventInput in place, the remaining $_POST defaulting block belongs there too, leaving a single write to $_POST. Add STRING_FIELDS to EventInput and rename normalizeNumericFields() to normalize(), since it now covers both field groups. The string defaults use ??=, which matches the previous !isset() check -- an explicit null is treated as missing either way. Add tests for the string defaults, including that a falsy '0' is preserved, and retarget the untouched-fields test at an unmanaged field now that type/email are normalized. --- src/Events/EventInput.php | 52 ++++++++++++++++ submit-event.php | 24 ++------ tests/Unit/Events/EventInputTest.php | 89 ++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 src/Events/EventInput.php create mode 100644 tests/Unit/Events/EventInputTest.php diff --git a/src/Events/EventInput.php b/src/Events/EventInput.php new file mode 100644 index 0000000000..e5b2747395 --- /dev/null +++ b/src/Events/EventInput.php @@ -0,0 +1,52 @@ + $post + * @return array + */ + public static function normalize(array $post): array + { + foreach (self::NUMERIC_FIELDS as $field) { + $value = $post[$field] ?? null; + $post[$field] = is_numeric($value) ? (int) $value : 0; + } + + foreach (self::STRING_FIELDS as $field) { + $post[$field] ??= ''; + } + + return $post; + } +} diff --git a/submit-event.php b/submit-event.php index 2d2ac20044..ab09f89d1c 100644 --- a/submit-event.php +++ b/submit-event.php @@ -1,4 +1,6 @@ '12', 'sday' => '25', 'syear' => '2026']); + + self::assertSame(12, $result['smonth']); + self::assertSame(25, $result['sday']); + self::assertSame(2026, $result['syear']); + } + + public function testCoercesNonNumericStringsToZero(): void + { + $result = EventInput::normalize(['smonth' => 'January', 'sday' => 'abc', 'syear' => '12abc']); + + self::assertSame(0, $result['smonth']); + self::assertSame(0, $result['sday']); + self::assertSame(0, $result['syear']); + } + + public function testCoercesArrayValuesToZero(): void + { + $result = EventInput::normalize(['smonth' => ['x'], 'sday' => [], 'syear' => ['1', '2']]); + + self::assertSame(0, $result['smonth']); + self::assertSame(0, $result['sday']); + self::assertSame(0, $result['syear']); + } + + public function testDefaultsMissingNumericFieldsToZero(): void + { + $result = EventInput::normalize([]); + + foreach (['sday', 'smonth', 'syear', 'eday', 'emonth', 'eyear', 'recur', 'recur_day'] as $field) { + self::assertSame(0, $result[$field], $field); + } + } + + public function testDefaultsMissingStringFieldsToEmptyString(): void + { + $result = EventInput::normalize([]); + + foreach (['type', 'country', 'category', 'email', 'url', 'ldesc', 'sdesc'] as $field) { + self::assertSame('', $result[$field], $field); + } + } + + public function testKeepsSuppliedStringFields(): void + { + $result = EventInput::normalize(['email' => 'a@b.com', 'type' => 'multi', 'sdesc' => '0']); + + self::assertSame('a@b.com', $result['email']); + self::assertSame('multi', $result['type']); + self::assertSame('0', $result['sdesc']); + } + + public function testLeavesUnknownFieldsUntouched(): void + { + $result = EventInput::normalize(['sname' => 'PHP UK', 'smonth' => '5']); + + self::assertSame('PHP UK', $result['sname']); + self::assertSame(5, $result['smonth']); + } + + /** + * Regression test for the production fatal: + * Uncaught TypeError: checkdate(): Argument #1 ($month) must be of type int, string given + * + * After normalization the date parts are always integers, so passing them to + * checkdate() (and mktime()) can no longer throw a TypeError; garbage input + * simply becomes an invalid (0) date that the normal validation rejects. + */ + public function testNormalizedValuesAreSafeForCheckdate(): void + { + $result = EventInput::normalize(['smonth' => 'notamonth', 'sday' => '1', 'syear' => '2026']); + + self::assertFalse(checkdate($result['smonth'], $result['sday'], $result['syear'])); + } +}